From f995bd22096134dbfe7cd93b0f0de07689bc86ab Mon Sep 17 00:00:00 2001 From: gentle_hu Date: Tue, 15 Nov 2022 15:31:38 +0800 Subject: [PATCH] pgfdw support join\agg\sort\limit\lockrows --- contrib/file_fdw/file_fdw.cpp | 20 +- contrib/gauss_connector/gc_fdw.cpp | 10 +- contrib/gc_fdw/gc_fdw_single.cpp | 8 +- contrib/log_fdw/log_fdw.cpp | 8 +- contrib/postgres_fdw/Makefile | 2 +- contrib/postgres_fdw/connection.cpp | 35 +- contrib/postgres_fdw/deparse.cpp | 1918 +++- .../postgres_fdw/expected/postgres_fdw.out | 8655 +++++++++++++-- .../expected/postgres_fdw_cstore.out | 7269 +++++++++++++ .../expected/postgres_fdw_partition.out | 9265 +++++++++++++++++ contrib/postgres_fdw/internal_interface.cpp | 663 ++ contrib/postgres_fdw/internal_interface.h | 68 + contrib/postgres_fdw/postgres_fdw.cpp | 3036 +++++- contrib/postgres_fdw/postgres_fdw.h | 126 +- contrib/postgres_fdw/sql/postgres_fdw.sql | 2225 +++- .../postgres_fdw/sql/postgres_fdw_cstore.sql | 1165 +++ .../sql/postgres_fdw_partition.sql | 1315 +++ src/bin/gs_guc/cluster_guc.conf | 1 + src/common/backend/nodes/copyfuncs.cpp | 14 + src/common/backend/nodes/list.cpp | 25 + src/common/backend/nodes/outfuncs.cpp | 12 + src/common/backend/nodes/readfuncs.cpp | 18 + src/common/backend/utils/adt/ruleutils.cpp | 2 + src/common/backend/utils/init/globals.cpp | 4 +- src/common/backend/utils/misc/guc.cpp | 12 + .../cbb/extension/foreign/foreign.cpp | 79 +- .../optimizer/commands/explain.cpp | 38 +- .../optimizer/commands/tablecmds.cpp | 2 - src/gausskernel/optimizer/path/joinpath.cpp | 10 + src/gausskernel/optimizer/path/pathkeys.cpp | 4 +- src/gausskernel/optimizer/plan/createplan.cpp | 97 +- src/gausskernel/optimizer/plan/planner.cpp | 87 + src/gausskernel/optimizer/plan/setrefs.cpp | 92 +- .../optimizer/plan/streamplan_utils.cpp | 5 +- src/gausskernel/optimizer/util/pathnode.cpp | 8 +- src/gausskernel/optimizer/util/plancat.cpp | 7 +- src/gausskernel/optimizer/util/relnode.cpp | 50 +- src/gausskernel/runtime/executor/execScan.cpp | 13 +- .../runtime/executor/nodeForeignscan.cpp | 114 +- .../storage/bulkload/foreignroutine.cpp | 6 +- .../storage/mot/fdw_adapter/mot_fdw.cpp | 15 +- src/include/bulkload/foreignroutine.h | 2 +- src/include/commands/explain.h | 1 + src/include/foreign/fdwapi.h | 17 +- src/include/foreign/foreign.h | 111 +- .../knl/knl_guc/knl_session_attr_common.h | 1 + src/include/miscadmin.h | 1 + src/include/nodes/execnodes.h | 1 + src/include/nodes/pg_list.h | 1 + src/include/nodes/plannodes.h | 11 +- src/include/nodes/relation.h | 49 +- src/include/optimizer/pathnode.h | 2 +- src/include/optimizer/paths.h | 2 +- src/include/optimizer/planmain.h | 2 +- src/test/regress/input/fdw_prepare.source | 24 + src/test/regress/output/fdw_prepare.source | 21 + .../regress/output/recovery_2pc_tools.source | 1 + src/test/regress/parallel_schedule0 | 8 +- 58 files changed, 34971 insertions(+), 1787 deletions(-) create mode 100644 contrib/postgres_fdw/expected/postgres_fdw_cstore.out create mode 100644 contrib/postgres_fdw/expected/postgres_fdw_partition.out create mode 100644 contrib/postgres_fdw/internal_interface.cpp create mode 100644 contrib/postgres_fdw/internal_interface.h create mode 100644 contrib/postgres_fdw/sql/postgres_fdw_cstore.sql create mode 100644 contrib/postgres_fdw/sql/postgres_fdw_partition.sql create mode 100644 src/test/regress/input/fdw_prepare.source create mode 100644 src/test/regress/output/fdw_prepare.source diff --git a/contrib/file_fdw/file_fdw.cpp b/contrib/file_fdw/file_fdw.cpp index 48f38a490..f1f9cb77d 100644 --- a/contrib/file_fdw/file_fdw.cpp +++ b/contrib/file_fdw/file_fdw.cpp @@ -106,8 +106,8 @@ PG_FUNCTION_INFO_V1(file_fdw_validator); */ static void fileGetForeignRelSize(PlannerInfo* root, RelOptInfo* baserel, Oid foreigntableid); static void fileGetForeignPaths(PlannerInfo* root, RelOptInfo* baserel, Oid foreigntableid); -static ForeignScan* fileGetForeignPlan(PlannerInfo* root, RelOptInfo* baserel, Oid foreigntableid, - ForeignPath* best_path, List* tlist, List* scan_clauses); +static ForeignScan *fileGetForeignPlan(PlannerInfo *root, RelOptInfo *baserel, Oid foreigntableid, + ForeignPath *best_path, List *tlist, List *scan_clauses, Plan *outer_plan); static void fileExplainForeignScan(ForeignScanState* node, ExplainState* es); static void fileBeginForeignScan(ForeignScanState* node, int eflags); static TupleTableSlot* fileIterateForeignScan(ForeignScanState* node); @@ -452,8 +452,9 @@ static void fileGetForeignPaths(PlannerInfo* root, RelOptInfo* baserel, Oid fore startup_cost, total_cost, NIL, /* no pathkeys */ - NULL, /* no outer rel either */ - NIL)); /* no fdw_private data */ + NULL, /* no outer rel either */ + NULL, /* no outer path either */ + NIL)); /* no fdw_private data */ /* * If data file was sorted, and we knew it somehow, we could insert @@ -464,10 +465,10 @@ static void fileGetForeignPaths(PlannerInfo* root, RelOptInfo* baserel, Oid fore /* * fileGetForeignPlan - * Create a ForeignScan plan node for scanning the foreign table + * Create a ForeignScan plan node for scanning the foreign table */ -static ForeignScan* fileGetForeignPlan( - PlannerInfo* root, RelOptInfo* baserel, Oid foreigntableid, ForeignPath* best_path, List* tlist, List* scan_clauses) +static ForeignScan *fileGetForeignPlan(PlannerInfo *root, RelOptInfo *baserel, Oid foreigntableid, + ForeignPath *best_path, List *tlist, List *scan_clauses, Plan *outer_plan) { Index scan_relid = baserel->relid; @@ -485,8 +486,11 @@ static ForeignScan* fileGetForeignPlan( scan_clauses, scan_relid, NIL, /* no expressions to evaluate */ + NIL, /* no private state either */ NIL, - EXEC_ON_DATANODES); /* no private state either */ + NIL, + NULL, + EXEC_ON_DATANODES); } /* diff --git a/contrib/gauss_connector/gc_fdw.cpp b/contrib/gauss_connector/gc_fdw.cpp index ede65e48c..a68e1dcb2 100644 --- a/contrib/gauss_connector/gc_fdw.cpp +++ b/contrib/gauss_connector/gc_fdw.cpp @@ -448,8 +448,9 @@ static void gcGetForeignPaths(PlannerInfo* root, RelOptInfo* baserel, Oid foreig fpinfo->startup_cost, fpinfo->total_cost, NIL, /* no pathkeys */ - NULL, /* no outer rel either */ - NIL, /* no fdw_private list */ + NULL, /* no outer rel either */ + NULL, /* no outer path either */ + NIL, /* no fdw_private list */ u_sess->opt_cxt.query_dop); add_path(root, baserel, (Path*)path); } @@ -459,7 +460,7 @@ static void gcGetForeignPaths(PlannerInfo* root, RelOptInfo* baserel, Oid foreig * Create ForeignScan plan node which implements selected best path */ static ForeignScan* gcGetForeignPlan(PlannerInfo* root, RelOptInfo* foreignrel, Oid foreigntableid, - ForeignPath* best_path, List* tlist, List* scan_clauses) + ForeignPath* best_path, List* tlist, List* scan_clauses, Plan* outer_plan) { GcFdwRelationInfo* fpinfo = (GcFdwRelationInfo*)foreignrel->fdw_private; Index scan_relid; @@ -617,7 +618,8 @@ static ForeignScan* gcGetForeignPlan(PlannerInfo* root, RelOptInfo* foreignrel, * field of the finished plan node; we can't keep them in private state * because then they wouldn't be subject to later planner processing. */ - return make_foreignscan(tlist, local_exprs, scan_relid, params_list, fdw_private, EXEC_ON_DATANODES); + return make_foreignscan(tlist, local_exprs, scan_relid, params_list, fdw_private, NIL, NIL, NULL, + EXEC_ON_DATANODES); } /* diff --git a/contrib/gc_fdw/gc_fdw_single.cpp b/contrib/gc_fdw/gc_fdw_single.cpp index 9e0fa8539..a0a373285 100644 --- a/contrib/gc_fdw/gc_fdw_single.cpp +++ b/contrib/gc_fdw/gc_fdw_single.cpp @@ -72,12 +72,8 @@ static void gcGetForeignPaths(PlannerInfo* root, RelOptInfo* baserel, Oid foreig return; } -static ForeignScan *gcGetForeignPlan(PlannerInfo *root, - RelOptInfo *baserel, - Oid foreigntableid, - ForeignPath *best_path, - List *tlist, - List *scan_clauses) +static ForeignScan *gcGetForeignPlan(PlannerInfo *root, RelOptInfo *baserel, Oid foreigntableid, + ForeignPath *best_path, List *tlist, List *scan_clauses, Plan *outer_plan) { DISTRIBUTED_FEATURE_NOT_SUPPORTED(); return NULL; diff --git a/contrib/log_fdw/log_fdw.cpp b/contrib/log_fdw/log_fdw.cpp index fb2dda5a7..753c2b0dc 100644 --- a/contrib/log_fdw/log_fdw.cpp +++ b/contrib/log_fdw/log_fdw.cpp @@ -2722,8 +2722,8 @@ static void log_rescan_foreign_scan(ForeignScanState* node) (void)MemoryContextSwitchTo(old_memcnxt); } -static ForeignScan* log_get_foreign_plan( - PlannerInfo* root, RelOptInfo* baserel, Oid foreigntableid, ForeignPath* best_path, List* tlist, List* scan_clauses) +static ForeignScan* log_get_foreign_plan(PlannerInfo* root, RelOptInfo* baserel, Oid foreigntableid, + ForeignPath* best_path, List* tlist, List* scan_clauses, Plan* outer_plan) { Index scan_relid = baserel->relid; List* fdw_data = best_path->fdw_private; @@ -2773,6 +2773,9 @@ static ForeignScan* log_get_foreign_plan( scan_relid, NIL, /* no expressions to evaluate */ fdw_data, + NIL, + NIL, + NULL, EXEC_ON_DATANODES); /* no private state either */ return fscan; @@ -2796,6 +2799,7 @@ static void log_get_foreign_paths(PlannerInfo* root, RelOptInfo* baserel, Oid fo total_cost, NIL, /* no pathkeys */ NULL, /* no outer rel either */ + NULL, /* no outer path either */ NIL, 1)); /* no fdw_private data */ diff --git a/contrib/postgres_fdw/Makefile b/contrib/postgres_fdw/Makefile index 89016adb9..6c86fd3d9 100644 --- a/contrib/postgres_fdw/Makefile +++ b/contrib/postgres_fdw/Makefile @@ -1,7 +1,7 @@ # contrib/postgres_fdw/Makefile MODULE_big = postgres_fdw -OBJS = postgres_fdw.o option.o deparse.o connection.o +OBJS = postgres_fdw.o option.o deparse.o connection.o internal_interface.o PG_CPPFLAGS = -I$(libpq_srcdir) SHLIB_LINK_INTERNAL = $(libpq) diff --git a/contrib/postgres_fdw/connection.cpp b/contrib/postgres_fdw/connection.cpp index 6cce384c4..9ed2b22c3 100644 --- a/contrib/postgres_fdw/connection.cpp +++ b/contrib/postgres_fdw/connection.cpp @@ -28,7 +28,7 @@ #include "utils/syscache.h" #include "utils/timestamp.h" #include "storage/ipc.h" - +#include "executor/executor.h" /* * Connection cache hash table entry @@ -243,6 +243,39 @@ PGconn *GetConnection(ForeignServer *server, UserMapping *user, bool will_prep_s return entry->conn; } +PGconn* GetConnectionByFScanState(ForeignScanState* node, bool will_prep_stmt) +{ + ForeignScan* fsplan = (ForeignScan *)node->ss.ps.plan; + EState* estate = node->ss.ps.state; + RangeTblEntry* rte = NULL; + Oid userid; + ForeignTable* table = NULL; + ForeignServer* server = NULL; + UserMapping* user = NULL; + int rtindex; + + /* + * Identify which user to do the remote access as. This should match what + * ExecCheckRTEPerms() does. In case of a join or aggregate, use the + * lowest-numbered member RTE as a representative; we would get the same + * result from any. + */ + if (fsplan->scan.scanrelid > 0) { + rtindex = fsplan->scan.scanrelid; + } else { + rtindex = bms_next_member(fsplan->fs_relids, -1); + } + rte = exec_rt_fetch(rtindex, estate); + userid = rte->checkAsUser ? rte->checkAsUser : GetUserId(); + + /* Get info about foreign table. */ + table = GetForeignTable(rte->relid); + server = GetForeignServer(table->serverid); + user = GetUserMapping(userid, table->serverid); + + return GetConnection(server, user, will_prep_stmt); +} + /* * Connect to remote server using specified server and user mapping properties. */ diff --git a/contrib/postgres_fdw/deparse.cpp b/contrib/postgres_fdw/deparse.cpp index b27ef2a7d..dc2ea7e0e 100644 --- a/contrib/postgres_fdw/deparse.cpp +++ b/contrib/postgres_fdw/deparse.cpp @@ -54,6 +54,11 @@ #include "utils/builtins.h" #include "utils/lsyscache.h" #include "utils/syscache.h" +#include "utils/typcache.h" +#include "optimizer/tlist.h" +#include "optimizer/prep.h" +#include "catalog/pg_aggregate.h" +#include "internal_interface.h" /* @@ -62,6 +67,7 @@ typedef struct foreign_glob_cxt { PlannerInfo *root; /* global planner state */ RelOptInfo *foreignrel; /* the foreign relation we are planning for */ + Relids relids; /* relids of base relations in the underlying scan */ } foreign_glob_cxt; /* @@ -88,30 +94,40 @@ typedef struct foreign_loc_cxt { typedef struct deparse_expr_cxt { PlannerInfo *root; /* global planner state */ RelOptInfo *foreignrel; /* the foreign relation we are planning for */ + RelOptInfo *scanrel; /* the underlying scan relation. Same as + * foreignrel, when that represents a join or + * a base relation. */ StringInfo buf; /* output buffer to append to */ List **params_list; /* exprs that will become remote Params */ } deparse_expr_cxt; +#define REL_ALIAS_PREFIX "r" +/* Handy macro to add relation name qualification */ +#define ADD_REL_QUALIFIER(buf, varno) \ + appendStringInfo((buf), "%s%d.", REL_ALIAS_PREFIX, (varno)) +#define SUBQUERY_REL_ALIAS_PREFIX "s" +#define SUBQUERY_COL_ALIAS_PREFIX "c" + /* * Functions to determine whether an expression can be evaluated safely on * remote server. */ -static bool foreign_expr_walker(Node *node, foreign_glob_cxt *glob_cxt, foreign_loc_cxt *outer_cxt); -static bool is_builtin(Oid procid); +static bool foreign_expr_walker(Node *node, foreign_glob_cxt *glob_cxt, foreign_loc_cxt *outer_cxt, + foreign_loc_cxt *case_arg_cxt); /* * Functions to construct string representation of a node tree. */ static void deparseTargetList(StringInfo buf, RangeTblEntry *rte, Index rtindex, Relation rel, bool is_returning, - Bitmapset *attrs_used, List **retrieved_attrs); + Bitmapset *attrs_used, bool qualify_col, List **retrieved_attrs); static void deparseReturningList(StringInfo buf, RangeTblEntry *root, Index rtindex, Relation rel, bool trig_after_row, List *returningList, List **retrieved_attrs); -static void deparseColumnRef(StringInfo buf, Index varno, int varattno, RangeTblEntry *rte); +static void deparseColumnRef(StringInfo buf, int varno, int varattno, RangeTblEntry *rte, bool qualify_col); static void deparseRelation(StringInfo buf, Relation rel); static void deparseStringLiteral(StringInfo buf, const char *val); static void deparseExpr(Expr *expr, deparse_expr_cxt *context); static void deparseVar(Var *node, deparse_expr_cxt *context); -static void deparseConst(Const *node, deparse_expr_cxt *context); +static void deparseConst(Const *node, deparse_expr_cxt *context, int showtype); static void deparseParam(Param *node, deparse_expr_cxt *context); static void deparseArrayRef(ArrayRef *node, deparse_expr_cxt *context); static void deparseFuncExpr(FuncExpr *node, deparse_expr_cxt *context); @@ -122,11 +138,33 @@ static void deparseScalarArrayOpExpr(ScalarArrayOpExpr *node, deparse_expr_cxt * static void deparseRelabelType(RelabelType *node, deparse_expr_cxt *context); static void deparseBoolExpr(BoolExpr *node, deparse_expr_cxt *context); static void deparseNullTest(NullTest *node, deparse_expr_cxt *context); +static void deparseCaseExpr(CaseExpr *node, deparse_expr_cxt *context); static void deparseArrayExpr(ArrayExpr *node, deparse_expr_cxt *context); +static void deparseAggref(Aggref *node, deparse_expr_cxt *context); +static void appendAggOrderBy(List *orderList, List *targetList, deparse_expr_cxt *context); static void printRemoteParam(int paramindex, Oid paramtype, int32 paramtypmod, deparse_expr_cxt *context); static void printRemotePlaceholder(Oid paramtype, int32 paramtypmod, deparse_expr_cxt *context); +static void deparseSelectSql(List *tlist, bool is_subquery, List **retrieved_attrs, deparse_expr_cxt *context); +static void deparseLockingClause(deparse_expr_cxt *context); +static void appendFunctionName(Oid funcid, deparse_expr_cxt *context); +static void deparseFromExpr(List *quals, deparse_expr_cxt *context); +static void deparseSubqueryTargetList(deparse_expr_cxt *context); +static void deparseExplicitTargetList(List *tlist, bool is_returning, List **retrieved_attrs, deparse_expr_cxt *context); +static void appendConditions(List *exprs, deparse_expr_cxt *context); +static void deparseFromExprForRel(StringInfo buf, PlannerInfo *root, RelOptInfo *foreignrel, bool use_alias, + Index ignore_rel, List **ignore_conds, List **params_list); +static void deparseRangeTblRef(StringInfo buf, PlannerInfo *root, RelOptInfo *foreignrel, bool make_subquery, + Index ignore_rel, List **ignore_conds, List **params_list); +static void appendOrderBySuffix(Oid sortop, Oid sortcoltype, bool nulls_first, deparse_expr_cxt *context); +static void appendAggOrderBy(List *orderList, List *targetList, deparse_expr_cxt *context); +static void appendLimitClause(deparse_expr_cxt *context); +static void appendOrderByClause(List *pathkeys, bool has_final_sort, deparse_expr_cxt *context); +static void appendGroupByClause(List *tlist, deparse_expr_cxt *context); +static Node *deparseSortGroupClause(Index ref, List *tlist, bool force_colno, deparse_expr_cxt *context); +static bool is_subquery_var(Var *node, RelOptInfo *foreignrel, int *relno, int *colno); +static void get_relation_column_alias_ids(Var *node, RelOptInfo *foreignrel, int *relno, int *colno); /* * Examine each qual clause in input_conds, and classify them into two groups, * which are returned as two lists: @@ -159,6 +197,7 @@ bool is_foreign_expr(PlannerInfo *root, RelOptInfo *baserel, Expr *expr) { foreign_glob_cxt glob_cxt; foreign_loc_cxt loc_cxt; + PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *)(baserel->fdw_private); /* * Check that the expression consists of nodes that are safe to execute @@ -166,15 +205,30 @@ bool is_foreign_expr(PlannerInfo *root, RelOptInfo *baserel, Expr *expr) */ glob_cxt.root = root; glob_cxt.foreignrel = baserel; + + /* + * For an upper relation, use relids from its underneath scan relation, + * because the upperrel's own relids currently aren't set to anything + * meaningful by the core code. For other relation, use their own relids. + */ + if (IS_UPPER_REL(baserel)) { + glob_cxt.relids = fpinfo->outerrel->relids; + } else { + glob_cxt.relids = baserel->relids; + } loc_cxt.collation = InvalidOid; loc_cxt.state = FDW_COLLATE_NONE; - if (!foreign_expr_walker((Node *)expr, &glob_cxt, &loc_cxt)) { + if (!foreign_expr_walker((Node *)expr, &glob_cxt, &loc_cxt, NULL)) { return false; } - /* Expressions examined here should be boolean, ie noncollatable */ - Assert(loc_cxt.collation == InvalidOid); - Assert(loc_cxt.state == FDW_COLLATE_NONE); + /* + * If the expression has a valid collation that does not arise from a + * foreign var, the expression can not be sent over. + */ + if (loc_cxt.state == FDW_COLLATE_UNSAFE) { + return false; + } /* * An expression which includes any mutable functions can't be sent over @@ -202,7 +256,7 @@ static bool foreign_expr_walker_var(Node *node, foreign_glob_cxt *glob_cxt, Oid * Param's collation, ie it's not safe for it to have a * non-default collation. */ - if (var->varno == glob_cxt->foreignrel->relid && var->varlevelsup == 0) { + if (bms_is_member(var->varno, glob_cxt->relids) && var->varlevelsup == 0) { /* Var belongs to foreign table * System columns other than ctid should not be sent to * the remote, since we don't make any effort to ensure @@ -235,7 +289,6 @@ static bool foreign_expr_walker_var(Node *node, foreign_glob_cxt *glob_cxt, Oid } return true; - } static bool foreign_expr_walker_const(Node *node, Oid *collation,FDWCollateState * state) @@ -275,7 +328,8 @@ static bool foreign_expr_walker_param(Node *node, Oid *collation,FDWCollateState return true; } -static bool foreign_expr_walker_arrayref(Node *node, foreign_glob_cxt *glob_cxt, foreign_loc_cxt *inner_cxt,Oid *collation,FDWCollateState * state) +static bool foreign_expr_walker_arrayref(Node *node, foreign_glob_cxt *glob_cxt, foreign_loc_cxt *inner_cxt, + Oid *collation,FDWCollateState *state, foreign_loc_cxt *case_arg_cxt) { ArrayRef *ar = (ArrayRef *)node; @@ -289,13 +343,13 @@ static bool foreign_expr_walker_arrayref(Node *node, foreign_glob_cxt *glob_cxt, * subscripts must yield (noncollatable) integers, they won't * affect the inner_cxt state. */ - if (!foreign_expr_walker((Node *)ar->refupperindexpr, glob_cxt, inner_cxt)) { + if (!foreign_expr_walker((Node *)ar->refupperindexpr, glob_cxt, inner_cxt, case_arg_cxt)) { return false; } - if (!foreign_expr_walker((Node *)ar->reflowerindexpr, glob_cxt, inner_cxt)) { + if (!foreign_expr_walker((Node *)ar->reflowerindexpr, glob_cxt, inner_cxt, case_arg_cxt)) { return false; } - if (!foreign_expr_walker((Node *)ar->refexpr, glob_cxt, inner_cxt)) { + if (!foreign_expr_walker((Node *)ar->refexpr, glob_cxt, inner_cxt, case_arg_cxt)) { return false; } /* @@ -316,7 +370,8 @@ static bool foreign_expr_walker_arrayref(Node *node, foreign_glob_cxt *glob_cxt, } -static bool foreign_expr_walker_funcexpr(Node *node, foreign_glob_cxt *glob_cxt, foreign_loc_cxt *inner_cxt,Oid *collation,FDWCollateState * state) +static bool foreign_expr_walker_funcexpr(Node *node, foreign_glob_cxt *glob_cxt, foreign_loc_cxt *inner_cxt, + Oid *collation,FDWCollateState *state, foreign_loc_cxt *case_arg_cxt) { FuncExpr *fe = (FuncExpr *)node; @@ -332,7 +387,7 @@ static bool foreign_expr_walker_funcexpr(Node *node, foreign_glob_cxt *glob_cxt, /* * Recurse to input subexpressions. */ - if (!foreign_expr_walker((Node *)fe->args, glob_cxt, inner_cxt)) { + if (!foreign_expr_walker((Node *)fe->args, glob_cxt, inner_cxt, case_arg_cxt)) { return false; } @@ -365,7 +420,8 @@ static bool foreign_expr_walker_funcexpr(Node *node, foreign_glob_cxt *glob_cxt, return true; } -static bool foreign_expr_walker_opexpr(Node *node, foreign_glob_cxt *glob_cxt, foreign_loc_cxt *inner_cxt,Oid *collation,FDWCollateState * state) +static bool foreign_expr_walker_opexpr_distinctexpr(Node *node, foreign_glob_cxt *glob_cxt, foreign_loc_cxt *inner_cxt, + Oid *collation,FDWCollateState *state, foreign_loc_cxt *case_arg_cxt) { OpExpr *oe = (OpExpr *)node; @@ -381,7 +437,7 @@ static bool foreign_expr_walker_opexpr(Node *node, foreign_glob_cxt *glob_cxt, /* * Recurse to input subexpressions. */ - if (!foreign_expr_walker((Node *)oe->args, glob_cxt, inner_cxt)) { + if (!foreign_expr_walker((Node *)oe->args, glob_cxt, inner_cxt, case_arg_cxt)) { return false; } @@ -409,7 +465,8 @@ static bool foreign_expr_walker_opexpr(Node *node, foreign_glob_cxt *glob_cxt, return true; } -static bool foreign_expr_walker_scalararray_opexpr(Node *node, foreign_glob_cxt *glob_cxt, foreign_loc_cxt *inner_cxt,Oid *collation,FDWCollateState * state) +static bool foreign_expr_walker_scalararray_opexpr(Node *node, foreign_glob_cxt *glob_cxt, foreign_loc_cxt *inner_cxt, + Oid *collation,FDWCollateState *state, foreign_loc_cxt *case_arg_cxt) { ScalarArrayOpExpr *oe = (ScalarArrayOpExpr *)node; /* @@ -422,7 +479,7 @@ static bool foreign_expr_walker_scalararray_opexpr(Node *node, foreign_glob_cxt /* * Recurse to input subexpressions. */ - if (!foreign_expr_walker((Node *)oe->args, glob_cxt, inner_cxt)) { + if (!foreign_expr_walker((Node *)oe->args, glob_cxt, inner_cxt, case_arg_cxt)) { return false; } @@ -442,14 +499,15 @@ static bool foreign_expr_walker_scalararray_opexpr(Node *node, foreign_glob_cxt return true; } -static bool foreign_expr_walker_relabeltype(Node *node, foreign_glob_cxt *glob_cxt, foreign_loc_cxt *inner_cxt,Oid *collation,FDWCollateState * state) +static bool foreign_expr_walker_relabeltype(Node *node, foreign_glob_cxt *glob_cxt, foreign_loc_cxt *inner_cxt, + Oid *collation,FDWCollateState *state, foreign_loc_cxt *case_arg_cxt) { RelabelType *r = (RelabelType *)node; /* * Recurse to input subexpression. */ - if (!foreign_expr_walker((Node *)r->arg, glob_cxt, inner_cxt)) { + if (!foreign_expr_walker((Node *)r->arg, glob_cxt, inner_cxt, case_arg_cxt)) { return false; } @@ -470,14 +528,15 @@ static bool foreign_expr_walker_relabeltype(Node *node, foreign_glob_cxt *glob_c return true; } -static bool foreign_expr_walker_boolexpr(Node *node, foreign_glob_cxt *glob_cxt, foreign_loc_cxt *inner_cxt,Oid *collation,FDWCollateState * state) +static bool foreign_expr_walker_boolexpr(Node *node, foreign_glob_cxt *glob_cxt, foreign_loc_cxt *inner_cxt, + Oid *collation,FDWCollateState *state, foreign_loc_cxt *case_arg_cxt) { BoolExpr *b = (BoolExpr *)node; /* * Recurse to input subexpressions. */ - if (!foreign_expr_walker((Node *)b->args, glob_cxt, inner_cxt)) { + if (!foreign_expr_walker((Node *)b->args, glob_cxt, inner_cxt, case_arg_cxt)) { return false; } @@ -487,14 +546,15 @@ static bool foreign_expr_walker_boolexpr(Node *node, foreign_glob_cxt *glob_cxt, return true; } -static bool foreign_expr_walker_nulltest(Node *node, foreign_glob_cxt *glob_cxt, foreign_loc_cxt *inner_cxt,Oid *collation,FDWCollateState * state) +static bool foreign_expr_walker_nulltest(Node *node, foreign_glob_cxt *glob_cxt, foreign_loc_cxt *inner_cxt, + Oid *collation, FDWCollateState *state, foreign_loc_cxt *case_arg_cxt) { NullTest *nt = (NullTest *)node; /* * Recurse to input subexpressions. */ - if (!foreign_expr_walker((Node *)nt->arg, glob_cxt, inner_cxt)) { + if (!foreign_expr_walker((Node *)nt->arg, glob_cxt, inner_cxt, case_arg_cxt)) { return false; } @@ -504,14 +564,136 @@ static bool foreign_expr_walker_nulltest(Node *node, foreign_glob_cxt *glob_cxt, return true; } -static bool foreign_expr_walker_arrayexpr(Node *node, foreign_glob_cxt *glob_cxt, foreign_loc_cxt *inner_cxt,Oid *collation,FDWCollateState * state) +static bool foreign_expr_walker_caseexpr(Node *node, foreign_glob_cxt *glob_cxt, foreign_loc_cxt *inner_cxt, + Oid *collation, FDWCollateState *state, foreign_loc_cxt *case_arg_cxt) +{ + CaseExpr *ce = (CaseExpr *)node; + foreign_loc_cxt arg_cxt; + foreign_loc_cxt tmp_cxt; + ListCell *lc = NULL; + + /* + * Recurse to CASE's arg expression, if any. Its collation + * has to be saved aside for use while examining CaseTestExprs + * within the WHEN expressions. + */ + arg_cxt.collation = InvalidOid; + arg_cxt.state = FDW_COLLATE_NONE; + if (ce->arg) { + if (!foreign_expr_walker((Node *)ce->arg, glob_cxt, &arg_cxt, case_arg_cxt)) { + return false; + } + } + + /* Examine the CaseWhen subexpressions. */ + foreach (lc, ce->args) { + CaseWhen *cw = lfirst_node(CaseWhen, lc); + + if (ce->arg) { + /* + * In a CASE-with-arg, the parser should have produced + * WHEN clauses of the form "CaseTestExpr = RHS", + * possibly with an implicit coercion inserted above + * the CaseTestExpr. However in an expression that's + * been through the optimizer, the WHEN clause could + * be almost anything (since the equality operator + * could have been expanded into an inline function). + * In such cases forbid pushdown, because + * deparseCaseExpr can't handle it. + */ + Node *whenExpr = (Node *)cw->expr; + List *opArgs = NULL; + + if (!IsA(whenExpr, OpExpr)) { + return false; + } + + opArgs = ((OpExpr *)whenExpr)->args; + if (list_length(opArgs) != 2 || !IsA(strip_implicit_coercions((Node*)linitial(opArgs)), CaseTestExpr)) { + return false; + } + } + + /* + * Recurse to WHEN expression, passing down the arg info. + * Its collation doesn't affect the result (really, it + * should be boolean and thus not have a collation). + */ + tmp_cxt.collation = InvalidOid; + tmp_cxt.state = FDW_COLLATE_NONE; + if (!foreign_expr_walker((Node *)cw->expr, glob_cxt, &tmp_cxt, &arg_cxt)) { + return false; + } + + /* Recurse to THEN expression. */ + if (!foreign_expr_walker((Node *)cw->result, glob_cxt, inner_cxt, case_arg_cxt)) { + return false; + } + } + + /* Recurse to ELSE expression. */ + if (!foreign_expr_walker((Node *)ce->defresult, glob_cxt, inner_cxt, case_arg_cxt)) { + return false; + } + + /* + * Detect whether node is introducing a collation not derived + * from a foreign Var. (If so, we just mark it unsafe for now + * rather than immediately returning false, since the parent + * node might not care.) This is the same as for function + * nodes, except that the input collation is derived from only + * the THEN and ELSE subexpressions. + */ + *collation = ce->casecollid; + if (*collation == InvalidOid) { + *state = FDW_COLLATE_NONE; + } else if (inner_cxt->state == FDW_COLLATE_SAFE && *collation == inner_cxt->collation) { + *state = FDW_COLLATE_SAFE; + } else if (*collation == DEFAULT_COLLATION_OID) { + *state = FDW_COLLATE_NONE; + } else { + *state = FDW_COLLATE_UNSAFE; + } + return true; +} + +static bool foreign_expr_walker_casetestexpr(Node *node, foreign_glob_cxt *glob_cxt, foreign_loc_cxt *inner_cxt, + Oid *collation, FDWCollateState *state, foreign_loc_cxt *case_arg_cxt) +{ + CaseTestExpr *c = (CaseTestExpr *)node; + + /* Punt if we seem not to be inside a CASE arg WHEN. */ + if (!case_arg_cxt) { + return false; + } + + /* + * Otherwise, any nondefault collation attached to the + * CaseTestExpr node must be derived from foreign Var(s) in + * the CASE arg. + */ + *collation = c->collation; + if (*collation == InvalidOid) { + *state = FDW_COLLATE_NONE; + } else if (case_arg_cxt->state == FDW_COLLATE_SAFE && *collation == case_arg_cxt->collation) { + *state = FDW_COLLATE_SAFE; + } else if (*collation == DEFAULT_COLLATION_OID) { + *state = FDW_COLLATE_NONE; + } else { + *state = FDW_COLLATE_UNSAFE; + } + return true; +} + +static bool foreign_expr_walker_arrayexpr(Node *node, foreign_glob_cxt *glob_cxt, foreign_loc_cxt *inner_cxt, + Oid *collation, FDWCollateState *state, foreign_loc_cxt *case_arg_cxt) { ArrayExpr *a = (ArrayExpr *)node; /* * Recurse to input subexpressions. */ - if (!foreign_expr_walker((Node *)a->elements, glob_cxt, inner_cxt)) { + if (!foreign_expr_walker((Node *)a->elements, glob_cxt, inner_cxt, case_arg_cxt)) { return false; } @@ -532,7 +714,8 @@ static bool foreign_expr_walker_arrayexpr(Node *node, foreign_glob_cxt *glob_cxt return true; } -static bool foreign_expr_walker_list(Node *node, foreign_glob_cxt *glob_cxt, foreign_loc_cxt *inner_cxt,Oid *collation,FDWCollateState * state) +static bool foreign_expr_walker_list(Node *node, foreign_glob_cxt *glob_cxt, foreign_loc_cxt *inner_cxt, + Oid *collation,FDWCollateState *state, foreign_loc_cxt *case_arg_cxt) { List *l = (List *)node; ListCell *lc = NULL; @@ -541,7 +724,7 @@ static bool foreign_expr_walker_list(Node *node, foreign_glob_cxt *glob_cxt, fo * Recurse to component subexpressions. */ foreach (lc, l) { - if (!foreign_expr_walker((Node *)lfirst(lc), glob_cxt, inner_cxt)) { + if (!foreign_expr_walker((Node *)lfirst(lc), glob_cxt, inner_cxt, case_arg_cxt)) { return false; } } @@ -555,9 +738,98 @@ static bool foreign_expr_walker_list(Node *node, foreign_glob_cxt *glob_cxt, fo return true; } +static bool foreign_expr_walker_aggref(Node *node, foreign_glob_cxt *glob_cxt, foreign_loc_cxt *inner_cxt, + Oid *collation,FDWCollateState *state, foreign_loc_cxt *case_arg_cxt) +{ + Aggref *agg = (Aggref *)node; + ListCell *lc = NULL; + + /* Not safe to pushdown when not in grouping context */ + if (!IS_UPPER_REL(glob_cxt->foreignrel)) { + return false; + } + + /* As usual, it must be shippable. */ + if (!is_builtin(agg->aggfnoid)) { + return false; + } + + /* + * Recurse to input args. aggdirectargs, aggorder and + * aggdistinct are all present in args, so no need to check + * their shippability explicitly. + */ + foreach (lc, agg->args) { + Node *n = (Node *)lfirst(lc); + + /* If TargetEntry, extract the expression from it */ + if (IsA(n, TargetEntry)) { + TargetEntry *tle = (TargetEntry *)n; + n = (Node *)tle->expr; + } + + if (!foreign_expr_walker(n, glob_cxt, inner_cxt, case_arg_cxt)) { + return false; + } + } + + /* + * For aggorder elements, check whether the sort operator, if + * specified, is shippable or not. + */ + if (agg->aggorder) { + ListCell *lc = NULL; + foreach (lc, agg->aggorder) { + SortGroupClause *srt = (SortGroupClause *)lfirst(lc); + Oid sortcoltype; + TypeCacheEntry *typentry = NULL; + TargetEntry *tle = NULL; + + tle = get_sortgroupref_tle(srt->tleSortGroupRef, agg->args); + sortcoltype = exprType((Node *)tle->expr); + typentry = lookup_type_cache(sortcoltype, TYPECACHE_LT_OPR | TYPECACHE_GT_OPR); + + /* Check shippability of non-default sort operator. */ + if (srt->sortop != typentry->lt_opr && srt->sortop != typentry->gt_opr && !is_builtin(srt->sortop)) { + return false; + } + } + } + + /* Check aggregate filter, but openGauss not support it, so do nothing here. */ + /* + * If aggregate's input collation is not derived from a + * foreign Var, it can't be sent to remote. + */ + if (agg->inputcollid == InvalidOid) { + /* OK, inputs are all noncollatable */; + } else if (inner_cxt->state != FDW_COLLATE_SAFE || agg->inputcollid != inner_cxt->collation) { + return false; + } + + /* + * Detect whether node is introducing a collation not derived + * from a foreign Var. (If so, we just mark it unsafe for now + * rather than immediately returning false, since the parent + * node might not care.) + */ + *collation = agg->aggcollid; + if (*collation == InvalidOid) { + *state = FDW_COLLATE_NONE; + } else if (inner_cxt->state == FDW_COLLATE_SAFE && *collation == inner_cxt->collation) { + *state = FDW_COLLATE_SAFE; + } else if (*collation == DEFAULT_COLLATION_OID) { + *state = FDW_COLLATE_NONE; + } else { + *state = FDW_COLLATE_UNSAFE; + } + + return true; +} + static void foreign_expr_walker_outer_cxt(Oid collation,FDWCollateState state,foreign_loc_cxt *outer_cxt) { - if (state > outer_cxt->state) { + if (state > outer_cxt->state) { /* Override previous parent state */ outer_cxt->collation = collation; outer_cxt->state = state; @@ -591,6 +863,35 @@ static void foreign_expr_walker_outer_cxt(Oid collation,FDWCollateState state,fo } } } +static bool foreign_expr_walker_targetentry(Node *node, foreign_glob_cxt *glob_cxt, foreign_loc_cxt *inner_cxt, + Oid *collation,FDWCollateState *state, foreign_loc_cxt *case_arg_cxt) +{ + TargetEntry* tentry = (TargetEntry*)node; + + /* + * Recurse to input subexpressions. + */ + if (!foreign_expr_walker((Node *)tentry->expr, glob_cxt, inner_cxt, case_arg_cxt)) { + return false; + } + + /* + * ArrayExpr must not introduce a collation not derived from + * an input foreign Var (same logic as for a function). + */ + *collation = inner_cxt->collation; + if (*collation == InvalidOid) { + *state = FDW_COLLATE_NONE; + } else if (inner_cxt->state == FDW_COLLATE_SAFE && *collation == inner_cxt->collation) { + *state = FDW_COLLATE_SAFE; + } else if (*collation == DEFAULT_COLLATION_OID) { + *state = FDW_COLLATE_NONE; + } else { + *state = FDW_COLLATE_UNSAFE; + } + return true; +} + /* * Check if expression is safe to execute remotely, and return true if so. @@ -604,85 +905,87 @@ static void foreign_expr_walker_outer_cxt(Oid collation,FDWCollateState state,fo * pretty close to assign_collations_walker() in parse_collate.c, though we * can assume here that the given expression is valid. */ -static bool foreign_expr_walker(Node *node, foreign_glob_cxt *glob_cxt, foreign_loc_cxt *outer_cxt) +static bool foreign_expr_walker(Node *node, foreign_glob_cxt *glob_cxt, foreign_loc_cxt *outer_cxt, + foreign_loc_cxt *case_arg_cxt) { bool check_type = true; + PgFdwRelationInfo *fpinfo = NULL; foreign_loc_cxt inner_cxt; Oid collation = InvalidOid; FDWCollateState state = FDW_COLLATE_NONE; - bool walker_result = false; + bool walker_result = false; /* Need do nothing for empty subexpressions */ if (node == NULL) { return true; } + /* May need server info from baserel's fdw_private struct */ + fpinfo = (PgFdwRelationInfo *)(glob_cxt->foreignrel->fdw_private); + /* Set up inner_cxt for possible recursion to child nodes */ inner_cxt.collation = InvalidOid; inner_cxt.state = FDW_COLLATE_NONE; switch (nodeTag(node)) { case T_Var: { - - walker_result = foreign_expr_walker_var(node, glob_cxt, &collation, &state); + walker_result = foreign_expr_walker_var(node, glob_cxt, &collation, &state); } break; case T_Const: { - - walker_result = foreign_expr_walker_const(node, &collation, &state); + walker_result = foreign_expr_walker_const(node, &collation, &state); } break; case T_Param: { - walker_result = foreign_expr_walker_param(node, &collation, &state); } break; case T_ArrayRef: { - - walker_result = foreign_expr_walker_arrayref(node, glob_cxt, &inner_cxt,&collation,&state); - + walker_result = foreign_expr_walker_arrayref(node, glob_cxt, &inner_cxt, &collation, &state, case_arg_cxt); } break; case T_FuncExpr: { - - walker_result = foreign_expr_walker_funcexpr(node, glob_cxt, &inner_cxt,&collation,&state); - + walker_result = foreign_expr_walker_funcexpr(node, glob_cxt, &inner_cxt, &collation, &state, case_arg_cxt); } break; case T_OpExpr: case T_DistinctExpr: /* struct-equivalent to OpExpr */ { - - walker_result = foreign_expr_walker_opexpr(node, glob_cxt, &inner_cxt,&collation,&state); - + walker_result = + foreign_expr_walker_opexpr_distinctexpr(node, glob_cxt, &inner_cxt, &collation, &state, case_arg_cxt); } break; case T_ScalarArrayOpExpr: { - - walker_result = foreign_expr_walker_scalararray_opexpr(node, glob_cxt, &inner_cxt,&collation,&state); - + walker_result = + foreign_expr_walker_scalararray_opexpr(node, glob_cxt, &inner_cxt, &collation, &state, case_arg_cxt); } break; case T_RelabelType: { - - walker_result = foreign_expr_walker_relabeltype(node, glob_cxt, &inner_cxt,&collation,&state); - + walker_result = + foreign_expr_walker_relabeltype(node, glob_cxt, &inner_cxt, &collation, &state, case_arg_cxt); } break; case T_BoolExpr: { - - walker_result = foreign_expr_walker_boolexpr(node, glob_cxt, &inner_cxt,&collation,&state); - + walker_result = foreign_expr_walker_boolexpr(node, glob_cxt, &inner_cxt, &collation, &state, case_arg_cxt); } break; case T_NullTest: { - - walker_result = foreign_expr_walker_nulltest(node, glob_cxt, &inner_cxt,&collation,&state); - + walker_result = foreign_expr_walker_nulltest(node, glob_cxt, &inner_cxt, &collation, &state, case_arg_cxt); + } break; + case T_CaseExpr: { + walker_result = foreign_expr_walker_caseexpr(node, glob_cxt, &inner_cxt, &collation, &state, case_arg_cxt); + } break; + case T_CaseTestExpr: { + walker_result = + foreign_expr_walker_casetestexpr(node, glob_cxt, &inner_cxt, &collation, &state, case_arg_cxt); } break; case T_ArrayExpr: { - - walker_result = foreign_expr_walker_arrayexpr(node, glob_cxt, &inner_cxt,&collation,&state); - + walker_result = foreign_expr_walker_arrayexpr(node, glob_cxt, &inner_cxt, &collation, &state, case_arg_cxt); } break; case T_List: { - - walker_result = foreign_expr_walker_list(node, glob_cxt, &inner_cxt,&collation,&state); - + walker_result = foreign_expr_walker_list(node, glob_cxt, &inner_cxt, &collation, &state, case_arg_cxt); /* Don't apply exprType() to the list. */ check_type = false; } break; + case T_Aggref: { + walker_result = foreign_expr_walker_aggref(node, glob_cxt, &inner_cxt, &collation, &state, case_arg_cxt); + } break; + case T_TargetEntry: { + walker_result = + foreign_expr_walker_targetentry(node, glob_cxt, &inner_cxt, &collation, &state, case_arg_cxt); + } + default: /* @@ -713,6 +1016,78 @@ static bool foreign_expr_walker(Node *node, foreign_glob_cxt *glob_cxt, foreign_ return true; } +/* + * Returns true if given expr is something we'd have to send the value of + * to the foreign server. + * + * This should return true when the expression is a shippable node that + * deparseExpr would add to context->params_list. Note that we don't care + * if the expression *contains* such a node, only whether one appears at top + * level. We need this to detect cases where setrefs.c would recognize a + * false match between an fdw_exprs item (which came from the params_list) + * and an entry in fdw_scan_tlist (which we're considering putting the given + * expression into). + */ +bool is_foreign_param(PlannerInfo *root, RelOptInfo *baserel, Expr *expr) +{ + if (expr == NULL) { + return false; + } + + switch (nodeTag(expr)) { + case T_Var: { + /* It would have to be sent unless it's a foreign Var */ + Var *var = (Var *)expr; + PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *)(baserel->fdw_private); + Relids relids; + + if (IS_UPPER_REL(baserel)) { + relids = fpinfo->outerrel->relids; + } else { + relids = baserel->relids; + } + + if (bms_is_member(var->varno, relids) && var->varlevelsup == 0) { + return false; /* foreign Var, so not a param */ + } else { + return true; /* it'd have to be a param */ + } + break; + } + case T_Param: + /* Params always have to be sent to the foreign server */ + return true; + default: + break; + } + return false; +} + +/* + * Returns true if it's safe to push down the sort expression described by + * 'pathkey' to the foreign server. + */ +bool is_foreign_pathkey(PlannerInfo *root, RelOptInfo *baserel, PathKey *pathkey) +{ + EquivalenceClass *pathkey_ec = pathkey->pk_eclass; + + /* + * 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; + } + + /* can't push down the sort if the pathkey's opfamily is not shippable */ + if (!is_builtin(pathkey->pk_opfamily)) { + return false; + } + + /* can push if a suitable EC member exists */ + return (find_em_for_rel(root, pathkey_ec, baserel) != NULL); +} + /* * Return true if given object is one of openGauss's built-in objects. * @@ -731,44 +1106,238 @@ static bool foreign_expr_walker(Node *node, foreign_glob_cxt *glob_cxt, foreign_ * be known to the remote server, if it's of an older version. But keeping * track of that would be a huge exercise. */ -static bool is_builtin(Oid oid) +bool is_builtin(Oid oid) { return (oid < FirstBootstrapObjectId); } +/* + * 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 + * expected to be known on the remote end. + * + * This is almost just format_type_with_typemod(), except that if left to its + * own devices, that function will make schema-qualification decisions based + * on the local search_path, which is wrong. We must schema-qualify all + * type names that are not in pg_catalog. We assume here that built-in types + * are all in pg_catalog and need not be qualified; otherwise, qualify. + */ +static char* deparse_type_name(Oid type_oid, int32 typemod) +{ + if (is_builtin(type_oid)) { + return format_type_with_typemod(type_oid, typemod); + } else { + elog(ERROR, "unsupported data type %u", type_oid); + return NULL; /* keep compiler silence */ + } +} + +/* + * Build the targetlist for given relation to be deparsed as SELECT clause. + * + * The output targetlist contains the columns that need to be fetched from the + * foreign server for the given relation. If foreignrel is an upper relation, + * then the output targetlist can also contain expressions to be evaluated on + * foreign server. + */ +List *build_tlist_to_deparse(RelOptInfo *foreignrel) +{ + List *tlist = NIL; + PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *)foreignrel->fdw_private; + ListCell *lc = NULL; + + /* + * For an upper relation, we have already built the target list while + * checking shippability, so just return that. + */ + if (IS_UPPER_REL(foreignrel)) { + return fpinfo->grouped_tlist; + } + + /* + * We require columns specified in foreignrel->reltarget->exprs and those + * required for evaluating the local conditions. + */ + tlist = add_to_flat_tlist(tlist, pull_var_clause((Node *)foreignrel->reltargetlist, PVC_RECURSE_AGGREGATES, PVC_RECURSE_PLACEHOLDERS)); + foreach (lc, fpinfo->local_conds) { + RestrictInfo *rinfo = (RestrictInfo *)lfirst_node(RestrictInfo, lc); + + tlist = add_to_flat_tlist(tlist, pull_var_clause((Node *)rinfo->clause, PVC_RECURSE_AGGREGATES, PVC_RECURSE_PLACEHOLDERS)); + } + + return tlist; +} + +/* + * Deparse SELECT statement for given relation into buf. + * + * tlist contains the list of desired columns to be fetched from foreign server. + * For a base relation fpinfo->attrs_used is used to construct SELECT clause, + * hence the tlist is ignored for a base relation. + * + * remote_conds is the list of conditions to be deparsed into the WHERE clause + * (or, in the case of upper relations, into the HAVING clause). + * + * If params_list is not NULL, it receives a list of Params and other-relation + * Vars used in the clauses; these values must be transmitted to the remote + * server as parameter values. + * + * If params_list is NULL, we're generating the query for EXPLAIN purposes, + * so Params and other-relation Vars should be replaced by dummy values. + * + * pathkeys is the list of pathkeys to order the result by. + * + * is_subquery is the flag to indicate whether to deparse the specified + * relation as a subquery. + * + * List of columns selected is returned in retrieved_attrs. + */ +void deparseSelectStmtForRel(StringInfo buf, PlannerInfo *root, RelOptInfo *rel, List *tlist, List *remote_conds, + List *pathkeys, bool has_final_sort, bool has_limit, bool is_subquery, List **retrieved_attrs, List **params_list) +{ + deparse_expr_cxt context; + PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *)rel->fdw_private; + List *quals = NIL; + + /* + * We handle relations for foreign tables, joins between those and upper + * relations. + */ + Assert(IS_JOIN_REL(rel) || IS_SIMPLE_REL(rel) || IS_UPPER_REL(rel)); + + /* Fill portions of context common to upper, join and base relation */ + context.buf = buf; + context.root = root; + context.foreignrel = rel; + context.scanrel = IS_UPPER_REL(rel) ? fpinfo->outerrel : rel; + context.params_list = params_list; + + /* Construct SELECT clause */ + deparseSelectSql(tlist, is_subquery, retrieved_attrs, &context); + + /* + * For upper relations, the WHERE clause is built from the remote + * conditions of the underlying scan relation; otherwise, we can use the + * supplied list of remote conditions directly. + */ + if (IS_UPPER_REL(rel)) { + PgFdwRelationInfo *ofpinfo; + + ofpinfo = (PgFdwRelationInfo *)fpinfo->outerrel->fdw_private; + quals = ofpinfo->remote_conds; + } else { + quals = remote_conds; + } + + /* Construct FROM and WHERE clauses */ + deparseFromExpr(quals, &context); + + if (IS_UPPER_REL(rel)) { + /* Append GROUP BY clause */ + appendGroupByClause(tlist, &context); + + /* Append HAVING clause */ + if (remote_conds) { + appendStringInfoString(buf, " HAVING "); + appendConditions(remote_conds, &context); + } + } + + /* Add ORDER BY clause if we found any useful pathkeys */ + if (pathkeys) { + appendOrderByClause(pathkeys, has_final_sort, &context); + } + + /* Add LIMIT clause if necessary */ + if (has_limit) { + appendLimitClause(&context); + } + + /* Add any necessary FOR UPDATE/SHARE. */ + deparseLockingClause(&context); +} /* * Construct a simple SELECT statement that retrieves desired columns * of the specified foreign table, and append it to "buf". The output - * contains just "SELECT ... FROM tablename". + * contains just "SELECT ... ". * * We also create an integer List of the columns being retrieved, which is - * returned to *retrieved_attrs. + * returned to *retrieved_attrs, unless we deparse the specified relation + * as a subquery. + * + * tlist is the list of desired columns. is_subquery is the flag to + * indicate whether to deparse the specified relation as a subquery. + * Read prologue of deparseSelectStmtForRel() for details. */ -void deparseSelectSql(StringInfo buf, PlannerInfo *root, RelOptInfo *baserel, Bitmapset *attrs_used, - List **retrieved_attrs) +static void deparseSelectSql(List *tlist, bool is_subquery, List **retrieved_attrs, deparse_expr_cxt *context) { - RangeTblEntry *rte = planner_rt_fetch(baserel->relid, root); - - /* - * Core code already has some lock on each rel being planned, so we can - * use NoLock here. - */ - Relation rel = heap_open(rte->relid, NoLock); + StringInfo buf = context->buf; + RelOptInfo *foreignrel = context->foreignrel; + PlannerInfo *root = context->root; + PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *)foreignrel->fdw_private; /* * Construct SELECT list */ appendStringInfoString(buf, "SELECT "); - deparseTargetList(buf, rte, baserel->relid, rel, false, attrs_used, retrieved_attrs); - /* - * Construct FROM clause - */ + if (is_subquery) { + /* + * For a relation that is deparsed as a subquery, emit expressions + * specified in the relation's reltarget. Note that since this is for + * the subquery, no need to care about *retrieved_attrs. + */ + deparseSubqueryTargetList(context); + } else if (IS_JOIN_REL(foreignrel) || IS_UPPER_REL(foreignrel)) { + /* + * For a join or upper relation the input tlist gives the list of + * columns required to be fetched from the foreign server. + */ + deparseExplicitTargetList(tlist, false, retrieved_attrs, context); + } else { + /* + * For a base relation fpinfo->attrs_used gives the list of columns + * required to be fetched from the foreign server. + */ + RangeTblEntry *rte = planner_rt_fetch(foreignrel->relid, root); + + /* + * Core code already has some lock on each rel being planned, so we + * can use NoLock here. + */ + Relation rel = heap_open(rte->relid, NoLock); + + deparseTargetList(buf, rte, foreignrel->relid, rel, false, fpinfo->attrs_used, false, retrieved_attrs); + heap_close(rel, NoLock); + } +} + +/* + * Construct a FROM clause and, if needed, a WHERE clause, and append those to + * "buf". + * + * quals is the list of clauses to be included in the WHERE clause. + * (These may or may not include RestrictInfo decoration.) + */ +static void deparseFromExpr(List *quals, deparse_expr_cxt *context) +{ + StringInfo buf = context->buf; + RelOptInfo *scanrel = context->scanrel; + + /* For upper relations, scanrel must be either a joinrel or a baserel */ + Assert(!IS_UPPER_REL(context->foreignrel) || IS_JOIN_REL(scanrel) || IS_SIMPLE_REL(scanrel)); + + /* Construct FROM clause */ appendStringInfoString(buf, " FROM "); - deparseRelation(buf, rel); + deparseFromExprForRel(buf, context->root, scanrel, (bms_membership(scanrel->relids) == BMS_MULTIPLE), (Index)0, + NULL, context->params_list); - heap_close(rel, NoLock); + /* Construct WHERE clause */ + if (quals != NIL) { + appendStringInfoString(buf, " WHERE "); + appendConditions(quals, context); + } } /* @@ -778,21 +1347,25 @@ void deparseSelectSql(StringInfo buf, PlannerInfo *root, RelOptInfo *baserel, Bi * * The tlist text is appended to buf, and we also create an integer List * of the columns being retrieved, which is returned to *retrieved_attrs. + * + * If qualify_col is true, add relation alias before the column name. */ static void deparseTargetList(StringInfo buf, RangeTblEntry *rte, Index rtindex, Relation rel, bool is_returning, - Bitmapset *attrs_used, List **retrieved_attrs) + Bitmapset *attrs_used, bool qualify_col, List **retrieved_attrs) { TupleDesc tupdesc = RelationGetDescr(rel); + bool have_wholerow; + bool first; int i; *retrieved_attrs = NIL; /* If there's a whole-row reference, we'll need all the columns. */ - bool have_wholerow = bms_is_member(0 - FirstLowInvalidHeapAttributeNumber, attrs_used); + have_wholerow = bms_is_member(0 - FirstLowInvalidHeapAttributeNumber, attrs_used); - bool first = true; + first = true; for (i = 1; i <= tupdesc->natts; i++) { - Form_pg_attribute attr = tupdesc->attrs[i - 1]; + Form_pg_attribute attr = TupleDescAttr(tupdesc, i - 1); /* Ignore dropped attributes. */ if (attr->attisdropped) { @@ -806,28 +1379,34 @@ static void deparseTargetList(StringInfo buf, RangeTblEntry *rte, Index rtindex, appendStringInfoString(buf, " RETURNING "); } first = false; - - deparseColumnRef(buf, rtindex, i, rte); - + deparseColumnRef(buf, rtindex, i, rte, qualify_col); *retrieved_attrs = lappend_int(*retrieved_attrs, i); } } /* - * Add ctid if needed. We currently don't support retrieving any other + * Add ctid and tableoid if needed. We currently don't support retrieving any other * system columns. */ - if (bms_is_member(SelfItemPointerAttributeNumber - FirstLowInvalidHeapAttributeNumber, attrs_used)) { - if (!first) { - appendStringInfoString(buf, ", "); - } else if (is_returning) { - appendStringInfoString(buf, " RETURNING "); + const int support_sys_num = 2; // look above. + int syscol[support_sys_num] = {SelfItemPointerAttributeNumber, TableOidAttributeNumber}; + char* syscol_name[support_sys_num] = {"ctid", "tableoid"}; + for (int i = 0; i < support_sys_num; i++) { + if (bms_is_member(syscol[i] - FirstLowInvalidHeapAttributeNumber, attrs_used)) { + if (!first) { + appendStringInfoString(buf, ", "); + } else if (is_returning) { + appendStringInfoString(buf, " RETURNING "); + } + first = false; + + if (qualify_col) { + ADD_REL_QUALIFIER(buf, rtindex); + } + appendStringInfoString(buf, syscol_name[i]); + + *retrieved_attrs = lappend_int(*retrieved_attrs, syscol[i]); } - first = false; - - appendStringInfoString(buf, "ctid"); - - *retrieved_attrs = lappend_int(*retrieved_attrs, SelfItemPointerAttributeNumber); } /* Don't generate bad syntax if no undropped columns */ @@ -836,52 +1415,338 @@ static void deparseTargetList(StringInfo buf, RangeTblEntry *rte, Index rtindex, } } -/* - * Deparse WHERE clauses in given list of RestrictInfos and append them to buf. - * - * baserel is the foreign table we're planning for. - * - * If no WHERE clause already exists in the buffer, is_first should be true. - * - * If params is not NULL, it receives a list of Params and other-relation Vars - * used in the clauses; these values must be transmitted to the remote server - * as parameter values. - * - * If params is NULL, we're generating the query for EXPLAIN purposes, - * so Params and other-relation Vars should be replaced by dummy values. - */ -void appendWhereClause(StringInfo buf, PlannerInfo *root, RelOptInfo *baserel, List *exprs, bool is_first, - List **params) -{ - deparse_expr_cxt context; - int nestlevel; - ListCell *lc = NULL; - if (params) { - *params = NIL; /* initialize result list to empty */ +/* + * Deparse the appropriate locking clause (FOR UPDATE or FOR SHARE) for a + * given relation (context->scanrel). + */ +static void deparseLockingClause(deparse_expr_cxt *context) +{ + StringInfo buf = context->buf; + PlannerInfo *root = context->root; + RelOptInfo *rel = context->scanrel; + PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *)rel->fdw_private; + int relid = -1; + + while ((relid = bms_next_member(rel->relids, relid)) >= 0) { + /* + * Ignore relation if it appears in a lower subquery. Locking clause + * for such a relation is included in the subquery if necessary. + */ + if (bms_is_member(relid, fpinfo->lower_subquery_rels)) { + continue; + } + + /* + * Add FOR UPDATE/SHARE if appropriate. We apply locking during the + * initial row fetch, rather than later on as is done for local + * tables. The extra roundtrips involved in trying to duplicate the + * local semantics exactly don't seem worthwhile (see also comments + * for RowMarkType). + * + * Note: because we actually run the query as a cursor, this assumes + * that DECLARE CURSOR ... FOR UPDATE is supported, which it isn't + * before 8.3. + */ + if (relid == root->parse->resultRelation && + (root->parse->commandType == CMD_UPDATE || root->parse->commandType == CMD_DELETE)) { + /* Relation is UPDATE/DELETE target, so use FOR UPDATE */ + appendStringInfoString(buf, " FOR UPDATE"); + + /* Add the relation alias if we are here for a join relation */ + if (IS_JOIN_REL(rel)) { + appendStringInfo(buf, " OF %s%d", REL_ALIAS_PREFIX, relid); + } + } else { + RowMarkClause *rc = get_parse_rowmark(root->parse, relid); + + if (rc != NULL) { + /* + * Relation is specified as a FOR UPDATE/SHARE target, so handle + * that. + * + * For now, just ignore any [NO] KEY specification, since (a) it's + * not clear what that means for a remote table that we don't have + * complete information about, and (b) it wouldn't work anyway on + * older remote servers. Likewise, we don't worry about NOWAIT. + */ + switch (rc->strength) { + case LCS_FORKEYSHARE: + case LCS_FORSHARE: + appendStringInfoString(buf, " FOR SHARE"); + break; + case LCS_FORNOKEYUPDATE: + case LCS_FORUPDATE: + appendStringInfoString(buf, " FOR UPDATE"); + break; + default: + ereport(ERROR, (errmsg("unknown lock type: %d", rc->strength))); + break; + } + + /* Add the relation alias if we are here for a join relation */ + if (bms_membership(rel->relids) == BMS_MULTIPLE) { + appendStringInfo(buf, " OF %s%d", REL_ALIAS_PREFIX, relid); + } + } + } + } +} + +/* + * Deparse given targetlist and append it to context->buf. + * + * tlist is list of TargetEntry's which in turn contain Var nodes. + * + * retrieved_attrs is the list of continuously increasing integers starting + * from 1. It has same number of entries as tlist. + * + * This is used for both SELECT and RETURNING targetlists; the is_returning + * parameter is true only for a RETURNING targetlist. + */ +static void deparseExplicitTargetList(List *tlist, bool is_returning, List **retrieved_attrs, deparse_expr_cxt *context) +{ + ListCell *lc = NULL; + StringInfo buf = context->buf; + int i = 0; + + *retrieved_attrs = NIL; + + foreach (lc, tlist) { + TargetEntry *tle = lfirst_node(TargetEntry, lc); + + if (i > 0) { + appendStringInfoString(buf, ", "); + } else if (is_returning) { + appendStringInfoString(buf, " RETURNING "); + } + + deparseExpr((Expr *)tle->expr, context); + + *retrieved_attrs = lappend_int(*retrieved_attrs, i + 1); + i++; } - /* Set up context struct for recursion */ - context.root = root; - context.foreignrel = baserel; - context.buf = buf; - context.params_list = params; + if (i == 0 && !is_returning) { + appendStringInfoString(buf, "NULL"); + } +} + +/* + * Emit expressions specified in the given relation's reltarget. + * + * This is used for deparsing the given relation as a subquery. + */ +static void deparseSubqueryTargetList(deparse_expr_cxt *context) +{ + StringInfo buf = context->buf; + RelOptInfo *foreignrel = context->foreignrel; + bool first; + ListCell *lc = NULL; + + /* Should only be called in these cases. */ + Assert(IS_SIMPLE_REL(foreignrel) || IS_JOIN_REL(foreignrel)); + + first = true; + foreach (lc, foreignrel->reltargetlist) { + Node *node = (Node *)lfirst(lc); + + if (!first) { + appendStringInfoString(buf, ", "); + } + first = false; + + deparseExpr((Expr *)node, context); + } + + /* Don't generate bad syntax if no expressions */ + if (first) { + appendStringInfoString(buf, "NULL"); + } +} + +/* + * Construct FROM clause for given relation + * + * The function constructs ... JOIN ... ON ... for join relation. For a base + * relation it just returns schema-qualified tablename, with the appropriate + * alias if so requested. + * + * 'ignore_rel' is either zero or the RT index of a target relation. In the + * latter case the function constructs FROM clause of UPDATE or USING clause + * of DELETE; it deparses the join relation as if the relation never contained + * the target relation, and creates a List of conditions to be deparsed into + * the top-level WHERE clause, which is returned to *ignore_conds. + */ +static void deparseFromExprForRel(StringInfo buf, PlannerInfo *root, RelOptInfo *foreignrel, bool use_alias, + Index ignore_rel, List **ignore_conds, List **params_list) +{ + PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *)foreignrel->fdw_private; + + if (IS_JOIN_REL(foreignrel)) { + StringInfoData join_sql_o; + StringInfoData join_sql_i; + RelOptInfo *outerrel = fpinfo->outerrel; + RelOptInfo *innerrel = fpinfo->innerrel; + bool outerrel_is_target = false; + bool innerrel_is_target = false; + + if (ignore_rel > 0 && bms_is_member(ignore_rel, foreignrel->relids)) { + /* + * If this is an inner join, add joinclauses to *ignore_conds and + * set it to empty so that those can be deparsed into the WHERE + * clause. Note that since the target relation can never be + * within the nullable side of an outer join, those could safely + * be pulled up into the WHERE clause (see foreign_join_ok()). + * Note also that since the target relation is only inner-joined + * to any other relation in the query, all conditions in the join + * tree mentioning the target relation could be deparsed into the + * WHERE clause by doing this recursively. + */ + if (fpinfo->jointype == JOIN_INNER) { + *ignore_conds = list_concat(*ignore_conds, fpinfo->joinclauses); + fpinfo->joinclauses = NIL; + } + + /* + * Check if either of the input relations is the target relation. + */ + if (outerrel->relid == ignore_rel) { + outerrel_is_target = true; + } else if (innerrel->relid == ignore_rel) { + innerrel_is_target = true; + } + } + + /* Deparse outer relation if not the target relation. */ + if (!outerrel_is_target) { + initStringInfo(&join_sql_o); + deparseRangeTblRef(&join_sql_o, root, outerrel, fpinfo->make_outerrel_subquery, ignore_rel, ignore_conds, + params_list); + + /* + * If inner relation is the target relation, skip deparsing it. + * Note that since the join of the target relation with any other + * relation in the query is an inner join and can never be within + * the nullable side of an outer join, the join could be + * interchanged with higher-level joins (cf. identity 1 on outer + * join reordering shown in src/backend/optimizer/README), which + * means it's safe to skip the target-relation deparsing here. + */ + if (innerrel_is_target) { + Assert(fpinfo->jointype == JOIN_INNER); + Assert(fpinfo->joinclauses == NIL); + appendBinaryStringInfo(buf, join_sql_o.data, join_sql_o.len); + return; + } + } + + /* Deparse inner relation if not the target relation. */ + if (!innerrel_is_target) { + initStringInfo(&join_sql_i); + deparseRangeTblRef(&join_sql_i, root, innerrel, fpinfo->make_innerrel_subquery, ignore_rel, ignore_conds, + params_list); + + /* + * If outer relation is the target relation, skip deparsing it. + * See the above note about safety. + */ + if (outerrel_is_target) { + Assert(fpinfo->jointype == JOIN_INNER); + Assert(fpinfo->joinclauses == NIL); + appendBinaryStringInfo(buf, join_sql_i.data, join_sql_i.len); + return; + } + } + + /* Neither of the relations is the target relation. */ + Assert(!outerrel_is_target && !innerrel_is_target); + + /* + * For a join relation FROM clause entry is deparsed as + * + * ((outer relation) (inner relation) ON (joinclauses)) + */ + appendStringInfo(buf, "(%s %s JOIN %s ON ", join_sql_o.data, get_jointype_name(fpinfo->jointype), + join_sql_i.data); + + /* Append join clause; (TRUE) if no join clause */ + if (fpinfo->joinclauses) { + deparse_expr_cxt context; + + context.buf = buf; + context.foreignrel = foreignrel; + context.scanrel = foreignrel; + context.root = root; + context.params_list = params_list; + + appendStringInfoChar(buf, '('); + appendConditions(fpinfo->joinclauses, &context); + appendStringInfoChar(buf, ')'); + } else { + appendStringInfoString(buf, "(TRUE)"); + } + + /* End the FROM clause entry. */ + appendStringInfoChar(buf, ')'); + } else { + RangeTblEntry *rte = planner_rt_fetch(foreignrel->relid, root); + + /* + * Core code already has some lock on each rel being planned, so we + * can use NoLock here. + */ + Relation rel = heap_open(rte->relid, NoLock); + + deparseRelation(buf, rel); + + /* + * Add a unique alias to avoid any conflict in relation names due to + * pulled up subqueries in the query being built for a pushed down + * join. + */ + if (use_alias) { + appendStringInfo(buf, " %s%d", REL_ALIAS_PREFIX, foreignrel->relid); + } + + heap_close(rel, NoLock); + } +} + +/* + * Deparse conditions from the provided list and append them to buf. + * + * The conditions in the list are assumed to be ANDed. This function is used to + * deparse WHERE clauses, JOIN .. ON clauses and HAVING clauses. + * + * Depending on the caller, the list elements might be either RestrictInfos + * or bare clauses. + */ +static void appendConditions(List *exprs, deparse_expr_cxt *context) +{ + int nestlevel; + ListCell *lc = NULL; + bool is_first = true; + StringInfo buf = context->buf; /* Make sure any constants in the exprs are printed portably */ nestlevel = set_transmission_modes(); foreach (lc, exprs) { - RestrictInfo *ri = (RestrictInfo *)lfirst(lc); + Expr *expr = (Expr *)lfirst(lc); + + /* Extract clause from RestrictInfo, if required */ + if (IsA(expr, RestrictInfo)) { + expr = ((RestrictInfo *)expr)->clause; + } /* Connect expressions with "AND" and parenthesize each condition. */ - if (is_first) { - appendStringInfoString(buf, " WHERE "); - } else { + if (!is_first) { appendStringInfoString(buf, " AND "); } appendStringInfoChar(buf, '('); - deparseExpr(ri->clause, &context); + deparseExpr(expr, context); appendStringInfoChar(buf, ')'); is_first = false; @@ -890,6 +1755,91 @@ void appendWhereClause(StringInfo buf, PlannerInfo *root, RelOptInfo *baserel, L reset_transmission_modes(nestlevel); } + +/* Output join name for given join type */ +const char *get_jointype_name(JoinType jointype) +{ + switch (jointype) { + case JOIN_INNER: + return "INNER"; + + case JOIN_LEFT: + return "LEFT"; + + case JOIN_RIGHT: + return "RIGHT"; + + case JOIN_FULL: + return "FULL"; + + default: + /* Shouldn't come here, but protect from buggy code. */ + elog(ERROR, "unsupported join type %d", jointype); + } + + /* Keep compiler happy */ + return NULL; +} + + +/* + * Append FROM clause entry for the given relation into buf. + */ +static void deparseRangeTblRef(StringInfo buf, PlannerInfo *root, RelOptInfo *foreignrel, bool make_subquery, + Index ignore_rel, List **ignore_conds, List **params_list) +{ + PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *)foreignrel->fdw_private; + + /* Should only be called in these cases. */ + Assert(IS_SIMPLE_REL(foreignrel) || IS_JOIN_REL(foreignrel)); + + Assert(fpinfo->local_conds == NIL); + + /* If make_subquery is true, deparse the relation as a subquery. */ + if (make_subquery) { + List *retrieved_attrs = NIL; + int ncols; + + /* + * The given relation shouldn't contain the target relation, because + * this should only happen for input relations for a full join, and + * such relations can never contain an UPDATE/DELETE target. + */ + Assert(ignore_rel == 0 || !bms_is_member(ignore_rel, foreignrel->relids)); + + /* Deparse the subquery representing the relation. */ + appendStringInfoChar(buf, '('); + deparseSelectStmtForRel(buf, root, foreignrel, NIL, fpinfo->remote_conds, NIL, false, false, true, + &retrieved_attrs, params_list); + appendStringInfoChar(buf, ')'); + + /* Append the relation alias. */ + appendStringInfo(buf, " %s%d", SUBQUERY_REL_ALIAS_PREFIX, fpinfo->relation_index); + + /* + * Append the column aliases if needed. Note that the subquery emits + * expressions specified in the relation's reltarget (see + * deparseSubqueryTargetList). + */ + ncols = list_length(foreignrel->reltargetlist); + if (ncols > 0) { + int i; + + appendStringInfoChar(buf, '('); + for (i = 1; i <= ncols; i++) { + if (i > 1) { + appendStringInfoString(buf, ", "); + } + + appendStringInfo(buf, "%s%d", SUBQUERY_COL_ALIAS_PREFIX, i); + } + appendStringInfoChar(buf, ')'); + } + } else { + deparseFromExprForRel(buf, root, foreignrel, true, ignore_rel, ignore_conds, params_list); + } +} + /* * deparse remote INSERT statement * @@ -918,7 +1868,7 @@ void deparseInsertSql(StringInfo buf, RangeTblEntry *rte, Index rtindex, Relatio } first = false; - deparseColumnRef(buf, rtindex, attnum, rte); + deparseColumnRef(buf, rtindex, attnum, rte, false); } appendStringInfoString(buf, ") VALUES ("); @@ -961,7 +1911,7 @@ void deparseUpdateSql(StringInfo buf, RangeTblEntry *rte, Index rtindex, Relatio deparseRelation(buf, rel); appendStringInfoString(buf, " SET "); - pindex = 2; /* ctid is always the first param */ + pindex = 3; /* ctid and tableoid is always the first and second param */ bool first = true; foreach (lc, targetAttrs) { int attnum = lfirst_int(lc); @@ -971,11 +1921,11 @@ void deparseUpdateSql(StringInfo buf, RangeTblEntry *rte, Index rtindex, Relatio } first = false; - deparseColumnRef(buf, rtindex, attnum, rte); + deparseColumnRef(buf, rtindex, attnum, rte, false); appendStringInfo(buf, " = $%d", pindex); pindex++; } - appendStringInfoString(buf, " WHERE ctid = $1"); + appendStringInfoString(buf, " WHERE ctid = $1 and tableoid = $2"); deparseReturningList(buf, rte, rtindex, rel, rel->trigdesc && rel->trigdesc->trig_update_after_row, returningList, retrieved_attrs); @@ -993,7 +1943,7 @@ void deparseDeleteSql(StringInfo buf, RangeTblEntry *rte, Index rtindex, Relatio { appendStringInfoString(buf, "DELETE FROM "); deparseRelation(buf, rel); - appendStringInfoString(buf, " WHERE ctid = $1"); + appendStringInfoString(buf, " WHERE ctid = $1 and tableoid = $2"); deparseReturningList(buf, rte, rtindex, rel, rel->trigdesc && rel->trigdesc->trig_delete_after_row, returningList, retrieved_attrs); @@ -1021,7 +1971,7 @@ static void deparseReturningList(StringInfo buf, RangeTblEntry *rte, Index rtind } if (attrs_used != NULL) { - deparseTargetList(buf, rte, rtindex, rel, true, attrs_used, retrieved_attrs); + deparseTargetList(buf, rte, rtindex, rel, true, attrs_used, false, retrieved_attrs); } else { *retrieved_attrs = NIL; } @@ -1109,38 +2059,119 @@ void deparseAnalyzeSql(StringInfo buf, Relation rel, List **retrieved_attrs) /* * Construct name to use for given column, and emit it into buf. * If it has a column_name FDW option, use that instead of attribute name. + * + * If qualify_col is true, qualify column name with the alias of relation. */ -static void deparseColumnRef(StringInfo buf, Index varno, int varattno, RangeTblEntry *rte) +static void deparseColumnRef(StringInfo buf, int varno, int varattno, RangeTblEntry *rte, bool qualify_col) { - char *colname = NULL; - ListCell *lc = NULL; - - /* varno must not be any of OUTER_VAR, INNER_VAR and INDEX_VAR. */ - Assert(!IS_SPECIAL_VARNO(varno)); - - /* - * If it's a column of a foreign table, and it has the column_name FDW - * option, use that value. - */ - List* options = GetForeignColumnOptions(rte->relid, varattno); - foreach (lc, options) { - DefElem *def = (DefElem *)lfirst(lc); - - if (strcmp(def->defname, "column_name") == 0) { - colname = defGetString(def); - break; + /* We support fetching the remote side's CTID and OID. */ + if (varattno == SelfItemPointerAttributeNumber) { + if (qualify_col) { + ADD_REL_QUALIFIER(buf, varno); } - } + appendStringInfoString(buf, "ctid"); + } else if (varattno < 0) { + /* + * All other system attributes are fetched as 0, except for table OID, + * which is fetched as the local table OID. However, we must be + * careful; the table could be beneath an outer join, in which case it + * must go to NULL whenever the rest of the row does. + */ + Oid fetchval = 0; - /* - * If it's a column of a regular table or it doesn't have column_name FDW - * option, use attribute name. - */ - if (colname == NULL) { - colname = get_relid_attribute_name(rte->relid, varattno); - } + if (varattno == TableOidAttributeNumber) { + fetchval = rte->relid; + } - appendStringInfoString(buf, quote_identifier(colname)); + if (qualify_col) { + appendStringInfoString(buf, "CASE WHEN ("); + ADD_REL_QUALIFIER(buf, varno); + appendStringInfo(buf, "*)::text IS NOT NULL THEN %u END", fetchval); + } else { + appendStringInfo(buf, "%u", fetchval); + } + } else if (varattno == 0) { + /* Whole row reference */ + Relation rel; + Bitmapset *attrs_used; + + /* Required only to be passed down to deparseTargetList(). */ + List *retrieved_attrs; + + /* + * The lock on the relation will be held by upper callers, so it's + * fine to open it with no lock here. + */ + rel = heap_open(rte->relid, NoLock); + + /* + * The local name of the foreign table can not be recognized by the + * foreign server and the table it references on foreign server might + * have different column ordering or different columns than those + * declared locally. Hence we have to deparse whole-row reference as + * ROW(columns referenced locally). Construct this by deparsing a + * "whole row" attribute. + */ + attrs_used = bms_add_member(NULL, 0 - FirstLowInvalidHeapAttributeNumber); + + /* + * In case the whole-row reference is under an outer join then it has + * to go NULL whenever the rest of the row goes NULL. Deparsing a join + * query would always involve multiple relations, thus qualify_col + * would be true. + */ + if (qualify_col) { + appendStringInfoString(buf, "CASE WHEN ("); + ADD_REL_QUALIFIER(buf, varno); + appendStringInfoString(buf, "*)::text IS NOT NULL THEN "); + } + + appendStringInfoString(buf, "ROW("); + deparseTargetList(buf, rte, varno, rel, false, attrs_used, qualify_col, &retrieved_attrs); + appendStringInfoChar(buf, ')'); + + /* Complete the CASE WHEN statement started above. */ + if (qualify_col) + appendStringInfoString(buf, " END"); + + heap_close(rel, NoLock); + bms_free(attrs_used); + } else { + char *colname = NULL; + List *options; + ListCell *lc; + + /* varno must not be any of OUTER_VAR, INNER_VAR and INDEX_VAR. */ + Assert(!IS_SPECIAL_VARNO(varno)); + + /* + * If it's a column of a foreign table, and it has the column_name FDW + * option, use that value. + */ + options = GetForeignColumnOptions(rte->relid, varattno); + foreach (lc, options) { + DefElem *def = (DefElem *)lfirst(lc); + + if (strcmp(def->defname, "column_name") == 0) { + colname = defGetString(def); + break; + } + } + + /* + * If it's a column of a regular table or it doesn't have column_name + * FDW option, use attribute name. + */ + if (colname == NULL) { + colname = get_attname(rte->relid, varattno); + } + + if (qualify_col) { + ADD_REL_QUALIFIER(buf, varno); + } + + appendStringInfoString(buf, quote_identifier(colname)); + } } /* @@ -1152,7 +2183,7 @@ static void deparseRelation(StringInfo buf, Relation rel) { const char *nspname = NULL; const char *relname = NULL; - ListCell *lc; + ListCell *lc = NULL; /* obtain additional catalog information. */ ForeignTable* table = GetForeignTable(RelationGetRelid(rel)); @@ -1263,7 +2294,7 @@ static void deparseExpr(Expr *node, deparse_expr_cxt *context) deparseVar((Var *)node, context); break; case T_Const: - deparseConst((Const *)node, context); + deparseConst((Const *) node, context, 0); break; case T_Param: deparseParam((Param *)node, context); @@ -1292,9 +2323,15 @@ static void deparseExpr(Expr *node, deparse_expr_cxt *context) case T_NullTest: deparseNullTest((NullTest *)node, context); break; + case T_CaseExpr: + deparseCaseExpr((CaseExpr *)node, context); + break; case T_ArrayExpr: deparseArrayExpr((ArrayExpr *)node, context); break; + case T_Aggref: + deparseAggref((Aggref *)node, context); + break; default: elog(ERROR, "unsupported expression type for deparse: %d", (int)nodeTag(node)); break; @@ -1311,16 +2348,31 @@ static void deparseExpr(Expr *node, deparse_expr_cxt *context) */ static void deparseVar(Var *node, deparse_expr_cxt *context) { - StringInfo buf = context->buf; + Relids relids = context->scanrel->relids; + int relno; + int colno; - if (node->varno == context->foreignrel->relid && node->varlevelsup == 0) { - /* Var belongs to foreign table */ - deparseColumnRef(buf, node->varno, node->varattno, planner_rt_fetch(node->varno, context->root)); - } else { + /* Qualify columns when multiple relations are involved. */ + bool qualify_col = (bms_membership(relids) == BMS_MULTIPLE); + + /* + * If the Var belongs to the foreign relation that is deparsed as a + * subquery, use the relation and column alias to the Var provided by the + * subquery, instead of the remote name. + */ + if (is_subquery_var(node, context->scanrel, &relno, &colno)) { + appendStringInfo(context->buf, "%s%d.%s%d", SUBQUERY_REL_ALIAS_PREFIX, relno, SUBQUERY_COL_ALIAS_PREFIX, colno); + return; + } + + if (bms_is_member(node->varno, relids) && node->varlevelsup == 0) + deparseColumnRef(context->buf, node->varno, node->varattno, planner_rt_fetch(node->varno, context->root), + qualify_col); + else { /* Treat like a Param */ if (context->params_list) { int pindex = 0; - ListCell *lc = NULL; + ListCell *lc; /* find its index in params_list */ foreach (lc, *context->params_list) { @@ -1346,23 +2398,35 @@ static void deparseVar(Var *node, deparse_expr_cxt *context) * Deparse given constant value into context->buf. * * This function has to be kept in sync with ruleutils.c's get_const_expr. + * + * As in that function, showtype can be -1 to never show "::typename" + * decoration, +1 to always show it, or 0 to show it only if the constant + * wouldn't be assumed to be the right type by default. + * + * In addition, this code allows showtype to be -2 to indicate that we should + * not show "::typename" decoration if the constant is printed as an untyped + * literal or NULL (while in other cases, behaving as for showtype == 0). */ -static void deparseConst(Const *node, deparse_expr_cxt *context) +static void deparseConst(Const *node, deparse_expr_cxt *context, int showtype) { StringInfo buf = context->buf; Oid typoutput; bool typIsVarlena; + char *extval = NULL; bool isfloat = false; - bool needlabel = false; + bool isstring = false; + bool needlabel; if (node->constisnull) { appendStringInfoString(buf, "NULL"); - appendStringInfo(buf, "::%s", format_type_with_typemod(node->consttype, node->consttypmod)); + if (showtype >= 0) { + appendStringInfo(buf, "::%s", deparse_type_name(node->consttype, node->consttypmod)); + } return; } getTypeOutputInfo(node->consttype, &typoutput, &typIsVarlena); - char *extval = OidOutputFunctionCall(typoutput, node->constvalue); + extval = OidOutputFunctionCall(typoutput, node->constvalue); switch (node->consttype) { case INT2OID: @@ -1402,12 +2466,19 @@ static void deparseConst(Const *node, deparse_expr_cxt *context) break; default: deparseStringLiteral(buf, extval); + isstring = true; break; } + pfree(extval); + + if (showtype == -1) { + return; /* never print type label */ + } + /* - * Append ::typename unless the constant will be implicitly typed as the - * right type when it is read in. + * For showtype == 0, append ::typename unless the constant will be + * implicitly typed as the right type when it is read in. * * XXX this code has to be kept in sync with the behavior of the parser, * especially make_const. @@ -1422,11 +2493,16 @@ static void deparseConst(Const *node, deparse_expr_cxt *context) needlabel = !isfloat || (node->consttypmod >= 0); break; default: - needlabel = true; + if (showtype == -2) { + /* label unless we printed it as an untyped string */ + needlabel = !isstring; + } else { + needlabel = true; + } break; } - if (needlabel) { - appendStringInfo(buf, "::%s", format_type_with_typemod(node->consttype, node->consttypmod)); + if (needlabel || showtype > 0) { + appendStringInfo(buf, "::%s", deparse_type_name(node->consttype, node->consttypmod)); } } @@ -1442,7 +2518,7 @@ static void deparseParam(Param *node, deparse_expr_cxt *context) { if (context->params_list) { int pindex = 0; - ListCell *lc; + ListCell *lc = NULL; /* find its index in params_list */ foreach (lc, *context->params_list) { @@ -1783,6 +2859,50 @@ static void deparseNullTest(NullTest *node, deparse_expr_cxt *context) } } +/* + * Deparse CASE expression + */ +static void deparseCaseExpr(CaseExpr *node, deparse_expr_cxt *context) +{ + StringInfo buf = context->buf; + ListCell *lc = NULL; + + appendStringInfoString(buf, "(CASE"); + + /* If this is a CASE arg WHEN then emit the arg expression */ + if (node->arg != NULL) { + appendStringInfoChar(buf, ' '); + deparseExpr(node->arg, context); + } + + /* Add each condition/result of the CASE clause */ + foreach (lc, node->args) { + CaseWhen *whenclause = (CaseWhen *)lfirst(lc); + + /* WHEN */ + appendStringInfoString(buf, " WHEN "); + if (node->arg == NULL) { /* CASE WHEN */ + deparseExpr(whenclause->expr, context); + } else { /* CASE arg WHEN */ + /* Ignore the CaseTestExpr and equality operator. */ + deparseExpr((Expr*)lsecond(castNode(OpExpr, whenclause->expr)->args), context); + } + + /* THEN */ + appendStringInfoString(buf, " THEN "); + deparseExpr(whenclause->result, context); + } + + /* add ELSE if present */ + if (node->defresult != NULL) { + appendStringInfoString(buf, " ELSE "); + deparseExpr(node->defresult, context); + } + + /* append END */ + appendStringInfoString(buf, " END)"); +} + /* * Deparse ARRAY[...] construct. */ @@ -1808,6 +2928,148 @@ static void deparseArrayExpr(ArrayExpr *node, deparse_expr_cxt *context) } } +/* + * Deparse an Aggref node. + */ +static void deparseAggref(Aggref *node, deparse_expr_cxt *context) +{ + StringInfo buf = context->buf; + bool use_variadic; + + /* Check if need to print VARIADIC (cf. ruleutils.c) */ + use_variadic = node->aggvariadic; + + /* Find aggregate name from aggfnoid which is a pg_proc entry */ + appendFunctionName(node->aggfnoid, context); + appendStringInfoChar(buf, '('); + + /* Add DISTINCT */ + appendStringInfoString(buf, (node->aggdistinct != NIL) ? "DISTINCT " : ""); + + if (AGGKIND_IS_ORDERED_SET(node->aggkind)) { + /* Add WITHIN GROUP (ORDER BY ..) */ + ListCell *arg; + bool first = true; + + Assert(!node->aggvariadic); + Assert(node->aggorder != NIL); + + foreach (arg, node->aggdirectargs) { + if (!first) { + appendStringInfoString(buf, ", "); + } + first = false; + + deparseExpr((Expr *)lfirst(arg), context); + } + + appendStringInfoString(buf, ") WITHIN GROUP (ORDER BY "); + appendAggOrderBy(node->aggorder, node->args, context); + } else { + /* aggstar can be set only in zero-argument aggregates */ + if (node->aggstar) { + appendStringInfoChar(buf, '*'); + } else { + ListCell *arg = NULL; + bool first = true; + + /* Add all the arguments */ + foreach (arg, node->args) { + TargetEntry *tle = (TargetEntry *)lfirst(arg); + Node *n = (Node *)tle->expr; + + if (tle->resjunk) { + continue; + } + + if (!first) { + appendStringInfoString(buf, ", "); + } + first = false; + + /* Add VARIADIC */ + if (use_variadic && lnext(arg) == NULL) { + appendStringInfoString(buf, "VARIADIC "); + } + + deparseExpr((Expr *)n, context); + } + } + + /* Add ORDER BY */ + if (node->aggorder != NIL) { + appendStringInfoString(buf, " ORDER BY "); + appendAggOrderBy(node->aggorder, node->args, context); + } + } + + appendStringInfoChar(buf, ')'); +} + +/* + * Append ORDER BY within aggregate function. + */ +static void appendAggOrderBy(List *orderList, List *targetList, deparse_expr_cxt *context) +{ + StringInfo buf = context->buf; + ListCell *lc = NULL; + bool first = true; + + foreach (lc, orderList) { + SortGroupClause *srt = (SortGroupClause *)lfirst(lc); + Node *sortexpr = NULL; + + if (!first) { + appendStringInfoString(buf, ", "); + } + first = false; + + /* Deparse the sort expression proper. */ + sortexpr = deparseSortGroupClause(srt->tleSortGroupRef, targetList, false, context); + /* Add decoration as needed. */ + appendOrderBySuffix(srt->sortop, exprType(sortexpr), srt->nulls_first, context); + } +} + +/* + * Append the ASC, DESC, USING and NULLS FIRST / NULLS LAST parts + * of an ORDER BY clause. + */ +static void appendOrderBySuffix(Oid sortop, Oid sortcoltype, bool nulls_first, deparse_expr_cxt *context) +{ + StringInfo buf = context->buf; + TypeCacheEntry *typentry = NULL; + + /* See whether operator is default < or > for sort expr's datatype. */ + 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); + } + + if (nulls_first) { + appendStringInfoString(buf, " NULLS FIRST"); + } else { + appendStringInfoString(buf, " NULLS LAST"); + } +} + /* * Print the representation of a parameter to be sent to the remote side. * @@ -1848,3 +3110,289 @@ static void printRemotePlaceholder(Oid paramtype, int32 paramtypmod, deparse_exp appendStringInfo(buf, "((SELECT null::%s)::%s)", ptypename, ptypename); } +/* + * Deparse GROUP BY clause. + */ +static void appendGroupByClause(List *tlist, deparse_expr_cxt *context) +{ + StringInfo buf = context->buf; + Query *query = context->root->parse; + ListCell *lc = NULL; + bool first = true; + + /* Nothing to be done, if there's no GROUP BY clause in the query. */ + if (!query->groupClause) { + return; + } + + appendStringInfoString(buf, " GROUP BY "); + + /* + * Queries with grouping sets are not pushed down, so we don't expect + * grouping sets here. + */ + Assert(!query->groupingSets); + + foreach (lc, query->groupClause) { + SortGroupClause *grp = (SortGroupClause *)lfirst(lc); + + if (!first) { + appendStringInfoString(buf, ", "); + } + first = false; + + deparseSortGroupClause(grp->tleSortGroupRef, tlist, true, context); + } +} + +/* + * Deparse ORDER BY clause defined by the given pathkeys. + * + * The clause should use Vars from context->scanrel if !has_final_sort, + * or from context->foreignrel's targetlist if has_final_sort. + * + * We find a suitable pathkey expression (some earlier step + * should have verified that there is one) and deparse it. + */ +static void appendOrderByClause(List *pathkeys, bool has_final_sort, deparse_expr_cxt *context) +{ + ListCell *lcell = NULL; + int nestlevel; + const char *delim = " "; + StringInfo buf = context->buf; + + /* Make sure any constants in the exprs are printed portably */ + nestlevel = set_transmission_modes(); + + appendStringInfoString(buf, " ORDER BY"); + foreach (lcell, pathkeys) { + PathKey *pathkey = (PathKey*)lfirst(lcell); + EquivalenceMember *em; + Expr *em_expr; + Oid oprid; + + if (has_final_sort) { + /* + * By construction, context->foreignrel is the input relation to + * the final sort. + */ + em = find_em_for_rel_target(context->root, pathkey->pk_eclass, context->foreignrel); + } else { + em = find_em_for_rel(context->root, pathkey->pk_eclass, context->scanrel); + } + + /* + * We don't expect any error here; it would mean that shippability + * wasn't verified earlier. For the same reason, we don't recheck + * shippability of the sort operator. + */ + if (em == NULL) { + elog(ERROR, "could not find pathkey item to sort"); + } + + em_expr = em->em_expr; + + /* + * 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). + */ + oprid = get_opfamily_member(pathkey->pk_opfamily, em->em_datatype, em->em_datatype, pathkey->pk_strategy); + if (!OidIsValid(oprid)) { + elog(ERROR, "missing operator %d(%u,%u) in opfamily %u", pathkey->pk_strategy, em->em_datatype, + em->em_datatype, pathkey->pk_opfamily); + } + + appendStringInfoString(buf, delim); + deparseExpr(em_expr, context); + + /* + * Here we need to use the expression's actual type to discover + * whether the desired operator will be the default or not. + */ + appendOrderBySuffix(oprid, exprType((Node *)em_expr), pathkey->pk_nulls_first, context); + + delim = ", "; + } + reset_transmission_modes(nestlevel); +} + +/* + * Deparse LIMIT/OFFSET clause. + */ +static void appendLimitClause(deparse_expr_cxt *context) +{ + PlannerInfo *root = context->root; + StringInfo buf = context->buf; + int nestlevel; + + /* Make sure any constants in the exprs are printed portably */ + nestlevel = set_transmission_modes(); + + if (root->parse->limitCount) { + appendStringInfoString(buf, " LIMIT "); + deparseExpr((Expr *)root->parse->limitCount, context); + } + if (root->parse->limitOffset) { + appendStringInfoString(buf, " OFFSET "); + deparseExpr((Expr *)root->parse->limitOffset, context); + } + + reset_transmission_modes(nestlevel); +} + +/* + * appendFunctionName + * Deparses function name from given function oid. + */ +static void appendFunctionName(Oid funcid, deparse_expr_cxt *context) +{ + StringInfo buf = context->buf; + HeapTuple proctup; + Form_pg_proc procform; + const char *proname = NULL; + + proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid)); + if (!HeapTupleIsValid(proctup)) { + elog(ERROR, "cache lookup failed for function %u", funcid); + } + procform = (Form_pg_proc)GETSTRUCT(proctup); + + /* Print schema name only if it's not pg_catalog */ + if (procform->pronamespace != PG_CATALOG_NAMESPACE) { + const char *schemaname = NULL; + + schemaname = get_namespace_name(procform->pronamespace); + appendStringInfo(buf, "%s.", quote_identifier(schemaname)); + } + + /* Always print the function name */ + proname = NameStr(procform->proname); + appendStringInfoString(buf, quote_identifier(proname)); + + ReleaseSysCache(proctup); +} + +/* + * Appends a sort or group clause. + * + * Like get_rule_sortgroupclause(), returns the expression tree, so caller + * need not find it again. + */ +static Node *deparseSortGroupClause(Index ref, List *tlist, bool force_colno, deparse_expr_cxt *context) +{ + StringInfo buf = context->buf; + TargetEntry *tle = NULL; + Expr *expr = NULL; + + tle = get_sortgroupref_tle(ref, tlist); + expr = tle->expr; + + if (force_colno) { + /* Use column-number form when requested by caller. */ + Assert(!tle->resjunk); + appendStringInfo(buf, "%d", tle->resno); + } else if (expr && IsA(expr, Const)) { + /* + * Force a typecast here so that we don't emit something like "GROUP + * BY 2", which will be misconstrued as a column position rather than + * a constant. + */ + deparseConst((Const *)expr, context, 1); + } else if (!expr || IsA(expr, Var)) { + deparseExpr(expr, context); + } else { + /* Always parenthesize the expression. */ + appendStringInfoChar(buf, '('); + deparseExpr(expr, context); + appendStringInfoChar(buf, ')'); + } + + return (Node *)expr; +} + +/* + * Returns true if given Var is deparsed as a subquery output column, in + * which case, *relno and *colno are set to the IDs for the relation and + * column alias to the Var provided by the subquery. + */ +static bool is_subquery_var(Var *node, RelOptInfo *foreignrel, int *relno, int *colno) +{ + PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *)foreignrel->fdw_private; + RelOptInfo *outerrel = fpinfo->outerrel; + RelOptInfo *innerrel = fpinfo->innerrel; + + /* Should only be called in these cases. */ + Assert(IS_SIMPLE_REL(foreignrel) || IS_JOIN_REL(foreignrel)); + + /* + * If the given relation isn't a join relation, it doesn't have any lower + * subqueries, so the Var isn't a subquery output column. + */ + if (!IS_JOIN_REL(foreignrel)) { + return false; + } + + /* + * If the Var doesn't belong to any lower subqueries, it isn't a subquery + * output column. + */ + if (!bms_is_member(node->varno, fpinfo->lower_subquery_rels)) { + return false; + } + + if (bms_is_member(node->varno, outerrel->relids)) { + /* + * If outer relation is deparsed as a subquery, the Var is an output + * column of the subquery; get the IDs for the relation/column alias. + */ + if (fpinfo->make_outerrel_subquery) { + get_relation_column_alias_ids(node, outerrel, relno, colno); + return true; + } + + /* Otherwise, recurse into the outer relation. */ + return is_subquery_var(node, outerrel, relno, colno); + } else { + Assert(bms_is_member(node->varno, innerrel->relids)); + + /* + * If inner relation is deparsed as a subquery, the Var is an output + * column of the subquery; get the IDs for the relation/column alias. + */ + if (fpinfo->make_innerrel_subquery) { + get_relation_column_alias_ids(node, innerrel, relno, colno); + return true; + } + + /* Otherwise, recurse into the inner relation. */ + return is_subquery_var(node, innerrel, relno, colno); + } +} + +/* + * Get the IDs for the relation and column alias to given Var belonging to + * given relation, which are returned into *relno and *colno. + */ +static void get_relation_column_alias_ids(Var *node, RelOptInfo *foreignrel, int *relno, int *colno) +{ + PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *)foreignrel->fdw_private; + int i; + ListCell *lc = NULL; + + /* Get the relation alias ID */ + *relno = fpinfo->relation_index; + + /* Get the column alias ID */ + i = 1; + foreach (lc, foreignrel->reltargetlist) { + if (equal(lfirst(lc), (Node *)node)) { + *colno = i; + return; + } + i++; + } + + /* Shouldn't get here */ + elog(ERROR, "unexpected expression in subquery output"); +} diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out index 7f5b26347..751ba8c6e 100755 --- a/contrib/postgres_fdw/expected/postgres_fdw.out +++ b/contrib/postgres_fdw/expected/postgres_fdw.out @@ -1,124 +1,241 @@ --- =================================================================== --- create FDW objects --- =================================================================== +-- ====================================================================================================================================== +-- README: +-- "--nspt " means the feature that openGauss not supported. We don't remove it directly, leave it for incremental adaptation. +-- +-- The following test cases are available: +-- postgres_fdw : This test case is modified from the commit:164d174bbf9a3aba719c845497863cd3c49a3ad0 of PG14.4. +-- postgres_fdw_cstore : Test column orientation table. +-- postgres_fdw_partition : This is a beta feature in openGauss, open it by "set sql_beta_feature='partition_fdw_on'" +-- ====================================================================================================================================== +create database postgresfdw_test_db; +\c postgresfdw_test_db +set show_fdw_remote_plan = on; +-- ====================================================================================================================================== +-- TEST-MODULE: create FDW objects +-- -------------------------------------- +-- ====================================================================================================================================== CREATE EXTENSION postgres_fdw; CREATE SERVER testserver1 FOREIGN DATA WRAPPER postgres_fdw; -CREATE SERVER loopback FOREIGN DATA WRAPPER postgres_fdw - OPTIONS (dbname 'contrib_regression'); +DO $d$ + BEGIN + EXECUTE $$CREATE SERVER loopback FOREIGN DATA WRAPPER postgres_fdw + OPTIONS (dbname '$$||current_database()||$$', + port '$$||current_setting('port')||$$' + )$$; + EXECUTE $$CREATE SERVER loopback2 FOREIGN DATA WRAPPER postgres_fdw + OPTIONS (dbname '$$||current_database()||$$', + port '$$||current_setting('port')||$$' + )$$; + EXECUTE $$CREATE SERVER loopback3 FOREIGN DATA WRAPPER postgres_fdw + OPTIONS (dbname '$$||current_database()||$$', + port '$$||current_setting('port')||$$' + )$$; + END; +$d$; CREATE USER MAPPING FOR public SERVER testserver1 - OPTIONS (user 'value', password 'value'); + OPTIONS (user 'value', password 'value'); CREATE USER MAPPING FOR CURRENT_USER SERVER loopback; --- =================================================================== --- create objects used through FDW loopback server --- =================================================================== +CREATE USER MAPPING FOR CURRENT_USER SERVER loopback2; +CREATE USER MAPPING FOR public SERVER loopback3; +-- ====================================================================================================================================== +-- TEST-MODULE: create objects used through FDW loopback server +-- -------------------------------------- +-- ====================================================================================================================================== CREATE TYPE user_enum AS ENUM ('foo', 'bar', 'buz'); CREATE SCHEMA "S 1"; CREATE TABLE "S 1"."T 1" ( - "C 1" int NOT NULL, - c2 int NOT NULL, - c3 text, - c4 timestamptz, - c5 timestamp, - c6 varchar(10), - c7 char(10), - c8 user_enum, - CONSTRAINT t1_pkey PRIMARY KEY ("C 1") + "C 1" int NOT NULL, + c2 int NOT NULL, + c3 text, + c4 timestamptz, + c5 timestamp, + c6 varchar(10), + c7 char(10), + c8 user_enum, + CONSTRAINT t1_pkey PRIMARY KEY ("C 1") ); +NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "t1_pkey" for table "T 1" CREATE TABLE "S 1"."T 2" ( - c1 int NOT NULL, - c2 text, - CONSTRAINT t2_pkey PRIMARY KEY (c1) + c1 int NOT NULL, + c2 text, + CONSTRAINT t2_pkey PRIMARY KEY (c1) ); +NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "t2_pkey" for table "T 2" +CREATE TABLE "S 1"."T 3" ( + c1 int NOT NULL, + c2 int NOT NULL, + c3 text, + CONSTRAINT t3_pkey PRIMARY KEY (c1) +); +NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "t3_pkey" for table "T 3" +CREATE TABLE "S 1"."T 4" ( + c1 int NOT NULL, + c2 int NOT NULL, + c3 text, + CONSTRAINT t4_pkey PRIMARY KEY (c1) +); +NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "t4_pkey" for table "T 4" +-- Disable autovacuum for these tables to avoid unexpected effects of that +ALTER TABLE "S 1"."T 1" SET (autovacuum_enabled = 'false'); +ALTER TABLE "S 1"."T 2" SET (autovacuum_enabled = 'false'); +ALTER TABLE "S 1"."T 3" SET (autovacuum_enabled = 'false'); +ALTER TABLE "S 1"."T 4" SET (autovacuum_enabled = 'false'); INSERT INTO "S 1"."T 1" - SELECT id, - id % 10, - to_char(id, 'FM00000'), - '1970-01-01'::timestamptz + ((id % 100) || ' days')::interval, - '1970-01-01'::timestamp + ((id % 100) || ' days')::interval, - id % 10, - id % 10, - 'foo'::user_enum - FROM generate_series(1, 1000) id; + SELECT id, + id % 10, + to_char(id, 'FM00000'), + '1970-01-01'::timestamptz + ((id % 100) || ' days')::interval, + '1970-01-01'::timestamp + ((id % 100) || ' days')::interval, + id % 10, + id % 10, + 'foo'::user_enum + FROM generate_series(1, 1000) id; INSERT INTO "S 1"."T 2" - SELECT id, - 'AAA' || to_char(id, 'FM000') - FROM generate_series(1, 100) id; + SELECT id, + 'AAA' || to_char(id, 'FM000') + FROM generate_series(1, 100) id; +INSERT INTO "S 1"."T 3" + SELECT id, + id + 1, + 'AAA' || to_char(id, 'FM000') + FROM generate_series(1, 100) id; +DELETE FROM "S 1"."T 3" WHERE c1 % 2 != 0; -- delete for outer join tests +INSERT INTO "S 1"."T 4" + SELECT id, + id + 1, + 'AAA' || to_char(id, 'FM000') + FROM generate_series(1, 100) id; +DELETE FROM "S 1"."T 4" WHERE c1 % 3 != 0; -- delete for outer join tests ANALYZE "S 1"."T 1"; ANALYZE "S 1"."T 2"; --- =================================================================== --- create foreign tables --- =================================================================== +ANALYZE "S 1"."T 3"; +ANALYZE "S 1"."T 4"; +-- ====================================================================================================================================== +-- TEST-MODULE: create foreign tables +-- -------------------------------------- +-- ====================================================================================================================================== CREATE FOREIGN TABLE ft1 ( - c0 int, - c1 int NOT NULL, - c2 int NOT NULL, - c3 text, - c4 timestamptz, - c5 timestamp, - c6 varchar(10), - c7 char(10) default 'ft1', - c8 user_enum + c0 int, + c1 int NOT NULL, + c2 int NOT NULL, + c3 text, + c4 timestamptz, + c5 timestamp, + c6 varchar(10), + c7 char(10) default 'ft1', + c8 user_enum ) SERVER loopback; ALTER FOREIGN TABLE ft1 DROP COLUMN c0; CREATE FOREIGN TABLE ft2 ( - c1 int NOT NULL, - c2 int NOT NULL, - cx int, - c3 text, - c4 timestamptz, - c5 timestamp, - c6 varchar(10), - c7 char(10) default 'ft2', - c8 user_enum + c1 int NOT NULL, + c2 int NOT NULL, + cx int, + c3 text, + c4 timestamptz, + c5 timestamp, + c6 varchar(10), + c7 char(10) default 'ft2', + c8 user_enum ) SERVER loopback; ALTER FOREIGN TABLE ft2 DROP COLUMN cx; --- =================================================================== --- tests for validator --- =================================================================== +CREATE FOREIGN TABLE ft4 ( + c1 int NOT NULL, + c2 int NOT NULL, + c3 text +) SERVER loopback OPTIONS (schema_name 'S 1', table_name 'T 3'); +CREATE FOREIGN TABLE ft5 ( + c1 int NOT NULL, + c2 int NOT NULL, + c3 text +) SERVER loopback OPTIONS (schema_name 'S 1', table_name 'T 4'); +CREATE FOREIGN TABLE ft6 ( + c1 int NOT NULL, + c2 int NOT NULL, + c3 text +) SERVER loopback2 OPTIONS (schema_name 'S 1', table_name 'T 4'); +CREATE FOREIGN TABLE ft7 ( + c1 int NOT NULL, + c2 int NOT NULL, + c3 text +) SERVER loopback3 OPTIONS (schema_name 'S 1', table_name 'T 4'); +-- ====================================================================================================================================== +-- TEST-MODULE: tests for validator +-- -------------------------------------- +-- ====================================================================================================================================== -- requiressl and some other parameters are omitted because -- valid values for them depend on configure options ALTER SERVER testserver1 OPTIONS ( - use_remote_estimate 'false', - updatable 'true', - fdw_startup_cost '123.456', - fdw_tuple_cost '0.123', - service 'value', - connect_timeout 'value', - dbname 'value', - host 'value', - hostaddr 'value', - port 'value', - --client_encoding 'value', - application_name 'value', - --fallback_application_name 'value', - keepalives 'value', - keepalives_idle 'value', - keepalives_interval 'value', - -- requiressl 'value', - sslcompression 'value', - sslmode 'value', - sslcert 'value', - sslkey 'value', - sslrootcert 'value', - sslcrl 'value', - --requirepeer 'value', - krbsrvname 'value', - gsslib 'value' - --replication 'value' + use_remote_estimate 'false', + updatable 'true', + fdw_startup_cost '123.456', + fdw_tuple_cost '0.123', + service 'value', + connect_timeout 'value', + dbname 'value', + host 'value', + hostaddr 'value', + port 'value', + --client_encoding 'value', + application_name 'value', + --fallback_application_name 'value', + keepalives 'value', + keepalives_idle 'value', + keepalives_interval 'value', + --nspt tcp_user_timeout 'value', + -- requiressl 'value', + sslcompression 'value', + sslmode 'value', + sslcert 'value', + sslkey 'value', + sslrootcert 'value', + sslcrl 'value', + --requirepeer 'value', + krbsrvname 'value' + --nspt gsslib 'value' + --replication 'value' ); -ALTER USER MAPPING FOR public SERVER testserver1 - OPTIONS (DROP user, DROP password); +-- Error, invalid list syntax +ALTER SERVER testserver1 OPTIONS (ADD extensions 'foo; bar'); --nspt +ERROR: invalid option "extensions" +HINT: Valid options in this context are: service, connect_timeout, dbname, host, remote_nodename, hostaddr, port, localhost, localport, application_name, fencedUdfRPCMode, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, rw_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, krbsrvname, prototype, connection_info, connectionExtraInfo, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, updatable +-- OK but gets a warning +ALTER SERVER testserver1 OPTIONS (ADD extensions 'foo, bar'); --nspt +ERROR: invalid option "extensions" +HINT: Valid options in this context are: service, connect_timeout, dbname, host, remote_nodename, hostaddr, port, localhost, localport, application_name, fencedUdfRPCMode, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, rw_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, krbsrvname, prototype, connection_info, connectionExtraInfo, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, updatable +ALTER SERVER testserver1 OPTIONS (DROP extensions); --nspt +ERROR: option "extensions" not found +ALTER USER MAPPING FOR public SERVER testserver1 OPTIONS (DROP user, DROP password); +-- Attempt to add a valid option that's not allowed in a user mapping +ALTER USER MAPPING FOR public SERVER testserver1 --nspt + OPTIONS (ADD sslmode 'require'); --nspt +ERROR: invalid option "sslmode" +HINT: Valid options in this context are: user, password +-- But we can add valid ones fine +ALTER USER MAPPING FOR public SERVER testserver1 --nspt + OPTIONS (ADD sslpassword 'dummy'); --nspt +ERROR: invalid option "sslpassword" +HINT: Valid options in this context are: user, password +-- Ensure valid options we haven't used in a user mapping yet are +-- permitted to check validation. +ALTER USER MAPPING FOR public SERVER testserver1 --nspt + OPTIONS (ADD sslkey 'value', ADD sslcert 'value'); --nspt +ERROR: invalid option "sslkey" +HINT: Valid options in this context are: user, password ALTER FOREIGN TABLE ft1 OPTIONS (schema_name 'S 1', table_name 'T 1'); ALTER FOREIGN TABLE ft2 OPTIONS (schema_name 'S 1', table_name 'T 1'); ALTER FOREIGN TABLE ft1 ALTER COLUMN c1 OPTIONS (column_name 'C 1'); ALTER FOREIGN TABLE ft2 ALTER COLUMN c1 OPTIONS (column_name 'C 1'); \det+ - List of foreign tables - Schema | Table | Server | FDW Options | Description ---------+-------+----------+---------------------------------------+------------- - public | ft1 | loopback | (schema_name 'S 1', table_name 'T 1') | - public | ft2 | loopback | (schema_name 'S 1', table_name 'T 1') | -(2 rows) + List of foreign tables + Schema | Table | Server | FDW Options | Description +--------+-------+-----------+---------------------------------------+------------- + public | ft1 | loopback | (schema_name 'S 1', table_name 'T 1') | + public | ft2 | loopback | (schema_name 'S 1', table_name 'T 1') | + public | ft4 | loopback | (schema_name 'S 1', table_name 'T 3') | + public | ft5 | loopback | (schema_name 'S 1', table_name 'T 4') | + public | ft6 | loopback2 | (schema_name 'S 1', table_name 'T 4') | + public | ft7 | loopback3 | (schema_name 'S 1', table_name 'T 4') | +(6 rows) -- Test that alteration of server options causes reconnection -- Remote's errors might be non-English, so hide them to ensure stable results @@ -163,18 +280,26 @@ SELECT c3, c4 FROM ft1 ORDER BY c3, c1 LIMIT 1; -- should work again -- and remote-estimate mode on ft2. ANALYZE ft1; ALTER FOREIGN TABLE ft2 OPTIONS (use_remote_estimate 'true'); --- =================================================================== --- simple queries --- =================================================================== --- single table, with/without alias -EXPLAIN (COSTS false) SELECT * FROM ft1 ORDER BY c3, c1 OFFSET 100 LIMIT 10; - QUERY PLAN ---------------------------------- - Limit - -> Sort - Sort Key: c3, c1 - -> Foreign Scan on ft1 -(4 rows) +-- ====================================================================================================================================== +-- TEST-MODULE: simple queries +-- -------------------------------------- +-- +-- ====================================================================================================================================== +-- single table without alias +EXPLAIN (COSTS OFF) SELECT * FROM ft1 ORDER BY c3, c1 OFFSET 100 LIMIT 10; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + Foreign Scan on ft1 + Node ID: 1 + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE OFF, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" ORDER BY c3 ASC NULLS LAST, "C 1" ASC NULLS LAST LIMIT 10::bigint OFFSET 100::bigint + Limit + -> Sort + Sort Key: c3, "C 1" + -> Seq Scan on "T 1" + +(10 rows) SELECT * FROM ft1 ORDER BY c3, c1 OFFSET 100 LIMIT 10; c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 @@ -191,47 +316,35 @@ SELECT * FROM ft1 ORDER BY c3, c1 OFFSET 100 LIMIT 10; 110 | 0 | 00110 | Sun Jan 11 00:00:00 1970 PST | Sun Jan 11 00:00:00 1970 | 0 | 0 | foo (10 rows) -EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; - QUERY PLAN -------------------------------------------------------------------------------------- - Limit - Output: c1, c2, c3, c4, c5, c6, c7, c8 - -> Sort - Output: c1, c2, c3, c4, c5, c6, c7, c8 - Sort Key: t1.c3, t1.c1 - -> Foreign Scan on public.ft1 t1 - 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" -(8 rows) - -SELECT * FROM ft1 t1 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; - c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 ------+----+-------+------------------------------+--------------------------+----+------------+----- - 101 | 1 | 00101 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1 | 1 | foo - 102 | 2 | 00102 | Sat Jan 03 00:00:00 1970 PST | Sat Jan 03 00:00:00 1970 | 2 | 2 | foo - 103 | 3 | 00103 | Sun Jan 04 00:00:00 1970 PST | Sun Jan 04 00:00:00 1970 | 3 | 3 | foo - 104 | 4 | 00104 | Mon Jan 05 00:00:00 1970 PST | Mon Jan 05 00:00:00 1970 | 4 | 4 | foo - 105 | 5 | 00105 | Tue Jan 06 00:00:00 1970 PST | Tue Jan 06 00:00:00 1970 | 5 | 5 | foo - 106 | 6 | 00106 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo - 107 | 7 | 00107 | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7 | 7 | foo - 108 | 8 | 00108 | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8 | 8 | foo - 109 | 9 | 00109 | Sat Jan 10 00:00:00 1970 PST | Sat Jan 10 00:00:00 1970 | 9 | 9 | foo - 110 | 0 | 00110 | Sun Jan 11 00:00:00 1970 PST | Sun Jan 11 00:00:00 1970 | 0 | 0 | foo -(10 rows) - +--nspt single table with alias - also test that tableoid sort is not pushed to remote side +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 ORDER BY t1.c3, t1.c1, t1.tableoid OFFSET 100 LIMIT 10; +ERROR: column t1.tableoid does not exist +LINE 1: ... OFF) SELECT * FROM ft1 t1 ORDER BY t1.c3, t1.c1, t1.tableoi... + ^ +SELECT * FROM ft1 t1 ORDER BY t1.c3, t1.c1, t1.tableoid OFFSET 100 LIMIT 10; +ERROR: column t1.tableoid does not exist +LINE 1: SELECT * FROM ft1 t1 ORDER BY t1.c3, t1.c1, t1.tableoid OFFS... + ^ -- whole-row reference -EXPLAIN (VERBOSE, COSTS false) SELECT t1 FROM ft1 t1 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; - QUERY PLAN -------------------------------------------------------------------------------------- - Limit +EXPLAIN (VERBOSE, COSTS OFF) SELECT t1 FROM ft1 t1 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 Output: t1.*, c3, c1 - -> Sort - Output: t1.*, c3, c1 - Sort Key: t1.c3, t1.c1 - -> Foreign Scan on public.ft1 t1 - Output: t1.*, c3, c1 - Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" -(8 rows) + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" ORDER BY c3 ASC NULLS LAST, "C 1" ASC NULLS LAST LIMIT 10::bigint OFFSET 100::bigint + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" ORDER BY c3 ASC NULLS LAST, "C 1" ASC NULLS LAST LIMIT 10::bigint OFFSET 100::bigint + Limit + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + -> Sort + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Sort Key: "T 1".c3, "T 1"."C 1" + -> Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + +(15 rows) SELECT t1 FROM ft1 t1 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; t1 @@ -255,13 +368,22 @@ SELECT * FROM ft1 WHERE false; (0 rows) -- with WHERE clause -EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE t1.c1 = 101 AND t1.c6 = '1' AND t1.c7 >= '1'; - QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------- +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE t1.c1 = 101 AND t1.c6 = '1' AND t1.c7 >= '1'; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Foreign Scan on public.ft1 t1 Output: c1, c2, c3, c4, c5, c6, c7, c8 + Node ID: 1 Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((c7 >= '1'::bpchar)) AND (("C 1" = 101)) AND ((c6 = '1'::text)) -(3 rows) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((c7 >= '1'::bpchar)) AND (("C 1" = 101)) AND ((c6 = '1'::text)) + Index Scan using t1_pkey on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Index Cond: ("T 1"."C 1" = 101) + Filter: (("T 1".c7 >= '1'::bpchar) AND (("T 1".c6)::text = '1'::text)) + +(12 rows) SELECT * FROM ft1 t1 WHERE t1.c1 = 101 AND t1.c6 = '1' AND t1.c7 >= '1'; c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 @@ -270,15 +392,24 @@ SELECT * FROM ft1 t1 WHERE t1.c1 = 101 AND t1.c6 = '1' AND t1.c7 >= '1'; (1 row) -- with FOR UPDATE/SHARE -EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE c1 = 101 FOR UPDATE; - QUERY PLAN ----------------------------------------------------------------------------------------------------------------- - LockRows +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 = 101 FOR UPDATE; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 Output: c1, c2, c3, c4, c5, c6, c7, c8, t1.* - -> Foreign Scan on public.ft1 t1 - Output: c1, c2, c3, c4, c5, c6, c7, c8, t1.* - Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 101)) FOR UPDATE -(5 rows) + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 101)) FOR UPDATE + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 101)) FOR UPDATE + [Bypass] + LockRows + Output: "C 1", c2, c3, c4, c5, c6, c7, c8, ctid + -> Index Scan using t1_pkey on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8, ctid + Index Cond: ("T 1"."C 1" = 101) + +(14 rows) SELECT * FROM ft1 t1 WHERE c1 = 101 FOR UPDATE; c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 @@ -286,15 +417,23 @@ SELECT * FROM ft1 t1 WHERE c1 = 101 FOR UPDATE; 101 | 1 | 00101 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1 | 1 | foo (1 row) -EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE c1 = 102 FOR SHARE; - QUERY PLAN ---------------------------------------------------------------------------------------------------------------- - LockRows +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 = 102 FOR SHARE; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------ + Foreign Scan on public.ft1 t1 Output: c1, c2, c3, c4, c5, c6, c7, c8, t1.* - -> Foreign Scan on public.ft1 t1 - Output: c1, c2, c3, c4, c5, c6, c7, c8, t1.* - Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 102)) FOR SHARE -(5 rows) + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 102)) FOR SHARE + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 102)) FOR SHARE + LockRows + Output: "C 1", c2, c3, c4, c5, c6, c7, c8, ctid + -> Index Scan using t1_pkey on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8, ctid + Index Cond: ("T 1"."C 1" = 102) + +(13 rows) SELECT * FROM ft1 t1 WHERE c1 = 102 FOR SHARE; c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 @@ -309,22 +448,6 @@ SELECT COUNT(*) FROM ft1 t1; 1000 (1 row) --- join two tables -SELECT t1.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; - c1 ------ - 101 - 102 - 103 - 104 - 105 - 106 - 107 - 108 - 109 - 110 -(10 rows) - -- subquery SELECT * FROM ft1 t1 WHERE t1.c3 IN (SELECT c3 FROM ft2 t2 WHERE c1 <= 10) ORDER BY c1; c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 @@ -371,167 +494,469 @@ SELECT 'fixed', NULL FROM ft1 t1 WHERE c1 = 1; fixed | (1 row) --- user-defined operator/function -CREATE FUNCTION postgres_fdw_abs(int) RETURNS int AS $$ -BEGIN -RETURN abs($1); -END -$$ LANGUAGE plpgsql IMMUTABLE; -CREATE OPERATOR === ( - LEFTARG = int, - RIGHTARG = int, - PROCEDURE = int4eq, - COMMUTATOR = ===, - NEGATOR = !== -); -EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE t1.c1 = postgres_fdw_abs(t1.c2); - QUERY PLAN -------------------------------------------------------------------------- - Foreign Scan on public.ft1 t1 - Output: c1, c2, c3, c4, c5, c6, c7, c8 - Filter: (t1.c1 = postgres_fdw_abs(t1.c2)) - Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" -(4 rows) +-- Test forcing the remote server to produce sorted data for a merge join. +SET enable_hashjoin TO false; +SET enable_nestloop TO false; +-- inner join; expressions in the clauses appear in the equivalence class list +EXPLAIN (VERBOSE, COSTS OFF) + SELECT t1.c1, t2."C 1" FROM ft2 t1 JOIN "S 1"."T 1" t2 ON (t1.c1 = t2."C 1") OFFSET 100 LIMIT 10; + QUERY PLAN +------------------------------------------------------------------------------------------------------ + Limit + Output: t1.c1, t2."C 1" + -> Merge Join + Output: t1.c1, t2."C 1" + Merge Cond: (t1.c1 = t2."C 1") + -> Foreign Scan on public.ft2 t1 + Output: t1.c1 + Node ID: 1 + Remote SQL: SELECT "C 1" FROM "S 1"."T 1" ORDER BY "C 1" ASC NULLS LAST + -> Index Only Scan using t1_pkey on "S 1"."T 1" t2 + Output: t2."C 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1" FROM "S 1"."T 1" ORDER BY "C 1" ASC NULLS LAST + [Bypass] + Index Only Scan using t1_pkey on "S 1"."T 1" + Output: "C 1" + +(18 rows) -EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE t1.c1 === t1.c2; - QUERY PLAN -------------------------------------------------------------------------- - Foreign Scan on public.ft1 t1 - Output: c1, c2, c3, c4, c5, c6, c7, c8 - Filter: (t1.c1 === t1.c2) - Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" -(4 rows) +SELECT t1.c1, t2."C 1" FROM ft2 t1 JOIN "S 1"."T 1" t2 ON (t1.c1 = t2."C 1") OFFSET 100 LIMIT 10; + c1 | C 1 +-----+----- + 101 | 101 + 102 | 102 + 103 | 103 + 104 | 104 + 105 | 105 + 106 | 106 + 107 | 107 + 108 | 108 + 109 | 109 + 110 | 110 +(10 rows) -EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE t1.c1 = abs(t1.c2); - QUERY PLAN ---------------------------------------------------------------------------------------------------- - Foreign Scan on public.ft1 t1 - 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" WHERE (("C 1" = abs(c2))) -(3 rows) +-- outer join; expressions in the clauses do not appear in equivalence class +-- list but no output change as compared to the previous query +EXPLAIN (VERBOSE, COSTS OFF) + SELECT t1.c1, t2."C 1" FROM ft2 t1 LEFT JOIN "S 1"."T 1" t2 ON (t1.c1 = t2."C 1") OFFSET 100 LIMIT 10; + QUERY PLAN +------------------------------------------------------------------------------------------------------ + Limit + Output: t1.c1, t2."C 1" + -> Merge Left Join + Output: t1.c1, t2."C 1" + Merge Cond: (t1.c1 = t2."C 1") + -> Foreign Scan on public.ft2 t1 + Output: t1.c1 + Node ID: 1 + Remote SQL: SELECT "C 1" FROM "S 1"."T 1" ORDER BY "C 1" ASC NULLS LAST + -> Index Only Scan using t1_pkey on "S 1"."T 1" t2 + Output: t2."C 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1" FROM "S 1"."T 1" ORDER BY "C 1" ASC NULLS LAST + [Bypass] + Index Only Scan using t1_pkey on "S 1"."T 1" + Output: "C 1" + +(18 rows) -EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE t1.c1 = t1.c2; - QUERY PLAN ----------------------------------------------------------------------------------------------- - Foreign Scan on public.ft1 t1 - 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" WHERE (("C 1" = c2)) -(3 rows) +SELECT t1.c1, t2."C 1" FROM ft2 t1 LEFT JOIN "S 1"."T 1" t2 ON (t1.c1 = t2."C 1") OFFSET 100 LIMIT 10; + c1 | C 1 +-----+----- + 101 | 101 + 102 | 102 + 103 | 103 + 104 | 104 + 105 | 105 + 106 | 106 + 107 | 107 + 108 | 108 + 109 | 109 + 110 | 110 +(10 rows) --- =================================================================== --- WHERE with remotely-executable conditions --- =================================================================== -EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE t1.c1 = 1; -- Var, OpExpr(b), Const - QUERY PLAN ---------------------------------------------------------------------------------------------- - Foreign Scan on public.ft1 t1 - 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" WHERE (("C 1" = 1)) -(3 rows) +-- A join between local table and foreign join. ORDER BY clause is added to the +-- foreign join so that the local table can be joined using merge join strategy. +EXPLAIN (VERBOSE, COSTS OFF) + SELECT t1."C 1" FROM "S 1"."T 1" t1 left join ft1 t2 join ft2 t3 on (t2.c1 = t3.c1) on (t3.c1 = t1."C 1") OFFSET 100 LIMIT 10; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + Limit + Output: t1."C 1" + -> Merge Right Join + Output: t1."C 1" + Merge Cond: (t3.c1 = t1."C 1") + -> Foreign Scan + Output: t3.c1 + Node ID: 1 + Relations: (public.ft1 t2) INNER JOIN (public.ft2 t3) + Remote SQL: SELECT r3."C 1" FROM ("S 1"."T 1" r2 INNER JOIN "S 1"."T 1" r3 ON (((r2."C 1" = r3."C 1")))) ORDER BY r2."C 1" ASC NULLS LAST + -> Index Only Scan using t1_pkey on "S 1"."T 1" t1 + Output: t1."C 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT r3."C 1" FROM ("S 1"."T 1" r2 INNER JOIN "S 1"."T 1" r3 ON (((r2."C 1" = r3."C 1")))) ORDER BY r2."C 1" ASC NULLS LAST + Merge Join + Output: r3."C 1", r2."C 1" + Merge Cond: (r2."C 1" = r3."C 1") + -> Index Only Scan using t1_pkey on "S 1"."T 1" r2 + Output: r2."C 1" + -> Index Only Scan using t1_pkey on "S 1"."T 1" r3 + Output: r3."C 1" + +(23 rows) -EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE t1.c1 = 100 AND t1.c2 = 0; -- BoolExpr - QUERY PLAN --------------------------------------------------------------------------------------------------------------- - Foreign Scan on public.ft1 t1 - 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" WHERE (("C 1" = 100)) AND ((c2 = 0)) -(3 rows) +SELECT t1."C 1" FROM "S 1"."T 1" t1 left join ft1 t2 join ft2 t3 on (t2.c1 = t3.c1) on (t3.c1 = t1."C 1") OFFSET 100 LIMIT 10; + C 1 +----- + 101 + 102 + 103 + 104 + 105 + 106 + 107 + 108 + 109 + 110 +(10 rows) -EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE c1 IS NULL; -- NullTest - QUERY PLAN -------------------------------------------------------------------------------------------------- - Foreign Scan on public.ft1 t1 - 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" WHERE (("C 1" IS NULL)) -(3 rows) +-- Test similar to above, except that the full join prevents any equivalence +-- classes from being merged. This produces single relation equivalence classes +-- included in join restrictions. +EXPLAIN (VERBOSE, COSTS OFF) + SELECT t1."C 1", t2.c1, t3.c1 FROM "S 1"."T 1" t1 left join ft1 t2 full join ft2 t3 on (t2.c1 = t3.c1) on (t3.c1 = t1."C 1") OFFSET 100 LIMIT 10; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Limit + Output: t1."C 1", t2.c1, t3.c1 + -> Merge Right Join + Output: t1."C 1", t2.c1, t3.c1 + Merge Cond: (t3.c1 = t1."C 1") + -> Foreign Scan + Output: t3.c1, t2.c1 + Node ID: 1 + Relations: (public.ft2 t3) LEFT JOIN (public.ft1 t2) + Remote SQL: SELECT r3."C 1", r2."C 1" FROM ("S 1"."T 1" r3 LEFT JOIN "S 1"."T 1" r2 ON (((r2."C 1" = r3."C 1")))) ORDER BY r3."C 1" ASC NULLS LAST + -> Index Only Scan using t1_pkey on "S 1"."T 1" t1 + Output: t1."C 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT r3."C 1", r2."C 1" FROM ("S 1"."T 1" r3 LEFT JOIN "S 1"."T 1" r2 ON (((r2."C 1" = r3."C 1")))) ORDER BY r3."C 1" ASC NULLS LAST + Merge Left Join + Output: r3."C 1", r2."C 1" + Merge Cond: (r3."C 1" = r2."C 1") + -> Index Only Scan using t1_pkey on "S 1"."T 1" r3 + Output: r3."C 1" + -> Index Only Scan using t1_pkey on "S 1"."T 1" r2 + Output: r2."C 1" + +(23 rows) -EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE c1 IS NOT NULL; -- NullTest - QUERY PLAN ------------------------------------------------------------------------------------------------------ - Foreign Scan on public.ft1 t1 - 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" WHERE (("C 1" IS NOT NULL)) -(3 rows) +SELECT t1."C 1", t2.c1, t3.c1 FROM "S 1"."T 1" t1 left join ft1 t2 full join ft2 t3 on (t2.c1 = t3.c1) on (t3.c1 = t1."C 1") OFFSET 100 LIMIT 10; + C 1 | c1 | c1 +-----+-----+----- + 101 | 101 | 101 + 102 | 102 | 102 + 103 | 103 | 103 + 104 | 104 | 104 + 105 | 105 | 105 + 106 | 106 | 106 + 107 | 107 | 107 + 108 | 108 | 108 + 109 | 109 | 109 + 110 | 110 | 110 +(10 rows) -EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE round(abs(c1), 0) = 1; -- FuncExpr - QUERY PLAN ---------------------------------------------------------------------------------------------------------------------- - Foreign Scan on public.ft1 t1 - 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" WHERE ((round(abs("C 1"), 0) = 1::numeric)) -(3 rows) +-- Test similar to above with all full outer joins +EXPLAIN (VERBOSE, COSTS OFF) + SELECT t1."C 1", t2.c1, t3.c1 FROM "S 1"."T 1" t1 full join ft1 t2 full join ft2 t3 on (t2.c1 = t3.c1) on (t3.c1 = t1."C 1") OFFSET 100 LIMIT 10; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Limit + Output: t1."C 1", t2.c1, t3.c1 + -> Merge Full Join + Output: t1."C 1", t2.c1, t3.c1 + Merge Cond: (t3.c1 = t1."C 1") + -> Foreign Scan + Output: t2.c1, t3.c1 + Node ID: 1 + Relations: (public.ft1 t2) FULL JOIN (public.ft2 t3) + Remote SQL: SELECT r2."C 1", r3."C 1" FROM ("S 1"."T 1" r2 FULL JOIN "S 1"."T 1" r3 ON (((r2."C 1" = r3."C 1")))) ORDER BY r3."C 1" ASC NULLS LAST + -> Index Only Scan using t1_pkey on "S 1"."T 1" t1 + Output: t1."C 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT r2."C 1", r3."C 1" FROM ("S 1"."T 1" r2 FULL JOIN "S 1"."T 1" r3 ON (((r2."C 1" = r3."C 1")))) ORDER BY r3."C 1" ASC NULLS LAST + Sort + Output: r2."C 1", r3."C 1" + Sort Key: r3."C 1" + -> Hash Full Join + Output: r2."C 1", r3."C 1" + Hash Cond: (r2."C 1" = r3."C 1") + -> Seq Scan on "S 1"."T 1" r2 + Output: r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8 + -> Hash + Output: r3."C 1" + -> Seq Scan on "S 1"."T 1" r3 + Output: r3."C 1" + +(28 rows) -EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE c1 = -c1; -- OpExpr(l) - QUERY PLAN ------------------------------------------------------------------------------------------------------ - Foreign Scan on public.ft1 t1 - 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" WHERE (("C 1" = (- "C 1"))) -(3 rows) +SELECT t1."C 1", t2.c1, t3.c1 FROM "S 1"."T 1" t1 full join ft1 t2 full join ft2 t3 on (t2.c1 = t3.c1) on (t3.c1 = t1."C 1") OFFSET 100 LIMIT 10; + C 1 | c1 | c1 +-----+-----+----- + 101 | 101 | 101 + 102 | 102 | 102 + 103 | 103 | 103 + 104 | 104 | 104 + 105 | 105 | 105 + 106 | 106 | 106 + 107 | 107 | 107 + 108 | 108 | 108 + 109 | 109 | 109 + 110 | 110 | 110 +(10 rows) -EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE 1 = c1!; -- OpExpr(r) +RESET enable_hashjoin; +RESET enable_nestloop; +-- Test executing assertion in estimate_path_cost_size() that makes sure that +-- retrieved_rows for foreign rel re-used to cost pre-sorted foreign paths is +-- a sensible value even when the rel has tuples=0 +CREATE TABLE loct_empty (c1 int NOT NULL, c2 text); +CREATE FOREIGN TABLE ft_empty (c1 int NOT NULL, c2 text) + SERVER loopback OPTIONS (table_name 'loct_empty'); +INSERT INTO loct_empty + SELECT id, 'AAA' || to_char(id, 'FM000') FROM generate_series(1, 100) id; +DELETE FROM loct_empty; +ANALYZE ft_empty; +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft_empty ORDER BY c1; QUERY PLAN ---------------------------------------------------------------------------------------------------------- - Foreign Scan on public.ft1 t1 - 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" WHERE ((1::numeric = ("C 1" !))) -(3 rows) + Foreign Scan on public.ft_empty + Output: c1, c2 + Node ID: 1 + Remote SQL: SELECT c1, c2 FROM public.loct_empty ORDER BY c1 ASC NULLS LAST + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c1, c2 FROM public.loct_empty ORDER BY c1 ASC NULLS LAST + Sort + Output: c1, c2 + Sort Key: loct_empty.c1 + -> Seq Scan on public.loct_empty + Output: c1, c2 + +(13 rows) -EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE (c1 IS NOT NULL) IS DISTINCT FROM (c1 IS NOT NULL); -- DistinctExpr - QUERY PLAN --------------------------------------------------------------------------------------------------------------------------------------------- +-- ====================================================================================================================================== +-- TEST-MODULE: WHERE with remotely-executable conditions +-- -------------------------------------- +-- ====================================================================================================================================== +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE t1.c1 = 1; -- Var, OpExpr(b), Const + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------ Foreign Scan on public.ft1 t1 Output: c1, c2, c3, c4, c5, c6, c7, c8 + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 1)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 1)) + [Bypass] + Index Scan using t1_pkey on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Index Cond: ("T 1"."C 1" = 1) + +(12 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE t1.c1 = 100 AND t1.c2 = 0; -- BoolExpr + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 100)) AND ((c2 = 0)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 100)) AND ((c2 = 0)) + Index Scan using t1_pkey on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Index Cond: ("T 1"."C 1" = 100) + Filter: ("T 1".c2 = 0) + +(12 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 IS NULL; -- NullTest + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" IS NULL)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" IS NULL)) + Result + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + One-Time Filter: false + -> Index Scan using t1_pkey on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Index Cond: ("T 1"."C 1" IS NULL) + +(14 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 IS NOT NULL; -- NullTest + QUERY PLAN +---------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" + Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + +(10 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE round(abs(c1), 0) = 1; -- FuncExpr + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------ + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((round(abs("C 1"), 0) = 1::numeric)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((round(abs("C 1"), 0) = 1::numeric)) + Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Filter: (round((abs("T 1"."C 1"))::numeric, 0) = 1::numeric) + +(11 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 = -c1; -- OpExpr(l) + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = (- "C 1"))) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = (- "C 1"))) + Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Filter: ("T 1"."C 1" = (- "T 1"."C 1")) + +(11 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE (c1 IS NOT NULL) IS DISTINCT FROM (c1 IS NOT NULL); -- DistinctExpr + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Node ID: 1 Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((("C 1" IS NOT NULL) IS DISTINCT FROM ("C 1" IS NOT NULL))) -(3 rows) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((("C 1" IS NOT NULL) IS DISTINCT FROM ("C 1" IS NOT NULL))) + Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Filter: (("T 1"."C 1" IS NOT NULL) IS DISTINCT FROM ("T 1"."C 1" IS NOT NULL)) + +(11 rows) -EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE c1 = ANY(ARRAY[c2, 1, c1 + 0]); -- ScalarArrayOpExpr - QUERY PLAN ---------------------------------------------------------------------------------------------------------------------------- +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 = ANY(ARRAY[c2, 1, c1 + 0]); -- ScalarArrayOpExpr + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------ Foreign Scan on public.ft1 t1 Output: c1, c2, c3, c4, c5, c6, c7, c8 + Node ID: 1 Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = ANY (ARRAY[c2, 1, ("C 1" + 0)]))) -(3 rows) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = ANY (ARRAY[c2, 1, ("C 1" + 0)]))) + Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Filter: ("T 1"."C 1" = ANY (ARRAY["T 1".c2, 1, ("T 1"."C 1" + 0)])) + +(11 rows) -EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE c1 = (ARRAY[c1,c2,3])[1]; -- ArrayRef - QUERY PLAN ----------------------------------------------------------------------------------------------------------------------- +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 = (ARRAY[c1,c2,3])[1]; -- SubscriptingRef + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------- Foreign Scan on public.ft1 t1 Output: c1, c2, c3, c4, c5, c6, c7, c8 + Node ID: 1 Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = ((ARRAY["C 1", c2, 3])[1]))) -(3 rows) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = ((ARRAY["C 1", c2, 3])[1]))) + Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Filter: ("T 1"."C 1" = (ARRAY["T 1"."C 1", "T 1".c2, 3])[1]) + +(11 rows) -EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE c6 = E'foo''s\\bar'; -- check special chars - QUERY PLAN -------------------------------------------------------------------------------------------------------------- +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c6 = E'foo''s\\bar'; -- check special chars + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------- Foreign Scan on public.ft1 t1 Output: c1, c2, c3, c4, c5, c6, c7, c8 + Node ID: 1 Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((c6 = E'foo''s\\bar'::text)) -(3 rows) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((c6 = E'foo''s\\bar'::text)) + Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Filter: (("T 1".c6)::text = 'foo''s\bar'::text) + +(11 rows) -EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE c8 = 'foo'; -- can't be sent to remote - QUERY PLAN -------------------------------------------------------------------------- +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c8 = 'foo'; -- can't be sent to remote + QUERY PLAN +---------------------------------------------------------------------------------------------------- Foreign Scan on public.ft1 t1 Output: c1, c2, c3, c4, c5, c6, c7, c8 Filter: (t1.c8 = 'foo'::user_enum) + Node ID: 1 Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" -(4 rows) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" + Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + +(11 rows) --- parameterized remote path -EXPLAIN (VERBOSE, COSTS false) - SELECT * FROM ft2 a, ft2 b WHERE a.c1 = 47 AND b.c1 = a.c2; +-- parameterized remote path for foreign table +EXPLAIN (VERBOSE, COSTS OFF) + SELECT * FROM "S 1"."T 1" a, ft2 b WHERE a."C 1" = 47 AND b.c1 = a.c2; QUERY PLAN ------------------------------------------------------------------------------------------------------------- Nested Loop - Output: a.c1, a.c2, a.c3, a.c4, a.c5, a.c6, a.c7, a.c8, b.c1, b.c2, b.c3, b.c4, b.c5, b.c6, b.c7, b.c8 - -> Foreign Scan on public.ft2 a - Output: a.c1, a.c2, a.c3, a.c4, a.c5, a.c6, a.c7, a.c8 - Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 47)) + Output: a."C 1", a.c2, a.c3, a.c4, a.c5, a.c6, a.c7, a.c8, b.c1, b.c2, b.c3, b.c4, b.c5, b.c6, b.c7, b.c8 + Join Filter: (a.c2 = b.c1) + -> Index Scan using t1_pkey on "S 1"."T 1" a + Output: a."C 1", a.c2, a.c3, a.c4, a.c5, a.c6, a.c7, a.c8 + Index Cond: (a."C 1" = 47) -> Foreign Scan on public.ft2 b Output: b.c1, b.c2, b.c3, b.c4, b.c5, b.c6, b.c7, b.c8 - Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (($1::integer = "C 1")) -(8 rows) + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" + Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + +(16 rows) SELECT * FROM ft2 a, ft2 b WHERE a.c1 = 47 AND b.c1 = a.c2; c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 | c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 @@ -540,22 +965,36 @@ SELECT * FROM ft2 a, ft2 b WHERE a.c1 = 47 AND b.c1 = a.c2; (1 row) -- check both safe and unsafe join conditions -EXPLAIN (VERBOSE, COSTS false) +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft2 a, ft2 b WHERE a.c2 = 6 AND b.c1 = a.c1 AND a.c8 = 'foo' AND b.c7 = upper(a.c7); - QUERY PLAN -------------------------------------------------------------------------------------------------------------- - Nested Loop + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------- + Hash Join Output: a.c1, a.c2, a.c3, a.c4, a.c5, a.c6, a.c7, a.c8, b.c1, b.c2, b.c3, b.c4, b.c5, b.c6, b.c7, b.c8 - -> Foreign Scan on public.ft2 a - Output: a.c1, a.c2, a.c3, a.c4, a.c5, a.c6, a.c7, a.c8 - Filter: (a.c8 = 'foo'::user_enum) - Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((c2 = 6)) + Hash Cond: ((b.c1 = a.c1) AND ((b.c7)::text = upper((a.c7)::text))) -> Foreign Scan on public.ft2 b Output: b.c1, b.c2, b.c3, b.c4, b.c5, b.c6, b.c7, b.c8 - Filter: (upper((a.c7)::text) = (b.c7)::text) - Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (($1::integer = "C 1")) -(10 rows) + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" + -> Hash + Output: a.c1, a.c2, a.c3, a.c4, a.c5, a.c6, a.c7, a.c8 + -> Foreign Scan on public.ft2 a + Output: a.c1, a.c2, a.c3, a.c4, a.c5, a.c6, a.c7, a.c8 + Filter: (a.c8 = 'foo'::user_enum) + Node ID: 2 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((c2 = 6)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" + Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((c2 = 6)) + Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Filter: ("T 1".c2 = 6) + +(24 rows) SELECT * FROM ft2 a, ft2 b WHERE a.c2 = 6 AND b.c1 = a.c1 AND a.c8 = 'foo' AND b.c7 = upper(a.c7); @@ -682,79 +1121,4231 @@ SELECT * FROM ft2 WHERE c1 = ANY (ARRAY(SELECT c1 FROM ft1 WHERE c1 < 5)); 4 | 4 | 00004 | Mon Jan 05 00:00:00 1970 PST | Mon Jan 05 00:00:00 1970 | 4 | 4 | foo (4 rows) --- bug #15613: bad plan for foreign table scan with lateral reference +-- we should not push order by clause with volatile expressions or unsafe +-- collations EXPLAIN (VERBOSE, COSTS OFF) -SELECT ref_0.c2, subq_1.* -FROM - "S 1"."T 1" AS ref_0, - LATERAL ( - SELECT ref_0."C 1" c1, subq_0.* - FROM (SELECT ref_0.c2, ref_1.c3 - FROM ft1 AS ref_1) AS subq_0 - RIGHT JOIN ft2 AS ref_3 ON (subq_0.c3 = ref_3.c3) - ) AS subq_1 -WHERE ref_0."C 1" < 10 AND subq_1.c3 = '00001' -ORDER BY ref_0."C 1"; - QUERY PLAN ---------------------------------------------------------------------------------------------------------- - Nested Loop - Output: ref_0.c2, ref_0."C 1", (ref_0.c2), ref_1.c3, ref_0."C 1" - -> Nested Loop - Output: ref_0.c2, ref_0."C 1", ref_1.c3, (ref_0.c2) - -> Index Scan using t1_pkey on "S 1"."T 1" ref_0 - Output: ref_0."C 1", ref_0.c2, ref_0.c3, ref_0.c4, ref_0.c5, ref_0.c6, ref_0.c7, ref_0.c8 - Index Cond: (ref_0."C 1" < 10) - -> Foreign Scan on public.ft1 ref_1 - Output: ref_1.c3, ref_0.c2 - Remote SQL: SELECT c3 FROM "S 1"."T 1" WHERE ((c3 = '00001'::text)) + SELECT * FROM ft2 ORDER BY ft2.c1, random(); + QUERY PLAN +---------------------------------------------------------------------------------------------------- + Sort + Output: c1, c2, c3, c4, c5, c6, c7, c8, (random()) + Sort Key: ft2.c1, (random()) + -> Foreign Scan on public.ft2 + Output: c1, c2, c3, c4, c5, c6, c7, c8, random() + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" + Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + +(13 rows) + +EXPLAIN (VERBOSE, COSTS OFF) + SELECT * FROM ft2 ORDER BY ft2.c1, ft2.c3 collate "C"; + QUERY PLAN +---------------------------------------------------------------------------------------------------- + Sort + Output: c1, c2, c3, c4, c5, c6, c7, c8, ((c3)::text) + Sort Key: ft2.c1, ft2.c3 COLLATE "C" + -> Foreign Scan on public.ft2 + Output: c1, c2, c3, c4, c5, c6, c7, c8, (c3)::text + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" + Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + +(13 rows) + +-- user-defined operator/function +CREATE FUNCTION postgres_fdw_abs(int) RETURNS int AS $$ +BEGIN +RETURN abs($1); +END +$$ LANGUAGE plpgsql IMMUTABLE; +CREATE OPERATOR === ( + LEFTARG = int, + RIGHTARG = int, + PROCEDURE = int4eq, + COMMUTATOR = === +); +-- built-in operators and functions can be shipped for remote execution +EXPLAIN (VERBOSE, COSTS OFF) + SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = abs(t1.c2); + QUERY PLAN +------------------------------------------------------------------------------------------------------ + Foreign Scan + Output: (count(c3)) + Node ID: 1 + Relations: Aggregate on (public.ft1 t1) + Remote SQL: SELECT count(c3) FROM "S 1"."T 1" WHERE (("C 1" = abs(c2))) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT count(c3) FROM "S 1"."T 1" WHERE (("C 1" = abs(c2))) + Aggregate + Output: count(c3) + -> Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Filter: ("T 1"."C 1" = abs("T 1".c2)) + +(14 rows) + +SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = abs(t1.c2); + count +------- + 9 +(1 row) + +EXPLAIN (VERBOSE, COSTS OFF) + SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = t1.c2; + QUERY PLAN +------------------------------------------------------------------------------------------------- + Foreign Scan + Output: (count(c3)) + Node ID: 1 + Relations: Aggregate on (public.ft1 t1) + Remote SQL: SELECT count(c3) FROM "S 1"."T 1" WHERE (("C 1" = c2)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT count(c3) FROM "S 1"."T 1" WHERE (("C 1" = c2)) + Aggregate + Output: count(c3) + -> Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Filter: ("T 1"."C 1" = "T 1".c2) + +(14 rows) + +SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = t1.c2; + count +------- + 9 +(1 row) + +-- by default, user-defined ones cannot +EXPLAIN (VERBOSE, COSTS OFF) + SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = postgres_fdw_abs(t1.c2); + QUERY PLAN +-------------------------------------------------------------------------------- + Aggregate + Output: count(c3) + -> Foreign Scan on public.ft1 t1 + Output: c3, c1, c2 + Filter: (t1.c1 = postgres_fdw_abs(t1.c2)) + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3 FROM "S 1"."T 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3 FROM "S 1"."T 1" + Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3 + +(13 rows) + +SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = postgres_fdw_abs(t1.c2); + count +------- + 9 +(1 row) + +EXPLAIN (VERBOSE, COSTS OFF) + SELECT count(c3) FROM ft1 t1 WHERE t1.c1 === t1.c2; + QUERY PLAN +-------------------------------------------------------------------------------- + Aggregate + Output: count(c3) + -> Foreign Scan on public.ft1 t1 + Output: c3 + Filter: (t1.c1 === t1.c2) + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3 FROM "S 1"."T 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3 FROM "S 1"."T 1" + Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3 + +(13 rows) + +SELECT count(c3) FROM ft1 t1 WHERE t1.c1 === t1.c2; + count +------- + 9 +(1 row) + +-- ORDER BY can be shipped, though +EXPLAIN (VERBOSE, COSTS OFF) + SELECT * FROM ft1 t1 WHERE t1.c1 === t1.c2 order by t1.c2 limit 1; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------- + Limit + Output: c1, c2, c3, c4, c5, c6, c7, c8 + -> Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Filter: (t1.c1 === t1.c2) + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" ORDER BY c2 ASC NULLS LAST + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" ORDER BY c2 ASC NULLS LAST + Sort + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Sort Key: "T 1".c2 + -> Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + +(16 rows) + +SELECT * FROM ft1 t1 WHERE t1.c1 === t1.c2 order by t1.c2 limit 1; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +----+----+-------+------------------------------+--------------------------+----+------------+----- + 1 | 1 | 00001 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1 | 1 | foo +(1 row) + +-- but let's put them in an extension ... +ALTER EXTENSION postgres_fdw ADD FUNCTION postgres_fdw_abs(int); +ALTER EXTENSION postgres_fdw ADD OPERATOR === (int, int); +ALTER SERVER loopback OPTIONS (ADD extensions 'postgres_fdw'); --nspt +ERROR: invalid option "extensions" +HINT: Valid options in this context are: service, connect_timeout, dbname, host, remote_nodename, hostaddr, port, localhost, localport, application_name, fencedUdfRPCMode, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, rw_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, krbsrvname, prototype, connection_info, connectionExtraInfo, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, updatable +-- ... now they can be shipped +EXPLAIN (VERBOSE, COSTS OFF) + SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = postgres_fdw_abs(t1.c2); + QUERY PLAN +-------------------------------------------------------------------------------- + Aggregate + Output: count(c3) + -> Foreign Scan on public.ft1 t1 + Output: c3, c1, c2 + Filter: (t1.c1 = postgres_fdw_abs(t1.c2)) + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3 FROM "S 1"."T 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3 FROM "S 1"."T 1" + Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3 + +(13 rows) + +SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = postgres_fdw_abs(t1.c2); + count +------- + 9 +(1 row) + +EXPLAIN (VERBOSE, COSTS OFF) + SELECT count(c3) FROM ft1 t1 WHERE t1.c1 === t1.c2; + QUERY PLAN +-------------------------------------------------------------------------------- + Aggregate + Output: count(c3) + -> Foreign Scan on public.ft1 t1 + Output: c3 + Filter: (t1.c1 === t1.c2) + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3 FROM "S 1"."T 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3 FROM "S 1"."T 1" + Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3 + +(13 rows) + +SELECT count(c3) FROM ft1 t1 WHERE t1.c1 === t1.c2; + count +------- + 9 +(1 row) + +-- and both ORDER BY and LIMIT can be shipped +EXPLAIN (VERBOSE, COSTS OFF) + SELECT * FROM ft1 t1 WHERE t1.c1 === t1.c2 order by t1.c2 limit 1; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------- + Limit + Output: c1, c2, c3, c4, c5, c6, c7, c8 + -> Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Filter: (t1.c1 === t1.c2) + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" ORDER BY c2 ASC NULLS LAST + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" ORDER BY c2 ASC NULLS LAST + Sort + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Sort Key: "T 1".c2 + -> Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + +(16 rows) + +SELECT * FROM ft1 t1 WHERE t1.c1 === t1.c2 order by t1.c2 limit 1; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +----+----+-------+------------------------------+--------------------------+----+------------+----- + 1 | 1 | 00001 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1 | 1 | foo +(1 row) + +-- case when expr +explain (verbose, costs off) select * from ft1 where case c1 when 1 then 0 end = 0; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (((CASE "C 1" WHEN 1 THEN 0::numeric ELSE NULL::numeric END) = 0::numeric)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (((CASE "C 1" WHEN 1 THEN 0::numeric ELSE NULL::numeric END) = 0::numeric)) + Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Filter: (CASE "T 1"."C 1" WHEN 1 THEN 0::numeric ELSE NULL::numeric END = 0::numeric) + +(11 rows) + +select * from ft1 where case c1 when 1 then 0 end = 0; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +----+----+-------+------------------------------+--------------------------+----+------------+----- + 1 | 1 | 00001 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1 | 1 | foo +(1 row) + +-- ====================================================================================================================================== +-- TEST-MODULE: JOIN queries +-- -------------------------------------- +-- ====================================================================================================================================== +-- Analyze ft4 and ft5 so that we have better statistics. These tables do not +-- have use_remote_estimate set. +ANALYZE ft4; +ANALYZE ft5; +-- join two tables +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: t1.c1, t2.c1, t1.c3 + Node ID: 1 + Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2) + Remote SQL: SELECT r1."C 1", r2."C 1", r1.c3 FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) ORDER BY r1.c3 ASC NULLS LAST, r1."C 1" ASC NULLS LAST LIMIT 10::bigint OFFSET 100::bigint + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT r1."C 1", r2."C 1", r1.c3 FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) ORDER BY r1.c3 ASC NULLS LAST, r1."C 1" ASC NULLS LAST LIMIT 10::bigint OFFSET 100::bigint + Limit + Output: r1."C 1", r2."C 1", r1.c3 + -> Sort + Output: r1."C 1", r2."C 1", r1.c3 + Sort Key: r1.c3, r1."C 1" + -> Hash Join + Output: r1."C 1", r2."C 1", r1.c3 + Hash Cond: (r1."C 1" = r2."C 1") + -> Seq Scan on "S 1"."T 1" r1 + Output: r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8 + -> Hash + Output: r2."C 1" + -> Seq Scan on "S 1"."T 1" r2 + Output: r2."C 1" + +(23 rows) + +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; + c1 | c1 +-----+----- + 101 | 101 + 102 | 102 + 103 | 103 + 104 | 104 + 105 | 105 + 106 | 106 + 107 | 107 + 108 | 108 + 109 | 109 + 110 | 110 +(10 rows) + +-- join three tables +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) JOIN ft4 t3 ON (t3.c1 = t1.c1) ORDER BY t1.c3, t1.c1 OFFSET 10 LIMIT 10; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + Foreign Scan + Output: t1.c1, t2.c2, t3.c3, t1.c3 + Node ID: 1 + Relations: ((public.ft1 t1) INNER JOIN (public.ft2 t2)) INNER JOIN (public.ft4 t3) + Remote SQL: SELECT r1."C 1", r2.c2, r4.c3, r1.c3 FROM (("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) INNER JOIN "S 1"."T 3" r4 ON (((r1."C 1" = r4.c1)))) ORDER BY r1.c3 ASC NULLS LAST, r1."C 1" ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT r1."C 1", r2.c2, r4.c3, r1.c3 FROM (("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) INNER JOIN "S 1"."T 3" r4 ON (((r1."C 1" = r4.c1)))) ORDER BY r1.c3 ASC NULLS LAST, r1."C 1" ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint + Limit + Output: r1."C 1", r2.c2, r4.c3, r1.c3 + -> Sort + Output: r1."C 1", r2.c2, r4.c3, r1.c3 + Sort Key: r1.c3, r1."C 1" + -> Merge Join + Output: r1."C 1", r2.c2, r4.c3, r1.c3 + Merge Cond: (r1."C 1" = r4.c1) + -> Merge Join + Output: r1."C 1", r1.c3, r2.c2, r2."C 1" + Merge Cond: (r1."C 1" = r2."C 1") + -> Index Scan using t1_pkey on "S 1"."T 1" r1 + Output: r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8 + -> Index Scan using t1_pkey on "S 1"."T 1" r2 + Output: r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8 + -> Sort + Output: r4.c3, r4.c1 + Sort Key: r4.c1 + -> Seq Scan on "S 1"."T 3" r4 + Output: r4.c3, r4.c1 + +(29 rows) + +SELECT t1.c1, t2.c2, t3.c3 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) JOIN ft4 t3 ON (t3.c1 = t1.c1) ORDER BY t1.c3, t1.c1 OFFSET 10 LIMIT 10; + c1 | c2 | c3 +----+----+-------- + 22 | 2 | AAA022 + 24 | 4 | AAA024 + 26 | 6 | AAA026 + 28 | 8 | AAA028 + 30 | 0 | AAA030 + 32 | 2 | AAA032 + 34 | 4 | AAA034 + 36 | 6 | AAA036 + 38 | 8 | AAA038 + 40 | 0 | AAA040 +(10 rows) + +-- left outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: t1.c1, t2.c1 + Node ID: 1 + Relations: (public.ft4 t1) LEFT JOIN (public.ft5 t2) + Remote SQL: SELECT r1.c1, r2.c1 FROM ("S 1"."T 3" r1 LEFT JOIN "S 1"."T 4" r2 ON (((r1.c1 = r2.c1)))) ORDER BY r1.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT r1.c1, r2.c1 FROM ("S 1"."T 3" r1 LEFT JOIN "S 1"."T 4" r2 ON (((r1.c1 = r2.c1)))) ORDER BY r1.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint + Limit + Output: r1.c1, r2.c1 + -> Sort + Output: r1.c1, r2.c1 + Sort Key: r1.c1, r2.c1 + -> Hash Left Join + Output: r1.c1, r2.c1 + Hash Cond: (r1.c1 = r2.c1) + -> Seq Scan on "S 1"."T 3" r1 + Output: r1.c1, r1.c2, r1.c3 + -> Hash + Output: r2.c1 + -> Seq Scan on "S 1"."T 4" r2 + Output: r2.c1 + +(23 rows) + +SELECT t1.c1, t2.c1 FROM ft4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; + c1 | c1 +----+---- + 22 | + 24 | 24 + 26 | + 28 | + 30 | 30 + 32 | + 34 | + 36 | 36 + 38 | + 40 | +(10 rows) + +-- left outer join three tables +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) LEFT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: t1.c1, t2.c2, t3.c3 + Node ID: 1 + Relations: ((public.ft2 t1) LEFT JOIN (public.ft2 t2)) LEFT JOIN (public.ft4 t3) + Remote SQL: SELECT r1."C 1", r2.c2, r4.c3 FROM (("S 1"."T 1" r1 LEFT JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) LEFT JOIN "S 1"."T 3" r4 ON (((r2."C 1" = r4.c1)))) LIMIT 10::bigint OFFSET 10::bigint + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT r1."C 1", r2.c2, r4.c3 FROM (("S 1"."T 1" r1 LEFT JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) LEFT JOIN "S 1"."T 3" r4 ON (((r2."C 1" = r4.c1)))) LIMIT 10::bigint OFFSET 10::bigint + Limit + Output: r1."C 1", r2.c2, r4.c3 + -> Merge Right Join + Output: r1."C 1", r2.c2, r4.c3 + Merge Cond: (r2."C 1" = r1."C 1") + -> Merge Left Join + Output: r2.c2, r2."C 1", r4.c3 + Merge Cond: (r2."C 1" = r4.c1) + -> Index Scan using t1_pkey on "S 1"."T 1" r2 + Output: r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8 + -> Index Scan using t3_pkey on "S 1"."T 3" r4 + Output: r4.c1, r4.c2, r4.c3 + -> Index Only Scan using t1_pkey on "S 1"."T 1" r1 + Output: r1."C 1" + +(23 rows) + +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) LEFT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + c1 | c2 | c3 +----+----+-------- + 11 | 1 | + 12 | 2 | AAA012 + 13 | 3 | + 14 | 4 | AAA014 + 15 | 5 | + 16 | 6 | AAA016 + 17 | 7 | + 18 | 8 | AAA018 + 19 | 9 | + 20 | 0 | AAA020 +(10 rows) + +-- left outer join + placement of clauses. +-- clauses within the nullable side are not pulled up, but top level clause on +-- non-nullable side is pushed into non-nullable side +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t1.c2, t2.c1, t2.c2 FROM ft4 t1 LEFT JOIN (SELECT * FROM ft5 WHERE c1 < 10) t2 ON (t1.c1 = t2.c1) WHERE t1.c1 < 10; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + Foreign Scan + Output: t1.c1, t1.c2, ft5.c1, ft5.c2 + Node ID: 1 + Relations: (public.ft4 t1) LEFT JOIN (public.ft5) + Remote SQL: SELECT r1.c1, r1.c2, r4.c1, r4.c2 FROM ("S 1"."T 3" r1 LEFT JOIN "S 1"."T 4" r4 ON (((r1.c1 = r4.c1)) AND ((r4.c1 < 10)))) WHERE ((r1.c1 < 10)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT r1.c1, r1.c2, r4.c1, r4.c2 FROM ("S 1"."T 3" r1 LEFT JOIN "S 1"."T 4" r4 ON (((r1.c1 = r4.c1)) AND ((r4.c1 < 10)))) WHERE ((r1.c1 < 10)) + Hash Left Join + Output: r1.c1, r1.c2, r4.c1, r4.c2 + Hash Cond: (r1.c1 = r4.c1) + -> Seq Scan on "S 1"."T 3" r1 + Output: r1.c1, r1.c2, r1.c3 + Filter: (r1.c1 < 10) + -> Hash + Output: r4.c1, r4.c2 + -> Seq Scan on "S 1"."T 4" r4 + Output: r4.c1, r4.c2 + Filter: (r4.c1 < 10) + +(20 rows) + +SELECT t1.c1, t1.c2, t2.c1, t2.c2 FROM ft4 t1 LEFT JOIN (SELECT * FROM ft5 WHERE c1 < 10) t2 ON (t1.c1 = t2.c1) WHERE t1.c1 < 10; + c1 | c2 | c1 | c2 +----+----+----+---- + 2 | 3 | | + 4 | 5 | | + 6 | 7 | 6 | 7 + 8 | 9 | | +(4 rows) + +-- clauses within the nullable side are not pulled up, but the top level clause +-- on nullable side is not pushed down into nullable side +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t1.c2, t2.c1, t2.c2 FROM ft4 t1 LEFT JOIN (SELECT * FROM ft5 WHERE c1 < 10) t2 ON (t1.c1 = t2.c1) + WHERE (t2.c1 < 10 OR t2.c1 IS NULL) AND t1.c1 < 10; + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: t1.c1, t1.c2, ft5.c1, ft5.c2 + Node ID: 1 + Relations: (public.ft4 t1) LEFT JOIN (public.ft5) + Remote SQL: SELECT r1.c1, r1.c2, r4.c1, r4.c2 FROM ("S 1"."T 3" r1 LEFT JOIN "S 1"."T 4" r4 ON (((r1.c1 = r4.c1)) AND ((r4.c1 < 10)))) WHERE (((r4.c1 < 10) OR (r4.c1 IS NULL))) AND ((r1.c1 < 10)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT r1.c1, r1.c2, r4.c1, r4.c2 FROM ("S 1"."T 3" r1 LEFT JOIN "S 1"."T 4" r4 ON (((r1.c1 = r4.c1)) AND ((r4.c1 < 10)))) WHERE (((r4.c1 < 10) OR (r4.c1 IS NULL))) AND ((r1.c1 < 10)) + Hash Left Join + Output: r1.c1, r1.c2, r4.c1, r4.c2 + Hash Cond: (r1.c1 = r4.c1) + Filter: ((r4.c1 < 10) OR (r4.c1 IS NULL)) + -> Seq Scan on "S 1"."T 3" r1 + Output: r1.c1, r1.c2, r1.c3 + Filter: (r1.c1 < 10) + -> Hash + Output: r4.c1, r4.c2 + -> Seq Scan on "S 1"."T 4" r4 + Output: r4.c1, r4.c2 + Filter: (r4.c1 < 10) + +(21 rows) + +SELECT t1.c1, t1.c2, t2.c1, t2.c2 FROM ft4 t1 LEFT JOIN (SELECT * FROM ft5 WHERE c1 < 10) t2 ON (t1.c1 = t2.c1) + WHERE (t2.c1 < 10 OR t2.c1 IS NULL) AND t1.c1 < 10; + c1 | c2 | c1 | c2 +----+----+----+---- + 2 | 3 | | + 4 | 5 | | + 6 | 7 | 6 | 7 + 8 | 9 | | +(4 rows) + +-- right outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft5 t1 RIGHT JOIN ft4 t2 ON (t1.c1 = t2.c1) ORDER BY t2.c1, t1.c1 OFFSET 10 LIMIT 10; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: t1.c1, t2.c1 + Node ID: 1 + Relations: (public.ft4 t2) LEFT JOIN (public.ft5 t1) + Remote SQL: SELECT r1.c1, r2.c1 FROM ("S 1"."T 3" r2 LEFT JOIN "S 1"."T 4" r1 ON (((r1.c1 = r2.c1)))) ORDER BY r2.c1 ASC NULLS LAST, r1.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT r1.c1, r2.c1 FROM ("S 1"."T 3" r2 LEFT JOIN "S 1"."T 4" r1 ON (((r1.c1 = r2.c1)))) ORDER BY r2.c1 ASC NULLS LAST, r1.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint + Limit + Output: r1.c1, r2.c1 + -> Sort + Output: r1.c1, r2.c1 + Sort Key: r2.c1, r1.c1 + -> Hash Left Join + Output: r1.c1, r2.c1 + Hash Cond: (r2.c1 = r1.c1) + -> Seq Scan on "S 1"."T 3" r2 + Output: r2.c1, r2.c2, r2.c3 + -> Hash + Output: r1.c1 + -> Seq Scan on "S 1"."T 4" r1 + Output: r1.c1 + +(23 rows) + +SELECT t1.c1, t2.c1 FROM ft5 t1 RIGHT JOIN ft4 t2 ON (t1.c1 = t2.c1) ORDER BY t2.c1, t1.c1 OFFSET 10 LIMIT 10; + c1 | c1 +----+---- + | 22 + 24 | 24 + | 26 + | 28 + 30 | 30 + | 32 + | 34 + 36 | 36 + | 38 + | 40 +(10 rows) + +-- right outer join three tables +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 RIGHT JOIN ft2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: t1.c1, t2.c2, t3.c3 + Node ID: 1 + Relations: ((public.ft4 t3) LEFT JOIN (public.ft2 t2)) LEFT JOIN (public.ft2 t1) + Remote SQL: SELECT r1."C 1", r2.c2, r4.c3 FROM (("S 1"."T 3" r4 LEFT JOIN "S 1"."T 1" r2 ON (((r2."C 1" = r4.c1)))) LEFT JOIN "S 1"."T 1" r1 ON (((r1."C 1" = r2."C 1")))) LIMIT 10::bigint OFFSET 10::bigint + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT r1."C 1", r2.c2, r4.c3 FROM (("S 1"."T 3" r4 LEFT JOIN "S 1"."T 1" r2 ON (((r2."C 1" = r4.c1)))) LEFT JOIN "S 1"."T 1" r1 ON (((r1."C 1" = r2."C 1")))) LIMIT 10::bigint OFFSET 10::bigint + Limit + Output: r1."C 1", r2.c2, r4.c3 + -> Merge Right Join + Output: r1."C 1", r2.c2, r4.c3 + Merge Cond: (r2."C 1" = r4.c1) + -> Merge Left Join + Output: r2.c2, r2."C 1", r1."C 1" + Merge Cond: (r2."C 1" = r1."C 1") + -> Index Scan using t1_pkey on "S 1"."T 1" r2 + Output: r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8 + -> Index Only Scan using t1_pkey on "S 1"."T 1" r1 + Output: r1."C 1" + -> Sort + Output: r4.c3, r4.c1 + Sort Key: r4.c1 + -> Seq Scan on "S 1"."T 3" r4 + Output: r4.c3, r4.c1 + +(26 rows) + +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 RIGHT JOIN ft2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + c1 | c2 | c3 +----+----+-------- + 22 | 2 | AAA022 + 24 | 4 | AAA024 + 26 | 6 | AAA026 + 28 | 8 | AAA028 + 30 | 0 | AAA030 + 32 | 2 | AAA032 + 34 | 4 | AAA034 + 36 | 6 | AAA036 + 38 | 8 | AAA038 + 40 | 0 | AAA040 +(10 rows) + +-- full outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft4 t1 FULL JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 45 LIMIT 10; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: t1.c1, t2.c1 + Node ID: 1 + Relations: (public.ft4 t1) FULL JOIN (public.ft5 t2) + Remote SQL: SELECT r1.c1, r2.c1 FROM ("S 1"."T 3" r1 FULL JOIN "S 1"."T 4" r2 ON (((r1.c1 = r2.c1)))) ORDER BY r1.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 45::bigint + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT r1.c1, r2.c1 FROM ("S 1"."T 3" r1 FULL JOIN "S 1"."T 4" r2 ON (((r1.c1 = r2.c1)))) ORDER BY r1.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 45::bigint + Limit + Output: r1.c1, r2.c1 + -> Sort + Output: r1.c1, r2.c1 + Sort Key: r1.c1, r2.c1 + -> Hash Full Join + Output: r1.c1, r2.c1 + Hash Cond: (r1.c1 = r2.c1) + -> Seq Scan on "S 1"."T 3" r1 + Output: r1.c1, r1.c2, r1.c3 + -> Hash + Output: r2.c1 + -> Seq Scan on "S 1"."T 4" r2 + Output: r2.c1 + +(23 rows) + +SELECT t1.c1, t2.c1 FROM ft4 t1 FULL JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 45 LIMIT 10; + c1 | c1 +-----+---- + 92 | + 94 | + 96 | 96 + 98 | + 100 | + | 3 + | 9 + | 15 + | 21 + | 27 +(10 rows) + +-- full outer join with restrictions on the joining relations +-- a. the joining relations are both base relations +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1; + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: ft4.c1, ft5.c1 + Node ID: 1 + Relations: (public.ft4) FULL JOIN (public.ft5) + Remote SQL: SELECT s4.c1, s5.c1 FROM ((SELECT c1 FROM "S 1"."T 3" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s4(c1) FULL JOIN (SELECT c1 FROM "S 1"."T 4" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s5(c1) ON (((s4.c1 = s5.c1)))) ORDER BY s4.c1 ASC NULLS LAST, s5.c1 ASC NULLS LAST + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT s4.c1, s5.c1 FROM ((SELECT c1 FROM "S 1"."T 3" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s4(c1) FULL JOIN (SELECT c1 FROM "S 1"."T 4" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s5(c1) ON (((s4.c1 = s5.c1)))) ORDER BY s4.c1 ASC NULLS LAST, s5.c1 ASC NULLS LAST + Sort + Output: "T 3".c1, "T 4".c1 + Sort Key: "T 3".c1, "T 4".c1 + -> Hash Full Join + Output: "T 3".c1, "T 4".c1 + Hash Cond: ("T 3".c1 = "T 4".c1) + -> Seq Scan on "S 1"."T 3" + Output: "T 3".c1, "T 3".c2, "T 3".c3 + Filter: (("T 3".c1 >= 50) AND ("T 3".c1 <= 60)) + -> Hash + Output: "T 4".c1 + -> Seq Scan on "S 1"."T 4" + Output: "T 4".c1 + Filter: (("T 4".c1 >= 50) AND ("T 4".c1 <= 60)) + +(23 rows) + +SELECT t1.c1, t2.c1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1; + c1 | c1 +----+---- + 50 | + 52 | + 54 | 54 + 56 | + 58 | + 60 | 60 + | 51 + | 57 +(8 rows) + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT 1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t2 ON (TRUE) OFFSET 10 LIMIT 10; + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: 1 + Node ID: 1 + Relations: (public.ft4) FULL JOIN (public.ft5) + Remote SQL: SELECT NULL FROM ((SELECT NULL FROM "S 1"."T 3" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s4 FULL JOIN (SELECT NULL FROM "S 1"."T 4" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s5 ON (TRUE)) LIMIT 10::bigint OFFSET 10::bigint + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT NULL FROM ((SELECT NULL FROM "S 1"."T 3" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s4 FULL JOIN (SELECT NULL FROM "S 1"."T 4" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s5 ON (TRUE)) LIMIT 10::bigint OFFSET 10::bigint + Limit + Output: (NULL::text) + -> Subquery Scan on subquery + Output: NULL::text + -> Result + Output: (NULL::text), (NULL::text) + -> Append + -> Nested Loop Left Join + Output: (NULL::text), (NULL::text) + -> Seq Scan on "S 1"."T 3" + Output: NULL::text + Filter: (("S 1"."T 3".c1 >= 50) AND ("S 1"."T 3".c1 <= 60)) + -> Materialize + Output: (NULL::text) + -> Seq Scan on "S 1"."T 4" + Output: NULL::text + Filter: (("S 1"."T 4".c1 >= 50) AND ("S 1"."T 4".c1 <= 60)) + -> Nested Loop Left Anti Full Join + Output: (NULL::text), (NULL::text) + -> Seq Scan on "S 1"."T 4" + Output: NULL::text + Filter: (("S 1"."T 4".c1 >= 50) AND ("S 1"."T 4".c1 <= 60)) + -> Materialize + Output: (NULL::text) + -> Seq Scan on "S 1"."T 3" + Output: NULL::text + Filter: (("S 1"."T 3".c1 >= 50) AND ("S 1"."T 3".c1 <= 60)) + +(36 rows) + +SELECT 1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t2 ON (TRUE) OFFSET 10 LIMIT 10; + ?column? +---------- + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 +(10 rows) + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT count(*) FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t2 ON (TRUE) OFFSET 10 LIMIT 2; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Limit + Output: (count(*)) + -> Aggregate + Output: count(*) + -> Foreign Scan + Node ID: 1 + Relations: (public.ft4) FULL JOIN (public.ft5) + Remote SQL: SELECT NULL FROM ((SELECT NULL FROM "S 1"."T 3" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s4 FULL JOIN (SELECT NULL FROM "S 1"."T 4" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s5 ON (TRUE)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT NULL FROM ((SELECT NULL FROM "S 1"."T 3" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s4 FULL JOIN (SELECT NULL FROM "S 1"."T 4" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s5 ON (TRUE)) + Subquery Scan on subquery + Output: NULL::text + -> Result + Output: (NULL::text), (NULL::text) + -> Append + -> Nested Loop Left Join + Output: (NULL::text), (NULL::text) + -> Seq Scan on "S 1"."T 3" + Output: NULL::text + Filter: (("S 1"."T 3".c1 >= 50) AND ("S 1"."T 3".c1 <= 60)) + -> Materialize + Output: (NULL::text) + -> Seq Scan on "S 1"."T 4" + Output: NULL::text + Filter: (("S 1"."T 4".c1 >= 50) AND ("S 1"."T 4".c1 <= 60)) + -> Nested Loop Left Anti Full Join + Output: (NULL::text), (NULL::text) + -> Seq Scan on "S 1"."T 4" + Output: NULL::text + Filter: (("S 1"."T 4".c1 >= 50) AND ("S 1"."T 4".c1 <= 60)) + -> Materialize + Output: (NULL::text) + -> Seq Scan on "S 1"."T 3" + Output: NULL::text + Filter: (("S 1"."T 3".c1 >= 50) AND ("S 1"."T 3".c1 <= 60)) + +(37 rows) + +SELECT count(*) FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t2 ON (TRUE) OFFSET 10 LIMIT 2; + count +------- +(0 rows) + +-- b. one of the joining relations is a base relation and the other is a join +-- relation +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, ss.a, ss.b FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT t2.c1, t3.c1 FROM ft4 t2 LEFT JOIN ft5 t3 ON (t2.c1 = t3.c1) WHERE (t2.c1 between 50 and 60)) ss(a, b) ON (t1.c1 = ss.a) ORDER BY t1.c1, ss.a, ss.b; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Sort + Output: ft4.c1, t2.c1, t3.c1 + Sort Key: ft4.c1, t2.c1, t3.c1 + -> Foreign Scan + Output: ft4.c1, t2.c1, t3.c1 + Node ID: 1 + Relations: (public.ft4) FULL JOIN ((public.ft4 t2) LEFT JOIN (public.ft5 t3)) + Remote SQL: SELECT s4.c1, s8.c1, s8.c2 FROM ((SELECT c1 FROM "S 1"."T 3" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s4(c1) FULL JOIN (SELECT r5.c1, r6.c1 FROM ("S 1"."T 3" r5 LEFT JOIN "S 1"."T 4" r6 ON (((r5.c1 = r6.c1)))) WHERE ((r5.c1 >= 50)) AND ((r5.c1 <= 60))) s8(c1, c2) ON (((s4.c1 = s8.c1)))) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT s4.c1, s8.c1, s8.c2 FROM ((SELECT c1 FROM "S 1"."T 3" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s4(c1) FULL JOIN (SELECT r5.c1, r6.c1 FROM ("S 1"."T 3" r5 LEFT JOIN "S 1"."T 4" r6 ON (((r5.c1 = r6.c1)))) WHERE ((r5.c1 >= 50)) AND ((r5.c1 <= 60))) s8(c1, c2) ON (((s4.c1 = s8.c1)))) + Hash Full Join + Output: "T 3".c1, r5.c1, r6.c1 + Hash Cond: ("T 3".c1 = r5.c1) + -> Seq Scan on "S 1"."T 3" + Output: "T 3".c1, "T 3".c2, "T 3".c3 + Filter: (("T 3".c1 >= 50) AND ("T 3".c1 <= 60)) + -> Hash + Output: r5.c1, r6.c1 + -> Hash Right Join + Output: r5.c1, r6.c1 + Hash Cond: (r6.c1 = r5.c1) + -> Seq Scan on "S 1"."T 4" r6 + Output: r6.c1, r6.c2, r6.c3 + -> Hash + Output: r5.c1 + -> Seq Scan on "S 1"."T 3" r5 + Output: r5.c1 + Filter: ((r5.c1 >= 50) AND (r5.c1 <= 60)) + +(30 rows) + +SELECT t1.c1, ss.a, ss.b FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT t2.c1, t3.c1 FROM ft4 t2 LEFT JOIN ft5 t3 ON (t2.c1 = t3.c1) WHERE (t2.c1 between 50 and 60)) ss(a, b) ON (t1.c1 = ss.a) ORDER BY t1.c1, ss.a, ss.b; + c1 | a | b +----+----+---- + 50 | 50 | + 52 | 52 | + 54 | 54 | 54 + 56 | 56 | + 58 | 58 | + 60 | 60 | 60 +(6 rows) + +-- c. test deparsing the remote query as nested subqueries +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, ss.a, ss.b FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT t2.c1, t3.c1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t2 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t3 ON (t2.c1 = t3.c1) WHERE t2.c1 IS NULL OR t2.c1 IS NOT NULL) ss(a, b) ON (t1.c1 = ss.a) ORDER BY t1.c1, ss.a, ss.b; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: public.ft4.c1, public.ft4.c1, ft5.c1 + Node ID: 1 + Relations: (public.ft4) FULL JOIN ((public.ft4) FULL JOIN (public.ft5)) + Remote SQL: SELECT s4.c1, s10.c1, s10.c2 FROM ((SELECT c1 FROM "S 1"."T 3" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s4(c1) FULL JOIN (SELECT s8.c1, s9.c1 FROM ((SELECT c1 FROM "S 1"."T 3" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s8(c1) FULL JOIN (SELECT c1 FROM "S 1"."T 4" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s9(c1) ON (((s8.c1 = s9.c1)))) WHERE (((s8.c1 IS NULL) OR (s8.c1 IS NOT NULL)))) s10(c1, c2) ON (((s4.c1 = s10.c1)))) ORDER BY s4.c1 ASC NULLS LAST, s10.c1 ASC NULLS LAST, s10.c2 ASC NULLS LAST + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT s4.c1, s10.c1, s10.c2 FROM ((SELECT c1 FROM "S 1"."T 3" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s4(c1) FULL JOIN (SELECT s8.c1, s9.c1 FROM ((SELECT c1 FROM "S 1"."T 3" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s8(c1) FULL JOIN (SELECT c1 FROM "S 1"."T 4" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s9(c1) ON (((s8.c1 = s9.c1)))) WHERE (((s8.c1 IS NULL) OR (s8.c1 IS NOT NULL)))) s10(c1, c2) ON (((s4.c1 = s10.c1)))) ORDER BY s4.c1 ASC NULLS LAST, s10.c1 ASC NULLS LAST, s10.c2 ASC NULLS LAST + Sort + Output: "S 1"."T 3".c1, "S 1"."T 3".c1, "T 4".c1 + Sort Key: "S 1"."T 3".c1, "S 1"."T 3".c1, "T 4".c1 + -> Hash Full Join + Output: "S 1"."T 3".c1, "S 1"."T 3".c1, "T 4".c1 + Hash Cond: ("S 1"."T 3".c1 = "S 1"."T 3".c1) + -> Hash Full Join + Output: "S 1"."T 3".c1, "T 4".c1 + Hash Cond: ("S 1"."T 3".c1 = "T 4".c1) + Filter: (("S 1"."T 3".c1 IS NULL) OR ("S 1"."T 3".c1 IS NOT NULL)) + -> Seq Scan on "S 1"."T 3" + Output: "S 1"."T 3".c1, "S 1"."T 3".c2, "S 1"."T 3".c3 + Filter: (("S 1"."T 3".c1 >= 50) AND ("S 1"."T 3".c1 <= 60)) + -> Hash + Output: "T 4".c1 + -> Seq Scan on "S 1"."T 4" + Output: "T 4".c1 + Filter: (("T 4".c1 >= 50) AND ("T 4".c1 <= 60)) + -> Hash + Output: "S 1"."T 3".c1 + -> Seq Scan on "S 1"."T 3" + Output: "S 1"."T 3".c1 + Filter: (("S 1"."T 3".c1 >= 50) AND ("S 1"."T 3".c1 <= 60)) + +(32 rows) + +SELECT t1.c1, ss.a, ss.b FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT t2.c1, t3.c1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t2 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t3 ON (t2.c1 = t3.c1) WHERE t2.c1 IS NULL OR t2.c1 IS NOT NULL) ss(a, b) ON (t1.c1 = ss.a) ORDER BY t1.c1, ss.a, ss.b; + c1 | a | b +----+----+---- + 50 | 50 | + 52 | 52 | + 54 | 54 | 54 + 56 | 56 | + 58 | 58 | + 60 | 60 | 60 + | | 51 + | | 57 +(8 rows) + +-- d. test deparsing rowmarked relations as subqueries +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, ss.a, ss.b FROM (SELECT c1 FROM "S 1"."T 3" WHERE c1 = 50) t1 INNER JOIN (SELECT t2.c1, t3.c1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t2 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t3 ON (t2.c1 = t3.c1) WHERE t2.c1 IS NULL OR t2.c1 IS NOT NULL) ss(a, b) ON (TRUE) ORDER BY t1.c1, ss.a, ss.b FOR UPDATE OF t1; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------- + Sort + Output: "T 3".c1, ft4.c1, ft5.c1, "T 3".ctid, ft4.*, ft5.* + Sort Key: ft4.c1, ft5.c1 + -> LockRows + Output: "T 3".c1, ft4.c1, ft5.c1, "T 3".ctid, ft4.*, ft5.* + -> Nested Loop + Output: "T 3".c1, ft4.c1, ft5.c1, "T 3".ctid, ft4.*, ft5.* + -> Index Scan using t3_pkey on "S 1"."T 3" + Output: "T 3".c1, "T 3".ctid + Index Cond: ("T 3".c1 = 50) + -> Hash Full Join + Output: ft4.c1, ft4.*, ft5.c1, ft5.* + Hash Cond: (ft4.c1 = ft5.c1) + Filter: ((ft4.c1 IS NULL) OR (ft4.c1 IS NOT NULL)) + -> Foreign Scan on public.ft4 + Output: ft4.c1, ft4.* + Node ID: 1 + Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 3" WHERE ((c1 >= 50)) AND ((c1 <= 60)) + -> Hash + Output: ft5.c1, ft5.* + -> Foreign Scan on public.ft5 + Output: ft5.c1, ft5.* + Node ID: 2 + Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 4" WHERE ((c1 >= 50)) AND ((c1 <= 60)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c1, c2, c3 FROM "S 1"."T 3" WHERE ((c1 >= 50)) AND ((c1 <= 60)) + Seq Scan on "S 1"."T 3" + Output: c1, c2, c3 + Filter: (("T 3".c1 >= 50) AND ("T 3".c1 <= 60)) + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c1, c2, c3 FROM "S 1"."T 4" WHERE ((c1 >= 50)) AND ((c1 <= 60)) + Seq Scan on "S 1"."T 4" + Output: c1, c2, c3 + Filter: (("T 4".c1 >= 50) AND ("T 4".c1 <= 60)) + +(35 rows) + +SELECT t1.c1, ss.a, ss.b FROM (SELECT c1 FROM "S 1"."T 3" WHERE c1 = 50) t1 INNER JOIN (SELECT t2.c1, t3.c1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t2 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t3 ON (t2.c1 = t3.c1) WHERE t2.c1 IS NULL OR t2.c1 IS NOT NULL) ss(a, b) ON (TRUE) ORDER BY t1.c1, ss.a, ss.b FOR UPDATE OF t1; + c1 | a | b +----+----+---- + 50 | 50 | + 50 | 52 | + 50 | 54 | 54 + 50 | 56 | + 50 | 58 | + 50 | 60 | 60 + 50 | | 51 + 50 | | 57 +(8 rows) + +-- full outer join + inner join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1, t3.c1 FROM ft4 t1 INNER JOIN ft5 t2 ON (t1.c1 = t2.c1 + 1 and t1.c1 between 50 and 60) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) ORDER BY t1.c1, t2.c1, t3.c1 LIMIT 10; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: t1.c1, t2.c1, t3.c1 + Node ID: 1 + Relations: ((public.ft4 t1) INNER JOIN (public.ft5 t2)) FULL JOIN (public.ft4 t3) + Remote SQL: SELECT r1.c1, r2.c1, r4.c1 FROM (("S 1"."T 3" r1 INNER JOIN "S 1"."T 4" r2 ON (((r1.c1 = (r2.c1 + 1))) AND (((r2.c1 + 1) >= 50)) AND (((r2.c1 + 1) <= 60)) AND ((r1.c1 >= 50)) AND ((r1.c1 <= 60)))) FULL JOIN "S 1"."T 3" r4 ON (((r2.c1 = r4.c1)))) ORDER BY r1.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST, r4.c1 ASC NULLS LAST LIMIT 10::bigint + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT r1.c1, r2.c1, r4.c1 FROM (("S 1"."T 3" r1 INNER JOIN "S 1"."T 4" r2 ON (((r1.c1 = (r2.c1 + 1))) AND (((r2.c1 + 1) >= 50)) AND (((r2.c1 + 1) <= 60)) AND ((r1.c1 >= 50)) AND ((r1.c1 <= 60)))) FULL JOIN "S 1"."T 3" r4 ON (((r2.c1 = r4.c1)))) ORDER BY r1.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST, r4.c1 ASC NULLS LAST LIMIT 10::bigint + Limit + Output: r1.c1, r2.c1, r4.c1 + -> Sort + Output: r1.c1, r2.c1, r4.c1 + Sort Key: r1.c1, r2.c1, r4.c1 + -> Hash Full Join + Output: r1.c1, r2.c1, r4.c1 + Hash Cond: (r4.c1 = r2.c1) + -> Seq Scan on "S 1"."T 3" r4 + Output: r4.c1, r4.c2, r4.c3 + -> Hash + Output: r1.c1, r2.c1 + -> Hash Join + Output: r1.c1, r2.c1 + Hash Cond: (r1.c1 = (r2.c1 + 1)) + -> Seq Scan on "S 1"."T 3" r1 + Output: r1.c1, r1.c2, r1.c3 + Filter: ((r1.c1 >= 50) AND (r1.c1 <= 60)) + -> Hash + Output: r2.c1 + -> Seq Scan on "S 1"."T 4" r2 + Output: r2.c1 + Filter: (((r2.c1 + 1) >= 50) AND ((r2.c1 + 1) <= 60)) + +(32 rows) + +SELECT t1.c1, t2.c1, t3.c1 FROM ft4 t1 INNER JOIN ft5 t2 ON (t1.c1 = t2.c1 + 1 and t1.c1 between 50 and 60) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) ORDER BY t1.c1, t2.c1, t3.c1 LIMIT 10; + c1 | c1 | c1 +----+----+---- + 52 | 51 | + 58 | 57 | + | | 2 + | | 4 + | | 6 + | | 8 + | | 10 + | | 12 + | | 14 + | | 16 +(10 rows) + +-- full outer join three tables +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: t1.c1, t2.c2, t3.c3 + Node ID: 1 + Relations: ((public.ft2 t1) FULL JOIN (public.ft2 t2)) FULL JOIN (public.ft4 t3) + Remote SQL: SELECT r1."C 1", r2.c2, r4.c3 FROM (("S 1"."T 1" r1 FULL JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) FULL JOIN "S 1"."T 3" r4 ON (((r2."C 1" = r4.c1)))) LIMIT 10::bigint OFFSET 10::bigint + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT r1."C 1", r2.c2, r4.c3 FROM (("S 1"."T 1" r1 FULL JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) FULL JOIN "S 1"."T 3" r4 ON (((r2."C 1" = r4.c1)))) LIMIT 10::bigint OFFSET 10::bigint + Limit + Output: r1."C 1", r2.c2, r4.c3 + -> Hash Full Join + Output: r1."C 1", r2.c2, r4.c3 + Hash Cond: (r2."C 1" = r4.c1) + -> Merge Full Join + Output: r1."C 1", r2.c2, r2."C 1" + Merge Cond: (r1."C 1" = r2."C 1") + -> Index Only Scan using t1_pkey on "S 1"."T 1" r1 + Output: r1."C 1" + -> Index Scan using t1_pkey on "S 1"."T 1" r2 + Output: r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8 + -> Hash + Output: r4.c3, r4.c1 + -> Seq Scan on "S 1"."T 3" r4 + Output: r4.c3, r4.c1 + +(25 rows) + +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + c1 | c2 | c3 +----+----+-------- + 11 | 1 | + 12 | 2 | AAA012 + 13 | 3 | + 14 | 4 | AAA014 + 15 | 5 | + 16 | 6 | AAA016 + 17 | 7 | + 18 | 8 | AAA018 + 19 | 9 | + 20 | 0 | AAA020 +(10 rows) + +-- full outer join + right outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: t1.c1, t2.c2, t3.c3 + Node ID: 1 + Relations: ((public.ft4 t3) LEFT JOIN (public.ft2 t2)) LEFT JOIN (public.ft2 t1) + Remote SQL: SELECT r1."C 1", r2.c2, r4.c3 FROM (("S 1"."T 3" r4 LEFT JOIN "S 1"."T 1" r2 ON (((r2."C 1" = r4.c1)))) LEFT JOIN "S 1"."T 1" r1 ON (((r1."C 1" = r2."C 1")))) LIMIT 10::bigint OFFSET 10::bigint + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT r1."C 1", r2.c2, r4.c3 FROM (("S 1"."T 3" r4 LEFT JOIN "S 1"."T 1" r2 ON (((r2."C 1" = r4.c1)))) LEFT JOIN "S 1"."T 1" r1 ON (((r1."C 1" = r2."C 1")))) LIMIT 10::bigint OFFSET 10::bigint + Limit + Output: r1."C 1", r2.c2, r4.c3 + -> Merge Right Join + Output: r1."C 1", r2.c2, r4.c3 + Merge Cond: (r2."C 1" = r4.c1) + -> Merge Left Join + Output: r2.c2, r2."C 1", r1."C 1" + Merge Cond: (r2."C 1" = r1."C 1") + -> Index Scan using t1_pkey on "S 1"."T 1" r2 + Output: r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8 + -> Index Only Scan using t1_pkey on "S 1"."T 1" r1 + Output: r1."C 1" + -> Sort + Output: r4.c3, r4.c1 + Sort Key: r4.c1 + -> Seq Scan on "S 1"."T 3" r4 + Output: r4.c3, r4.c1 + +(26 rows) + +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + c1 | c2 | c3 +----+----+-------- + 22 | 2 | AAA022 + 24 | 4 | AAA024 + 26 | 6 | AAA026 + 28 | 8 | AAA028 + 30 | 0 | AAA030 + 32 | 2 | AAA032 + 34 | 4 | AAA034 + 36 | 6 | AAA036 + 38 | 8 | AAA038 + 40 | 0 | AAA040 +(10 rows) + +-- right outer join + full outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 RIGHT JOIN ft2 t2 ON (t1.c1 = t2.c1) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: t1.c1, t2.c2, t3.c3 + Node ID: 1 + Relations: ((public.ft2 t2) LEFT JOIN (public.ft2 t1)) FULL JOIN (public.ft4 t3) + Remote SQL: SELECT r1."C 1", r2.c2, r4.c3 FROM (("S 1"."T 1" r2 LEFT JOIN "S 1"."T 1" r1 ON (((r1."C 1" = r2."C 1")))) FULL JOIN "S 1"."T 3" r4 ON (((r2."C 1" = r4.c1)))) LIMIT 10::bigint OFFSET 10::bigint + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT r1."C 1", r2.c2, r4.c3 FROM (("S 1"."T 1" r2 LEFT JOIN "S 1"."T 1" r1 ON (((r1."C 1" = r2."C 1")))) FULL JOIN "S 1"."T 3" r4 ON (((r2."C 1" = r4.c1)))) LIMIT 10::bigint OFFSET 10::bigint + Limit + Output: r1."C 1", r2.c2, r4.c3 + -> Merge Full Join + Output: r1."C 1", r2.c2, r4.c3 + Merge Cond: (r2."C 1" = r4.c1) + -> Merge Left Join + Output: r2.c2, r2."C 1", r1."C 1" + Merge Cond: (r2."C 1" = r1."C 1") + -> Index Scan using t1_pkey on "S 1"."T 1" r2 + Output: r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8 + -> Index Only Scan using t1_pkey on "S 1"."T 1" r1 + Output: r1."C 1" + -> Index Scan using t3_pkey on "S 1"."T 3" r4 + Output: r4.c1, r4.c2, r4.c3 + +(23 rows) + +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 RIGHT JOIN ft2 t2 ON (t1.c1 = t2.c1) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + c1 | c2 | c3 +----+----+-------- + 11 | 1 | + 12 | 2 | AAA012 + 13 | 3 | + 14 | 4 | AAA014 + 15 | 5 | + 16 | 6 | AAA016 + 17 | 7 | + 18 | 8 | AAA018 + 19 | 9 | + 20 | 0 | AAA020 +(10 rows) + +-- full outer join + left outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) LEFT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: t1.c1, t2.c2, t3.c3 + Node ID: 1 + Relations: ((public.ft2 t1) FULL JOIN (public.ft2 t2)) LEFT JOIN (public.ft4 t3) + Remote SQL: SELECT r1."C 1", r2.c2, r4.c3 FROM (("S 1"."T 1" r1 FULL JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) LEFT JOIN "S 1"."T 3" r4 ON (((r2."C 1" = r4.c1)))) LIMIT 10::bigint OFFSET 10::bigint + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT r1."C 1", r2.c2, r4.c3 FROM (("S 1"."T 1" r1 FULL JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) LEFT JOIN "S 1"."T 3" r4 ON (((r2."C 1" = r4.c1)))) LIMIT 10::bigint OFFSET 10::bigint + Limit + Output: r1."C 1", r2.c2, r4.c3 + -> Hash Left Join + Output: r1."C 1", r2.c2, r4.c3 + Hash Cond: (r2."C 1" = r4.c1) + -> Merge Full Join + Output: r1."C 1", r2.c2, r2."C 1" + Merge Cond: (r1."C 1" = r2."C 1") + -> Index Only Scan using t1_pkey on "S 1"."T 1" r1 + Output: r1."C 1" + -> Index Scan using t1_pkey on "S 1"."T 1" r2 + Output: r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8 + -> Hash + Output: r4.c3, r4.c1 + -> Seq Scan on "S 1"."T 3" r4 + Output: r4.c3, r4.c1 + +(25 rows) + +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) LEFT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + c1 | c2 | c3 +----+----+-------- + 11 | 1 | + 12 | 2 | AAA012 + 13 | 3 | + 14 | 4 | AAA014 + 15 | 5 | + 16 | 6 | AAA016 + 17 | 7 | + 18 | 8 | AAA018 + 19 | 9 | + 20 | 0 | AAA020 +(10 rows) + +-- left outer join + full outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: t1.c1, t2.c2, t3.c3 + Node ID: 1 + Relations: ((public.ft2 t1) LEFT JOIN (public.ft2 t2)) FULL JOIN (public.ft4 t3) + Remote SQL: SELECT r1."C 1", r2.c2, r4.c3 FROM (("S 1"."T 1" r1 LEFT JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) FULL JOIN "S 1"."T 3" r4 ON (((r2."C 1" = r4.c1)))) LIMIT 10::bigint OFFSET 10::bigint + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT r1."C 1", r2.c2, r4.c3 FROM (("S 1"."T 1" r1 LEFT JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) FULL JOIN "S 1"."T 3" r4 ON (((r2."C 1" = r4.c1)))) LIMIT 10::bigint OFFSET 10::bigint + Limit + Output: r1."C 1", r2.c2, r4.c3 + -> Hash Full Join + Output: r1."C 1", r2.c2, r4.c3 + Hash Cond: (r2."C 1" = r4.c1) + -> Merge Left Join + Output: r1."C 1", r2.c2, r2."C 1" + Merge Cond: (r1."C 1" = r2."C 1") + -> Index Only Scan using t1_pkey on "S 1"."T 1" r1 + Output: r1."C 1" + -> Index Scan using t1_pkey on "S 1"."T 1" r2 + Output: r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8 + -> Hash + Output: r4.c3, r4.c1 + -> Seq Scan on "S 1"."T 3" r4 + Output: r4.c3, r4.c1 + +(25 rows) + +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + c1 | c2 | c3 +----+----+-------- + 11 | 1 | + 12 | 2 | AAA012 + 13 | 3 | + 14 | 4 | AAA014 + 15 | 5 | + 16 | 6 | AAA016 + 17 | 7 | + 18 | 8 | AAA018 + 19 | 9 | + 20 | 0 | AAA020 +(10 rows) + +SET enable_memoize TO off; --nspt +ERROR: unrecognized configuration parameter "enable_memoize" +-- right outer join + left outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 RIGHT JOIN ft2 t2 ON (t1.c1 = t2.c1) LEFT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: t1.c1, t2.c2, t3.c3 + Node ID: 1 + Relations: ((public.ft2 t2) LEFT JOIN (public.ft2 t1)) LEFT JOIN (public.ft4 t3) + Remote SQL: SELECT r1."C 1", r2.c2, r4.c3 FROM (("S 1"."T 1" r2 LEFT JOIN "S 1"."T 1" r1 ON (((r1."C 1" = r2."C 1")))) LEFT JOIN "S 1"."T 3" r4 ON (((r2."C 1" = r4.c1)))) LIMIT 10::bigint OFFSET 10::bigint + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT r1."C 1", r2.c2, r4.c3 FROM (("S 1"."T 1" r2 LEFT JOIN "S 1"."T 1" r1 ON (((r1."C 1" = r2."C 1")))) LEFT JOIN "S 1"."T 3" r4 ON (((r2."C 1" = r4.c1)))) LIMIT 10::bigint OFFSET 10::bigint + Limit + Output: r1."C 1", r2.c2, r4.c3 + -> Merge Left Join + Output: r1."C 1", r2.c2, r4.c3 + Merge Cond: (r2."C 1" = r4.c1) + -> Merge Left Join + Output: r2.c2, r2."C 1", r1."C 1" + Merge Cond: (r2."C 1" = r1."C 1") + -> Index Scan using t1_pkey on "S 1"."T 1" r2 + Output: r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8 + -> Index Only Scan using t1_pkey on "S 1"."T 1" r1 + Output: r1."C 1" + -> Index Scan using t3_pkey on "S 1"."T 3" r4 + Output: r4.c1, r4.c2, r4.c3 + +(23 rows) + +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 RIGHT JOIN ft2 t2 ON (t1.c1 = t2.c1) LEFT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + c1 | c2 | c3 +----+----+-------- + 11 | 1 | + 12 | 2 | AAA012 + 13 | 3 | + 14 | 4 | AAA014 + 15 | 5 | + 16 | 6 | AAA016 + 17 | 7 | + 18 | 8 | AAA018 + 19 | 9 | + 20 | 0 | AAA020 +(10 rows) + +RESET enable_memoize; --nspt +ERROR: unrecognized configuration parameter "enable_memoize" +-- left outer join + right outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: t1.c1, t2.c2, t3.c3 + Node ID: 1 + Relations: (public.ft4 t3) LEFT JOIN ((public.ft2 t1) INNER JOIN (public.ft2 t2)) + Remote SQL: SELECT r1."C 1", r2.c2, r4.c3 FROM ("S 1"."T 3" r4 LEFT JOIN ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) ON (((r2."C 1" = r4.c1)))) LIMIT 10::bigint OFFSET 10::bigint + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT r1."C 1", r2.c2, r4.c3 FROM ("S 1"."T 3" r4 LEFT JOIN ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) ON (((r2."C 1" = r4.c1)))) LIMIT 10::bigint OFFSET 10::bigint + Limit + Output: r1."C 1", r2.c2, r4.c3 + -> Merge Right Join + Output: r1."C 1", r2.c2, r4.c3 + Merge Cond: (r2."C 1" = r4.c1) + -> Merge Join + Output: r1."C 1", r2.c2, r2."C 1" + Merge Cond: (r1."C 1" = r2."C 1") + -> Index Only Scan using t1_pkey on "S 1"."T 1" r1 + Output: r1."C 1" + -> Index Scan using t1_pkey on "S 1"."T 1" r2 + Output: r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8 + -> Sort + Output: r4.c3, r4.c1 + Sort Key: r4.c1 + -> Seq Scan on "S 1"."T 3" r4 + Output: r4.c3, r4.c1 + +(26 rows) + +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + c1 | c2 | c3 +----+----+-------- + 22 | 2 | AAA022 + 24 | 4 | AAA024 + 26 | 6 | AAA026 + 28 | 8 | AAA028 + 30 | 0 | AAA030 + 32 | 2 | AAA032 + 34 | 4 | AAA034 + 36 | 6 | AAA036 + 38 | 8 | AAA038 + 40 | 0 | AAA040 +(10 rows) + +-- full outer join + WHERE clause, only matched rows +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft4 t1 FULL JOIN ft5 t2 ON (t1.c1 = t2.c1) WHERE (t1.c1 = t2.c1 OR t1.c1 IS NULL) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Limit + Output: t1.c1, t2.c1 + -> Sort + Output: t1.c1, t2.c1 + Sort Key: t1.c1, t2.c1 + -> Foreign Scan + Output: t1.c1, t2.c1 + Node ID: 1 + Relations: (public.ft4 t1) FULL JOIN (public.ft5 t2) + Remote SQL: SELECT r1.c1, r2.c1 FROM ("S 1"."T 3" r1 FULL JOIN "S 1"."T 4" r2 ON (((r1.c1 = r2.c1)))) WHERE (((r1.c1 = r2.c1) OR (r1.c1 IS NULL))) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT r1.c1, r2.c1 FROM ("S 1"."T 3" r1 FULL JOIN "S 1"."T 4" r2 ON (((r1.c1 = r2.c1)))) WHERE (((r1.c1 = r2.c1) OR (r1.c1 IS NULL))) + Hash Full Join + Output: r1.c1, r2.c1 + Hash Cond: (r1.c1 = r2.c1) + Filter: ((r1.c1 = r2.c1) OR (r1.c1 IS NULL)) + -> Seq Scan on "S 1"."T 3" r1 + Output: r1.c1, r1.c2, r1.c3 + -> Hash + Output: r2.c1 + -> Seq Scan on "S 1"."T 4" r2 + Output: r2.c1 + +(24 rows) + +SELECT t1.c1, t2.c1 FROM ft4 t1 FULL JOIN ft5 t2 ON (t1.c1 = t2.c1) WHERE (t1.c1 = t2.c1 OR t1.c1 IS NULL) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; + c1 | c1 +----+---- + 66 | 66 + 72 | 72 + 78 | 78 + 84 | 84 + 90 | 90 + 96 | 96 + | 3 + | 9 + | 15 + | 21 +(10 rows) + +-- full outer join + WHERE clause with shippable extensions set +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t1.c3 FROM ft1 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE postgres_fdw_abs(t1.c1) > 0 OFFSET 10 LIMIT 10; + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------------- + Limit + Output: t1.c1, t2.c2, t1.c3 + -> Foreign Scan + Output: t1.c1, t2.c2, t1.c3 + Filter: (postgres_fdw_abs(t1.c1) > 0) + Node ID: 1 + Relations: (public.ft1 t1) FULL JOIN (public.ft2 t2) + Remote SQL: SELECT r1."C 1", r1.c3, r2.c2 FROM ("S 1"."T 1" r1 FULL JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT r1."C 1", r1.c3, r2.c2 FROM ("S 1"."T 1" r1 FULL JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) + Hash Full Join + Output: r1."C 1", r1.c3, r2.c2 + Hash Cond: (r1."C 1" = r2."C 1") + -> Seq Scan on "S 1"."T 1" r1 + Output: r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8 + -> Hash + Output: r2.c2, r2."C 1" + -> Seq Scan on "S 1"."T 1" r2 + Output: r2.c2, r2."C 1" + +(21 rows) + +ALTER SERVER loopback OPTIONS (DROP extensions); --nspt +ERROR: option "extensions" not found +-- full outer join + WHERE clause with shippable extensions not set +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t1.c3 FROM ft1 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE postgres_fdw_abs(t1.c1) > 0 OFFSET 10 LIMIT 10; + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------------- + Limit + Output: t1.c1, t2.c2, t1.c3 + -> Foreign Scan + Output: t1.c1, t2.c2, t1.c3 + Filter: (postgres_fdw_abs(t1.c1) > 0) + Node ID: 1 + Relations: (public.ft1 t1) FULL JOIN (public.ft2 t2) + Remote SQL: SELECT r1."C 1", r1.c3, r2.c2 FROM ("S 1"."T 1" r1 FULL JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT r1."C 1", r1.c3, r2.c2 FROM ("S 1"."T 1" r1 FULL JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) + Hash Full Join + Output: r1."C 1", r1.c3, r2.c2 + Hash Cond: (r1."C 1" = r2."C 1") + -> Seq Scan on "S 1"."T 1" r1 + Output: r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8 + -> Hash + Output: r2.c2, r2."C 1" + -> Seq Scan on "S 1"."T 1" r2 + Output: r2.c2, r2."C 1" + +(21 rows) + +ALTER SERVER loopback OPTIONS (ADD extensions 'postgres_fdw'); --nspt +ERROR: invalid option "extensions" +HINT: Valid options in this context are: service, connect_timeout, dbname, host, remote_nodename, hostaddr, port, localhost, localport, application_name, fencedUdfRPCMode, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, rw_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, krbsrvname, prototype, connection_info, connectionExtraInfo, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, updatable +-- join two tables with FOR UPDATE clause +-- tests whole-row reference for row marks +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR UPDATE OF t1; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------- + Limit + Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* + -> Sort + Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* + Sort Key: t1.c3, t1.c1 + -> LockRows + Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* + -> Hash Join + Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* + Hash Cond: (t1.c1 = t2.c1) + -> Foreign Scan on public.ft1 t1 + Output: t1.c1, t1.c3, t1.* + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" FOR UPDATE + -> Hash + Output: t2.c1, t2.* + -> Foreign Scan on public.ft2 t2 + Output: t2.c1, t2.* + Node ID: 2 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" FOR UPDATE + LockRows + Output: "C 1", c2, c3, c4, c5, c6, c7, c8, ctid + -> Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8, ctid + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" + Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + +(31 rows) + +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR UPDATE OF t1; + c1 | c1 +-----+----- + 101 | 101 + 102 | 102 + 103 | 103 + 104 | 104 + 105 | 105 + 106 | 106 + 107 | 107 + 108 | 108 + 109 | 109 + 110 | 110 +(10 rows) + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR UPDATE; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------ + Limit + Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* + -> Sort + Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* + Sort Key: t1.c3, t1.c1 + -> LockRows + Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* + -> Hash Join + Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* + Hash Cond: (t2.c1 = t1.c1) + -> Foreign Scan on public.ft2 t2 + Output: t2.c1, t2.* + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" FOR UPDATE + -> Hash + Output: t1.c1, t1.c3, t1.* + -> Foreign Scan on public.ft1 t1 + Output: t1.c1, t1.c3, t1.* + Node ID: 2 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" FOR UPDATE + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" FOR UPDATE + LockRows + Output: "C 1", c2, c3, c4, c5, c6, c7, c8, ctid + -> Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8, ctid + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" FOR UPDATE + LockRows + Output: "C 1", c2, c3, c4, c5, c6, c7, c8, ctid + -> Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8, ctid + +(33 rows) + +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR UPDATE; + c1 | c1 +-----+----- + 101 | 101 + 102 | 102 + 103 | 103 + 104 | 104 + 105 | 105 + 106 | 106 + 107 | 107 + 108 | 108 + 109 | 109 + 110 | 110 +(10 rows) + +-- join two tables with FOR SHARE clause +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR SHARE OF t1; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------- + Limit + Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* + -> Sort + Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* + Sort Key: t1.c3, t1.c1 + -> LockRows + Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* + -> Hash Join + Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* + Hash Cond: (t1.c1 = t2.c1) + -> Foreign Scan on public.ft1 t1 + Output: t1.c1, t1.c3, t1.* + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" FOR SHARE + -> Hash + Output: t2.c1, t2.* + -> Foreign Scan on public.ft2 t2 + Output: t2.c1, t2.* + Node ID: 2 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" FOR SHARE + LockRows + Output: "C 1", c2, c3, c4, c5, c6, c7, c8, ctid + -> Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8, ctid + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" + Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + +(31 rows) + +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR SHARE OF t1; + c1 | c1 +-----+----- + 101 | 101 + 102 | 102 + 103 | 103 + 104 | 104 + 105 | 105 + 106 | 106 + 107 | 107 + 108 | 108 + 109 | 109 + 110 | 110 +(10 rows) + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR SHARE; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------- + Limit + Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* + -> Sort + Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* + Sort Key: t1.c3, t1.c1 + -> LockRows + Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* + -> Hash Join + Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* + Hash Cond: (t2.c1 = t1.c1) + -> Foreign Scan on public.ft2 t2 + Output: t2.c1, t2.* + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" FOR SHARE + -> Hash + Output: t1.c1, t1.c3, t1.* + -> Foreign Scan on public.ft1 t1 + Output: t1.c1, t1.c3, t1.* + Node ID: 2 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" FOR SHARE + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" FOR SHARE + LockRows + Output: "C 1", c2, c3, c4, c5, c6, c7, c8, ctid + -> Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8, ctid + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" FOR SHARE + LockRows + Output: "C 1", c2, c3, c4, c5, c6, c7, c8, ctid + -> Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8, ctid + +(33 rows) + +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR SHARE; + c1 | c1 +-----+----- + 101 | 101 + 102 | 102 + 103 | 103 + 104 | 104 + 105 | 105 + 106 | 106 + 107 | 107 + 108 | 108 + 109 | 109 + 110 | 110 +(10 rows) + +-- join in CTE +EXPLAIN (VERBOSE, COSTS OFF) +WITH t (c1_1, c1_3, c2_1) AS MATERIALIZED (SELECT t1.c1, t1.c3, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1)) SELECT c1_1, c2_1 FROM t ORDER BY c1_3, c1_1 OFFSET 100 LIMIT 10; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------------- + Limit + Output: t.c1_1, t.c2_1, t.c1_3 + CTE t + -> Foreign Scan + Output: t1.c1, t1.c3, t2.c1 + Node ID: 1 + Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2) + Remote SQL: SELECT r1."C 1", r1.c3, r2."C 1" FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) + -> Sort + Output: t.c1_1, t.c2_1, t.c1_3 + Sort Key: t.c1_3, t.c1_1 + -> CTE Scan on t + Output: t.c1_1, t.c2_1, t.c1_3 + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT r1."C 1", r1.c3, r2."C 1" FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) + Hash Join + Output: r1."C 1", r1.c3, r2."C 1" + Hash Cond: (r1."C 1" = r2."C 1") + -> Seq Scan on "S 1"."T 1" r1 + Output: r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8 + -> Hash + Output: r2."C 1" + -> Seq Scan on "S 1"."T 1" r2 + Output: r2."C 1" + +(26 rows) + +WITH t (c1_1, c1_3, c2_1) AS MATERIALIZED (SELECT t1.c1, t1.c3, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1)) SELECT c1_1, c2_1 FROM t ORDER BY c1_3, c1_1 OFFSET 100 LIMIT 10; + c1_1 | c2_1 +------+------ + 101 | 101 + 102 | 102 + 103 | 103 + 104 | 104 + 105 | 105 + 106 | 106 + 107 | 107 + 108 | 108 + 109 | 109 + 110 | 110 +(10 rows) + +-- ctid with whole-row reference, currently, does not support system column +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.ctid, t1, t2, t1.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; +ERROR: column t1.ctid does not exist +LINE 2: SELECT t1.ctid, t1, t2, t1.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1... + ^ +CONTEXT: referenced column: ctid +-- SEMI JOIN, not pushed down +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1 FROM ft1 t1 WHERE EXISTS (SELECT 1 FROM ft2 t2 WHERE t1.c1 = t2.c1) ORDER BY t1.c1 OFFSET 100 LIMIT 10; + QUERY PLAN +------------------------------------------------------------------------ + Limit + Output: t1.c1 + -> Sort + Output: t1.c1 + Sort Key: t1.c1 + -> Hash Semi Join + Output: t1.c1 + Hash Cond: (t1.c1 = t2.c1) + -> Foreign Scan on public.ft1 t1 + Output: t1.c1 + Node ID: 1 + Remote SQL: SELECT "C 1" FROM "S 1"."T 1" + -> Hash + Output: t2.c1 + -> Foreign Scan on public.ft2 t2 + Output: t2.c1 + Node ID: 2 + Remote SQL: SELECT "C 1" FROM "S 1"."T 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1" FROM "S 1"."T 1" + Seq Scan on "S 1"."T 1" + Output: "C 1" + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1" FROM "S 1"."T 1" + Seq Scan on "S 1"."T 1" + Output: "C 1" + +(27 rows) + +SELECT t1.c1 FROM ft1 t1 WHERE EXISTS (SELECT 1 FROM ft2 t2 WHERE t1.c1 = t2.c1) ORDER BY t1.c1 OFFSET 100 LIMIT 10; + c1 +----- + 101 + 102 + 103 + 104 + 105 + 106 + 107 + 108 + 109 + 110 +(10 rows) + +-- ANTI JOIN, not pushed down +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1 FROM ft1 t1 WHERE NOT EXISTS (SELECT 1 FROM ft2 t2 WHERE t1.c1 = t2.c2) ORDER BY t1.c1 OFFSET 100 LIMIT 10; + QUERY PLAN +------------------------------------------------------------------------------------------------------ + Limit + Output: t1.c1 + -> Merge Anti Join + Output: t1.c1 + Merge Cond: (t1.c1 = t2.c2) + -> Foreign Scan on public.ft1 t1 + Output: t1.c1 + Node ID: 1 + Remote SQL: SELECT "C 1" FROM "S 1"."T 1" ORDER BY "C 1" ASC NULLS LAST + -> Materialize + Output: t2.c2 + -> Foreign Scan on public.ft2 t2 + Output: t2.c2 + Node ID: 2 + Remote SQL: SELECT c2 FROM "S 1"."T 1" ORDER BY c2 ASC NULLS LAST + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1" FROM "S 1"."T 1" ORDER BY "C 1" ASC NULLS LAST + [Bypass] + Index Only Scan using t1_pkey on "S 1"."T 1" + Output: "C 1" + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c2 FROM "S 1"."T 1" ORDER BY c2 ASC NULLS LAST + Sort + Output: c2 + Sort Key: "T 1".c2 + -> Seq Scan on "S 1"."T 1" + Output: c2 + +(28 rows) + +SELECT t1.c1 FROM ft1 t1 WHERE NOT EXISTS (SELECT 1 FROM ft2 t2 WHERE t1.c1 = t2.c2) ORDER BY t1.c1 OFFSET 100 LIMIT 10; + c1 +----- + 110 + 111 + 112 + 113 + 114 + 115 + 116 + 117 + 118 + 119 +(10 rows) + +-- CROSS JOIN can be pushed down +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 CROSS JOIN ft2 t2 ORDER BY t1.c1, t2.c1 OFFSET 100 LIMIT 10; + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: t1.c1, t2.c1 + Node ID: 1 + Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2) + Remote SQL: SELECT r1."C 1", r2."C 1" FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (TRUE)) ORDER BY r1."C 1" ASC NULLS LAST, r2."C 1" ASC NULLS LAST LIMIT 10::bigint OFFSET 100::bigint + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT r1."C 1", r2."C 1" FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (TRUE)) ORDER BY r1."C 1" ASC NULLS LAST, r2."C 1" ASC NULLS LAST LIMIT 10::bigint OFFSET 100::bigint + Limit + Output: r1."C 1", r2."C 1" + -> Sort + Output: r1."C 1", r2."C 1" + Sort Key: r1."C 1", r2."C 1" + -> Nested Loop + Output: r1."C 1", r2."C 1" + -> Seq Scan on "S 1"."T 1" r1 + Output: r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8 + -> Materialize + Output: r2."C 1" + -> Seq Scan on "S 1"."T 1" r2 + Output: r2."C 1" + +(22 rows) + +SELECT t1.c1, t2.c1 FROM ft1 t1 CROSS JOIN ft2 t2 ORDER BY t1.c1, t2.c1 OFFSET 100 LIMIT 10; + c1 | c1 +----+----- + 1 | 101 + 1 | 102 + 1 | 103 + 1 | 104 + 1 | 105 + 1 | 106 + 1 | 107 + 1 | 108 + 1 | 109 + 1 | 110 +(10 rows) + +-- different server, not pushed down. No result expected. +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft5 t1 JOIN ft6 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 100 LIMIT 10; + QUERY PLAN +------------------------------------------------------------------------------------------------ + Limit + Output: t1.c1, t2.c1 + -> Merge Join + Output: t1.c1, t2.c1 + Merge Cond: (t1.c1 = t2.c1) + -> Foreign Scan on public.ft5 t1 + Output: t1.c1 + Node ID: 1 + Remote SQL: SELECT c1 FROM "S 1"."T 4" ORDER BY c1 ASC NULLS LAST + -> Materialize + Output: t2.c1 + -> Foreign Scan on public.ft6 t2 + Output: t2.c1 + Node ID: 2 + Remote SQL: SELECT c1 FROM "S 1"."T 4" ORDER BY c1 ASC NULLS LAST + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c1 FROM "S 1"."T 4" ORDER BY c1 ASC NULLS LAST + Sort + Output: c1 + Sort Key: "T 4".c1 + -> Seq Scan on "S 1"."T 4" + Output: c1 + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c1 FROM "S 1"."T 4" ORDER BY c1 ASC NULLS LAST + Sort + Output: c1 + Sort Key: "T 4".c1 + -> Seq Scan on "S 1"."T 4" + Output: c1 + +(30 rows) + +SELECT t1.c1, t2.c1 FROM ft5 t1 JOIN ft6 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 100 LIMIT 10; + c1 | c1 +----+---- +(0 rows) + +-- unsafe join conditions (c8 has a UDT), not pushed down. Practically a CROSS +-- JOIN since c8 in both tables has same value. +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 LEFT JOIN ft2 t2 ON (t1.c8 = t2.c8) ORDER BY t1.c1, t2.c1 OFFSET 100 LIMIT 10; + QUERY PLAN +---------------------------------------------------------------------------- + Limit + Output: t1.c1, t2.c1 + -> Sort + Output: t1.c1, t2.c1 + Sort Key: t1.c1, t2.c1 + -> Hash Left Join + Output: t1.c1, t2.c1 + Hash Cond: (t1.c8 = t2.c8) + -> Foreign Scan on public.ft1 t1 + Output: t1.c1, t1.c8 + Node ID: 1 + Remote SQL: SELECT "C 1", c8 FROM "S 1"."T 1" + -> Hash + Output: t2.c1, t2.c8 + -> Foreign Scan on public.ft2 t2 + Output: t2.c1, t2.c8 + Node ID: 2 + Remote SQL: SELECT "C 1", c8 FROM "S 1"."T 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c8 FROM "S 1"."T 1" + Seq Scan on "S 1"."T 1" + Output: "C 1", c8 + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c8 FROM "S 1"."T 1" + Seq Scan on "S 1"."T 1" + Output: "C 1", c8 + +(27 rows) + +SELECT t1.c1, t2.c1 FROM ft1 t1 LEFT JOIN ft2 t2 ON (t1.c8 = t2.c8) ORDER BY t1.c1, t2.c1 OFFSET 100 LIMIT 10; + c1 | c1 +----+----- + 1 | 101 + 1 | 102 + 1 | 103 + 1 | 104 + 1 | 105 + 1 | 106 + 1 | 107 + 1 | 108 + 1 | 109 + 1 | 110 +(10 rows) + +-- unsafe conditions on one side (c8 has a UDT), not pushed down. +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE t1.c8 = 'foo' ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; + QUERY PLAN +-------------------------------------------------------------------------------- + Limit + Output: t1.c1, t2.c1, t1.c3 + -> Sort + Output: t1.c1, t2.c1, t1.c3 + Sort Key: t1.c3, t1.c1 + -> Hash Left Join + Output: t1.c1, t2.c1, t1.c3 + Hash Cond: (t1.c1 = t2.c1) + -> Foreign Scan on public.ft1 t1 + Output: t1.c1, t1.c3 + Filter: (t1.c8 = 'foo'::user_enum) + Node ID: 1 + Remote SQL: SELECT "C 1", c3, c8 FROM "S 1"."T 1" + -> Hash + Output: t2.c1 + -> Foreign Scan on public.ft2 t2 + Output: t2.c1 + Node ID: 2 + Remote SQL: SELECT "C 1" FROM "S 1"."T 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c3, c8 FROM "S 1"."T 1" + Seq Scan on "S 1"."T 1" + Output: "C 1", c3, c8 + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1" FROM "S 1"."T 1" + Seq Scan on "S 1"."T 1" + Output: "C 1" + +(28 rows) + +SELECT t1.c1, t2.c1 FROM ft1 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE t1.c8 = 'foo' ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; + c1 | c1 +-----+----- + 101 | 101 + 102 | 102 + 103 | 103 + 104 | 104 + 105 | 105 + 106 | 106 + 107 | 107 + 108 | 108 + 109 | 109 + 110 | 110 +(10 rows) + +-- join where unsafe to pushdown condition in WHERE clause has a column not +-- in the SELECT clause. In this test unsafe clause needs to have column +-- references from both joining sides so that the clause is not pushed down +-- into one of the joining sides. +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE t1.c8 = t2.c8 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Limit + Output: t1.c1, t2.c1, t1.c3 + -> Sort + Output: t1.c1, t2.c1, t1.c3 + Sort Key: t1.c3, t1.c1 + -> Foreign Scan + Output: t1.c1, t2.c1, t1.c3 + Filter: (t1.c8 = t2.c8) + Node ID: 1 + Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2) + Remote SQL: SELECT r1."C 1", r1.c3, r2."C 1", r1.c8, r2.c8 FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT r1."C 1", r1.c3, r2."C 1", r1.c8, r2.c8 FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) + Hash Join + Output: r1."C 1", r1.c3, r2."C 1", r1.c8, r2.c8 + Hash Cond: (r1."C 1" = r2."C 1") + -> Seq Scan on "S 1"."T 1" r1 + Output: r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8 + -> Hash + Output: r2."C 1", r2.c8 + -> Seq Scan on "S 1"."T 1" r2 + Output: r2."C 1", r2.c8 + +(24 rows) + +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE t1.c8 = t2.c8 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; + c1 | c1 +-----+----- + 101 | 101 + 102 | 102 + 103 | 103 + 104 | 104 + 105 | 105 + 106 | 106 + 107 | 107 + 108 | 108 + 109 | 109 + 110 | 110 +(10 rows) + +-- Aggregate after UNION, for testing setrefs +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1c1, avg(t1c1 + t2c1) FROM (SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) UNION SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1)) AS t (t1c1, t2c1) GROUP BY t1c1 ORDER BY t1c1 OFFSET 100 LIMIT 10; + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------------- + Limit + Output: t1.c1, (avg((t1.c1 + t2.c1))) + -> Sort + Output: t1.c1, (avg((t1.c1 + t2.c1))) + Sort Key: t1.c1 + -> HashAggregate + Output: t1.c1, avg((t1.c1 + t2.c1)) + Group By Key: t1.c1 + -> HashAggregate + Output: t1.c1, t2.c1 + Group By Key: t1.c1, t2.c1 + -> Append + -> Foreign Scan + Output: t1.c1, t2.c1 + Node ID: 1 + Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2) + Remote SQL: SELECT r1."C 1", r2."C 1" FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) + -> Foreign Scan + Output: t1.c1, t2.c1 + Node ID: 2 + Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2) + Remote SQL: SELECT r1."C 1", r2."C 1" FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT r1."C 1", r2."C 1" FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) + Hash Join + Output: r1."C 1", r2."C 1" + Hash Cond: (r1."C 1" = r2."C 1") + -> Seq Scan on "S 1"."T 1" r1 + Output: r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8 + -> Hash + Output: r2."C 1" + -> Seq Scan on "S 1"."T 1" r2 + Output: r2."C 1" + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT r1."C 1", r2."C 1" FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) + Hash Join + Output: r1."C 1", r2."C 1" + Hash Cond: (r1."C 1" = r2."C 1") + -> Seq Scan on "S 1"."T 1" r1 + Output: r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8 + -> Hash + Output: r2."C 1" + -> Seq Scan on "S 1"."T 1" r2 + Output: r2."C 1" + +(45 rows) + +SELECT t1c1, avg(t1c1 + t2c1) FROM (SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) UNION SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1)) AS t (t1c1, t2c1) GROUP BY t1c1 ORDER BY t1c1 OFFSET 100 LIMIT 10; + t1c1 | avg +------+---------------------- + 101 | 202.0000000000000000 + 102 | 204.0000000000000000 + 103 | 206.0000000000000000 + 104 | 208.0000000000000000 + 105 | 210.0000000000000000 + 106 | 212.0000000000000000 + 107 | 214.0000000000000000 + 108 | 216.0000000000000000 + 109 | 218.0000000000000000 + 110 | 220.0000000000000000 +(10 rows) + +-- join with lateral reference +EXPLAIN (VERBOSE, COSTS OFF) -- openGauss not support LATERAL +SELECT t1."C 1" FROM "S 1"."T 1" t1, LATERAL (SELECT DISTINCT t2.c1, t3.c1 FROM ft1 t2, ft2 t3 WHERE t2.c1 = t3.c1 AND t2.c2 = t1.c2) q ORDER BY t1."C 1" OFFSET 10 LIMIT 10; +ERROR: syntax error at or near "SELECT" +LINE 2: SELECT t1."C 1" FROM "S 1"."T 1" t1, LATERAL (SELECT DISTINC... + ^ +SELECT t1."C 1" FROM "S 1"."T 1" t1, LATERAL (SELECT DISTINCT t2.c1, t3.c1 FROM ft1 t2, ft2 t3 WHERE t2.c1 = t3.c1 AND t2.c2 = t1.c2) q ORDER BY t1."C 1" OFFSET 10 LIMIT 10; +ERROR: syntax error at or near "SELECT" +LINE 1: SELECT t1."C 1" FROM "S 1"."T 1" t1, LATERAL (SELECT DISTINC... + ^ +-- non-Var items in targetlist of the nullable rel of a join preventing +-- push-down in some cases +-- unable to push {ft1, ft2} +EXPLAIN (VERBOSE, COSTS OFF) +SELECT q.a, ft2.c1 FROM (SELECT 13 FROM ft1 WHERE c1 = 13) q(a) RIGHT JOIN ft2 ON (q.a = ft2.c1) WHERE ft2.c1 BETWEEN 10 AND 15; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------ + Nested Loop Left Join + Output: (13), ft2.c1 + Join Filter: (13 = ft2.c1) + -> Foreign Scan on public.ft2 + Output: ft2.c1 + Node ID: 1 + Remote SQL: SELECT "C 1" FROM "S 1"."T 1" WHERE (("C 1" >= 10)) AND (("C 1" <= 15)) ORDER BY "C 1" ASC NULLS LAST -> Materialize - Output: ref_3.c3 - -> Foreign Scan on public.ft2 ref_3 - Output: ref_3.c3 - Remote SQL: SELECT c3 FROM "S 1"."T 1" WHERE ((c3 = '00001'::text)) + Output: (13) + -> Foreign Scan on public.ft1 + Output: 13 + Node ID: 2 + Remote SQL: SELECT NULL FROM "S 1"."T 1" WHERE (("C 1" = 13)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1" FROM "S 1"."T 1" WHERE (("C 1" >= 10)) AND (("C 1" <= 15)) ORDER BY "C 1" ASC NULLS LAST + [Bypass] + Index Only Scan using t1_pkey on "S 1"."T 1" + Output: "C 1" + Index Cond: (("T 1"."C 1" >= 10) AND ("T 1"."C 1" <= 15)) + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT NULL FROM "S 1"."T 1" WHERE (("C 1" = 13)) + Index Only Scan using t1_pkey on "S 1"."T 1" + Output: NULL::text + Index Cond: ("T 1"."C 1" = 13) + +(25 rows) + +SELECT q.a, ft2.c1 FROM (SELECT 13 FROM ft1 WHERE c1 = 13) q(a) RIGHT JOIN ft2 ON (q.a = ft2.c1) WHERE ft2.c1 BETWEEN 10 AND 15; + a | c1 +----+---- + | 10 + | 11 + | 12 + 13 | 13 + | 14 + | 15 +(6 rows) + +-- ok to push {ft1, ft2} but not {ft1, ft2, ft4} +EXPLAIN (VERBOSE, COSTS OFF) +SELECT ft4.c1, q.* FROM ft4 LEFT JOIN (SELECT 13, ft1.c1, ft2.c1 FROM ft1 RIGHT JOIN ft2 ON (ft1.c1 = ft2.c1) WHERE ft1.c1 = 12) q(a, b, c) ON (ft4.c1 = q.b) WHERE ft4.c1 BETWEEN 10 AND 15; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Nested Loop Left Join + Output: ft4.c1, (13), ft1.c1, ft2.c1 + Join Filter: (ft4.c1 = ft1.c1) + -> Foreign Scan on public.ft4 + Output: ft4.c1 + Node ID: 1 + Remote SQL: SELECT c1 FROM "S 1"."T 3" WHERE ((c1 >= 10)) AND ((c1 <= 15)) + -> Materialize + Output: ft1.c1, ft2.c1, (13) + -> Foreign Scan + Output: ft1.c1, ft2.c1, 13 + Node ID: 2 + Relations: (public.ft1) INNER JOIN (public.ft2) + Remote SQL: SELECT r4."C 1", r5."C 1" FROM ("S 1"."T 1" r4 INNER JOIN "S 1"."T 1" r5 ON (((r5."C 1" = 12)) AND ((r4."C 1" = 12)))) ORDER BY r4."C 1" ASC NULLS LAST + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c1 FROM "S 1"."T 3" WHERE ((c1 >= 10)) AND ((c1 <= 15)) + Seq Scan on "S 1"."T 3" + Output: c1 + Filter: (("T 3".c1 >= 10) AND ("T 3".c1 <= 15)) + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT r4."C 1", r5."C 1" FROM ("S 1"."T 1" r4 INNER JOIN "S 1"."T 1" r5 ON (((r5."C 1" = 12)) AND ((r4."C 1" = 12)))) ORDER BY r4."C 1" ASC NULLS LAST + Nested Loop + Output: r4."C 1", r5."C 1" + -> Index Only Scan using t1_pkey on "S 1"."T 1" r4 + Output: r4."C 1" + Index Cond: (r4."C 1" = 12) + -> Index Only Scan using t1_pkey on "S 1"."T 1" r5 + Output: r5."C 1" + Index Cond: (r5."C 1" = 12) + +(30 rows) + +SELECT ft4.c1, q.* FROM ft4 LEFT JOIN (SELECT 13, ft1.c1, ft2.c1 FROM ft1 RIGHT JOIN ft2 ON (ft1.c1 = ft2.c1) WHERE ft1.c1 = 12) q(a, b, c) ON (ft4.c1 = q.b) WHERE ft4.c1 BETWEEN 10 AND 15; + c1 | a | b | c +----+----+----+---- + 10 | | | + 12 | 13 | 12 | 12 + 14 | | | +(3 rows) + +-- join with nullable side with some columns with null values +UPDATE ft5 SET c3 = null where c1 % 9 = 0; +EXPLAIN (VERBOSE, COSTS OFF) +SELECT ft5, ft5.c1, ft5.c2, ft5.c3, ft4.c1, ft4.c2 FROM ft5 left join ft4 on ft5.c1 = ft4.c1 WHERE ft4.c1 BETWEEN 10 and 30 ORDER BY ft5.c1, ft4.c1; + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: ft5.*, ft5.c1, ft5.c2, ft5.c3, ft4.c1, ft4.c2 + Node ID: 1 + Relations: (public.ft5) INNER JOIN (public.ft4) + Remote SQL: SELECT CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1.c1, r1.c2, r1.c3) END, r1.c1, r1.c2, r1.c3, r2.c1, r2.c2 FROM ("S 1"."T 4" r1 INNER JOIN "S 1"."T 3" r2 ON (((r1.c1 = r2.c1)) AND ((r2.c1 >= 10)) AND ((r2.c1 <= 30)) AND ((r1.c1 >= 10)) AND ((r1.c1 <= 30)))) ORDER BY r1.c1 ASC NULLS LAST + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1.c1, r1.c2, r1.c3) END, r1.c1, r1.c2, r1.c3, r2.c1, r2.c2 FROM ("S 1"."T 4" r1 INNER JOIN "S 1"."T 3" r2 ON (((r1.c1 = r2.c1)) AND ((r2.c1 >= 10)) AND ((r2.c1 <= 30)) AND ((r1.c1 >= 10)) AND ((r1.c1 <= 30)))) ORDER BY r1.c1 ASC NULLS LAST + Sort + Output: (CASE WHEN ((r1.*)::text IS NOT NULL) THEN ROW(r1.c1, r1.c2, r1.c3) ELSE NULL::record END), r1.c1, r1.c2, r1.c3, r2.c1, r2.c2 + Sort Key: r1.c1 + -> Hash Join + Output: CASE WHEN ((r1.*)::text IS NOT NULL) THEN ROW(r1.c1, r1.c2, r1.c3) ELSE NULL::record END, r1.c1, r1.c2, r1.c3, r2.c1, r2.c2 + Hash Cond: (r2.c1 = r1.c1) + -> Seq Scan on "S 1"."T 3" r2 + Output: r2.c1, r2.c2, r2.c3 + Filter: ((r2.c1 >= 10) AND (r2.c1 <= 30)) + -> Hash + Output: r1.*, r1.c1, r1.c2, r1.c3 + -> Seq Scan on "S 1"."T 4" r1 + Output: r1.*, r1.c1, r1.c2, r1.c3 + Filter: ((r1.c1 >= 10) AND (r1.c1 <= 30)) + +(23 rows) + +SELECT ft5, ft5.c1, ft5.c2, ft5.c3, ft4.c1, ft4.c2 FROM ft5 left join ft4 on ft5.c1 = ft4.c1 WHERE ft4.c1 BETWEEN 10 and 30 ORDER BY ft5.c1, ft4.c1; + ft5 | c1 | c2 | c3 | c1 | c2 +----------------+----+----+--------+----+---- + (12,13,AAA012) | 12 | 13 | AAA012 | 12 | 13 + (18,19,) | 18 | 19 | | 18 | 19 + (24,25,AAA024) | 24 | 25 | AAA024 | 24 | 25 + (30,31,AAA030) | 30 | 31 | AAA030 | 30 | 31 +(4 rows) + +-- multi-way join involving multiple merge joins +-- (this case used to have EPQ-related planning problems) +CREATE TABLE local_tbl (c1 int NOT NULL, c2 int NOT NULL, c3 text, CONSTRAINT local_tbl_pkey PRIMARY KEY (c1)); +NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "local_tbl_pkey" for table "local_tbl" +INSERT INTO local_tbl SELECT id, id % 10, to_char(id, 'FM0000') FROM generate_series(1, 1000) id; +ANALYZE local_tbl; +SET enable_nestloop TO false; +SET enable_hashjoin TO false; +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM ft1, ft2, ft4, ft5, local_tbl WHERE ft1.c1 = ft2.c1 AND ft1.c2 = ft4.c1 + AND ft1.c2 = ft5.c1 AND ft1.c2 = local_tbl.c1 AND ft1.c1 < 100 AND ft2.c1 < 100 FOR UPDATE; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + LockRows + Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft4.c1, ft4.c2, ft4.c3, ft5.c1, ft5.c2, ft5.c3, local_tbl.c1, local_tbl.c2, local_tbl.c3, local_tbl.ctid, ft1.*, ft2.*, ft4.*, ft5.* + -> Merge Join + Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft4.c1, ft4.c2, ft4.c3, ft5.c1, ft5.c2, ft5.c3, local_tbl.c1, local_tbl.c2, local_tbl.c3, local_tbl.ctid, ft1.*, ft2.*, ft4.*, ft5.* + Merge Cond: (ft2.c1 = ft1.c1) + -> Foreign Scan on public.ft2 + Output: ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.* + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" < 100)) ORDER BY "C 1" ASC NULLS LAST FOR UPDATE + -> Sort + Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft4.c1, ft4.c2, ft4.c3, ft4.*, ft5.c1, ft5.c2, ft5.c3, ft5.*, local_tbl.c1, local_tbl.c2, local_tbl.c3, local_tbl.ctid + Sort Key: ft1.c1 + -> Merge Join + Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft4.c1, ft4.c2, ft4.c3, ft4.*, ft5.c1, ft5.c2, ft5.c3, ft5.*, local_tbl.c1, local_tbl.c2, local_tbl.c3, local_tbl.ctid + Merge Cond: (ft4.c1 = ft1.c2) + -> Merge Join + Output: ft4.c1, ft4.c2, ft4.c3, ft4.*, ft5.c1, ft5.c2, ft5.c3, ft5.*, local_tbl.c1, local_tbl.c2, local_tbl.c3, local_tbl.ctid + Merge Cond: (ft4.c1 = local_tbl.c1) + -> Merge Join + Output: ft4.c1, ft4.c2, ft4.c3, ft4.*, ft5.c1, ft5.c2, ft5.c3, ft5.* + Merge Cond: (ft4.c1 = ft5.c1) + -> Sort + Output: ft4.c1, ft4.c2, ft4.c3, ft4.* + Sort Key: ft4.c1 + -> Foreign Scan on public.ft4 + Output: ft4.c1, ft4.c2, ft4.c3, ft4.* + Node ID: 2 + Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 3" FOR UPDATE + -> Sort + Output: ft5.c1, ft5.c2, ft5.c3, ft5.* + Sort Key: ft5.c1 + -> Foreign Scan on public.ft5 + Output: ft5.c1, ft5.c2, ft5.c3, ft5.* + Node ID: 3 + Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 4" FOR UPDATE + -> Index Scan using local_tbl_pkey on public.local_tbl + Output: local_tbl.c1, local_tbl.c2, local_tbl.c3, local_tbl.ctid + -> Sort + Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.* + Sort Key: ft1.c2 + -> Foreign Scan on public.ft1 + Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.* + Node ID: 4 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" < 100)) FOR UPDATE + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" < 100)) ORDER BY "C 1" ASC NULLS LAST FOR UPDATE + [Bypass] + LockRows + Output: "C 1", c2, c3, c4, c5, c6, c7, c8, ctid + -> Index Scan using t1_pkey on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8, ctid + Index Cond: ("T 1"."C 1" < 100) + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c1, c2, c3 FROM "S 1"."T 3" FOR UPDATE + LockRows + Output: c1, c2, c3, ctid + -> Seq Scan on "S 1"."T 3" + Output: c1, c2, c3, ctid + Node 3: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c1, c2, c3 FROM "S 1"."T 4" FOR UPDATE + LockRows + Output: c1, c2, c3, ctid + -> Seq Scan on "S 1"."T 4" + Output: c1, c2, c3, ctid + Node 4: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" < 100)) FOR UPDATE + [Bypass] + LockRows + Output: "C 1", c2, c3, c4, c5, c6, c7, c8, ctid + -> Index Scan using t1_pkey on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8, ctid + Index Cond: ("T 1"."C 1" < 100) + +(71 rows) + +SELECT * FROM ft1, ft2, ft4, ft5, local_tbl WHERE ft1.c1 = ft2.c1 AND ft1.c2 = ft4.c1 + AND ft1.c2 = ft5.c1 AND ft1.c2 = local_tbl.c1 AND ft1.c1 < 100 AND ft2.c1 < 100 FOR UPDATE; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 | c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 | c1 | c2 | c3 | c1 | c2 | c3 | c1 | c2 | c3 +----+----+-------+------------------------------+--------------------------+----+------------+-----+----+----+-------+------------------------------+--------------------------+----+------------+-----+----+----+--------+----+----+--------+----+----+------ + 6 | 6 | 00006 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo | 6 | 6 | 00006 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo | 6 | 7 | AAA006 | 6 | 7 | AAA006 | 6 | 6 | 0006 + 16 | 6 | 00016 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo | 16 | 6 | 00016 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo | 6 | 7 | AAA006 | 6 | 7 | AAA006 | 6 | 6 | 0006 + 26 | 6 | 00026 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo | 26 | 6 | 00026 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo | 6 | 7 | AAA006 | 6 | 7 | AAA006 | 6 | 6 | 0006 + 36 | 6 | 00036 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo | 36 | 6 | 00036 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo | 6 | 7 | AAA006 | 6 | 7 | AAA006 | 6 | 6 | 0006 + 46 | 6 | 00046 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo | 46 | 6 | 00046 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo | 6 | 7 | AAA006 | 6 | 7 | AAA006 | 6 | 6 | 0006 + 56 | 6 | 00056 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo | 56 | 6 | 00056 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo | 6 | 7 | AAA006 | 6 | 7 | AAA006 | 6 | 6 | 0006 + 66 | 6 | 00066 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo | 66 | 6 | 00066 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo | 6 | 7 | AAA006 | 6 | 7 | AAA006 | 6 | 6 | 0006 + 76 | 6 | 00076 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo | 76 | 6 | 00076 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo | 6 | 7 | AAA006 | 6 | 7 | AAA006 | 6 | 6 | 0006 + 86 | 6 | 00086 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo | 86 | 6 | 00086 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo | 6 | 7 | AAA006 | 6 | 7 | AAA006 | 6 | 6 | 0006 + 96 | 6 | 00096 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo | 96 | 6 | 00096 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo | 6 | 7 | AAA006 | 6 | 7 | AAA006 | 6 | 6 | 0006 +(10 rows) + +RESET enable_nestloop; +RESET enable_hashjoin; +DROP TABLE local_tbl; +-- check join pushdown in situations where multiple userids are involved +CREATE ROLE regress_view_owner sysadmin password 'QWERT@12345'; +CREATE USER MAPPING FOR regress_view_owner SERVER loopback; +GRANT SELECT ON ft4 TO regress_view_owner; +GRANT SELECT ON ft5 TO regress_view_owner; +CREATE VIEW v4 AS SELECT * FROM ft4; +CREATE VIEW v5 AS SELECT * FROM ft5; +ALTER VIEW v5 OWNER TO regress_view_owner; +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; -- can't be pushed down, different view owners + QUERY PLAN +------------------------------------------------------------------------- + Limit + Output: ft4.c1, ft5.c2, ft5.c1 + -> Sort + Output: ft4.c1, ft5.c2, ft5.c1 + Sort Key: ft4.c1, ft5.c1 + -> Hash Left Join + Output: ft4.c1, ft5.c2, ft5.c1 + Hash Cond: (ft4.c1 = ft5.c1) + -> Foreign Scan on public.ft4 + Output: ft4.c1 + Node ID: 1 + Remote SQL: SELECT c1 FROM "S 1"."T 3" + -> Hash + Output: ft5.c2, ft5.c1 + -> Foreign Scan on public.ft5 + Output: ft5.c2, ft5.c1 + Node ID: 2 + Remote SQL: SELECT c1, c2 FROM "S 1"."T 4" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c1 FROM "S 1"."T 3" + Seq Scan on "S 1"."T 3" + Output: c1 + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c1, c2 FROM "S 1"."T 4" + Seq Scan on "S 1"."T 4" + Output: c1, c2 + +(27 rows) + +SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; + c1 | c2 +----+---- + 22 | + 24 | 25 + 26 | + 28 | + 30 | 31 + 32 | + 34 | + 36 | 37 + 38 | + 40 | +(10 rows) + +ALTER VIEW v4 OWNER TO regress_view_owner; +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; -- can be pushed down + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: ft4.c1, ft5.c2, ft5.c1 + Node ID: 1 + Relations: (public.ft4) LEFT JOIN (public.ft5) + Remote SQL: SELECT r6.c1, r9.c2, r9.c1 FROM ("S 1"."T 3" r6 LEFT JOIN "S 1"."T 4" r9 ON (((r6.c1 = r9.c1)))) ORDER BY r6.c1 ASC NULLS LAST, r9.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT r6.c1, r9.c2, r9.c1 FROM ("S 1"."T 3" r6 LEFT JOIN "S 1"."T 4" r9 ON (((r6.c1 = r9.c1)))) ORDER BY r6.c1 ASC NULLS LAST, r9.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint + Limit + Output: r6.c1, r9.c2, r9.c1 + -> Sort + Output: r6.c1, r9.c2, r9.c1 + Sort Key: r6.c1, r9.c1 + -> Hash Left Join + Output: r6.c1, r9.c2, r9.c1 + Hash Cond: (r6.c1 = r9.c1) + -> Seq Scan on "S 1"."T 3" r6 + Output: r6.c1, r6.c2, r6.c3 + -> Hash + Output: r9.c2, r9.c1 + -> Seq Scan on "S 1"."T 4" r9 + Output: r9.c2, r9.c1 + +(23 rows) + +SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; + c1 | c2 +----+---- + 22 | + 24 | 25 + 26 | + 28 | + 30 | 31 + 32 | + 34 | + 36 | 37 + 38 | + 40 | +(10 rows) + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; -- can't be pushed down, view owner not current user + QUERY PLAN +------------------------------------------------------------------------- + Limit + Output: ft4.c1, t2.c2, t2.c1 + -> Sort + Output: ft4.c1, t2.c2, t2.c1 + Sort Key: ft4.c1, t2.c1 + -> Hash Left Join + Output: ft4.c1, t2.c2, t2.c1 + Hash Cond: (ft4.c1 = t2.c1) + -> Foreign Scan on public.ft4 + Output: ft4.c1 + Node ID: 1 + Remote SQL: SELECT c1 FROM "S 1"."T 3" + -> Hash + Output: t2.c2, t2.c1 + -> Foreign Scan on public.ft5 t2 + Output: t2.c2, t2.c1 + Node ID: 2 + Remote SQL: SELECT c1, c2 FROM "S 1"."T 4" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c1 FROM "S 1"."T 3" + Seq Scan on "S 1"."T 3" + Output: c1 + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c1, c2 FROM "S 1"."T 4" + Seq Scan on "S 1"."T 4" + Output: c1, c2 + +(27 rows) + +SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; + c1 | c2 +----+---- + 22 | + 24 | 25 + 26 | + 28 | + 30 | 31 + 32 | + 34 | + 36 | 37 + 38 | + 40 | +(10 rows) + +ALTER VIEW v4 OWNER TO regress_view_owner; +-- PlaceHolder +explain(verbose, costs off) select * from ft1 t1 left join (select c1, case when c2 is not null then 1 else 0 end as cc from ft2) t2 on t1.c1 = t2.c1; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------ + Hash Left Join + Output: t1.c1, t1.c2, t1.c3, t1.c4, t1.c5, t1.c6, t1.c7, t1.c8, ft2.c1, (CASE WHEN (ft2.c2 IS NOT NULL) THEN 1 ELSE 0 END) + Hash Cond: (t1.c1 = ft2.c1) + -> Foreign Scan on public.ft1 t1 + Output: t1.c1, t1.c2, t1.c3, t1.c4, t1.c5, t1.c6, t1.c7, t1.c8 + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" + -> Hash + Output: ft2.c1, (CASE WHEN (ft2.c2 IS NOT NULL) THEN 1 ELSE 0 END) + -> Foreign Scan on public.ft2 + Output: ft2.c1, CASE WHEN (ft2.c2 IS NOT NULL) THEN 1 ELSE 0 END + Node ID: 2 + Remote SQL: SELECT "C 1", c2 FROM "S 1"."T 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" + Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2 FROM "S 1"."T 1" + Seq Scan on "S 1"."T 1" + Output: "C 1", c2 + +(22 rows) + +-- cleanup +DROP OWNED BY regress_view_owner; +DROP ROLE regress_view_owner; +-- ====================================================================================================================================== +-- TEST-MODULE: Aggregate and grouping queries +-- -------------------------------------- +-- openGauss not support filter +-- ====================================================================================================================================== +-- Simple aggregates +explain (verbose, costs off) +select count(c6), sum(c1), avg(c1), min(c2), max(c1), stddev(c2), sum(c1) * (random() <= 1)::int as sum2 from ft1 where c2 < 5 group by c2 order by 1, 2; + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: (count(c6)), (sum(c1)), (avg(c1)), (min(c2)), (max(c1)), (stddev(c2)), ((sum(c1)) * ((random() <= 1::double precision))::integer), c2 + Node ID: 1 + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT count(c6), sum("C 1"), avg("C 1"), min(c2), max("C 1"), stddev(c2), c2 FROM "S 1"."T 1" WHERE ((c2 < 5)) GROUP BY 7 ORDER BY count(c6) ASC NULLS LAST, sum("C 1") ASC NULLS LAST + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT count(c6), sum("C 1"), avg("C 1"), min(c2), max("C 1"), stddev(c2), c2 FROM "S 1"."T 1" WHERE ((c2 < 5)) GROUP BY 7 ORDER BY count(c6) ASC NULLS LAST, sum("C 1") ASC NULLS LAST + Sort + Output: (count(c6)), (sum("C 1")), (avg("C 1")), (min(c2)), (max("C 1")), (stddev(c2)), c2 + Sort Key: (count("T 1".c6)), (sum("T 1"."C 1")) + -> HashAggregate + Output: count(c6), sum("C 1"), avg("C 1"), min(c2), max("C 1"), stddev(c2), c2 + Group By Key: "T 1".c2 + -> Seq Scan on "S 1"."T 1" + Output: c2, c6, "C 1" + Filter: ("T 1".c2 < 5) + +(18 rows) + +select count(c6), sum(c1), avg(c1), min(c2), max(c1), stddev(c2), sum(c1) * (random() <= 1)::int as sum2 from ft1 where c2 < 5 group by c2 order by 1, 2; + count | sum | avg | min | max | stddev | sum2 +-------+-------+----------------------+-----+------+--------+------- + 100 | 49600 | 496.0000000000000000 | 1 | 991 | 0 | 49600 + 100 | 49700 | 497.0000000000000000 | 2 | 992 | 0 | 49700 + 100 | 49800 | 498.0000000000000000 | 3 | 993 | 0 | 49800 + 100 | 49900 | 499.0000000000000000 | 4 | 994 | 0 | 49900 + 100 | 50500 | 505.0000000000000000 | 0 | 1000 | 0 | 50500 +(5 rows) + +explain (verbose, costs off) +select count(c6), sum(c1), avg(c1), min(c2), max(c1), stddev(c2), sum(c1) * (random() <= 1)::int as sum2 from ft1 where c2 < 5 group by c2 order by 1, 2 limit 1; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: (count(c6)), (sum(c1)), (avg(c1)), (min(c2)), (max(c1)), (stddev(c2)), ((sum(c1)) * ((random() <= 1::double precision))::integer), c2 + Node ID: 1 + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT count(c6), sum("C 1"), avg("C 1"), min(c2), max("C 1"), stddev(c2), c2 FROM "S 1"."T 1" WHERE ((c2 < 5)) GROUP BY 7 ORDER BY count(c6) ASC NULLS LAST, sum("C 1") ASC NULLS LAST LIMIT 1::bigint + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT count(c6), sum("C 1"), avg("C 1"), min(c2), max("C 1"), stddev(c2), c2 FROM "S 1"."T 1" WHERE ((c2 < 5)) GROUP BY 7 ORDER BY count(c6) ASC NULLS LAST, sum("C 1") ASC NULLS LAST LIMIT 1::bigint + Limit + Output: (count(c6)), (sum("C 1")), (avg("C 1")), (min(c2)), (max("C 1")), (stddev(c2)), c2 + -> Sort + Output: (count(c6)), (sum("C 1")), (avg("C 1")), (min(c2)), (max("C 1")), (stddev(c2)), c2 + Sort Key: (count("T 1".c6)), (sum("T 1"."C 1")) + -> HashAggregate + Output: count(c6), sum("C 1"), avg("C 1"), min(c2), max("C 1"), stddev(c2), c2 + Group By Key: "T 1".c2 + -> Seq Scan on "S 1"."T 1" + Output: c2, c6, "C 1" + Filter: ("T 1".c2 < 5) + +(20 rows) + +select count(c6), sum(c1), avg(c1), min(c2), max(c1), stddev(c2), sum(c1) * (random() <= 1)::int as sum2 from ft1 where c2 < 5 group by c2 order by 1, 2 limit 1; + count | sum | avg | min | max | stddev | sum2 +-------+-------+----------------------+-----+-----+--------+------- + 100 | 49600 | 496.0000000000000000 | 1 | 991 | 0 | 49600 +(1 row) + +-- Aggregate is not pushed down as aggregation contains random() +explain (verbose, costs off) +select sum(c1 * (random() <= 1)::int) as sum, avg(c1) from ft1; + QUERY PLAN +----------------------------------------------------------------------------- + Aggregate + Output: sum((c1 * ((random() <= 1::double precision))::integer)), avg(c1) + -> Foreign Scan on public.ft1 + Output: c1 + Node ID: 1 + Remote SQL: SELECT "C 1" FROM "S 1"."T 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1" FROM "S 1"."T 1" + Seq Scan on "S 1"."T 1" + Output: "C 1" + +(12 rows) + +-- Aggregate over join query +explain (verbose, costs off) +select count(*), sum(t1.c1), avg(t2.c1) from ft1 t1 inner join ft1 t2 on (t1.c2 = t2.c2) where t1.c2 = 6; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: (count(*)), (sum(t1.c1)), (avg(t2.c1)) + Node ID: 1 + Relations: Aggregate on ((public.ft1 t1) INNER JOIN (public.ft1 t2)) + Remote SQL: SELECT count(*), sum(r1."C 1"), avg(r2."C 1") FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r2.c2 = 6)) AND ((r1.c2 = 6)))) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT count(*), sum(r1."C 1"), avg(r2."C 1") FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r2.c2 = 6)) AND ((r1.c2 = 6)))) + Aggregate + Output: count(*), sum(r1."C 1"), avg(r2."C 1") + -> Nested Loop + Output: r1."C 1", r2."C 1" + -> Seq Scan on "S 1"."T 1" r1 + Output: r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8 + Filter: (r1.c2 = 6) + -> Materialize + Output: r2."C 1" + -> Seq Scan on "S 1"."T 1" r2 + Output: r2."C 1" + Filter: (r2.c2 = 6) + +(21 rows) + +select count(*), sum(t1.c1), avg(t2.c1) from ft1 t1 inner join ft1 t2 on (t1.c2 = t2.c2) where t1.c2 = 6; + count | sum | avg +-------+---------+---------------------- + 10000 | 5010000 | 501.0000000000000000 +(1 row) + +-- Not pushed down due to local conditions present in underneath input rel +explain (verbose, costs off) +select sum(t1.c1), count(t2.c1) from ft1 t1 inner join ft2 t2 on (t1.c1 = t2.c1) where ((t1.c1 * t2.c1)/(t1.c1 * t2.c1)) * random() <= 1; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------- + Aggregate + Output: sum(t1.c1), count(t2.c1) + -> Foreign Scan + Output: t1.c1, t2.c1 + Filter: ((((t1.c1 * t2.c1) / (t1.c1 * t2.c1)) * random()) <= 1::double precision) + Node ID: 1 + Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2) + Remote SQL: SELECT r1."C 1", r2."C 1" FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT r1."C 1", r2."C 1" FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) + Hash Join + Output: r1."C 1", r2."C 1" + Hash Cond: (r1."C 1" = r2."C 1") + -> Seq Scan on "S 1"."T 1" r1 + Output: r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8 + -> Hash + Output: r2."C 1" + -> Seq Scan on "S 1"."T 1" r2 + Output: r2."C 1" + +(21 rows) + +-- GROUP BY clause having expressions +explain (verbose, costs off) +select c2/2, sum(c2) * (c2/2) from ft1 group by c2/2 order by c2/2; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: ((c2 / 2)), (((sum(c2))::double precision * (c2 / 2))) + Node ID: 1 + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT (c2 / 2), (sum(c2) * (c2 / 2)) FROM "S 1"."T 1" GROUP BY 1 ORDER BY (c2 / 2) ASC NULLS LAST + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT (c2 / 2), (sum(c2) * (c2 / 2)) FROM "S 1"."T 1" GROUP BY 1 ORDER BY (c2 / 2) ASC NULLS LAST + Sort + Output: ((c2 / 2)), (((sum(c2))::double precision * ((c2 / 2)))) + Sort Key: (("T 1".c2 / 2)) + -> HashAggregate + Output: ((c2 / 2)), ((sum(c2))::double precision * ((c2 / 2))) + Group By Key: ("T 1".c2 / 2) + -> Seq Scan on "S 1"."T 1" + Output: (c2 / 2), c2 + +(17 rows) + +select c2/2, sum(c2) * (c2/2) from ft1 group by c2/2 order by c2/2; + ?column? | ?column? +----------+---------- + 0 | 0 + .5 | 50 + 1 | 200 + 1.5 | 450 + 2 | 800 + 2.5 | 1250 + 3 | 1800 + 3.5 | 2450 + 4 | 3200 + 4.5 | 4050 +(10 rows) + +-- Aggregates in subquery are pushed down. +explain (verbose, costs off) +select count(x.a), sum(x.a) from (select c2 a, sum(c1) b from ft1 group by c2, sqrt(c1) order by 1, 2) x; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------ + Aggregate + Output: count(ft1.c2), sum(ft1.c2) + -> Foreign Scan + Output: ft1.c2, (sum(ft1.c1)), (sqrt((ft1.c1)::double precision)) + Node ID: 1 + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT c2, sum("C 1"), sqrt("C 1") FROM "S 1"."T 1" GROUP BY 1, 3 ORDER BY c2 ASC NULLS LAST, sum("C 1") ASC NULLS LAST + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c2, sum("C 1"), sqrt("C 1") FROM "S 1"."T 1" GROUP BY 1, 3 ORDER BY c2 ASC NULLS LAST, sum("C 1") ASC NULLS LAST + Sort + Output: c2, (sum("C 1")), (sqrt(("C 1")::double precision)) + Sort Key: "T 1".c2, (sum("T 1"."C 1")) + -> HashAggregate + Output: c2, sum("C 1"), (sqrt(("C 1")::double precision)) + Group By Key: "T 1".c2, sqrt(("T 1"."C 1")::double precision) + -> Seq Scan on "S 1"."T 1" + Output: c2, sqrt(("C 1")::double precision), "C 1" + +(19 rows) + +select count(x.a), sum(x.a) from (select c2 a, sum(c1) b from ft1 group by c2, sqrt(c1) order by 1, 2) x; + count | sum +-------+------ + 1000 | 4500 +(1 row) + +-- Aggregate is still pushed down by taking unshippable expression out +explain (verbose, costs off) +select c2 * (random() <= 1)::int as sum1, sum(c1) * c2 as sum2 from ft1 group by c2 order by 1, 2; + QUERY PLAN +------------------------------------------------------------------------------------------------- + Sort + Output: ((c2 * ((random() <= 1::double precision))::integer)), ((sum(c1) * c2)), c2 + Sort Key: ((ft1.c2 * ((random() <= 1::double precision))::integer)), ((sum(ft1.c1) * ft1.c2)) + -> HashAggregate + Output: (c2 * ((random() <= 1::double precision))::integer), (sum(c1) * c2), c2 + Group By Key: ft1.c2 + -> Foreign Scan on public.ft1 + Output: c2, c1 + Node ID: 1 + Remote SQL: SELECT "C 1", c2 FROM "S 1"."T 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2 FROM "S 1"."T 1" + Seq Scan on "S 1"."T 1" + Output: "C 1", c2 + +(16 rows) + +select c2 * (random() <= 1)::int as sum1, sum(c1) * c2 as sum2 from ft1 group by c2 order by 1, 2; + sum1 | sum2 +------+-------- + 0 | 0 + 1 | 49600 + 2 | 99400 + 3 | 149400 + 4 | 199600 + 5 | 250000 + 6 | 300600 + 7 | 351400 + 8 | 402400 + 9 | 453600 +(10 rows) + +-- Aggregate with unshippable GROUP BY clause are not pushed +explain (verbose, costs off) +select c2 * (random() <= 1)::int as c2 from ft2 group by c2 * (random() <= 1)::int order by 1; + QUERY PLAN +----------------------------------------------------------------------------- + Group + Output: ((c2 * ((random() <= 1::double precision))::integer)) + Group By Key: ((ft2.c2 * ((random() <= 1::double precision))::integer)) + -> Sort + Output: ((c2 * ((random() <= 1::double precision))::integer)) + Sort Key: ((ft2.c2 * ((random() <= 1::double precision))::integer)) + -> Foreign Scan on public.ft2 + Output: (c2 * ((random() <= 1::double precision))::integer) + Node ID: 1 + Remote SQL: SELECT c2 FROM "S 1"."T 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c2 FROM "S 1"."T 1" + Seq Scan on "S 1"."T 1" + Output: c2 + +(16 rows) + +-- GROUP BY clause in various forms, cardinal, alias and constant expression +explain (verbose, costs off) +select count(c2) w, c2 x, 5 y, 7.0 z from ft1 group by 2, y, 9.0::int order by 2; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: (count(c2)), c2, (5), (7.0), (9) + Node ID: 1 + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT count(c2), c2, 5, 7.0, 9 FROM "S 1"."T 1" GROUP BY 2, 3, 5 ORDER BY c2 ASC NULLS LAST + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT count(c2), c2, 5, 7.0, 9 FROM "S 1"."T 1" GROUP BY 2, 3, 5 ORDER BY c2 ASC NULLS LAST + Sort + Output: (count(c2)), c2, (5), (7.0), (9) + Sort Key: "T 1".c2 + -> HashAggregate + Output: count(c2), c2, (5), 7.0, (9) + Group By Key: "T 1".c2, 5, 9 + -> Seq Scan on "S 1"."T 1" + Output: c2, 5, 9 + +(17 rows) + +select count(c2) w, c2 x, 5 y, 7.0 z from ft1 group by 2, y, 9.0::int order by 2; + w | x | y | z +-----+---+---+----- + 100 | 0 | 5 | 7.0 + 100 | 1 | 5 | 7.0 + 100 | 2 | 5 | 7.0 + 100 | 3 | 5 | 7.0 + 100 | 4 | 5 | 7.0 + 100 | 5 | 5 | 7.0 + 100 | 6 | 5 | 7.0 + 100 | 7 | 5 | 7.0 + 100 | 8 | 5 | 7.0 + 100 | 9 | 5 | 7.0 +(10 rows) + +-- GROUP BY clause referring to same column multiple times +-- Also, ORDER BY contains an aggregate function +explain (verbose, costs off) +select c2, c2 from ft1 where c2 > 6 group by 1, 2 order by sum(c1); + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: c2, c2, (sum(c1)) + Node ID: 1 + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT c2, c2, sum("C 1") FROM "S 1"."T 1" WHERE ((c2 > 6)) GROUP BY 1, 2 ORDER BY sum("C 1") ASC NULLS LAST + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c2, c2, sum("C 1") FROM "S 1"."T 1" WHERE ((c2 > 6)) GROUP BY 1, 2 ORDER BY sum("C 1") ASC NULLS LAST + Sort + Output: c2, c2, (sum("C 1")) + Sort Key: (sum("T 1"."C 1")) + -> HashAggregate + Output: c2, c2, sum("C 1") + Group By Key: "T 1".c2, "T 1".c2 + -> Seq Scan on "S 1"."T 1" + Output: c2, "C 1" + Filter: ("T 1".c2 > 6) + +(18 rows) + +select c2, c2 from ft1 where c2 > 6 group by 1, 2 order by sum(c1); + c2 | c2 +----+---- + 7 | 7 + 8 | 8 + 9 | 9 +(3 rows) + +-- Testing HAVING clause shippability +explain (verbose, costs off) +select c2, sum(c1) from ft2 group by c2 having avg(c1) < 500 and sum(c1) < 49800 order by c2; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: c2, (sum(c1)) + Node ID: 1 + Relations: Aggregate on (public.ft2) + Remote SQL: SELECT c2, sum("C 1") FROM "S 1"."T 1" GROUP BY 1 HAVING ((avg("C 1") < 500::numeric)) AND ((sum("C 1") < 49800)) ORDER BY c2 ASC NULLS LAST + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c2, sum("C 1") FROM "S 1"."T 1" GROUP BY 1 HAVING ((avg("C 1") < 500::numeric)) AND ((sum("C 1") < 49800)) ORDER BY c2 ASC NULLS LAST + Sort + Output: c2, (sum("C 1")) + Sort Key: "T 1".c2 + -> HashAggregate + Output: c2, sum("C 1") + Group By Key: "T 1".c2 + Filter: ((avg("T 1"."C 1") < 500::numeric) AND (sum("T 1"."C 1") < 49800)) + -> Seq Scan on "S 1"."T 1" + Output: c2, "C 1" + +(18 rows) + +select c2, sum(c1) from ft2 group by c2 having avg(c1) < 500 and sum(c1) < 49800 order by c2; + c2 | sum +----+------- + 1 | 49600 + 2 | 49700 +(2 rows) + +-- Unshippable HAVING clause will be evaluated locally, and other qual in HAVING clause is pushed down +explain (verbose, costs off) +select count(*) from (select c5, count(c1) from ft1 group by c5, sqrt(c2) having (avg(c1) / avg(c1)) * random() <= 1 and avg(c1) < 500) x; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------ + Aggregate + Output: count(*) + -> Foreign Scan + Output: ft1.c5, (count(ft1.c1)), (sqrt((ft1.c2)::double precision)) + Filter: (((((avg(ft1.c1)) / (avg(ft1.c1))))::double precision * random()) <= 1::double precision) + Node ID: 1 + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT c5, count("C 1"), sqrt(c2), avg("C 1") FROM "S 1"."T 1" GROUP BY 1, 3 HAVING ((avg("C 1") < 500::numeric)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c5, count("C 1"), sqrt(c2), avg("C 1") FROM "S 1"."T 1" GROUP BY 1, 3 HAVING ((avg("C 1") < 500::numeric)) + HashAggregate + Output: c5, count("C 1"), (sqrt((c2)::double precision)), avg("C 1") + Group By Key: "T 1".c5, sqrt(("T 1".c2)::double precision) + Filter: (avg("T 1"."C 1") < 500::numeric) + -> Seq Scan on "S 1"."T 1" + Output: c5, sqrt((c2)::double precision), "C 1" + +(18 rows) + +select count(*) from (select c5, count(c1) from ft1 group by c5, sqrt(c2) having (avg(c1) / avg(c1)) * random() <= 1 and avg(c1) < 500) x; + count +------- + 49 +(1 row) + +-- Aggregate in HAVING clause is not pushable, and thus aggregation is not pushed down +explain (verbose, costs off) +select sum(c1) from ft1 group by c2 having avg(c1 * (random() <= 1)::int) > 100 order by 1; + QUERY PLAN +----------------------------------------------------------------------------------------------- + Sort + Output: (sum(c1)), c2 + Sort Key: (sum(ft1.c1)) + -> HashAggregate + Output: sum(c1), c2 + Group By Key: ft1.c2 + Filter: (avg((ft1.c1 * ((random() <= 1::double precision))::integer)) > 100::numeric) + -> Foreign Scan on public.ft1 + Output: c2, c1 + Node ID: 1 + Remote SQL: SELECT "C 1", c2 FROM "S 1"."T 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2 FROM "S 1"."T 1" + Seq Scan on "S 1"."T 1" + Output: "C 1", c2 + +(17 rows) + +-- Remote aggregate in combination with a local Param (for the output +-- of an initplan) can be trouble, per bug #15781 +explain (verbose, costs off) +select exists(select 1 from pg_enum), sum(c1) from ft1; + QUERY PLAN +----------------------------------------------------------------------------- + Foreign Scan + Output: $0, (sum(ft1.c1)) + Node ID: 1 + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT sum("C 1") FROM "S 1"."T 1" + InitPlan 1 (returns $0) + -> Seq Scan on pg_catalog.pg_enum + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT sum("C 1") FROM "S 1"."T 1" + Aggregate + Output: sum("C 1") + -> Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + (15 rows) -SELECT ref_0.c2, subq_1.* -FROM - "S 1"."T 1" AS ref_0, - LATERAL ( - SELECT ref_0."C 1" c1, subq_0.* - FROM (SELECT ref_0.c2, ref_1.c3 - FROM ft1 AS ref_1) AS subq_0 - RIGHT JOIN ft2 AS ref_3 ON (subq_0.c3 = ref_3.c3) - ) AS subq_1 -WHERE ref_0."C 1" < 10 AND subq_1.c3 = '00001' -ORDER BY ref_0."C 1"; - c2 | c1 | c2 | c3 -----+----+----+------- - 1 | 1 | 1 | 00001 - 2 | 2 | 2 | 00001 - 3 | 3 | 3 | 00001 - 4 | 4 | 4 | 00001 - 5 | 5 | 5 | 00001 - 6 | 6 | 6 | 00001 - 7 | 7 | 7 | 00001 - 8 | 8 | 8 | 00001 - 9 | 9 | 9 | 00001 -(9 rows) +select exists(select 1 from pg_enum), sum(c1) from ft1; + exists | sum +--------+-------- + t | 500500 +(1 row) --- =================================================================== --- parameterized queries --- =================================================================== +explain (verbose, costs off) +select exists(select 1 from pg_enum), sum(c1) from ft1 group by 1; + QUERY PLAN +------------------------------------------------------------------------ + GroupAggregate + Output: ($0), sum(ft1.c1) + Group By Key: $0 + InitPlan 1 (returns $0) + -> Seq Scan on pg_catalog.pg_enum + -> Foreign Scan on public.ft1 + Output: $0, ft1.c1 + Node ID: 1 + Remote SQL: SELECT "C 1" FROM "S 1"."T 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1" FROM "S 1"."T 1" + Seq Scan on "S 1"."T 1" + Output: "C 1" + +(15 rows) + +select exists(select 1 from pg_enum), sum(c1) from ft1 group by 1; + exists | sum +--------+-------- + t | 500500 +(1 row) + +-- Testing ORDER BY, DISTINCT, FILTER, Ordered-sets and VARIADIC within aggregates +-- ORDER BY within aggregate, same column used to order +explain (verbose, costs off) +select array_agg(c1 order by c1) from ft1 where c1 < 100 group by c2 order by 1; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: (array_agg(c1 ORDER BY c1)), c2 + Node ID: 1 + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT array_agg("C 1" ORDER BY "C 1" ASC NULLS LAST), c2 FROM "S 1"."T 1" WHERE (("C 1" < 100)) GROUP BY 2 ORDER BY array_agg("C 1" ORDER BY "C 1" ASC NULLS LAST) ASC NULLS LAST + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT array_agg("C 1" ORDER BY "C 1" ASC NULLS LAST), c2 FROM "S 1"."T 1" WHERE (("C 1" < 100)) GROUP BY 2 ORDER BY array_agg("C 1" ORDER BY "C 1" ASC NULLS LAST) ASC NULLS LAST + Sort + Output: (array_agg("C 1" ORDER BY "C 1")), c2 + Sort Key: (array_agg("T 1"."C 1" ORDER BY "T 1"."C 1")) + -> GroupAggregate + Output: array_agg("C 1" ORDER BY "C 1"), c2 + Group By Key: "T 1".c2 + -> Sort + Output: c2, "C 1" + Sort Key: "T 1".c2 + -> Index Scan using t1_pkey on "S 1"."T 1" + Output: c2, "C 1" + Index Cond: ("T 1"."C 1" < 100) + +(21 rows) + +select array_agg(c1 order by c1) from ft1 where c1 < 100 group by c2 order by 1; + array_agg +-------------------------------- + {1,11,21,31,41,51,61,71,81,91} + {2,12,22,32,42,52,62,72,82,92} + {3,13,23,33,43,53,63,73,83,93} + {4,14,24,34,44,54,64,74,84,94} + {5,15,25,35,45,55,65,75,85,95} + {6,16,26,36,46,56,66,76,86,96} + {7,17,27,37,47,57,67,77,87,97} + {8,18,28,38,48,58,68,78,88,98} + {9,19,29,39,49,59,69,79,89,99} + {10,20,30,40,50,60,70,80,90} +(10 rows) + +-- ORDER BY within aggregate, different column used to order also using DESC +explain (verbose, costs off) +select array_agg(c5 order by c1 desc) from ft2 where c2 = 6 and c1 < 50; + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: (array_agg(c5 ORDER BY c1 DESC)) + Node ID: 1 + Relations: Aggregate on (public.ft2) + Remote SQL: SELECT array_agg(c5 ORDER BY "C 1" DESC NULLS FIRST) FROM "S 1"."T 1" WHERE (("C 1" < 50)) AND ((c2 = 6)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT array_agg(c5 ORDER BY "C 1" DESC NULLS FIRST) FROM "S 1"."T 1" WHERE (("C 1" < 50)) AND ((c2 = 6)) + Aggregate + Output: array_agg(c5 ORDER BY "C 1" DESC) + -> Index Scan using t1_pkey on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Index Cond: ("T 1"."C 1" < 50) + Filter: ("T 1".c2 = 6) + +(15 rows) + +select array_agg(c5 order by c1 desc) from ft2 where c2 = 6 and c1 < 50; + array_agg +------------------------------------------------------------------------------------------------------------------------------------------ + {"Mon Feb 16 00:00:00 1970","Fri Feb 06 00:00:00 1970","Tue Jan 27 00:00:00 1970","Sat Jan 17 00:00:00 1970","Wed Jan 07 00:00:00 1970"} +(1 row) + +-- DISTINCT within aggregate +explain (verbose, costs off) +select array_agg(distinct (t1.c1)%5) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: (array_agg(DISTINCT (t1.c1 % 5))), ((t2.c1 % 3)) + Node ID: 1 + Relations: Aggregate on ((public.ft4 t1) FULL JOIN (public.ft5 t2)) + Remote SQL: SELECT array_agg(DISTINCT (r1.c1 % 5)), (r2.c1 % 3) FROM ("S 1"."T 3" r1 FULL JOIN "S 1"."T 4" r2 ON (((r1.c1 = r2.c1)))) WHERE (((r1.c1 < 20) OR ((r1.c1 IS NULL) AND (r2.c1 < 5)))) GROUP BY 2 ORDER BY array_agg(DISTINCT (r1.c1 % 5)) ASC NULLS LAST + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT array_agg(DISTINCT (r1.c1 % 5)), (r2.c1 % 3) FROM ("S 1"."T 3" r1 FULL JOIN "S 1"."T 4" r2 ON (((r1.c1 = r2.c1)))) WHERE (((r1.c1 < 20) OR ((r1.c1 IS NULL) AND (r2.c1 < 5)))) GROUP BY 2 ORDER BY array_agg(DISTINCT (r1.c1 % 5)) ASC NULLS LAST + Sort + Output: (array_agg(DISTINCT (r1.c1 % 5))), ((r2.c1 % 3)) + Sort Key: (array_agg(DISTINCT (r1.c1 % 5))) + -> GroupAggregate + Output: array_agg(DISTINCT (r1.c1 % 5)), ((r2.c1 % 3)) + Group By Key: ((r2.c1 % 3)) + -> Sort + Output: ((r2.c1 % 3)), r1.c1 + Sort Key: ((r2.c1 % 3)) + -> Hash Full Join + Output: (r2.c1 % 3), r1.c1 + Hash Cond: (r1.c1 = r2.c1) + Filter: ((r1.c1 < 20) OR ((r1.c1 IS NULL) AND (r2.c1 < 5))) + -> Seq Scan on "S 1"."T 3" r1 + Output: r1.c1, r1.c2, r1.c3 + -> Hash + Output: r2.c1 + -> Seq Scan on "S 1"."T 4" r2 + Output: r2.c1 + +(28 rows) + +select array_agg(distinct (t1.c1)%5) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1; + array_agg +-------------- + {0,1,2,3,4} + {1,2,3,NULL} +(2 rows) + +-- DISTINCT combined with ORDER BY within aggregate +explain (verbose, costs off) +select array_agg(distinct (t1.c1)%5 order by (t1.c1)%5) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: (array_agg(DISTINCT (t1.c1 % 5) ORDER BY (t1.c1 % 5))), ((t2.c1 % 3)) + Node ID: 1 + Relations: Aggregate on ((public.ft4 t1) FULL JOIN (public.ft5 t2)) + Remote SQL: SELECT array_agg(DISTINCT (r1.c1 % 5) ORDER BY ((r1.c1 % 5)) ASC NULLS LAST), (r2.c1 % 3) FROM ("S 1"."T 3" r1 FULL JOIN "S 1"."T 4" r2 ON (((r1.c1 = r2.c1)))) WHERE (((r1.c1 < 20) OR ((r1.c1 IS NULL) AND (r2.c1 < 5)))) GROUP BY 2 ORDER BY array_agg(DISTINCT (r1.c1 % 5) ORDER BY ((r1.c1 % 5)) ASC NULLS LAST) ASC NULLS LAST + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT array_agg(DISTINCT (r1.c1 % 5) ORDER BY ((r1.c1 % 5)) ASC NULLS LAST), (r2.c1 % 3) FROM ("S 1"."T 3" r1 FULL JOIN "S 1"."T 4" r2 ON (((r1.c1 = r2.c1)))) WHERE (((r1.c1 < 20) OR ((r1.c1 IS NULL) AND (r2.c1 < 5)))) GROUP BY 2 ORDER BY array_agg(DISTINCT (r1.c1 % 5) ORDER BY ((r1.c1 % 5)) ASC NULLS LAST) ASC NULLS LAST + Sort + Output: (array_agg(DISTINCT (r1.c1 % 5) ORDER BY (r1.c1 % 5))), ((r2.c1 % 3)) + Sort Key: (array_agg(DISTINCT (r1.c1 % 5) ORDER BY (r1.c1 % 5))) + -> GroupAggregate + Output: array_agg(DISTINCT (r1.c1 % 5) ORDER BY (r1.c1 % 5)), ((r2.c1 % 3)) + Group By Key: ((r2.c1 % 3)) + -> Sort + Output: ((r2.c1 % 3)), r1.c1 + Sort Key: ((r2.c1 % 3)) + -> Hash Full Join + Output: (r2.c1 % 3), r1.c1 + Hash Cond: (r1.c1 = r2.c1) + Filter: ((r1.c1 < 20) OR ((r1.c1 IS NULL) AND (r2.c1 < 5))) + -> Seq Scan on "S 1"."T 3" r1 + Output: r1.c1, r1.c2, r1.c3 + -> Hash + Output: r2.c1 + -> Seq Scan on "S 1"."T 4" r2 + Output: r2.c1 + +(28 rows) + +select array_agg(distinct (t1.c1)%5 order by (t1.c1)%5) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1; + array_agg +-------------- + {0,1,2,3,4} + {1,2,3,NULL} +(2 rows) + +explain (verbose, costs off) +select array_agg(distinct (t1.c1)%5 order by (t1.c1)%5 desc nulls last) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: (array_agg(DISTINCT (t1.c1 % 5) ORDER BY (t1.c1 % 5) DESC NULLS LAST)), ((t2.c1 % 3)) + Node ID: 1 + Relations: Aggregate on ((public.ft4 t1) FULL JOIN (public.ft5 t2)) + Remote SQL: SELECT array_agg(DISTINCT (r1.c1 % 5) ORDER BY ((r1.c1 % 5)) DESC NULLS LAST), (r2.c1 % 3) FROM ("S 1"."T 3" r1 FULL JOIN "S 1"."T 4" r2 ON (((r1.c1 = r2.c1)))) WHERE (((r1.c1 < 20) OR ((r1.c1 IS NULL) AND (r2.c1 < 5)))) GROUP BY 2 ORDER BY array_agg(DISTINCT (r1.c1 % 5) ORDER BY ((r1.c1 % 5)) DESC NULLS LAST) ASC NULLS LAST + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT array_agg(DISTINCT (r1.c1 % 5) ORDER BY ((r1.c1 % 5)) DESC NULLS LAST), (r2.c1 % 3) FROM ("S 1"."T 3" r1 FULL JOIN "S 1"."T 4" r2 ON (((r1.c1 = r2.c1)))) WHERE (((r1.c1 < 20) OR ((r1.c1 IS NULL) AND (r2.c1 < 5)))) GROUP BY 2 ORDER BY array_agg(DISTINCT (r1.c1 % 5) ORDER BY ((r1.c1 % 5)) DESC NULLS LAST) ASC NULLS LAST + Sort + Output: (array_agg(DISTINCT (r1.c1 % 5) ORDER BY (r1.c1 % 5) DESC NULLS LAST)), ((r2.c1 % 3)) + Sort Key: (array_agg(DISTINCT (r1.c1 % 5) ORDER BY (r1.c1 % 5) DESC NULLS LAST)) + -> GroupAggregate + Output: array_agg(DISTINCT (r1.c1 % 5) ORDER BY (r1.c1 % 5) DESC NULLS LAST), ((r2.c1 % 3)) + Group By Key: ((r2.c1 % 3)) + -> Sort + Output: ((r2.c1 % 3)), r1.c1 + Sort Key: ((r2.c1 % 3)) + -> Hash Full Join + Output: (r2.c1 % 3), r1.c1 + Hash Cond: (r1.c1 = r2.c1) + Filter: ((r1.c1 < 20) OR ((r1.c1 IS NULL) AND (r2.c1 < 5))) + -> Seq Scan on "S 1"."T 3" r1 + Output: r1.c1, r1.c2, r1.c3 + -> Hash + Output: r2.c1 + -> Seq Scan on "S 1"."T 4" r2 + Output: r2.c1 + +(28 rows) + +select array_agg(distinct (t1.c1)%5 order by (t1.c1)%5 desc nulls last) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1; + array_agg +-------------- + {3,2,1,NULL} + {4,3,2,1,0} +(2 rows) + +--nspt FILTER within aggregate +-- openGauss not support FILTER within aggregate, leve a err case, make others run as normal +explain (verbose, costs off) +select sum(c1) filter (where c1 < 100 and c2 > 5) from ft1 group by c2 order by 1 nulls last; +ERROR: syntax error at or near "filter" +LINE 2: select sum(c1) filter (where c1 < 100 and c2 > 5) from ft1 g... + ^ +select sum(c1) filter (where c1 < 100 and c2 > 5) from ft1 group by c2 order by 1 nulls last; +ERROR: syntax error at or near "filter" +LINE 1: select sum(c1) filter (where c1 < 100 and c2 > 5) from ft1 g... + ^ +explain (verbose, costs off) +select sum(c1)/* filter (where c1 < 100 and c2 > 5)*/ from ft1 group by c2 order by 1 nulls last; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: (sum(c1)), c2 + Node ID: 1 + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT sum("C 1"), c2 FROM "S 1"."T 1" GROUP BY 2 ORDER BY sum("C 1") ASC NULLS LAST + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT sum("C 1"), c2 FROM "S 1"."T 1" GROUP BY 2 ORDER BY sum("C 1") ASC NULLS LAST + Sort + Output: (sum("C 1")), c2 + Sort Key: (sum("T 1"."C 1")) + -> HashAggregate + Output: sum("C 1"), c2 + Group By Key: "T 1".c2 + -> Seq Scan on "S 1"."T 1" + Output: c2, "C 1" + +(17 rows) + +select sum(c1)/* filter (where c1 < 100 and c2 > 5)*/ from ft1 group by c2 order by 1 nulls last; + sum +------- + 49600 + 49700 + 49800 + 49900 + 50000 + 50100 + 50200 + 50300 + 50400 + 50500 +(10 rows) + +-- DISTINCT, ORDER BY and FILTER within aggregate +explain (verbose, costs off) +select sum(c1%3), sum(distinct c1%3 order by c1%3)/* filter (where c1%3 < 2)*/, c2 from ft1 where c2 = 6 group by c2; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + Foreign Scan + Output: (sum((c1 % 3))), (sum(DISTINCT (c1 % 3) ORDER BY (c1 % 3))), c2 + Node ID: 1 + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT sum(("C 1" % 3)), sum(DISTINCT ("C 1" % 3) ORDER BY (("C 1" % 3)) ASC NULLS LAST), c2 FROM "S 1"."T 1" WHERE ((c2 = 6)) GROUP BY 3 + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT sum(("C 1" % 3)), sum(DISTINCT ("C 1" % 3) ORDER BY (("C 1" % 3)) ASC NULLS LAST), c2 FROM "S 1"."T 1" WHERE ((c2 = 6)) GROUP BY 3 + GroupAggregate + Output: sum(("C 1" % 3)), sum(DISTINCT ("C 1" % 3) ORDER BY ("C 1" % 3)), c2 + Group By Key: "T 1".c2 + -> Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Filter: ("T 1".c2 = 6) + +(15 rows) + +select sum(c1%3), sum(distinct c1%3 order by c1%3)/* filter (where c1%3 < 2)*/, c2 from ft1 where c2 = 6 group by c2; + sum | sum | c2 +-----+-----+---- + 99 | 3 | 6 +(1 row) + +-- Outer query is aggregation query +explain (verbose, costs off) +select distinct (select count(*) /*filter (where t2.c2 = 6 and t2.c1 < 10)*/ from ft1 t1 where t1.c1 = 6) from ft2 t2 where t2.c2 % 6 = 0 order by 1; + QUERY PLAN +---------------------------------------------------------------------------------------------- + Unique + Output: ($0) + InitPlan 1 (returns $0) + -> Aggregate + Output: count(*) + -> Foreign Scan on public.ft1 t1 + Node ID: 1 + Remote SQL: SELECT NULL FROM "S 1"."T 1" WHERE (("C 1" = 6)) + -> Foreign Scan on public.ft2 t2 + Output: $0 + Node ID: 2 + Remote SQL: SELECT NULL FROM "S 1"."T 1" WHERE (((c2 % 6) = 0)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT NULL FROM "S 1"."T 1" WHERE (("C 1" = 6)) + Index Only Scan using t1_pkey on "S 1"."T 1" + Output: NULL::text + Index Cond: ("T 1"."C 1" = 6) + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT NULL FROM "S 1"."T 1" WHERE (((c2 % 6) = 0)) + Seq Scan on "S 1"."T 1" + Output: NULL::text + Filter: (("T 1".c2 % 6) = 0) + +(23 rows) + +select distinct (select count(*) /*filter (where t2.c2 = 6 and t2.c1 < 10)*/ from ft1 t1 where t1.c1 = 6) from ft2 t2 where t2.c2 % 6 = 0 order by 1; + count +------- + 1 +(1 row) + +-- Inner query is aggregation query +explain (verbose, costs off) +select distinct (select count(t1.c1) /*filter (where t2.c2 = 6 and t2.c1 < 10)*/ from ft1 t1 where t1.c1 = 6) from ft2 t2 where t2.c2 % 6 = 0 order by 1; + QUERY PLAN +---------------------------------------------------------------------------------------------- + Unique + Output: ($0) + InitPlan 1 (returns $0) + -> Aggregate + Output: count(t1.c1) + -> Foreign Scan on public.ft1 t1 + Output: t1.c1 + Node ID: 1 + Remote SQL: SELECT "C 1" FROM "S 1"."T 1" WHERE (("C 1" = 6)) + -> Foreign Scan on public.ft2 t2 + Output: $0 + Node ID: 2 + Remote SQL: SELECT NULL FROM "S 1"."T 1" WHERE (((c2 % 6) = 0)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1" FROM "S 1"."T 1" WHERE (("C 1" = 6)) + [Bypass] + Index Only Scan using t1_pkey on "S 1"."T 1" + Output: "C 1" + Index Cond: ("T 1"."C 1" = 6) + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT NULL FROM "S 1"."T 1" WHERE (((c2 % 6) = 0)) + Seq Scan on "S 1"."T 1" + Output: NULL::text + Filter: (("T 1".c2 % 6) = 0) + +(25 rows) + +select distinct (select count(t1.c1) /*filter (where t2.c2 = 6 and t2.c1 < 10)*/ from ft1 t1 where t1.c1 = 6) from ft2 t2 where t2.c2 % 6 = 0 order by 1; + count +------- + 1 +(1 row) + +-- Aggregate not pushed down as FILTER condition is not pushable +explain (verbose, costs off) select sum(c1) /*filter (where (c1 / c1) * random() <= 1)*/ from ft1 group by c2 order by 1; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: (sum(c1)), c2 + Node ID: 1 + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT sum("C 1"), c2 FROM "S 1"."T 1" GROUP BY 2 ORDER BY sum("C 1") ASC NULLS LAST + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT sum("C 1"), c2 FROM "S 1"."T 1" GROUP BY 2 ORDER BY sum("C 1") ASC NULLS LAST + Sort + Output: (sum("C 1")), c2 + Sort Key: (sum("T 1"."C 1")) + -> HashAggregate + Output: sum("C 1"), c2 + Group By Key: "T 1".c2 + -> Seq Scan on "S 1"."T 1" + Output: c2, "C 1" + +(17 rows) + +explain (verbose, costs off) select sum(c2) /*filter (where c2 in (select c2 from ft1 where c2 < 5))*/ from ft1; + QUERY PLAN +-------------------------------------------------------------------------- + Foreign Scan + Output: (sum(c2)) + Node ID: 1 + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT sum(c2) FROM "S 1"."T 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT sum(c2) FROM "S 1"."T 1" + Aggregate + Output: sum(c2) + -> Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + +(13 rows) + +--nspt Ordered-sets within aggregate +--nspt Using multiple arguments within aggregates +-- User defined function for user defined aggregate, VARIADIC +create function least_accum(anyelement, variadic anyarray) +returns anyelement language sql as + 'select least($1, min($2[i])) from generate_subscripts($2,1) g(i)'; +create aggregate least_agg(variadic items anyarray) ( + stype = anyelement, sfunc = least_accum +); +ERROR: syntax error at or near "variadic" +LINE 1: create aggregate least_agg(variadic items anyarray) ( + ^ +-- Disable hash aggregation for plan stability. +set enable_hashagg to false; +-- Not pushed down due to user defined aggregate +explain (verbose, costs off) +select c2, least_agg(c1) from ft1 group by c2 order by c2; +ERROR: function least_agg(integer) does not exist +LINE 2: select c2, least_agg(c1) from ft1 group by c2 order by c2; + ^ +HINT: No function matches the given name and argument types. You might need to add explicit type casts. +CONTEXT: referenced column: least_agg +-- Add function and aggregate into extension +alter extension postgres_fdw add function least_accum(anyelement, variadic anyarray); +ERROR: function least_accum(anyelement,anyarray) is already a member of extension "postgres_fdw" +alter extension postgres_fdw add aggregate least_agg(variadic items anyarray); +ERROR: syntax error at or near "variadic" +LINE 1: ...er extension postgres_fdw add aggregate least_agg(variadic i... + ^ +alter server loopback options (set extensions 'postgres_fdw'); +ERROR: option "extensions" not found +-- Now aggregate will be pushed. Aggregate will display VARIADIC argument. +explain (verbose, costs off) +select c2, least_agg(c1) from ft1 where c2 < 100 group by c2 order by c2; +ERROR: function least_agg(integer) does not exist +LINE 2: select c2, least_agg(c1) from ft1 where c2 < 100 group by c2... + ^ +HINT: No function matches the given name and argument types. You might need to add explicit type casts. +CONTEXT: referenced column: least_agg +select c2, least_agg(c1) from ft1 where c2 < 100 group by c2 order by c2; +ERROR: function least_agg(integer) does not exist +LINE 1: select c2, least_agg(c1) from ft1 where c2 < 100 group by c2... + ^ +HINT: No function matches the given name and argument types. You might need to add explicit type casts. +CONTEXT: referenced column: least_agg +-- Remove function and aggregate from extension +alter extension postgres_fdw drop function least_accum(anyelement, variadic anyarray); +alter extension postgres_fdw drop aggregate least_agg(variadic items anyarray); +ERROR: syntax error at or near "variadic" +LINE 1: ...r extension postgres_fdw drop aggregate least_agg(variadic i... + ^ +alter server loopback options (set extensions 'postgres_fdw'); +ERROR: option "extensions" not found +-- Not pushed down as we have dropped objects from extension. +explain (verbose, costs off) +select c2, least_agg(c1) from ft1 group by c2 order by c2; +ERROR: function least_agg(integer) does not exist +LINE 2: select c2, least_agg(c1) from ft1 group by c2 order by c2; + ^ +HINT: No function matches the given name and argument types. You might need to add explicit type casts. +CONTEXT: referenced column: least_agg +-- Cleanup +reset enable_hashagg; +drop aggregate least_agg(variadic items anyarray); +ERROR: syntax error at or near "variadic" +LINE 1: drop aggregate least_agg(variadic items anyarray); + ^ +drop function least_accum(anyelement, variadic anyarray); +--nspt Testing USING OPERATOR() in ORDER BY within aggregate. +--nspt For this, we need user defined operators along with operator family and +--nspt operator class. Create those and then add them in extension. Note that +--nspt user defined objects are considered unshippable unless they are part of +--nspt the extension. +create operator public.<^ ( + leftarg = int4, + rightarg = int4, + procedure = int4eq +); +create operator public.=^ ( + leftarg = int4, + rightarg = int4, + procedure = int4lt +); +create operator public.>^ ( + leftarg = int4, + rightarg = int4, + procedure = int4gt +); +create operator family my_op_family using btree; +ERROR: user defined operator is not yet supported. +create function my_op_cmp(a int, b int) returns int as + $$begin return btint4cmp(a, b); end $$ language plpgsql; +create operator class my_op_class for type int using btree family my_op_family as + operator 1 public.<^, + operator 3 public.=^, + operator 5 public.>^, + function 1 my_op_cmp(int, int); +ERROR: operator family "my_op_family" does not exist for access method "btree" +-- NOTICES: +-- openGauss not support create operator class. remove the case. If you want to restore the test case later, refer to README in the file header. +-- This will not be pushed as sort operator is now removed from the extension. +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; +ERROR: operator <^ is not a valid ordering operator +LINE 2: select array_agg(c1 order by c1 using operator(public.<^)) f... + ^ +HINT: Ordering operators must be "<" or ">" members of btree operator families. +CONTEXT: referenced column: array_agg +-- Cleanup +drop operator class my_op_class using btree; +ERROR: operator class "my_op_class" does not exist for access method "btree" +drop function my_op_cmp(a int, b int); +drop operator family my_op_family using btree; +ERROR: operator family "my_op_family" does not exist for access method "btree" +drop operator public.>^(int, int); +drop operator public.=^(int, int); +drop operator public.<^(int, int); +-- Input relation to aggregate push down hook is not safe to pushdown and thus +-- the aggregate cannot be pushed down to foreign server. +explain (verbose, costs off) +select count(t1.c3) from ft2 t1 left join ft2 t2 on (t1.c1 = random() * t2.c2); + QUERY PLAN +------------------------------------------------------------------------------------------- + Aggregate + Output: count(t1.c3) + -> Nested Loop Left Join + Output: t1.c3 + Join Filter: ((t1.c1)::double precision = (random() * (t2.c2)::double precision)) + -> Foreign Scan on public.ft2 t1 + Output: t1.c3, t1.c1 + Node ID: 1 + Remote SQL: SELECT "C 1", c3 FROM "S 1"."T 1" + -> Materialize + Output: t2.c2 + -> Foreign Scan on public.ft2 t2 + Output: t2.c2 + Node ID: 2 + Remote SQL: SELECT c2 FROM "S 1"."T 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c3 FROM "S 1"."T 1" + Seq Scan on "S 1"."T 1" + Output: "C 1", c3 + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c2 FROM "S 1"."T 1" + Seq Scan on "S 1"."T 1" + Output: c2 + +(24 rows) + +-- Subquery in FROM clause having aggregate +explain (verbose, costs off) +select count(*), x.b from ft1, (select c2 a, sum(c1) b from ft1 group by c2) x where ft1.c2 = x.a group by x.b order by 1, 2; + QUERY PLAN +----------------------------------------------------------------------------------------------- + Sort + Output: (count(*)), x.b + Sort Key: (count(*)), x.b + -> HashAggregate + Output: count(*), x.b + Group By Key: x.b + -> Hash Join + Output: x.b + Hash Cond: (public.ft1.c2 = x.a) + -> Foreign Scan on public.ft1 + Output: public.ft1.c2 + Node ID: 1 + Remote SQL: SELECT c2 FROM "S 1"."T 1" + -> Hash + Output: x.b, x.a + -> Subquery Scan on x + Output: x.b, x.a + -> Foreign Scan + Output: public.ft1.c2, (sum(public.ft1.c1)) + Node ID: 2 + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT c2, sum("C 1") FROM "S 1"."T 1" GROUP BY 1 + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c2 FROM "S 1"."T 1" + Seq Scan on "S 1"."T 1" + Output: c2 + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c2, sum("C 1") FROM "S 1"."T 1" GROUP BY 1 + HashAggregate + Output: c2, sum("C 1") + Group By Key: "T 1".c2 + -> Seq Scan on "S 1"."T 1" + Output: c2, "C 1" + +(34 rows) + +select count(*), x.b from ft1, (select c2 a, sum(c1) b from ft1 group by c2) x where ft1.c2 = x.a group by x.b order by 1, 2; + count | b +-------+------- + 100 | 49600 + 100 | 49700 + 100 | 49800 + 100 | 49900 + 100 | 50000 + 100 | 50100 + 100 | 50200 + 100 | 50300 + 100 | 50400 + 100 | 50500 +(10 rows) + +-- FULL join with IS NULL check in HAVING +explain (verbose, costs off) +select avg(t1.c1), sum(t2.c1) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) group by t2.c1 having (avg(t1.c1) is null and sum(t2.c1) < 10) or sum(t2.c1) is null order by 1 nulls last, 2; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: (avg(t1.c1)), (sum(t2.c1)), t2.c1 + Node ID: 1 + Relations: Aggregate on ((public.ft4 t1) FULL JOIN (public.ft5 t2)) + Remote SQL: SELECT avg(r1.c1), sum(r2.c1), r2.c1 FROM ("S 1"."T 3" r1 FULL JOIN "S 1"."T 4" r2 ON (((r1.c1 = r2.c1)))) GROUP BY 3 HAVING ((((avg(r1.c1) IS NULL) AND (sum(r2.c1) < 10)) OR (sum(r2.c1) IS NULL))) ORDER BY avg(r1.c1) ASC NULLS LAST, sum(r2.c1) ASC NULLS LAST + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT avg(r1.c1), sum(r2.c1), r2.c1 FROM ("S 1"."T 3" r1 FULL JOIN "S 1"."T 4" r2 ON (((r1.c1 = r2.c1)))) GROUP BY 3 HAVING ((((avg(r1.c1) IS NULL) AND (sum(r2.c1) < 10)) OR (sum(r2.c1) IS NULL))) ORDER BY avg(r1.c1) ASC NULLS LAST, sum(r2.c1) ASC NULLS LAST + Sort + Output: (avg(r1.c1)), (sum(r2.c1)), r2.c1 + Sort Key: (avg(r1.c1)), (sum(r2.c1)) + -> HashAggregate + Output: avg(r1.c1), sum(r2.c1), r2.c1 + Group By Key: r2.c1 + Filter: (((avg(r1.c1) IS NULL) AND (sum(r2.c1) < 10)) OR (sum(r2.c1) IS NULL)) + -> Hash Full Join + Output: r1.c1, r2.c1 + Hash Cond: (r1.c1 = r2.c1) + -> Seq Scan on "S 1"."T 3" r1 + Output: r1.c1, r1.c2, r1.c3 + -> Hash + Output: r2.c1 + -> Seq Scan on "S 1"."T 4" r2 + Output: r2.c1 + +(25 rows) + +select avg(t1.c1), sum(t2.c1) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) group by t2.c1 having (avg(t1.c1) is null and sum(t2.c1) < 10) or sum(t2.c1) is null order by 1 nulls last, 2; + avg | sum +---------------------+----- + 51.0000000000000000 | + | 3 + | 9 +(3 rows) + +-- Aggregate over FULL join needing to deparse the joining relations as +-- subqueries. +explain (verbose, costs off) +select count(*), sum(t1.c1), avg(t2.c1) from (select c1 from ft4 where c1 between 50 and 60) t1 full join (select c1 from ft5 where c1 between 50 and 60) t2 on (t1.c1 = t2.c1); + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: (count(*)), (sum(ft4.c1)), (avg(ft5.c1)) + Node ID: 1 + Relations: Aggregate on ((public.ft4) FULL JOIN (public.ft5)) + Remote SQL: SELECT count(*), sum(s4.c1), avg(s5.c1) FROM ((SELECT c1 FROM "S 1"."T 3" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s4(c1) FULL JOIN (SELECT c1 FROM "S 1"."T 4" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s5(c1) ON (((s4.c1 = s5.c1)))) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT count(*), sum(s4.c1), avg(s5.c1) FROM ((SELECT c1 FROM "S 1"."T 3" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s4(c1) FULL JOIN (SELECT c1 FROM "S 1"."T 4" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s5(c1) ON (((s4.c1 = s5.c1)))) + Aggregate + Output: count(*), sum("T 3".c1), avg("T 4".c1) + -> Hash Full Join + Output: "T 3".c1, "T 4".c1 + Hash Cond: ("T 3".c1 = "T 4".c1) + -> Seq Scan on "S 1"."T 3" + Output: "T 3".c1, "T 3".c2, "T 3".c3 + Filter: (("T 3".c1 >= 50) AND ("T 3".c1 <= 60)) + -> Hash + Output: "T 4".c1 + -> Seq Scan on "S 1"."T 4" + Output: "T 4".c1 + Filter: (("T 4".c1 >= 50) AND ("T 4".c1 <= 60)) + +(22 rows) + +select count(*), sum(t1.c1), avg(t2.c1) from (select c1 from ft4 where c1 between 50 and 60) t1 full join (select c1 from ft5 where c1 between 50 and 60) t2 on (t1.c1 = t2.c1); + count | sum | avg +-------+-----+--------------------- + 8 | 330 | 55.5000000000000000 +(1 row) + +-- ORDER BY expression is part of the target list but not pushed down to +-- foreign server. +explain (verbose, costs off) +select sum(c2) * (random() <= 1)::int as sum from ft1 order by 1; + QUERY PLAN +---------------------------------------------------------------------------- + Sort + Output: ((sum(c2) * ((random() <= 1::double precision))::integer)) + Sort Key: ((sum(ft1.c2) * ((random() <= 1::double precision))::integer)) + -> Aggregate + Output: (sum(c2) * ((random() <= 1::double precision))::integer) + -> Foreign Scan on public.ft1 + Output: c2 + Node ID: 1 + Remote SQL: SELECT c2 FROM "S 1"."T 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c2 FROM "S 1"."T 1" + Seq Scan on "S 1"."T 1" + Output: c2 + +(15 rows) + +select sum(c2) * (random() <= 1)::int as sum from ft1 order by 1; + sum +------ + 4500 +(1 row) + +--nspt LATERAL join, with parameterization +-- NOTICES: +-- openGauss not support create operator class. remove the case. If you want to restore the test case later, refer to README in the file header. +-- Check with placeHolderVars +explain (verbose, costs off) +select sum(q.a), count(q.b) from ft4 left join (select 13, avg(ft1.c1), sum(ft2.c1) from ft1 right join ft2 on (ft1.c1 = ft2.c1)) q(a, b, c) on (ft4.c1 <= q.b); + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------------------- + Aggregate + Output: sum(q.a), count(q.b) + -> Nested Loop Left Join + Output: q.a, q.b + Join Filter: ((ft4.c1)::numeric <= q.b) + -> Foreign Scan on public.ft4 + Output: ft4.c1 + Node ID: 1 + Remote SQL: SELECT c1 FROM "S 1"."T 3" + -> Materialize + Output: q.a, q.b + -> Subquery Scan on q + Output: q.a, q.b + -> Foreign Scan + Output: (13), (avg(ft1.c1)), (sum(ft2.c1)) + Node ID: 2 + Relations: Aggregate on ((public.ft2) LEFT JOIN (public.ft1)) + Remote SQL: SELECT 13, avg(r1."C 1"), sum(r2."C 1") FROM ("S 1"."T 1" r2 LEFT JOIN "S 1"."T 1" r1 ON (((r1."C 1" = r2."C 1")))) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c1 FROM "S 1"."T 3" + Seq Scan on "S 1"."T 3" + Output: c1 + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT 13, avg(r1."C 1"), sum(r2."C 1") FROM ("S 1"."T 1" r2 LEFT JOIN "S 1"."T 1" r1 ON (((r1."C 1" = r2."C 1")))) + Aggregate + Output: 13, avg(r1."C 1"), sum(r2."C 1") + -> Hash Left Join + Output: r2."C 1", r1."C 1" + Hash Cond: (r2."C 1" = r1."C 1") + -> Seq Scan on "S 1"."T 1" r2 + Output: r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8 + -> Hash + Output: r1."C 1" + -> Seq Scan on "S 1"."T 1" r1 + Output: r1."C 1" + +(36 rows) + +select sum(q.a), count(q.b) from ft4 left join (select 13, avg(ft1.c1), sum(ft2.c1) from ft1 right join ft2 on (ft1.c1 = ft2.c1)) q(a, b, c) on (ft4.c1 <= q.b); + sum | count +-----+------- + 650 | 50 +(1 row) + +-- Not supported cases +-- Grouping sets +explain (verbose, costs off) +select c2, sum(c1) from ft1 where c2 < 3 group by rollup(c2) order by 1 nulls last; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------ + Sort + Output: c2, (sum(c1)) + Sort Key: ft1.c2 + -> GroupAggregate + Output: c2, sum(c1) + Group By Key: ft1.c2 + Group By Key: () + -> Foreign Scan on public.ft1 + Output: c2, c1 + Node ID: 1 + Remote SQL: SELECT "C 1", c2 FROM "S 1"."T 1" WHERE ((c2 < 3)) ORDER BY c2 ASC NULLS LAST + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2 FROM "S 1"."T 1" WHERE ((c2 < 3)) ORDER BY c2 ASC NULLS LAST + Sort + Output: "C 1", c2 + Sort Key: "T 1".c2 + -> Seq Scan on "S 1"."T 1" + Output: "C 1", c2 + Filter: ("T 1".c2 < 3) + +(21 rows) + +select c2, sum(c1) from ft1 where c2 < 3 group by rollup(c2) order by 1 nulls last; + c2 | sum +----+-------- + 0 | 50500 + 1 | 49600 + 2 | 49700 + | 149800 +(4 rows) + +explain (verbose, costs off) +select c2, sum(c1) from ft1 where c2 < 3 group by cube(c2) order by 1 nulls last; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------ + Sort + Output: c2, (sum(c1)) + Sort Key: ft1.c2 + -> GroupAggregate + Output: c2, sum(c1) + Group By Key: ft1.c2 + Group By Key: () + -> Foreign Scan on public.ft1 + Output: c2, c1 + Node ID: 1 + Remote SQL: SELECT "C 1", c2 FROM "S 1"."T 1" WHERE ((c2 < 3)) ORDER BY c2 ASC NULLS LAST + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2 FROM "S 1"."T 1" WHERE ((c2 < 3)) ORDER BY c2 ASC NULLS LAST + Sort + Output: "C 1", c2 + Sort Key: "T 1".c2 + -> Seq Scan on "S 1"."T 1" + Output: "C 1", c2 + Filter: ("T 1".c2 < 3) + +(21 rows) + +select c2, sum(c1) from ft1 where c2 < 3 group by cube(c2) order by 1 nulls last; + c2 | sum +----+-------- + 0 | 50500 + 1 | 49600 + 2 | 49700 + | 149800 +(4 rows) + +explain (verbose, costs off) +select c2, c6, sum(c1) from ft1 where c2 < 3 group by grouping sets(c2, c6) order by 1 nulls last, 2 nulls last; + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------- + Sort + Output: c2, c6, (sum(c1)) + Sort Key: ft1.c2, ft1.c6 + -> GroupAggregate + Output: c2, c6, sum(c1) + Group By Key: ft1.c2 + Sort Key: ft1.c6 + Group By Key: ft1.c6 + -> Foreign Scan on public.ft1 + Output: c2, c6, c1 + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c6 FROM "S 1"."T 1" WHERE ((c2 < 3)) ORDER BY c2 ASC NULLS LAST + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c6 FROM "S 1"."T 1" WHERE ((c2 < 3)) ORDER BY c2 ASC NULLS LAST + Sort + Output: "C 1", c2, c6 + Sort Key: "T 1".c2 + -> Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c6 + Filter: ("T 1".c2 < 3) + +(22 rows) + +select c2, c6, sum(c1) from ft1 where c2 < 3 group by grouping sets(c2, c6) order by 1 nulls last, 2 nulls last; + c2 | c6 | sum +----+----+------- + 0 | | 50500 + 1 | | 49600 + 2 | | 49700 + | 0 | 50500 + | 1 | 49600 + | 2 | 49700 +(6 rows) + +explain (verbose, costs off) +select c2, sum(c1), grouping(c2) from ft1 where c2 < 3 group by c2 order by 1 nulls last; + QUERY PLAN +--------------------------------------------------------------------------------------------- + Sort + Output: c2, (sum(c1)), (GROUPING(c2)) + Sort Key: ft1.c2 + -> HashAggregate + Output: c2, sum(c1), GROUPING(c2) + Group By Key: ft1.c2 + -> Foreign Scan on public.ft1 + Output: c2, c1 + Node ID: 1 + Remote SQL: SELECT "C 1", c2 FROM "S 1"."T 1" WHERE ((c2 < 3)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2 FROM "S 1"."T 1" WHERE ((c2 < 3)) + Seq Scan on "S 1"."T 1" + Output: "C 1", c2 + Filter: ("T 1".c2 < 3) + +(17 rows) + +select c2, sum(c1), grouping(c2) from ft1 where c2 < 3 group by c2 order by 1 nulls last; + c2 | sum | grouping +----+-------+---------- + 0 | 50500 | 0 + 1 | 49600 | 0 + 2 | 49700 | 0 +(3 rows) + +-- DISTINCT itself is not pushed down, whereas underneath aggregate is pushed +explain (verbose, costs off) +select distinct sum(c1)/1000 s from ft2 where c2 < 6 group by c2 order by 1; + QUERY PLAN +--------------------------------------------------------------------------------------------- + Unique + Output: ((sum(c1) / 1000)), c2 + -> Sort + Output: ((sum(c1) / 1000)), c2 + Sort Key: ((sum(ft2.c1) / 1000)) + -> HashAggregate + Output: (sum(c1) / 1000), c2 + Group By Key: ft2.c2 + -> Foreign Scan on public.ft2 + Output: c2, c1 + Node ID: 1 + Remote SQL: SELECT "C 1", c2 FROM "S 1"."T 1" WHERE ((c2 < 6)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2 FROM "S 1"."T 1" WHERE ((c2 < 6)) + Seq Scan on "S 1"."T 1" + Output: "C 1", c2 + Filter: ("T 1".c2 < 6) + +(19 rows) + +select distinct sum(c1)/1000 s from ft2 where c2 < 6 group by c2 order by 1; + s +------ + 49.6 + 49.7 + 49.8 + 49.9 + 50 + 50.5 +(6 rows) + +-- WindowAgg +explain (verbose, costs off) +select c2, sum(c2), count(c2) over (partition by c2%2) from ft2 where c2 < 10 group by c2 order by 1; + QUERY PLAN +------------------------------------------------------------------------------------------ + Sort + Output: c2, (sum(c2)), (count(c2) OVER (PARTITION BY ((c2 % 2)))), ((c2 % 2)) + Sort Key: ft2.c2 + -> WindowAgg + Output: c2, (sum(c2)), count(c2) OVER (PARTITION BY ((c2 % 2))), ((c2 % 2)) + -> Sort + Output: c2, ((c2 % 2)), (sum(c2)) + Sort Key: ((ft2.c2 % 2)) + -> GroupAggregate + Output: c2, (c2 % 2), sum(c2) + Group By Key: ft2.c2 + -> Sort + Output: c2 + Sort Key: ft2.c2 + -> Foreign Scan on public.ft2 + Output: c2 + Node ID: 1 + Remote SQL: SELECT c2 FROM "S 1"."T 1" WHERE ((c2 < 10)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c2 FROM "S 1"."T 1" WHERE ((c2 < 10)) + Seq Scan on "S 1"."T 1" + Output: c2 + Filter: ("T 1".c2 < 10) + +(25 rows) + +select c2, sum(c2), count(c2) over (partition by c2%2) from ft2 where c2 < 10 group by c2 order by 1; + c2 | sum | count +----+-----+------- + 0 | 0 | 5 + 1 | 100 | 5 + 2 | 200 | 5 + 3 | 300 | 5 + 4 | 400 | 5 + 5 | 500 | 5 + 6 | 600 | 5 + 7 | 700 | 5 + 8 | 800 | 5 + 9 | 900 | 5 +(10 rows) + +explain (verbose, costs off) +select c2, array_agg(c2) over (partition by c2%2 order by c2 desc) from ft1 where c2 < 10 group by c2 order by 1; + QUERY PLAN +------------------------------------------------------------------------------------------------------------- + Sort + Output: c2, (array_agg(c2) OVER (PARTITION BY ((c2 % 2)) ORDER BY c2 USING = NULLS LAST)), ((c2 % 2)) + Sort Key: ft1.c2 + -> WindowAgg + Output: c2, array_agg(c2) OVER (PARTITION BY ((c2 % 2)) ORDER BY c2 USING = NULLS LAST), ((c2 % 2)) + -> Sort + Output: c2, ((c2 % 2)) + Sort Key: ((ft1.c2 % 2)), ft1.c2 DESC + -> HashAggregate + Output: c2, (c2 % 2) + Group By Key: ft1.c2 + -> Foreign Scan on public.ft1 + Output: c2 + Node ID: 1 + Remote SQL: SELECT c2 FROM "S 1"."T 1" WHERE ((c2 < 10)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c2 FROM "S 1"."T 1" WHERE ((c2 < 10)) + Seq Scan on "S 1"."T 1" + Output: c2 + Filter: ("T 1".c2 < 10) + +(22 rows) + +select c2, array_agg(c2) over (partition by c2%2 order by c2 desc) from ft1 where c2 < 10 group by c2 order by 1; + c2 | array_agg +----+------------- + 0 | {8,6,4,2,0} + 1 | {9,7,5,3,1} + 2 | {8,6,4,2} + 3 | {9,7,5,3} + 4 | {8,6,4} + 5 | {9,7,5} + 6 | {8,6} + 7 | {9,7} + 8 | {8} + 9 | {9} +(10 rows) + +explain (verbose, costs off) +select c2, array_agg(c2) over (partition by c2%2 order by c2 range between current row and unbounded following) from ft1 where c2 < 10 group by c2 order by 1; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------------------------------------- + Sort + Output: c2, (array_agg(c2) OVER (PARTITION BY ((c2 % 2)) ORDER BY c2 USING = NULLS LAST RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)), ((c2 % 2)) + Sort Key: ft1.c2 + -> WindowAgg + Output: c2, array_agg(c2) OVER (PARTITION BY ((c2 % 2)) ORDER BY c2 USING = NULLS LAST RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING), ((c2 % 2)) + -> Sort + Output: c2, ((c2 % 2)) + Sort Key: ((ft1.c2 % 2)), ft1.c2 + -> HashAggregate + Output: c2, (c2 % 2) + Group By Key: ft1.c2 + -> Foreign Scan on public.ft1 + Output: c2 + Node ID: 1 + Remote SQL: SELECT c2 FROM "S 1"."T 1" WHERE ((c2 < 10)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c2 FROM "S 1"."T 1" WHERE ((c2 < 10)) + Seq Scan on "S 1"."T 1" + Output: c2 + Filter: ("T 1".c2 < 10) + +(22 rows) + +select c2, array_agg(c2) over (partition by c2%2 order by c2 range between current row and unbounded following) from ft1 where c2 < 10 group by c2 order by 1; + c2 | array_agg +----+------------- + 0 | {0,2,4,6,8} + 1 | {1,3,5,7,9} + 2 | {2,4,6,8} + 3 | {3,5,7,9} + 4 | {4,6,8} + 5 | {5,7,9} + 6 | {6,8} + 7 | {7,9} + 8 | {8} + 9 | {9} +(10 rows) + +-- Same group by +explain verbose select c1,c1,c1,count(*) from ft1 group by 1,1,1; + QUERY PLAN +--------------------------------------------------------------------------- + HashAggregate (cost=146.00..156.00 rows=1000 width=12) + Output: c1, c1, c1, count(*) + Group By Key: ft1.c1 + -> Foreign Scan on public.ft1 (cost=100.00..141.00 rows=1000 width=4) + Output: c1 + Node ID: 1 + Remote SQL: SELECT "C 1" FROM "S 1"."T 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS ON) SELECT "C 1" FROM "S 1"."T 1" + Seq Scan on "S 1"."T 1" (cost=0.00..21.00 rows=1000 width=4) + Output: "C 1" + +(13 rows) + +set enable_hashagg = off; +explain verbose select c1,c1,c1,count(*) from ft1 group by 1,1,1; + QUERY PLAN +-------------------------------------------------------------------------------------------- + Foreign Scan (cost=105.00..156.00 rows=1000 width=0) + Output: c1, c1, c1, (count(*)) + Node ID: 1 + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT "C 1", count(*) FROM "S 1"."T 1" GROUP BY 1 + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS ON) SELECT "C 1", count(*) FROM "S 1"."T 1" GROUP BY 1 + HashAggregate (cost=26.00..36.00 rows=1000 width=12) + Output: "C 1", count(*) + Group By Key: "T 1"."C 1" + -> Seq Scan on "S 1"."T 1" (cost=0.00..21.00 rows=1000 width=4) + Output: "C 1" + +(14 rows) + +set enable_hashagg = on; +-- ====================================================================================================================================== +-- TEST-MODULE: parameterized queries +-- -------------------------------------- +-- ====================================================================================================================================== -- simple join PREPARE st1(int, int) AS SELECT t1.c3, t2.c3 FROM ft1 t1, ft2 t2 WHERE t1.c1 = $1 AND t2.c1 = $2; -EXPLAIN (VERBOSE, COSTS false) EXECUTE st1(1, 2); - QUERY PLAN --------------------------------------------------------------------- - Nested Loop +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st1(1, 2); + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan Output: t1.c3, t2.c3 - -> Foreign Scan on public.ft1 t1 - Output: t1.c3 - Remote SQL: SELECT c3 FROM "S 1"."T 1" WHERE (("C 1" = 1)) - -> Foreign Scan on public.ft2 t2 - Output: t2.c3 - Remote SQL: SELECT c3 FROM "S 1"."T 1" WHERE (("C 1" = 2)) -(8 rows) + Node ID: 1 + Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2) + Remote SQL: SELECT r1.c3, r2.c3 FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r2."C 1" = $1::integer)) AND ((r1."C 1" = $2::integer)))) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT r1.c3, r2.c3 FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r2."C 1" = $1::integer)) AND ((r1."C 1" = $2::integer)))) + failed to get remote plan, error massage is: + there is no parameter $1 + +(11 rows) EXECUTE st1(1, 1); c3 | c3 @@ -769,26 +5360,37 @@ EXECUTE st1(101, 101); (1 row) -- subquery using stable function (can't be sent to remote) -PREPARE st2(int) AS SELECT * FROM ft1 t1 WHERE t1.c1 < $2 AND t1.c3 IN (SELECT c3 FROM ft2 t2 WHERE c1 > $1 AND date(c4) = '1970-01-17'::date) ORDER BY c1; -EXPLAIN (VERBOSE, COSTS false) EXECUTE st2(10, 20); - QUERY PLAN ----------------------------------------------------------------------------------------------------------- +PREPARE st2(int) AS SELECT * FROM ft1 t1 WHERE t1.c1 < $2 AND t1.c3 IN (SELECT c3 FROM ft2 t2 WHERE c1 > $1 AND "date"(c4) = '1970-01-17'::date) ORDER BY c1; +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st2(10, 20); + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------- Sort Output: t1.c1, t1.c2, t1.c3, t1.c4, t1.c5, t1.c6, t1.c7, t1.c8 Sort Key: t1.c1 - -> Nested Loop Semi Join + -> Hash Semi Join Output: t1.c1, t1.c2, t1.c3, t1.c4, t1.c5, t1.c6, t1.c7, t1.c8 - Join Filter: (t1.c3 = t2.c3) + Hash Cond: (t1.c3 = t2.c3) -> Foreign Scan on public.ft1 t1 Output: t1.c1, t1.c2, t1.c3, t1.c4, t1.c5, t1.c6, t1.c7, t1.c8 - Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" < 20)) - -> Materialize + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" < $1::integer)) + -> Hash Output: t2.c3 -> Foreign Scan on public.ft2 t2 Output: t2.c3 - Filter: (date(t2.c4) = '01-17-1970'::date) - Remote SQL: SELECT c3, c4 FROM "S 1"."T 1" WHERE (("C 1" > 10)) -(15 rows) + Filter: ("date"(t2.c4) = 'Sat Jan 17 00:00:00 1970'::timestamp(0) without time zone) + Node ID: 2 + Remote SQL: SELECT c3, c4 FROM "S 1"."T 1" WHERE (("C 1" > $1::integer)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" < $1::integer)) + failed to get remote plan, error massage is: + there is no parameter $1 + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c3, c4 FROM "S 1"."T 1" WHERE (("C 1" > $1::integer)) + failed to get remote plan, error massage is: + current transaction is aborted, commands ignored until end of transaction block, firstChar[Q] + +(26 rows) EXECUTE st2(10, 20); c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 @@ -803,25 +5405,36 @@ EXECUTE st2(101, 121); (1 row) -- subquery using immutable function (can be sent to remote) -PREPARE st3(int) AS SELECT * FROM ft1 t1 WHERE t1.c1 < $2 AND t1.c3 IN (SELECT c3 FROM ft2 t2 WHERE c1 > $1 AND date(c5) = '1970-01-17'::date) ORDER BY c1; -EXPLAIN (VERBOSE, COSTS false) EXECUTE st3(10, 20); - QUERY PLAN ------------------------------------------------------------------------------------------------------------------------ +PREPARE st3(int) AS SELECT * FROM ft1 t1 WHERE t1.c1 < $2 AND t1.c3 IN (SELECT c3 FROM ft2 t2 WHERE c1 > $1 AND "date"(c5) = '1970-01-17'::date) ORDER BY c1; +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st3(10, 20); + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ Sort Output: t1.c1, t1.c2, t1.c3, t1.c4, t1.c5, t1.c6, t1.c7, t1.c8 Sort Key: t1.c1 - -> Nested Loop Semi Join + -> Hash Semi Join Output: t1.c1, t1.c2, t1.c3, t1.c4, t1.c5, t1.c6, t1.c7, t1.c8 - Join Filter: (t1.c3 = t2.c3) + Hash Cond: (t1.c3 = t2.c3) -> Foreign Scan on public.ft1 t1 Output: t1.c1, t1.c2, t1.c3, t1.c4, t1.c5, t1.c6, t1.c7, t1.c8 - Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" < 20)) - -> Materialize + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" < $1::integer)) + -> Hash Output: t2.c3 -> Foreign Scan on public.ft2 t2 Output: t2.c3 - Remote SQL: SELECT c3 FROM "S 1"."T 1" WHERE (("C 1" > 10)) AND ((date(c5) = '1970-01-17'::date)) -(14 rows) + Node ID: 2 + Remote SQL: SELECT c3 FROM "S 1"."T 1" WHERE (("C 1" > $1::integer)) AND (("date"(c5) = '1970-01-17 00:00:00'::timestamp(0) without time zone)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" < $1::integer)) + failed to get remote plan, error massage is: + there is no parameter $1 + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c3 FROM "S 1"."T 1" WHERE (("C 1" > $1::integer)) AND (("date"(c5) = '1970-01-17 00:00:00'::timestamp(0) without time zone)) + failed to get remote plan, error massage is: + current transaction is aborted, commands ignored until end of transaction block, firstChar[Q] + +(25 rows) EXECUTE st3(10, 20); c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 @@ -836,110 +5449,194 @@ EXECUTE st3(20, 30); -- custom plan should be chosen initially PREPARE st4(int) AS SELECT * FROM ft1 t1 WHERE t1.c1 = $1; -EXPLAIN (VERBOSE, COSTS false) EXECUTE st4(1); - QUERY PLAN ---------------------------------------------------------------------------------------------- +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st4(1); + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------- Foreign Scan on public.ft1 t1 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" WHERE (("C 1" = 1)) -(3 rows) + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = $1::integer)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = $1::integer)) + failed to get remote plan, error massage is: + there is no parameter $1 + +(10 rows) -EXPLAIN (VERBOSE, COSTS false) EXECUTE st4(1); - QUERY PLAN ---------------------------------------------------------------------------------------------- +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st4(1); + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------- Foreign Scan on public.ft1 t1 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" WHERE (("C 1" = 1)) -(3 rows) + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = $1::integer)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = $1::integer)) + failed to get remote plan, error massage is: + there is no parameter $1 + +(10 rows) -EXPLAIN (VERBOSE, COSTS false) EXECUTE st4(1); - QUERY PLAN ---------------------------------------------------------------------------------------------- +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st4(1); + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------- Foreign Scan on public.ft1 t1 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" WHERE (("C 1" = 1)) -(3 rows) + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = $1::integer)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = $1::integer)) + failed to get remote plan, error massage is: + there is no parameter $1 + +(10 rows) -EXPLAIN (VERBOSE, COSTS false) EXECUTE st4(1); - QUERY PLAN ---------------------------------------------------------------------------------------------- +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st4(1); + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------- Foreign Scan on public.ft1 t1 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" WHERE (("C 1" = 1)) -(3 rows) + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = $1::integer)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = $1::integer)) + failed to get remote plan, error massage is: + there is no parameter $1 + +(10 rows) -EXPLAIN (VERBOSE, COSTS false) EXECUTE st4(1); - QUERY PLAN ---------------------------------------------------------------------------------------------- +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st4(1); + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------- Foreign Scan on public.ft1 t1 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" WHERE (("C 1" = 1)) -(3 rows) + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = $1::integer)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = $1::integer)) + failed to get remote plan, error massage is: + there is no parameter $1 + +(10 rows) -- once we try it enough times, should switch to generic plan -EXPLAIN (VERBOSE, COSTS false) EXECUTE st4(1); - QUERY PLAN -------------------------------------------------------------------------------------------------------- +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st4(1); + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------- Foreign Scan on public.ft1 t1 Output: c1, c2, c3, c4, c5, c6, c7, c8 + Node ID: 1 Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = $1::integer)) -(3 rows) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = $1::integer)) + failed to get remote plan, error massage is: + there is no parameter $1 + +(10 rows) -- value of $1 should not be sent to remote PREPARE st5(user_enum,int) AS SELECT * FROM ft1 t1 WHERE c8 = $1 and c1 = $2; -EXPLAIN (VERBOSE, COSTS false) EXECUTE st5('foo', 1); - QUERY PLAN ---------------------------------------------------------------------------------------------- - Foreign Scan on public.ft1 t1 - Output: c1, c2, c3, c4, c5, c6, c7, c8 - Filter: (t1.c8 = 'foo'::user_enum) - Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 1)) -(4 rows) - -EXPLAIN (VERBOSE, COSTS false) EXECUTE st5('foo', 1); - QUERY PLAN ---------------------------------------------------------------------------------------------- - Foreign Scan on public.ft1 t1 - Output: c1, c2, c3, c4, c5, c6, c7, c8 - Filter: (t1.c8 = 'foo'::user_enum) - Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 1)) -(4 rows) - -EXPLAIN (VERBOSE, COSTS false) EXECUTE st5('foo', 1); - QUERY PLAN ---------------------------------------------------------------------------------------------- - Foreign Scan on public.ft1 t1 - Output: c1, c2, c3, c4, c5, c6, c7, c8 - Filter: (t1.c8 = 'foo'::user_enum) - Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 1)) -(4 rows) - -EXPLAIN (VERBOSE, COSTS false) EXECUTE st5('foo', 1); - QUERY PLAN ---------------------------------------------------------------------------------------------- - Foreign Scan on public.ft1 t1 - Output: c1, c2, c3, c4, c5, c6, c7, c8 - Filter: (t1.c8 = 'foo'::user_enum) - Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 1)) -(4 rows) - -EXPLAIN (VERBOSE, COSTS false) EXECUTE st5('foo', 1); - QUERY PLAN ---------------------------------------------------------------------------------------------- - Foreign Scan on public.ft1 t1 - Output: c1, c2, c3, c4, c5, c6, c7, c8 - Filter: (t1.c8 = 'foo'::user_enum) - Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 1)) -(4 rows) - -EXPLAIN (VERBOSE, COSTS false) EXECUTE st5('foo', 1); - QUERY PLAN -------------------------------------------------------------------------------------------------------- +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st5('foo', 1); + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------- Foreign Scan on public.ft1 t1 Output: c1, c2, c3, c4, c5, c6, c7, c8 Filter: (t1.c8 = $1) + Node ID: 1 Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = $1::integer)) -(4 rows) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = $1::integer)) + failed to get remote plan, error massage is: + there is no parameter $1 + +(11 rows) + +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st5('foo', 1); + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Filter: (t1.c8 = $1) + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = $1::integer)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = $1::integer)) + failed to get remote plan, error massage is: + there is no parameter $1 + +(11 rows) + +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st5('foo', 1); + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Filter: (t1.c8 = $1) + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = $1::integer)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = $1::integer)) + failed to get remote plan, error massage is: + there is no parameter $1 + +(11 rows) + +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st5('foo', 1); + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Filter: (t1.c8 = $1) + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = $1::integer)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = $1::integer)) + failed to get remote plan, error massage is: + there is no parameter $1 + +(11 rows) + +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st5('foo', 1); + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Filter: (t1.c8 = $1) + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = $1::integer)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = $1::integer)) + failed to get remote plan, error massage is: + there is no parameter $1 + +(11 rows) + +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st5('foo', 1); + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Filter: (t1.c8 = $1) + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = $1::integer)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = $1::integer)) + failed to get remote plan, error massage is: + there is no parameter $1 + +(11 rows) EXECUTE st5('foo', 1); c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 @@ -950,34 +5647,49 @@ EXECUTE st5('foo', 1); -- altering FDW options requires replanning PREPARE st6 AS SELECT * FROM ft1 t1 WHERE t1.c1 = t1.c2; EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st6; - QUERY PLAN ----------------------------------------------------------------------------------------------- + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------- Foreign Scan on public.ft1 t1 Output: c1, c2, c3, c4, c5, c6, c7, c8 + Node ID: 1 Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = c2)) -(3 rows) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = c2)) + Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Filter: ("T 1"."C 1" = "T 1".c2) + +(11 rows) PREPARE st7 AS INSERT INTO ft1 (c1,c2,c3) VALUES (1001,101,'foo'); EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st7; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Insert on public.ft1 - Remote SQL: INSERT INTO "S 1"."T 1"("C 1", c2, c3, c4, c5, c6, c7, c8) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) -> Result Output: NULL::integer, 1001, 101, 'foo'::text, NULL::timestamp with time zone, NULL::timestamp without time zone, NULL::character varying, 'ft1 '::character(10), NULL::user_enum -(4 rows) +(3 rows) ALTER TABLE "S 1"."T 1" RENAME TO "T 0"; ALTER FOREIGN TABLE ft1 OPTIONS (SET table_name 'T 0'); EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st6; - QUERY PLAN ----------------------------------------------------------------------------------------------- + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------- Foreign Scan on public.ft1 t1 Output: c1, c2, c3, c4, c5, c6, c7, c8 + Node ID: 1 Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 0" WHERE (("C 1" = c2)) -(3 rows) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 0" WHERE (("C 1" = c2)) + Seq Scan on "S 1"."T 0" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Filter: ("T 0"."C 1" = "T 0".c2) + +(11 rows) -EXECUTE st6; +EXECUTE st6; --nspt c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 ----+----+-------+------------------------------+--------------------------+----+------------+----- 1 | 1 | 00001 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1 | 1 | foo @@ -995,13 +5707,60 @@ EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st7; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Insert on public.ft1 - Remote SQL: INSERT INTO "S 1"."T 0"("C 1", c2, c3, c4, c5, c6, c7, c8) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) -> Result Output: NULL::integer, 1001, 101, 'foo'::text, NULL::timestamp with time zone, NULL::timestamp without time zone, NULL::character varying, 'ft1 '::character(10), NULL::user_enum -(4 rows) +(3 rows) ALTER TABLE "S 1"."T 0" RENAME TO "T 1"; ALTER FOREIGN TABLE ft1 OPTIONS (SET table_name 'T 1'); +PREPARE st8 AS SELECT count(c3) FROM ft1 t1 WHERE t1.c1 === t1.c2; +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st8; + QUERY PLAN +-------------------------------------------------------------------------------- + Aggregate + Output: count(c3) + -> Foreign Scan on public.ft1 t1 + Output: c3 + Filter: (t1.c1 === t1.c2) + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3 FROM "S 1"."T 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3 FROM "S 1"."T 1" + Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3 + +(13 rows) + +ALTER SERVER loopback OPTIONS (DROP extensions); +ERROR: option "extensions" not found +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st8; + QUERY PLAN +-------------------------------------------------------------------------------- + Aggregate + Output: count(c3) + -> Foreign Scan on public.ft1 t1 + Output: c3 + Filter: (t1.c1 === t1.c2) + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3 FROM "S 1"."T 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3 FROM "S 1"."T 1" + Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3 + +(13 rows) + +EXECUTE st8; + count +------- + 9 +(1 row) + +ALTER SERVER loopback OPTIONS (ADD extensions 'postgres_fdw'); +ERROR: invalid option "extensions" +HINT: Valid options in this context are: service, connect_timeout, dbname, host, remote_nodename, hostaddr, port, localhost, localport, application_name, fencedUdfRPCMode, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, rw_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, krbsrvname, prototype, connection_info, connectionExtraInfo, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, updatable -- cleanup DEALLOCATE st1; DEALLOCATE st2; @@ -1010,80 +5769,56 @@ DEALLOCATE st4; DEALLOCATE st5; DEALLOCATE st6; DEALLOCATE st7; --- System columns, except ctid, should not be sent to remote -EXPLAIN (VERBOSE, COSTS false) +DEALLOCATE st8; +-- System columns, except ctid and oid, should not be sent to remote +-- openGauss NOT SUPPROT select system columns in ftable. +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE t1.tableoid = 'pg_class'::regclass LIMIT 1; - QUERY PLAN -------------------------------------------------------------------------------- - Limit - Output: c1, c2, c3, c4, c5, c6, c7, c8 - -> Foreign Scan on public.ft1 t1 - Output: c1, c2, c3, c4, c5, c6, c7, c8 - Filter: (t1.tableoid = 1259::oid) - Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" -(6 rows) - +ERROR: column t1.tableoid does not exist +LINE 2: SELECT * FROM ft1 t1 WHERE t1.tableoid = 'pg_class'::regclas... + ^ SELECT * FROM ft1 t1 WHERE t1.tableoid = 'ft1'::regclass LIMIT 1; - c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 -----+----+-------+------------------------------+--------------------------+----+------------+----- - 1 | 1 | 00001 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1 | 1 | foo -(1 row) - -EXPLAIN (VERBOSE, COSTS false) +ERROR: column t1.tableoid does not exist +LINE 1: SELECT * FROM ft1 t1 WHERE t1.tableoid = 'ft1'::regclass LIM... + ^ +EXPLAIN (VERBOSE, COSTS OFF) SELECT tableoid::regclass, * FROM ft1 t1 LIMIT 1; - QUERY PLAN -------------------------------------------------------------------------------- - Limit - Output: ((tableoid)::regclass), c1, c2, c3, c4, c5, c6, c7, c8 - -> Foreign Scan on public.ft1 t1 - Output: (tableoid)::regclass, 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" -(5 rows) - +ERROR: column "tableoid" does not exist +LINE 2: SELECT tableoid::regclass, * FROM ft1 t1 LIMIT 1; + ^ +CONTEXT: referenced column: tableoid SELECT tableoid::regclass, * FROM ft1 t1 LIMIT 1; - tableoid | c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 -----------+----+----+-------+------------------------------+--------------------------+----+------------+----- - ft1 | 1 | 1 | 00001 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1 | 1 | foo -(1 row) - -EXPLAIN (VERBOSE, COSTS false) +ERROR: column "tableoid" does not exist +LINE 1: SELECT tableoid::regclass, * FROM ft1 t1 LIMIT 1; + ^ +CONTEXT: referenced column: tableoid +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE t1.ctid = '(0,2)'; - QUERY PLAN -------------------------------------------------------------------------------------------------------- - Foreign Scan on public.ft1 t1 - 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" WHERE ((ctid = '(0,2)'::tid)) -(3 rows) - +ERROR: column t1.ctid does not exist +LINE 2: SELECT * FROM ft1 t1 WHERE t1.ctid = '(0,2)'; + ^ SELECT * FROM ft1 t1 WHERE t1.ctid = '(0,2)'; - c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 -----+----+-------+------------------------------+--------------------------+----+------------+----- - 2 | 2 | 00002 | Sat Jan 03 00:00:00 1970 PST | Sat Jan 03 00:00:00 1970 | 2 | 2 | foo -(1 row) - -EXPLAIN (VERBOSE, COSTS false) +ERROR: column t1.ctid does not exist +LINE 1: SELECT * FROM ft1 t1 WHERE t1.ctid = '(0,2)'; + ^ +EXPLAIN (VERBOSE, COSTS OFF) SELECT ctid, * FROM ft1 t1 LIMIT 1; - QUERY PLAN -------------------------------------------------------------------------------------- - Limit - Output: ctid, c1, c2, c3, c4, c5, c6, c7, c8 - -> Foreign Scan on public.ft1 t1 - Output: ctid, c1, c2, c3, c4, c5, c6, c7, c8 - Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8, ctid FROM "S 1"."T 1" -(5 rows) - +ERROR: column "ctid" does not exist +LINE 2: SELECT ctid, * FROM ft1 t1 LIMIT 1; + ^ +CONTEXT: referenced column: ctid SELECT ctid, * FROM ft1 t1 LIMIT 1; - ctid | c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 --------+----+----+-------+------------------------------+--------------------------+----+------------+----- - (0,1) | 1 | 1 | 00001 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1 | 1 | foo -(1 row) - --- =================================================================== --- used in pl/pgsql function --- =================================================================== +ERROR: column "ctid" does not exist +LINE 1: SELECT ctid, * FROM ft1 t1 LIMIT 1; + ^ +CONTEXT: referenced column: ctid +-- ====================================================================================================================================== +-- TEST-MODULE: used in PL/pgSQL function +-- -------------------------------------- +-- ====================================================================================================================================== CREATE OR REPLACE FUNCTION f_test(p_c1 int) RETURNS int AS $$ DECLARE - v_c1 int; + v_c1 int; BEGIN SELECT c1 INTO v_c1 FROM ft1 WHERE c1 = p_c1 LIMIT 1; PERFORM c1 FROM ft1 WHERE c1 = p_c1 AND p_c1 = v_c1 LIMIT 1; @@ -1097,18 +5832,66 @@ SELECT f_test(100); (1 row) DROP FUNCTION f_test(int); --- =================================================================== --- conversion error --- =================================================================== +-- ====================================================================================================================================== +-- TEST-MODULE: REINDEX +-- -------------------------------------- +-- ====================================================================================================================================== +-- remote table is not created here +CREATE FOREIGN TABLE reindex_foreign (c1 int, c2 int) + SERVER loopback2 OPTIONS (table_name 'reindex_local'); +REINDEX TABLE reindex_foreign; -- error +ERROR: "reindex_foreign" is not a table or materialized view +REINDEX TABLE CONCURRENTLY reindex_foreign; -- error --nspt +ERROR: "reindex_foreign" is not a table or materialized view +DROP FOREIGN TABLE reindex_foreign; +-- partitions and foreign tables +CREATE TABLE reind_fdw_parent (c1 int) PARTITION BY RANGE (c1) ( + partition reind_fdw_0_10 values less than(11), + partition reind_fdw_10_20 values less than(21) +); +CREATE FOREIGN TABLE reind_fdw_10_20_fp(c1 int) + SERVER loopback OPTIONS (table_name 'reind_fdw_parent'); +CREATE FOREIGN TABLE reind_fdw_10_20_f1(c1 int) + SERVER loopback OPTIONS (table_name 'reind_fdw_parent partition(reind_fdw_10_20)'); +REINDEX TABLE reind_fdw_10_20_fp; -- error +ERROR: "reind_fdw_10_20_fp" is not a table or materialized view +REINDEX TABLE reind_fdw_10_20_f1; -- error +ERROR: "reind_fdw_10_20_f1" is not a table or materialized view +REINDEX TABLE reind_fdw_parent; -- ok +NOTICE: table "reind_fdw_parent" has no indexes +REINDEX TABLE CONCURRENTLY reind_fdw_parent; -- ok --nspt +NOTICE: table "reind_fdw_parent" has no indexes +DROP TABLE reind_fdw_parent; +DROP FOREIGN TABLE reind_fdw_10_20_fp; +DROP FOREIGN TABLE reind_fdw_10_20_f1; +-- ====================================================================================================================================== +-- TEST-MODULE: conversion error +-- -------------------------------------- +-- ====================================================================================================================================== ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 TYPE int; -SELECT * FROM ft1 WHERE c1 = 1; -- ERROR +SELECT * FROM ft1 ftx(x1,x2,x3,x4,x5,x6,x7,x8) WHERE x1 = 1; -- ERROR +ERROR: invalid input syntax for integer: "foo" +CONTEXT: column "x8" of foreign table "ftx" +SELECT ftx.x1, ft2.c2, ftx.x8 FROM ft1 ftx(x1,x2,x3,x4,x5,x6,x7,x8), ft2 + WHERE ftx.x1 = ft2.c1 AND ftx.x1 = 1; -- ERROR +ERROR: invalid input syntax for integer: "foo" +CONTEXT: column "x8" of foreign table "ftx" +SELECT ftx.x1, ft2.c2, ftx FROM ft1 ftx(x1,x2,x3,x4,x5,x6,x7,x8), ft2 + WHERE ftx.x1 = ft2.c1 AND ftx.x1 = 1; -- ERROR +ERROR: invalid input syntax for integer: "foo" +CONTEXT: whole-row reference to foreign table "ftx" +SELECT sum(c2), array_agg(c8) FROM ft1 GROUP BY c8; -- ERROR +ERROR: invalid input syntax for integer: "foo" +CONTEXT: processing expression at position 2 in select list +ANALYZE ft1; -- ERROR ERROR: invalid input syntax for integer: "foo" CONTEXT: column "c8" of foreign table "ft1" ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 TYPE user_enum; --- =================================================================== --- subtransaction --- + local/remote error doesn't break cursor --- =================================================================== +-- ====================================================================================================================================== +-- TEST-MODULE: subtransaction +-- + local/remote error doesn't break cursor +-- -------------------------------------- +-- ====================================================================================================================================== BEGIN; DECLARE c CURSOR FOR SELECT * FROM ft1 ORDER BY c1; FETCH c; @@ -1132,7 +5915,7 @@ FETCH c; SAVEPOINT s; SELECT * FROM ft1 WHERE 1 / (c1 - 1) > 0; -- ERROR ERROR: division by zero -CONTEXT: Remote SQL command: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (((1 / ("C 1" - 1)) > 0)) +CONTEXT: Remote SQL command: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (((1 / ("C 1" - 1)) > 0::double precision)) ROLLBACK TO s; FETCH c; c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 @@ -1147,130 +5930,216 @@ SELECT * FROM ft1 ORDER BY c1 LIMIT 1; (1 row) COMMIT; --- =================================================================== --- test handling of collations --- =================================================================== +-- ====================================================================================================================================== +-- TEST-MODULE: test handling of collations +-- -------------------------------------- +-- ====================================================================================================================================== create table loct3 (f1 text collate "C" unique, f2 text, f3 varchar(10) unique); +NOTICE: CREATE TABLE / UNIQUE will create implicit index "loct3_f1_key" for table "loct3" +NOTICE: CREATE TABLE / UNIQUE will create implicit index "loct3_f3_key" for table "loct3" create foreign table ft3 (f1 text collate "C", f2 text, f3 varchar(10)) server loopback options (table_name 'loct3', use_remote_estimate 'true'); -- can be sent to remote explain (verbose, costs off) select * from ft3 where f1 = 'foo'; - QUERY PLAN ------------------------------------------------------------------------------- + QUERY PLAN +--------------------------------------------------------------------------------------------------------- Foreign Scan on public.ft3 Output: f1, f2, f3 + Node ID: 1 Remote SQL: SELECT f1, f2, f3 FROM public.loct3 WHERE ((f1 = 'foo'::text)) -(3 rows) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT f1, f2, f3 FROM public.loct3 WHERE ((f1 = 'foo'::text)) + [Bypass] + Index Scan using loct3_f1_key on public.loct3 + Output: f1, f2, f3 + Index Cond: (loct3.f1 = 'foo'::text) + +(12 rows) explain (verbose, costs off) select * from ft3 where f1 COLLATE "C" = 'foo'; - QUERY PLAN ------------------------------------------------------------------------------- + QUERY PLAN +--------------------------------------------------------------------------------------------------------- Foreign Scan on public.ft3 Output: f1, f2, f3 + Node ID: 1 Remote SQL: SELECT f1, f2, f3 FROM public.loct3 WHERE ((f1 = 'foo'::text)) -(3 rows) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT f1, f2, f3 FROM public.loct3 WHERE ((f1 = 'foo'::text)) + [Bypass] + Index Scan using loct3_f1_key on public.loct3 + Output: f1, f2, f3 + Index Cond: (loct3.f1 = 'foo'::text) + +(12 rows) explain (verbose, costs off) select * from ft3 where f2 = 'foo'; - QUERY PLAN ------------------------------------------------------------------------------- + QUERY PLAN +--------------------------------------------------------------------------------------------------------- Foreign Scan on public.ft3 Output: f1, f2, f3 + Node ID: 1 Remote SQL: SELECT f1, f2, f3 FROM public.loct3 WHERE ((f2 = 'foo'::text)) -(3 rows) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT f1, f2, f3 FROM public.loct3 WHERE ((f2 = 'foo'::text)) + Seq Scan on public.loct3 + Output: f1, f2, f3 + Filter: (loct3.f2 = 'foo'::text) + +(11 rows) explain (verbose, costs off) select * from ft3 where f3 = 'foo'; - QUERY PLAN ------------------------------------------------------------------------------- + QUERY PLAN +--------------------------------------------------------------------------------------------------------- Foreign Scan on public.ft3 Output: f1, f2, f3 + Node ID: 1 Remote SQL: SELECT f1, f2, f3 FROM public.loct3 WHERE ((f3 = 'foo'::text)) -(3 rows) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT f1, f2, f3 FROM public.loct3 WHERE ((f3 = 'foo'::text)) + [Bypass] + Index Scan using loct3_f3_key on public.loct3 + Output: f1, f2, f3 + Index Cond: ((loct3.f3)::text = 'foo'::text) + +(12 rows) explain (verbose, costs off) select * from ft3 f, loct3 l where f.f3 = l.f3 and l.f1 = 'foo'; - QUERY PLAN --------------------------------------------------------------------------------------------------- + QUERY PLAN +------------------------------------------------------------------------------ Nested Loop Output: f.f1, f.f2, f.f3, l.f1, l.f2, l.f3 + Join Filter: ((f.f3)::text = (l.f3)::text) -> Index Scan using loct3_f1_key on public.loct3 l Output: l.f1, l.f2, l.f3 Index Cond: (l.f1 = 'foo'::text) -> Foreign Scan on public.ft3 f Output: f.f1, f.f2, f.f3 - Remote SQL: SELECT f1, f2, f3 FROM public.loct3 WHERE (($1::character varying(10) = f3)) -(8 rows) + Node ID: 1 + Remote SQL: SELECT f1, f2, f3 FROM public.loct3 + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT f1, f2, f3 FROM public.loct3 + Seq Scan on public.loct3 + Output: f1, f2, f3 + +(16 rows) -- can't be sent to remote explain (verbose, costs off) select * from ft3 where f1 COLLATE "POSIX" = 'foo'; - QUERY PLAN ---------------------------------------------------- + QUERY PLAN +------------------------------------------------------------------------------ Foreign Scan on public.ft3 Output: f1, f2, f3 Filter: ((ft3.f1)::text = 'foo'::text) + Node ID: 1 Remote SQL: SELECT f1, f2, f3 FROM public.loct3 -(4 rows) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT f1, f2, f3 FROM public.loct3 + Seq Scan on public.loct3 + Output: f1, f2, f3 + +(11 rows) explain (verbose, costs off) select * from ft3 where f1 = 'foo' COLLATE "C"; - QUERY PLAN ---------------------------------------------------- + QUERY PLAN +------------------------------------------------------------------------------ Foreign Scan on public.ft3 Output: f1, f2, f3 Filter: (ft3.f1 = 'foo'::text COLLATE "C") + Node ID: 1 Remote SQL: SELECT f1, f2, f3 FROM public.loct3 -(4 rows) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT f1, f2, f3 FROM public.loct3 + Seq Scan on public.loct3 + Output: f1, f2, f3 + +(11 rows) explain (verbose, costs off) select * from ft3 where f2 COLLATE "C" = 'foo'; - QUERY PLAN ---------------------------------------------------- + QUERY PLAN +------------------------------------------------------------------------------ Foreign Scan on public.ft3 Output: f1, f2, f3 Filter: ((ft3.f2)::text = 'foo'::text) + Node ID: 1 Remote SQL: SELECT f1, f2, f3 FROM public.loct3 -(4 rows) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT f1, f2, f3 FROM public.loct3 + Seq Scan on public.loct3 + Output: f1, f2, f3 + +(11 rows) explain (verbose, costs off) select * from ft3 where f2 = 'foo' COLLATE "C"; - QUERY PLAN ---------------------------------------------------- + QUERY PLAN +------------------------------------------------------------------------------ Foreign Scan on public.ft3 Output: f1, f2, f3 Filter: (ft3.f2 = 'foo'::text COLLATE "C") + Node ID: 1 Remote SQL: SELECT f1, f2, f3 FROM public.loct3 -(4 rows) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT f1, f2, f3 FROM public.loct3 + Seq Scan on public.loct3 + Output: f1, f2, f3 + +(11 rows) explain (verbose, costs off) select * from ft3 f, loct3 l where f.f3 = l.f3 COLLATE "POSIX" and l.f1 = 'foo'; - QUERY PLAN -------------------------------------------------------------- - Hash Join + QUERY PLAN +------------------------------------------------------------------------------ + Nested Loop Output: f.f1, f.f2, f.f3, l.f1, l.f2, l.f3 - Hash Cond: ((f.f3)::text = (l.f3)::text) + Join Filter: ((f.f3)::text = (l.f3)::text) + -> Index Scan using loct3_f1_key on public.loct3 l + Output: l.f1, l.f2, l.f3 + Index Cond: (l.f1 = 'foo'::text) -> Foreign Scan on public.ft3 f Output: f.f1, f.f2, f.f3 + Node ID: 1 Remote SQL: SELECT f1, f2, f3 FROM public.loct3 - -> Hash - Output: l.f1, l.f2, l.f3 - -> Index Scan using loct3_f1_key on public.loct3 l - Output: l.f1, l.f2, l.f3 - Index Cond: (l.f1 = 'foo'::text) -(11 rows) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT f1, f2, f3 FROM public.loct3 + Seq Scan on public.loct3 + Output: f1, f2, f3 + +(16 rows) --- =================================================================== --- test writable foreign table stuff --- =================================================================== +-- ====================================================================================================================================== +-- TEST-MODULE: test writable foreign table stuff +-- -------------------------------------- +-- ====================================================================================================================================== EXPLAIN (verbose, costs off) INSERT INTO ft2 (c1,c2,c3) SELECT c1+1000,c2+100, c3 || c3 FROM ft2 LIMIT 20; - QUERY PLAN --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Insert on public.ft2 - Remote SQL: INSERT INTO "S 1"."T 1"("C 1", c2, c3, c4, c5, c6, c7, c8) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) -> Subquery Scan on "*SELECT*" - Output: "*SELECT*"."?column?", "*SELECT*"."?column?_1", NULL::integer, "*SELECT*"."?column?_2", NULL::timestamp with time zone, NULL::timestamp without time zone, NULL::character varying, 'ft2 '::character(10), NULL::user_enum - -> Limit - Output: ((ft2_1.c1 + 1000)), ((ft2_1.c2 + 100)), ((ft2_1.c3 || ft2_1.c3)) - -> Foreign Scan on public.ft2 ft2_1 - Output: (ft2_1.c1 + 1000), (ft2_1.c2 + 100), (ft2_1.c3 || ft2_1.c3) - Remote SQL: SELECT "C 1", c2, c3 FROM "S 1"."T 1" -(9 rows) + Output: "*SELECT*"."?column?", "*SELECT*"."?column?", NULL::integer, "*SELECT*"."?column?", NULL::timestamp with time zone, NULL::timestamp without time zone, NULL::character varying, 'ft2 '::character(10), NULL::user_enum + -> Foreign Scan on public.ft2 + Output: (public.ft2.c1 + 1000), (public.ft2.c2 + 100), (public.ft2.c3 || public.ft2.c3) + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3 FROM "S 1"."T 1" LIMIT 20::bigint + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3 FROM "S 1"."T 1" LIMIT 20::bigint + Limit + Output: "C 1", c2, c3 + -> Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3 + +(15 rows) INSERT INTO ft2 (c1,c2,c3) SELECT c1+1000,c2+100, c3 || c3 FROM ft2 LIMIT 20; INSERT INTO ft2 (c1,c2,c3) @@ -1283,7 +6152,48 @@ INSERT INTO ft2 (c1,c2,c3) (3 rows) INSERT INTO ft2 (c1,c2,c3) VALUES (1104,204,'ddd'), (1105,205,'eee'); +EXPLAIN (verbose, costs off) +UPDATE ft2 SET c2 = c2 + 300, c3 = c3 || '_update3' WHERE c1 % 10 = 3; -- can be pushed down + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------------------- + Update on public.ft2 + -> Foreign Scan on public.ft2 + Output: c1, (c2 + 300), NULL::integer, (c3 || '_update3'::text), c4, c5, c6, c7, c8, ctid, tableoid + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8, ctid, tableoid FROM "S 1"."T 1" WHERE ((("C 1" % 10) = 3)) FOR UPDATE + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8, ctid, tableoid FROM "S 1"."T 1" WHERE ((("C 1" % 10) = 3)) FOR UPDATE + LockRows + Output: "C 1", c2, c3, c4, c5, c6, c7, c8, ctid, tableoid, ctid + -> Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8, ctid, tableoid, ctid + Filter: (("T 1"."C 1" % 10) = 3) + +(14 rows) + UPDATE ft2 SET c2 = c2 + 300, c3 = c3 || '_update3' WHERE c1 % 10 = 3; +EXPLAIN (verbose, costs off) +UPDATE ft2 SET c2 = c2 + 400, c3 = c3 || '_update7' WHERE c1 % 10 = 7 RETURNING *; -- can be pushed down + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------------------- + Update on public.ft2 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + -> Foreign Scan on public.ft2 + Output: c1, (c2 + 400), NULL::integer, (c3 || '_update7'::text), c4, c5, c6, c7, c8, ctid, tableoid + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8, ctid, tableoid FROM "S 1"."T 1" WHERE ((("C 1" % 10) = 7)) FOR UPDATE + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8, ctid, tableoid FROM "S 1"."T 1" WHERE ((("C 1" % 10) = 7)) FOR UPDATE + LockRows + Output: "C 1", c2, c3, c4, c5, c6, c7, c8, ctid, tableoid, ctid + -> Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8, ctid, tableoid, ctid + Filter: (("T 1"."C 1" % 10) = 7) + +(15 rows) + UPDATE ft2 SET c2 = c2 + 400, c3 = c3 || '_update7' WHERE c1 % 10 = 7 RETURNING *; c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 ------+-----+--------------------+------------------------------+--------------------------+----+------------+----- @@ -1393,37 +6303,60 @@ UPDATE ft2 SET c2 = c2 + 400, c3 = c3 || '_update7' WHERE c1 % 10 = 7 RETURNING EXPLAIN (verbose, costs off) UPDATE ft2 SET c2 = ft2.c2 + 500, c3 = ft2.c3 || '_update9', c7 = DEFAULT - FROM ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 9; - QUERY PLAN -------------------------------------------------------------------------------------------------------------------------------------------------------------------- + FROM ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 9; -- can be pushed down + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Update on public.ft2 - Remote SQL: UPDATE "S 1"."T 1" SET c2 = $2, c3 = $3, c7 = $4 WHERE ctid = $1 - -> Hash Join - Output: ft2.c1, (ft2.c2 + 500), NULL::integer, (ft2.c3 || '_update9'::text), ft2.c4, ft2.c5, ft2.c6, 'ft2 '::character(10), ft2.c8, ft2.ctid, ft1.* - Hash Cond: (ft2.c2 = ft1.c1) + -> Nested Loop + Output: ft2.c1, (ft2.c2 + 500), NULL::integer, (ft2.c3 || '_update9'::text), ft2.c4, ft2.c5, ft2.c6, 'ft2 '::character(10), ft2.c8, ft2.ctid, ft2.tableoid, ft1.* + Join Filter: (ft2.c2 = ft1.c1) -> Foreign Scan on public.ft2 - Output: ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c8, ft2.ctid - Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c8, ctid FROM "S 1"."T 1" FOR UPDATE - -> Hash + Output: ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c8, ft2.ctid, ft2.tableoid + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c8, ctid, tableoid FROM "S 1"."T 1" WHERE (((c2 % 10) = 9)) FOR UPDATE + -> Materialize Output: ft1.*, ft1.c1 -> Foreign Scan on public.ft1 Output: ft1.*, ft1.c1 + Node ID: 2 Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((("C 1" % 10) = 9)) -(13 rows) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c8, ctid, tableoid FROM "S 1"."T 1" WHERE (((c2 % 10) = 9)) FOR UPDATE + LockRows + Output: "C 1", c2, c3, c4, c5, c6, c8, ctid, tableoid, ctid + -> Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c8, ctid, tableoid, ctid + Filter: (("T 1".c2 % 10) = 9) + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((("C 1" % 10) = 9)) + Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Filter: (("T 1"."C 1" % 10) = 9) + +(27 rows) UPDATE ft2 SET c2 = ft2.c2 + 500, c3 = ft2.c3 || '_update9', c7 = DEFAULT FROM ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 9; EXPLAIN (verbose, costs off) - DELETE FROM ft2 WHERE c1 % 10 = 5 RETURNING c1, c4; - QUERY PLAN ----------------------------------------------------------------------------------------- + DELETE FROM ft2 WHERE c1 % 10 = 5 RETURNING c1, c4; -- can be pushed down + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------- Delete on public.ft2 Output: c1, c4 - Remote SQL: DELETE FROM "S 1"."T 1" WHERE ctid = $1 RETURNING "C 1", c4 -> Foreign Scan on public.ft2 - Output: ctid - Remote SQL: SELECT ctid FROM "S 1"."T 1" WHERE ((("C 1" % 10) = 5)) FOR UPDATE -(6 rows) + Output: ctid, tableoid + Node ID: 1 + Remote SQL: SELECT ctid, tableoid FROM "S 1"."T 1" WHERE ((("C 1" % 10) = 5)) FOR UPDATE + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT ctid, tableoid FROM "S 1"."T 1" WHERE ((("C 1" % 10) = 5)) FOR UPDATE + LockRows + Output: ctid, tableoid, ctid + -> Seq Scan on "S 1"."T 1" + Output: ctid, tableoid, ctid + Filter: (("T 1"."C 1" % 10) = 5) + +(15 rows) DELETE FROM ft2 WHERE c1 % 10 = 5 RETURNING c1, c4; c1 | c4 @@ -1534,23 +6467,37 @@ DELETE FROM ft2 WHERE c1 % 10 = 5 RETURNING c1, c4; (103 rows) EXPLAIN (verbose, costs off) -DELETE FROM ft2 USING ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 2; - QUERY PLAN ----------------------------------------------------------------------------------------------------------------------- +DELETE FROM ft2 USING ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 2; -- can be pushed down + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------- Delete on public.ft2 - Remote SQL: DELETE FROM "S 1"."T 1" WHERE ctid = $1 - -> Hash Join - Output: ft2.ctid, ft1.* - Hash Cond: (ft2.c2 = ft1.c1) + -> Nested Loop + Output: ft2.ctid, ft2.tableoid, ft1.* + Join Filter: (ft2.c2 = ft1.c1) -> Foreign Scan on public.ft2 - Output: ft2.ctid, ft2.c2 - Remote SQL: SELECT c2, ctid FROM "S 1"."T 1" FOR UPDATE - -> Hash + Output: ft2.ctid, ft2.tableoid, ft2.c2 + Node ID: 1 + Remote SQL: SELECT c2, ctid, tableoid FROM "S 1"."T 1" WHERE (((c2 % 10) = 2)) FOR UPDATE + -> Materialize Output: ft1.*, ft1.c1 -> Foreign Scan on public.ft1 Output: ft1.*, ft1.c1 + Node ID: 2 Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((("C 1" % 10) = 2)) -(13 rows) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c2, ctid, tableoid FROM "S 1"."T 1" WHERE (((c2 % 10) = 2)) FOR UPDATE + LockRows + Output: c2, ctid, tableoid, ctid + -> Seq Scan on "S 1"."T 1" + Output: c2, ctid, tableoid, ctid + Filter: (("T 1".c2 % 10) = 2) + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((("C 1" % 10) = 2)) + Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Filter: (("T 1"."C 1" % 10) = 2) + +(27 rows) DELETE FROM ft2 USING ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 2; SELECT c1,c2,c3,c4 FROM ft2 ORDER BY c1; @@ -2377,59 +7324,424 @@ SELECT c1,c2,c3,c4 FROM ft2 ORDER BY c1; 1104 | 204 | ddd | (819 rows) +--nspt openGauss not have tableoid, also not support select system columns. EXPLAIN (verbose, costs off) -INSERT INTO ft2 (c1,c2,c3) VALUES (9999,999,'foo') RETURNING tableoid::regclass; - QUERY PLAN -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - Insert on public.ft2 - Output: (tableoid)::regclass - Remote SQL: INSERT INTO "S 1"."T 1"("C 1", c2, c3, c4, c5, c6, c7, c8) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) - -> Result - Output: 9999, 999, NULL::integer, 'foo'::text, NULL::timestamp with time zone, NULL::timestamp without time zone, NULL::character varying, 'ft2 '::character(10), NULL::user_enum -(5 rows) - -INSERT INTO ft2 (c1,c2,c3) VALUES (9999,999,'foo') RETURNING tableoid::regclass; - tableoid ----------- - ft2 -(1 row) - +INSERT INTO ft2 (c1,c2,c3) VALUES (1200,999,'foo') RETURNING tableoid::regclass; +ERROR: column "tableoid" does not exist +LINE 2: ... ft2 (c1,c2,c3) VALUES (1200,999,'foo') RETURNING tableoid::... + ^ +CONTEXT: referenced column: tableoid +INSERT INTO ft2 (c1,c2,c3) VALUES (1200,999,'foo') RETURNING tableoid::regclass; +ERROR: column "tableoid" does not exist +LINE 1: ... ft2 (c1,c2,c3) VALUES (1200,999,'foo') RETURNING tableoid::... + ^ +CONTEXT: referenced column: tableoid EXPLAIN (verbose, costs off) -UPDATE ft2 SET c3 = 'bar' WHERE c1 = 9999 RETURNING tableoid::regclass; - QUERY PLAN -------------------------------------------------------------------------------------------------------------------- +UPDATE ft2 SET c3 = 'bar' WHERE c1 = 1200 RETURNING tableoid::regclass; -- can be pushed down +ERROR: column "tableoid" does not exist +LINE 2: ...DATE ft2 SET c3 = 'bar' WHERE c1 = 1200 RETURNING tableoid::... + ^ +CONTEXT: referenced column: tableoid +UPDATE ft2 SET c3 = 'bar' WHERE c1 = 1200 RETURNING tableoid::regclass; +ERROR: column "tableoid" does not exist +LINE 1: ...DATE ft2 SET c3 = 'bar' WHERE c1 = 1200 RETURNING tableoid::... + ^ +CONTEXT: referenced column: tableoid +EXPLAIN (verbose, costs off) +DELETE FROM ft2 WHERE c1 = 1200 RETURNING tableoid::regclass; -- can be pushed down +ERROR: column "tableoid" does not exist +LINE 2: DELETE FROM ft2 WHERE c1 = 1200 RETURNING tableoid::regclass... + ^ +CONTEXT: referenced column: tableoid +DELETE FROM ft2 WHERE c1 = 1200 RETURNING tableoid::regclass; +ERROR: column "tableoid" does not exist +LINE 1: DELETE FROM ft2 WHERE c1 = 1200 RETURNING tableoid::regclass... + ^ +CONTEXT: referenced column: tableoid +-- Test UPDATE/DELETE with RETURNING on a three-table join +INSERT INTO ft2 (c1,c2,c3) + SELECT id, id - 1200, to_char(id, 'FM00000') FROM generate_series(1201, 1300) id; +EXPLAIN (verbose, costs off) +UPDATE ft2 SET c3 = 'foo' + FROM ft4 INNER JOIN ft5 ON (ft4.c1 = ft5.c1) + WHERE ft2.c1 > 1200 AND ft2.c2 = ft4.c1 + RETURNING ft2, ft2.*, ft4, ft4.*; -- can be pushed down + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------ Update on public.ft2 - Output: (tableoid)::regclass - Remote SQL: UPDATE "S 1"."T 1" SET c3 = $2 WHERE ctid = $1 - -> Foreign Scan on public.ft2 - Output: c1, c2, NULL::integer, 'bar'::text, c4, c5, c6, c7, c8, ctid - Remote SQL: SELECT "C 1", c2, c4, c5, c6, c7, c8, ctid FROM "S 1"."T 1" WHERE (("C 1" = 9999)) FOR UPDATE -(6 rows) + Output: ft2.*, ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft4.*, ft4.c1, ft4.c2, ft4.c3 + -> Hash Join + Output: ft2.c1, ft2.c2, NULL::integer, 'foo'::text, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.ctid, ft2.tableoid, ft4.*, ft5.*, ft4.c1, ft4.c2, ft4.c3 + Hash Cond: (ft2.c2 = ft4.c1) + -> Foreign Scan on public.ft2 + Output: ft2.c1, ft2.c2, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.ctid, ft2.tableoid + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c4, c5, c6, c7, c8, ctid, tableoid FROM "S 1"."T 1" WHERE (("C 1" > 1200)) FOR UPDATE + -> Hash + Output: ft4.*, ft4.c1, ft4.c2, ft4.c3, ft5.*, ft5.c1 + -> Hash Join + Output: ft4.*, ft4.c1, ft4.c2, ft4.c3, ft5.*, ft5.c1 + Hash Cond: (ft4.c1 = ft5.c1) + -> Foreign Scan on public.ft4 + Output: ft4.*, ft4.c1, ft4.c2, ft4.c3 + Node ID: 2 + Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 3" + -> Hash + Output: ft5.*, ft5.c1 + -> Foreign Scan on public.ft5 + Output: ft5.*, ft5.c1 + Node ID: 3 + Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 4" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c4, c5, c6, c7, c8, ctid, tableoid FROM "S 1"."T 1" WHERE (("C 1" > 1200)) FOR UPDATE + LockRows + Output: "C 1", c2, c4, c5, c6, c7, c8, ctid, tableoid, ctid + -> Index Scan using t1_pkey on "S 1"."T 1" + Output: "C 1", c2, c4, c5, c6, c7, c8, ctid, tableoid, ctid + Index Cond: ("T 1"."C 1" > 1200) + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c1, c2, c3 FROM "S 1"."T 3" + Seq Scan on "S 1"."T 3" + Output: c1, c2, c3 + Node 3: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c1, c2, c3 FROM "S 1"."T 4" + Seq Scan on "S 1"."T 4" + Output: c1, c2, c3 + +(39 rows) -UPDATE ft2 SET c3 = 'bar' WHERE c1 = 9999 RETURNING tableoid::regclass; - tableoid +UPDATE ft2 SET c3 = 'foo' + FROM ft4 INNER JOIN ft5 ON (ft4.c1 = ft5.c1) + WHERE ft2.c1 > 1200 AND ft2.c2 = ft4.c1 + RETURNING ft2, ft2.*, ft4, ft4.*; + ft2 | c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 | ft4 | c1 | c2 | c3 +--------------------------------+------+----+-----+----+----+----+------------+----+----------------+----+----+-------- + (1206,6,foo,,,,"ft2 ",) | 1206 | 6 | foo | | | | ft2 | | (6,7,AAA006) | 6 | 7 | AAA006 + (1212,12,foo,,,,"ft2 ",) | 1212 | 12 | foo | | | | ft2 | | (12,13,AAA012) | 12 | 13 | AAA012 + (1218,18,foo,,,,"ft2 ",) | 1218 | 18 | foo | | | | ft2 | | (18,19,AAA018) | 18 | 19 | AAA018 + (1224,24,foo,,,,"ft2 ",) | 1224 | 24 | foo | | | | ft2 | | (24,25,AAA024) | 24 | 25 | AAA024 + (1230,30,foo,,,,"ft2 ",) | 1230 | 30 | foo | | | | ft2 | | (30,31,AAA030) | 30 | 31 | AAA030 + (1236,36,foo,,,,"ft2 ",) | 1236 | 36 | foo | | | | ft2 | | (36,37,AAA036) | 36 | 37 | AAA036 + (1242,42,foo,,,,"ft2 ",) | 1242 | 42 | foo | | | | ft2 | | (42,43,AAA042) | 42 | 43 | AAA042 + (1248,48,foo,,,,"ft2 ",) | 1248 | 48 | foo | | | | ft2 | | (48,49,AAA048) | 48 | 49 | AAA048 + (1254,54,foo,,,,"ft2 ",) | 1254 | 54 | foo | | | | ft2 | | (54,55,AAA054) | 54 | 55 | AAA054 + (1260,60,foo,,,,"ft2 ",) | 1260 | 60 | foo | | | | ft2 | | (60,61,AAA060) | 60 | 61 | AAA060 + (1266,66,foo,,,,"ft2 ",) | 1266 | 66 | foo | | | | ft2 | | (66,67,AAA066) | 66 | 67 | AAA066 + (1272,72,foo,,,,"ft2 ",) | 1272 | 72 | foo | | | | ft2 | | (72,73,AAA072) | 72 | 73 | AAA072 + (1278,78,foo,,,,"ft2 ",) | 1278 | 78 | foo | | | | ft2 | | (78,79,AAA078) | 78 | 79 | AAA078 + (1284,84,foo,,,,"ft2 ",) | 1284 | 84 | foo | | | | ft2 | | (84,85,AAA084) | 84 | 85 | AAA084 + (1290,90,foo,,,,"ft2 ",) | 1290 | 90 | foo | | | | ft2 | | (90,91,AAA090) | 90 | 91 | AAA090 + (1296,96,foo,,,,"ft2 ",) | 1296 | 96 | foo | | | | ft2 | | (96,97,AAA096) | 96 | 97 | AAA096 +(16 rows) + +EXPLAIN (verbose, costs off) +DELETE FROM ft2 + USING ft4 LEFT JOIN ft5 ON (ft4.c1 = ft5.c1) + WHERE ft2.c1 > 1200 AND ft2.c1 % 10 = 0 AND ft2.c2 = ft4.c1 + RETURNING 100; -- can be pushed down + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------ + Delete on public.ft2 + Output: 100 + -> Nested Loop Left Join + Output: ft2.ctid, ft2.tableoid, ft4.*, ft5.* + Join Filter: (ft4.c1 = ft5.c1) + -> Nested Loop + Output: ft2.ctid, ft2.tableoid, ft4.*, ft4.c1 + Join Filter: (ft2.c2 = ft4.c1) + -> Foreign Scan on public.ft2 + Output: ft2.ctid, ft2.tableoid, ft2.c2 + Node ID: 1 + Remote SQL: SELECT c2, ctid, tableoid FROM "S 1"."T 1" WHERE (("C 1" > 1200)) AND ((("C 1" % 10) = 0)) FOR UPDATE + -> Foreign Scan on public.ft4 + Output: ft4.*, ft4.c1 + Node ID: 2 + Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 3" + -> Foreign Scan on public.ft5 + Output: ft5.*, ft5.c1 + Node ID: 3 + Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 4" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c2, ctid, tableoid FROM "S 1"."T 1" WHERE (("C 1" > 1200)) AND ((("C 1" % 10) = 0)) FOR UPDATE + LockRows + Output: c2, ctid, tableoid, ctid + -> Index Scan using t1_pkey on "S 1"."T 1" + Output: c2, ctid, tableoid, ctid + Index Cond: ("T 1"."C 1" > 1200) + Filter: (("T 1"."C 1" % 10) = 0) + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c1, c2, c3 FROM "S 1"."T 3" + Seq Scan on "S 1"."T 3" + Output: c1, c2, c3 + Node 3: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c1, c2, c3 FROM "S 1"."T 4" + Seq Scan on "S 1"."T 4" + Output: c1, c2, c3 + +(36 rows) + +DELETE FROM ft2 + USING ft4 LEFT JOIN ft5 ON (ft4.c1 = ft5.c1) + WHERE ft2.c1 > 1200 AND ft2.c1 % 10 = 0 AND ft2.c2 = ft4.c1 + RETURNING 100; + ?column? ---------- - ft2 + 100 + 100 + 100 + 100 + 100 + 100 + 100 + 100 + 100 + 100 +(10 rows) + +DELETE FROM ft2 WHERE ft2.c1 > 1200; +-- Test UPDATE with a MULTIEXPR sub-select +-- (maybe someday this'll be remotely executable, but not today) +EXPLAIN (verbose, costs off) +UPDATE ft2 AS target SET (c2, c7) = ( + SELECT c2 * 10, c7 + FROM ft2 AS src + WHERE target.c1 = src.c1 +) WHERE c1 > 1100; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------------------------------- + Update on public.ft2 target + -> Foreign Scan on public.ft2 target + Output: target.c1, (SubPlan 1), NULL::integer, target.c3, target.c4, target.c5, target.c6, (SubPlan 2), target.c8, target.ctid, target.tableoid + Node ID: 1 + Remote SQL: SELECT "C 1", c3, c4, c5, c6, c8, ctid, tableoid FROM "S 1"."T 1" WHERE (("C 1" > 1100)) FOR UPDATE + SubPlan 1 + -> Foreign Scan on public.ft2 src + Output: (src.c2 * 10) + Node ID: 2 + Remote SQL: SELECT c2 FROM "S 1"."T 1" WHERE (($1::integer = "C 1")) + SubPlan 2 + -> Foreign Scan on public.ft2 src + Output: src.c7 + Node ID: 3 + Remote SQL: SELECT c7 FROM "S 1"."T 1" WHERE (($1::integer = "C 1")) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c3, c4, c5, c6, c8, ctid, tableoid FROM "S 1"."T 1" WHERE (("C 1" > 1100)) FOR UPDATE + LockRows + Output: "C 1", c3, c4, c5, c6, c8, ctid, tableoid, ctid + -> Index Scan using t1_pkey on "S 1"."T 1" + Output: "C 1", c3, c4, c5, c6, c8, ctid, tableoid, ctid + Index Cond: ("T 1"."C 1" > 1100) + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c2 FROM "S 1"."T 1" WHERE (($1::integer = "C 1")) + failed to get remote plan, error massage is: + there is no parameter $1 + Node 3: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c7 FROM "S 1"."T 1" WHERE (($1::integer = "C 1")) + failed to get remote plan, error massage is: + current transaction is aborted, commands ignored until end of transaction block, firstChar[Q] + +(30 rows) + +UPDATE ft2 AS target SET (c2, c7) = ( + SELECT c2 * 10, c7 + FROM ft2 AS src + WHERE target.c1 = src.c1 +) WHERE c1 > 1100; +UPDATE ft2 AS target SET (c2) = ( + SELECT c2 / 10 + FROM ft2 AS src + WHERE target.c1 = src.c1 +) WHERE c1 > 1100; +-- Test UPDATE involving a join that can be pushed down, +-- but a SET clause that can't be +EXPLAIN (VERBOSE, COSTS OFF) +UPDATE ft2 d SET c2 = CASE WHEN random() >= 0 THEN d.c2 ELSE 0 END + FROM ft2 AS t WHERE d.c1 = t.c1 AND d.c1 > 1000; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + Update on public.ft2 d + -> Merge Join + Output: d.c1, CASE WHEN (random() >= 0::double precision) THEN d.c2 ELSE 0 END, NULL::integer, d.c3, d.c4, d.c5, d.c6, d.c7, d.c8, d.ctid, d.tableoid, t.* + Merge Cond: (d.c1 = t.c1) + -> Foreign Scan on public.ft2 d + Output: d.c1, d.c2, d.c3, d.c4, d.c5, d.c6, d.c7, d.c8, d.ctid, d.tableoid + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8, ctid, tableoid FROM "S 1"."T 1" WHERE (("C 1" > 1000)) ORDER BY "C 1" ASC NULLS LAST FOR UPDATE + -> Materialize + Output: t.*, t.c1 + -> Foreign Scan on public.ft2 t + Output: t.*, t.c1 + Node ID: 2 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" > 1000)) ORDER BY "C 1" ASC NULLS LAST + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8, ctid, tableoid FROM "S 1"."T 1" WHERE (("C 1" > 1000)) ORDER BY "C 1" ASC NULLS LAST FOR UPDATE + LockRows + Output: "C 1", c2, c3, c4, c5, c6, c7, c8, ctid, tableoid, ctid + -> Index Scan using t1_pkey on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8, ctid, tableoid, ctid + Index Cond: ("T 1"."C 1" > 1000) + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" > 1000)) ORDER BY "C 1" ASC NULLS LAST + [Bypass] + Index Scan using t1_pkey on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Index Cond: ("T 1"."C 1" > 1000) + +(28 rows) + +UPDATE ft2 d SET c2 = CASE WHEN random() >= 0 THEN d.c2 ELSE 0 END + FROM ft2 AS t WHERE d.c1 = t.c1 AND d.c1 > 1000; +-- Test UPDATE/DELETE with WHERE or JOIN/ON conditions containing +-- user-defined operators/functions +ALTER SERVER loopback OPTIONS (DROP extensions); +ERROR: option "extensions" not found +INSERT INTO ft2 (c1,c2,c3) + SELECT id, id % 10, to_char(id, 'FM00000') FROM generate_series(2001, 2010) id; +EXPLAIN (verbose, costs off) +UPDATE ft2 SET c3 = 'bar' WHERE postgres_fdw_abs(c1) > 2000 RETURNING *; -- can't be pushed down + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------- + Update on public.ft2 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + -> Foreign Scan on public.ft2 + Output: c1, c2, NULL::integer, 'bar'::text, c4, c5, c6, c7, c8, ctid, tableoid + Filter: (postgres_fdw_abs(ft2.c1) > 2000) + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c4, c5, c6, c7, c8, ctid, tableoid FROM "S 1"."T 1" FOR UPDATE + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c4, c5, c6, c7, c8, ctid, tableoid FROM "S 1"."T 1" FOR UPDATE + LockRows + Output: "C 1", c2, c4, c5, c6, c7, c8, ctid, tableoid, ctid + -> Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c4, c5, c6, c7, c8, ctid, tableoid, ctid + +(15 rows) + +UPDATE ft2 SET c3 = 'bar' WHERE postgres_fdw_abs(c1) > 2000 RETURNING *; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +------+----+-----+----+----+----+------------+---- + 2001 | 1 | bar | | | | ft2 | + 2002 | 2 | bar | | | | ft2 | + 2003 | 3 | bar | | | | ft2 | + 2004 | 4 | bar | | | | ft2 | + 2005 | 5 | bar | | | | ft2 | + 2006 | 6 | bar | | | | ft2 | + 2007 | 7 | bar | | | | ft2 | + 2008 | 8 | bar | | | | ft2 | + 2009 | 9 | bar | | | | ft2 | + 2010 | 0 | bar | | | | ft2 | +(10 rows) + +EXPLAIN (verbose, costs off) +UPDATE ft2 SET c3 = 'baz' + FROM ft4 INNER JOIN ft5 ON (ft4.c1 = ft5.c1) + WHERE ft2.c1 > 2000 AND ft2.c2 === ft4.c1 + RETURNING ft2.*, ft4.*, ft5.*; -- can't be pushed down + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + Update on public.ft2 + Output: ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft4.c1, ft4.c2, ft4.c3, ft5.c1, ft5.c2, ft5.c3 + -> Hash Join + Output: ft2.c1, ft2.c2, NULL::integer, 'baz'::text, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.ctid, ft2.tableoid, ft4.*, ft5.*, ft4.c1, ft4.c2, ft4.c3, ft5.c1, ft5.c2, ft5.c3 + Hash Cond: (ft4.c1 = ft5.c1) + -> Nested Loop + Output: ft2.c1, ft2.c2, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.ctid, ft2.tableoid, ft4.*, ft4.c1, ft4.c2, ft4.c3 + Join Filter: (ft2.c2 === ft4.c1) + -> Foreign Scan on public.ft2 + Output: ft2.c1, ft2.c2, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.ctid, ft2.tableoid + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c4, c5, c6, c7, c8, ctid, tableoid FROM "S 1"."T 1" WHERE (("C 1" > 2000)) FOR UPDATE + -> Foreign Scan on public.ft4 + Output: ft4.*, ft4.c1, ft4.c2, ft4.c3 + Node ID: 2 + Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 3" + -> Hash + Output: ft5.*, ft5.c1, ft5.c2, ft5.c3 + -> Foreign Scan on public.ft5 + Output: ft5.*, ft5.c1, ft5.c2, ft5.c3 + Node ID: 3 + Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 4" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c4, c5, c6, c7, c8, ctid, tableoid FROM "S 1"."T 1" WHERE (("C 1" > 2000)) FOR UPDATE + LockRows + Output: "C 1", c2, c4, c5, c6, c7, c8, ctid, tableoid, ctid + -> Index Scan using t1_pkey on "S 1"."T 1" + Output: "C 1", c2, c4, c5, c6, c7, c8, ctid, tableoid, ctid + Index Cond: ("T 1"."C 1" > 2000) + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c1, c2, c3 FROM "S 1"."T 3" + Seq Scan on "S 1"."T 3" + Output: c1, c2, c3 + Node 3: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c1, c2, c3 FROM "S 1"."T 4" + Seq Scan on "S 1"."T 4" + Output: c1, c2, c3 + +(37 rows) + +UPDATE ft2 SET c3 = 'baz' + FROM ft4 INNER JOIN ft5 ON (ft4.c1 = ft5.c1) + WHERE ft2.c1 > 2000 AND ft2.c2 === ft4.c1 + RETURNING ft2.*, ft4.*, ft5.*; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 | c1 | c2 | c3 | c1 | c2 | c3 +------+----+-----+----+----+----+------------+----+----+----+--------+----+----+-------- + 2006 | 6 | baz | | | | ft2 | | 6 | 7 | AAA006 | 6 | 7 | AAA006 (1 row) EXPLAIN (verbose, costs off) -DELETE FROM ft2 WHERE c1 = 9999 RETURNING tableoid::regclass; - QUERY PLAN ------------------------------------------------------------------------------------- +DELETE FROM ft2 + USING ft4 INNER JOIN ft5 ON (ft4.c1 === ft5.c1) + WHERE ft2.c1 > 2000 AND ft2.c2 = ft4.c1 + RETURNING ft2.c1, ft2.c2, ft2.c3; -- can't be pushed down + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------- Delete on public.ft2 - Output: (tableoid)::regclass - Remote SQL: DELETE FROM "S 1"."T 1" WHERE ctid = $1 - -> Foreign Scan on public.ft2 - Output: ctid - Remote SQL: SELECT ctid FROM "S 1"."T 1" WHERE (("C 1" = 9999)) FOR UPDATE -(6 rows) + Output: ft2.c1, ft2.c2, ft2.c3 + -> Nested Loop + Output: ft2.ctid, ft2.tableoid, ft4.*, ft5.* + Join Filter: (ft4.c1 === ft5.c1) + -> Nested Loop + Output: ft2.ctid, ft2.tableoid, ft4.*, ft4.c1 + Join Filter: (ft2.c2 = ft4.c1) + -> Foreign Scan on public.ft2 + Output: ft2.ctid, ft2.tableoid, ft2.c2 + Node ID: 1 + Remote SQL: SELECT c2, ctid, tableoid FROM "S 1"."T 1" WHERE (("C 1" > 2000)) FOR UPDATE + -> Foreign Scan on public.ft4 + Output: ft4.*, ft4.c1 + Node ID: 2 + Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 3" + -> Foreign Scan on public.ft5 + Output: ft5.*, ft5.c1 + Node ID: 3 + Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 4" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c2, ctid, tableoid FROM "S 1"."T 1" WHERE (("C 1" > 2000)) FOR UPDATE + LockRows + Output: c2, ctid, tableoid, ctid + -> Index Scan using t1_pkey on "S 1"."T 1" + Output: c2, ctid, tableoid, ctid + Index Cond: ("T 1"."C 1" > 2000) + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c1, c2, c3 FROM "S 1"."T 3" + Seq Scan on "S 1"."T 3" + Output: c1, c2, c3 + Node 3: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c1, c2, c3 FROM "S 1"."T 4" + Seq Scan on "S 1"."T 4" + Output: c1, c2, c3 + +(35 rows) -DELETE FROM ft2 WHERE c1 = 9999 RETURNING tableoid::regclass; - tableoid ----------- - ft2 +DELETE FROM ft2 + USING ft4 INNER JOIN ft5 ON (ft4.c1 === ft5.c1) + WHERE ft2.c1 > 2000 AND ft2.c2 = ft4.c1 + RETURNING ft2.c1, ft2.c2, ft2.c3; + c1 | c2 | c3 +------+----+----- + 2006 | 6 | baz (1 row) +DELETE FROM ft2 WHERE ft2.c1 > 2000; +ALTER SERVER loopback OPTIONS (ADD extensions 'postgres_fdw'); +ERROR: invalid option "extensions" +HINT: Valid options in this context are: service, connect_timeout, dbname, host, remote_nodename, hostaddr, port, localhost, localport, application_name, fencedUdfRPCMode, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, rw_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, krbsrvname, prototype, connection_info, connectionExtraInfo, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, updatable -- Test that trigger on remote table works as expected CREATE OR REPLACE FUNCTION "S 1".F_BRTRIG() RETURNS trigger AS $$ BEGIN @@ -2564,14 +7876,18 @@ INSERT INTO ft1(c1, c2) VALUES(11, 12); -- duplicate key ERROR: duplicate key value violates unique constraint "t1_pkey" DETAIL: Key ("C 1")=(11) already exists. CONTEXT: Remote SQL command: INSERT INTO "S 1"."T 1"("C 1", c2, c3, c4, c5, c6, c7, c8) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) +INSERT INTO ft1(c1, c2) VALUES(11, 12) ON DUPLICATE KEY UPDATE NOTHING; -- works +ERROR: INSERT ON DUPLICATE KEY UPDATE is not supported on foreign table. +INSERT INTO ft1(c1, c2) VALUES(11, 12) ON DUPLICATE KEY UPDATE c3 = 'ffg'; -- unsupported +ERROR: INSERT ON DUPLICATE KEY UPDATE is not supported on foreign table. INSERT INTO ft1(c1, c2) VALUES(1111, -2); -- c2positive ERROR: new row for relation "T 1" violates check constraint "c2positive" -DETAIL: Failing row contains (1111, -2, null, null, null, null, ft1 , null). +DETAIL: N/A CONTEXT: Remote SQL command: INSERT INTO "S 1"."T 1"("C 1", c2, c3, c4, c5, c6, c7, c8) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) UPDATE ft1 SET c2 = -c2 WHERE c1 = 1; -- c2positive ERROR: new row for relation "T 1" violates check constraint "c2positive" -DETAIL: Failing row contains (1, -1, 00001_trig_update, 1970-01-02 08:00:00+00, 1970-01-02 00:00:00, 1, 1 , foo). -CONTEXT: Remote SQL command: UPDATE "S 1"."T 1" SET c2 = $2 WHERE ctid = $1 +DETAIL: N/A +CONTEXT: Remote SQL command: UPDATE "S 1"."T 1" SET c2 = $3 WHERE ctid = $1 and tableoid = $2 -- Test savepoint/rollback behavior select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; c2 | count @@ -2729,8 +8045,8 @@ select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; savepoint s3; update ft2 set c2 = -2 where c2 = 42 and c1 = 10; -- fail on remote side ERROR: new row for relation "T 1" violates check constraint "c2positive" -DETAIL: Failing row contains (10, -2, 00010_trig_update_trig_update, 1970-01-11 08:00:00+00, 1970-01-11 00:00:00, 0, 0 , foo). -CONTEXT: Remote SQL command: UPDATE "S 1"."T 1" SET c2 = $2 WHERE ctid = $1 +DETAIL: N/A +CONTEXT: Remote SQL command: UPDATE "S 1"."T 1" SET c2 = $3 WHERE ctid = $1 and tableoid = $2 rollback to savepoint s3; select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; c2 | count @@ -2825,12 +8141,272 @@ select c2, count(*) from "S 1"."T 1" where c2 < 500 group by 1 order by 1; 407 | 100 (13 rows) --- =================================================================== --- test serial columns (ie, sequence-based defaults) --- =================================================================== +VACUUM ANALYZE "S 1"."T 1"; +-- Above DMLs add data with c6 as NULL in ft1, so test ORDER BY NULLS LAST and NULLs +-- FIRST behavior here. +-- ORDER BY DESC NULLS LAST options +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 ORDER BY c6 DESC NULLS LAST, c1 OFFSET 795 LIMIT 10; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + Foreign Scan on public.ft1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" ORDER BY c6::text DESC NULLS LAST, "C 1" ASC NULLS LAST LIMIT 10::bigint OFFSET 795::bigint + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" ORDER BY c6::text DESC NULLS LAST, "C 1" ASC NULLS LAST LIMIT 10::bigint OFFSET 795::bigint + Limit + Output: "C 1", c2, c3, c4, c5, c6, c7, c8, ((c6)::text) + -> Sort + Output: "C 1", c2, c3, c4, c5, c6, c7, c8, ((c6)::text) + Sort Key: "T 1".c6 DESC NULLS LAST, "T 1"."C 1" + -> Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8, (c6)::text + +(15 rows) + +SELECT * FROM ft1 ORDER BY c6 DESC NULLS LAST, c1 OFFSET 795 LIMIT 10; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +------+-----+--------------------+------------------------------+--------------------------+------+------------+----- + 960 | 42 | 00960_trig_update | Mon Mar 02 00:00:00 1970 PST | Mon Mar 02 00:00:00 1970 | 0 | 0 | foo + 970 | 42 | 00970_trig_update | Thu Mar 12 00:00:00 1970 PST | Thu Mar 12 00:00:00 1970 | 0 | 0 | foo + 980 | 42 | 00980_trig_update | Sun Mar 22 00:00:00 1970 PST | Sun Mar 22 00:00:00 1970 | 0 | 0 | foo + 990 | 42 | 00990_trig_update | Wed Apr 01 00:00:00 1970 PST | Wed Apr 01 00:00:00 1970 | 0 | 0 | foo + 1000 | 42 | 01000_trig_update | Thu Jan 01 00:00:00 1970 PST | Thu Jan 01 00:00:00 1970 | 0 | 0 | foo + 1218 | 818 | ggg_trig_update | | | (--; | ft2 | + 1001 | 101 | 0000100001 | | | | ft2 | + 1003 | 403 | 0000300003_update3 | | | | ft2 | + 1004 | 104 | 0000400004 | | | | ft2 | + 1006 | 106 | 0000600006 | | | | ft2 | +(10 rows) + +-- ORDER BY DESC NULLS FIRST options +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 ORDER BY c6 DESC NULLS FIRST, c1 OFFSET 15 LIMIT 10; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + Foreign Scan on public.ft1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" ORDER BY c6::text DESC NULLS FIRST, "C 1" ASC NULLS LAST LIMIT 10::bigint OFFSET 15::bigint + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" ORDER BY c6::text DESC NULLS FIRST, "C 1" ASC NULLS LAST LIMIT 10::bigint OFFSET 15::bigint + Limit + Output: "C 1", c2, c3, c4, c5, c6, c7, c8, ((c6)::text) + -> Sort + Output: "C 1", c2, c3, c4, c5, c6, c7, c8, ((c6)::text) + Sort Key: "T 1".c6 DESC, "T 1"."C 1" + -> Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8, (c6)::text + +(15 rows) + +SELECT * FROM ft1 ORDER BY c6 DESC NULLS FIRST, c1 OFFSET 15 LIMIT 10; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +------+-----+-----------------+------------------------------+--------------------------+----+------------+----- + 1020 | 100 | 0002000020 | | | | ft2 | + 1101 | 201 | aaa | | | | ft2 | + 1103 | 503 | ccc_update3 | | | | ft2 | + 1104 | 204 | ddd | | | | ft2 | + 1208 | 818 | fff_trig_update | | | | ft2 | + 9 | 509 | 00009_update9 | Sat Jan 10 00:00:00 1970 PST | Sat Jan 10 00:00:00 1970 | 9 | ft2 | foo + 19 | 509 | 00019_update9 | Tue Jan 20 00:00:00 1970 PST | Tue Jan 20 00:00:00 1970 | 9 | ft2 | foo + 29 | 509 | 00029_update9 | Fri Jan 30 00:00:00 1970 PST | Fri Jan 30 00:00:00 1970 | 9 | ft2 | foo + 39 | 509 | 00039_update9 | Mon Feb 09 00:00:00 1970 PST | Mon Feb 09 00:00:00 1970 | 9 | ft2 | foo + 49 | 509 | 00049_update9 | Thu Feb 19 00:00:00 1970 PST | Thu Feb 19 00:00:00 1970 | 9 | ft2 | foo +(10 rows) + +-- ORDER BY ASC NULLS FIRST options +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 ORDER BY c6 ASC NULLS FIRST, c1 OFFSET 15 LIMIT 10; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" ORDER BY c6::text ASC NULLS FIRST, "C 1" ASC NULLS LAST LIMIT 10::bigint OFFSET 15::bigint + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" ORDER BY c6::text ASC NULLS FIRST, "C 1" ASC NULLS LAST LIMIT 10::bigint OFFSET 15::bigint + Limit + Output: "C 1", c2, c3, c4, c5, c6, c7, c8, ((c6)::text) + -> Sort + Output: "C 1", c2, c3, c4, c5, c6, c7, c8, ((c6)::text) + Sort Key: "T 1".c6 NULLS FIRST, "T 1"."C 1" + -> Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8, (c6)::text + +(15 rows) + +SELECT * FROM ft1 ORDER BY c6 ASC NULLS FIRST, c1 OFFSET 15 LIMIT 10; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +------+-----+-------------------+------------------------------+--------------------------+------+------------+----- + 1020 | 100 | 0002000020 | | | | ft2 | + 1101 | 201 | aaa | | | | ft2 | + 1103 | 503 | ccc_update3 | | | | ft2 | + 1104 | 204 | ddd | | | | ft2 | + 1208 | 818 | fff_trig_update | | | | ft2 | + 1218 | 818 | ggg_trig_update | | | (--; | ft2 | + 10 | 42 | 00010_trig_update | Sun Jan 11 00:00:00 1970 PST | Sun Jan 11 00:00:00 1970 | 0 | 0 | foo + 20 | 42 | 00020_trig_update | Wed Jan 21 00:00:00 1970 PST | Wed Jan 21 00:00:00 1970 | 0 | 0 | foo + 30 | 42 | 00030_trig_update | Sat Jan 31 00:00:00 1970 PST | Sat Jan 31 00:00:00 1970 | 0 | 0 | foo + 40 | 42 | 00040_trig_update | Tue Feb 10 00:00:00 1970 PST | Tue Feb 10 00:00:00 1970 | 0 | 0 | foo +(10 rows) + +-- ====================================================================================================================================== +-- TEST-MODULE: check constraints +-- -------------------------------------- +-- openGauss not support to "ALTER FOREIGN TABLE ft1 ADD CONSTRAINT", so this test module +-- is unuseful. +-- ====================================================================================================================================== +-- Consistent check constraints provide consistent results +ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c2positive CHECK (c2 >= 0); +ERROR: "ft1" is not a table +EXPLAIN (VERBOSE, COSTS OFF) SELECT count(*) FROM ft1 WHERE c2 < 0; + QUERY PLAN +---------------------------------------------------------------------------------------- + Aggregate + Output: count(*) + -> Foreign Scan on public.ft1 + Node ID: 1 + Remote SQL: SELECT NULL FROM "S 1"."T 1" WHERE ((c2 < 0)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT NULL FROM "S 1"."T 1" WHERE ((c2 < 0)) + Seq Scan on "S 1"."T 1" + Output: NULL::text + Filter: ("T 1".c2 < 0) + +(12 rows) + +SELECT count(*) FROM ft1 WHERE c2 < 0; + count +------- + 0 +(1 row) + +SET constraint_exclusion = 'on'; +EXPLAIN (VERBOSE, COSTS OFF) SELECT count(*) FROM ft1 WHERE c2 < 0; + QUERY PLAN +---------------------------------------------------------------------------------------- + Aggregate + Output: count(*) + -> Foreign Scan on public.ft1 + Node ID: 1 + Remote SQL: SELECT NULL FROM "S 1"."T 1" WHERE ((c2 < 0)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT NULL FROM "S 1"."T 1" WHERE ((c2 < 0)) + Seq Scan on "S 1"."T 1" + Output: NULL::text + Filter: ("T 1".c2 < 0) + +(12 rows) + +SELECT count(*) FROM ft1 WHERE c2 < 0; + count +------- + 0 +(1 row) + +RESET constraint_exclusion; +-- check constraint is enforced on the remote side, not locally +INSERT INTO ft1(c1, c2) VALUES(1111, -2); -- c2positive +ERROR: new row for relation "T 1" violates check constraint "c2positive" +DETAIL: N/A +CONTEXT: Remote SQL command: INSERT INTO "S 1"."T 1"("C 1", c2, c3, c4, c5, c6, c7, c8) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) +UPDATE ft1 SET c2 = -c2 WHERE c1 = 1; -- c2positive +ERROR: new row for relation "T 1" violates check constraint "c2positive" +DETAIL: N/A +CONTEXT: Remote SQL command: UPDATE "S 1"."T 1" SET c2 = $3 WHERE ctid = $1 and tableoid = $2 +ALTER FOREIGN TABLE ft1 DROP CONSTRAINT ft1_c2positive; +ERROR: constraint "ft1_c2positive" of relation "ft1" does not exist +-- But inconsistent check constraints provide inconsistent results +ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c2negative CHECK (c2 < 0); +ERROR: "ft1" is not a table +EXPLAIN (VERBOSE, COSTS OFF) SELECT count(*) FROM ft1 WHERE c2 >= 0; + QUERY PLAN +--------------------------------------------------------------------------------------------- + Foreign Scan + Output: (count(*)) + Node ID: 1 + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT count(*) FROM "S 1"."T 1" WHERE ((c2 >= 0)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT count(*) FROM "S 1"."T 1" WHERE ((c2 >= 0)) + Aggregate + Output: count(*) + -> Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Filter: ("T 1".c2 >= 0) + +(14 rows) + +SELECT count(*) FROM ft1 WHERE c2 >= 0; + count +------- + 821 +(1 row) + +SET constraint_exclusion = 'on'; +EXPLAIN (VERBOSE, COSTS OFF) SELECT count(*) FROM ft1 WHERE c2 >= 0; + QUERY PLAN +--------------------------------------------------------------------------------------------- + Foreign Scan + Output: (count(*)) + Node ID: 1 + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT count(*) FROM "S 1"."T 1" WHERE ((c2 >= 0)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT count(*) FROM "S 1"."T 1" WHERE ((c2 >= 0)) + Aggregate + Output: count(*) + -> Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Filter: ("T 1".c2 >= 0) + +(14 rows) + +SELECT count(*) FROM ft1 WHERE c2 >= 0; + count +------- + 821 +(1 row) + +RESET constraint_exclusion; +-- local check constraint is not actually enforced +INSERT INTO ft1(c1, c2) VALUES(1111, 2); +UPDATE ft1 SET c2 = c2 + 1 WHERE c1 = 1; +ALTER FOREIGN TABLE ft1 DROP CONSTRAINT ft1_c2negative; +ERROR: constraint "ft1_c2negative" of relation "ft1" does not exist +-- ====================================================================================================================================== +-- TEST-MODULE: WITH CHECK OPTION constraints +-- -------------------------------------- +-- openGauss not support WITH CHECK OPTION, so this test module is unuseful and cannot be fixed, remove it. +-- ====================================================================================================================================== +CREATE FUNCTION row_before_insupd_trigfunc() RETURNS trigger AS $$BEGIN NEW.a := NEW.a + 10; RETURN NEW; END$$ LANGUAGE plpgsql; +CREATE TABLE base_tbl (a int, b int); +ALTER TABLE base_tbl SET (autovacuum_enabled = 'false'); +CREATE TRIGGER row_before_insupd_trigger BEFORE INSERT OR UPDATE ON base_tbl FOR EACH ROW EXECUTE PROCEDURE row_before_insupd_trigfunc(); +CREATE FOREIGN TABLE foreign_tbl (a int, b int) + SERVER loopback OPTIONS (table_name 'base_tbl'); +-- CREATE VIEW rw_view AS SELECT * FROM foreign_tbl +-- WHERE a < b WITH CHECK OPTION; +-- \d+ rw_view +DROP FOREIGN TABLE foreign_tbl CASCADE; +DROP TRIGGER row_before_insupd_trigger ON base_tbl; +DROP TABLE base_tbl; +-- ====================================================================================================================================== +-- TEST-MODULE: test serial columns (ie, sequence-based defaults) +-- -------------------------------------- +-- ====================================================================================================================================== create table loc1 (f1 serial, f2 text); +NOTICE: CREATE TABLE will create implicit sequence "loc1_f1_seq" for serial column "loc1.f1" +alter table loc1 set (autovacuum_enabled = 'false'); create foreign table rem1 (f1 serial, f2 text) server loopback options(table_name 'loc1'); +NOTICE: CREATE FOREIGN TABLE will create implicit sequence "rem1_f1_seq" for serial column "rem1.f1" select pg_catalog.setval('rem1_f1_seq', 10, false); setval -------- @@ -2859,157 +8435,404 @@ select * from rem1; 11 | bye remote (4 rows) --- =================================================================== --- test local triggers --- =================================================================== +-- ====================================================================================================================================== +-- TEST-MODULE: test generated columns +-- -------------------------------------- +-- CURRENTLY NOT SUPPORT +-- ====================================================================================================================================== +create table gloc1 ( + a int, + b int generated always as (a * 2) stored); +alter table gloc1 set (autovacuum_enabled = 'false'); +create foreign table grem1 ( + a int, + b int generated always as (a * 2) stored) + server loopback options(table_name 'gloc1'); +explain (verbose, costs off) +insert into grem1 (a) values (1), (2); --nspt + QUERY PLAN +--------------------------------------------------- + Insert on public.grem1 + -> Values Scan on "*VALUES*" + Output: "*VALUES*".column1, NULL::integer +(3 rows) + +insert into grem1 (a) values (1), (2); --nspt +ERROR: cannot insert into column "b" +DETAIL: Column "b" is a generated column. +CONTEXT: Remote SQL command: INSERT INTO public.gloc1(a, b) VALUES ($1, $2) +explain (verbose, costs off) +update grem1 set a = 22 where a = 2; + QUERY PLAN +---------------------------------------------------------------------------------------------------------------- + Update on public.grem1 + -> Foreign Scan on public.grem1 + Output: 22, b, ctid, tableoid + Node ID: 1 + Remote SQL: SELECT b, ctid, tableoid FROM public.gloc1 WHERE ((a = 2)) FOR UPDATE + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT b, ctid, tableoid FROM public.gloc1 WHERE ((a = 2)) FOR UPDATE + LockRows + Output: b, ctid, tableoid, ctid + -> Seq Scan on public.gloc1 + Output: b, ctid, tableoid, ctid + Filter: (gloc1.a = 2) + +(14 rows) + +update grem1 set a = 22 where a = 2; +select * from gloc1; + a | b +---+--- +(0 rows) + +select * from grem1; + a | b +---+--- +(0 rows) + +delete from grem1; +-- test copy from +copy grem1 from stdin; +ERROR: cannot insert into column "b" +DETAIL: Column "b" is a generated column. +CONTEXT: Remote SQL command: INSERT INTO public.gloc1(a, b) VALUES ($1, $2) +COPY grem1, line 1: "1" +select * from gloc1; + a | b +---+--- +(0 rows) + +select * from grem1; + a | b +---+--- +(0 rows) + +delete from grem1; +-- test batch insert +alter server loopback options (add batch_size '10'); +ERROR: invalid option "batch_size" +HINT: Valid options in this context are: service, connect_timeout, dbname, host, remote_nodename, hostaddr, port, localhost, localport, application_name, fencedUdfRPCMode, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, rw_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, krbsrvname, prototype, connection_info, connectionExtraInfo, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, updatable +explain (verbose, costs off) +insert into grem1 (a) values (1), (2); + QUERY PLAN +--------------------------------------------------- + Insert on public.grem1 + -> Values Scan on "*VALUES*" + Output: "*VALUES*".column1, NULL::integer +(3 rows) + +insert into grem1 (a) values (1), (2); +ERROR: cannot insert into column "b" +DETAIL: Column "b" is a generated column. +CONTEXT: Remote SQL command: INSERT INTO public.gloc1(a, b) VALUES ($1, $2) +select * from gloc1; + a | b +---+--- +(0 rows) + +select * from grem1; + a | b +---+--- +(0 rows) + +delete from grem1; +alter server loopback options (drop batch_size); +ERROR: option "batch_size" not found +-- ====================================================================================================================================== +-- TEST-MODULE: test local triggers +-- -------------------------------------- +-- openGauss not support create trigger on foreign table, so this module is unuseful. +-- we adapt the test to create trigger on base table of foreign table. +-- ====================================================================================================================================== +create table tglog(id serial, context text); +NOTICE: CREATE TABLE will create implicit sequence "tglog_id_seq" for serial column "tglog.id" +create table previd(a int); +insert into previd values(0); +create or replace function showtrigger(id out int, context out text) returns setof record LANGUAGE plpgsql as +$$ +DECLARE + r RECORD; + prev int; +BEGIN + select max(a) from previd into prev; + update previd set a = (select max(id) from tglog); + + FOR r IN SELECT * FROM tglog where id > prev ORDER BY id + LOOP + id := r.id; + context := r.context; + return next; + END LOOP; +END;$$; + -- Trigger functions "borrowed" from triggers regress test. CREATE FUNCTION trigger_func() RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN - RAISE NOTICE 'trigger_func(%) called: action = %, when = %, level = %', - TG_ARGV[0], TG_OP, TG_WHEN, TG_LEVEL; - RETURN NULL; + insert into tglog(context) values( + format('trigger_func(%s) called: action = %s, when = %s, level = %s', + TG_ARGV[0], TG_OP, TG_WHEN, TG_LEVEL)); + RETURN NULL; END;$$; +-- error, openGauss not support create trigger on foreign table CREATE TRIGGER trig_stmt_before BEFORE DELETE OR INSERT OR UPDATE ON rem1 - FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func(); + FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func(); +ERROR: "rem1" is not a table or view CREATE TRIGGER trig_stmt_after AFTER DELETE OR INSERT OR UPDATE ON rem1 - FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func(); + FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func(); +ERROR: "rem1" is not a table or view +-- success +CREATE TRIGGER trig_stmt_before BEFORE DELETE OR INSERT OR UPDATE ON loc1 + FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func(); +CREATE TRIGGER trig_stmt_after AFTER DELETE OR INSERT OR UPDATE ON loc1 + FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func(); CREATE OR REPLACE FUNCTION trigger_data() RETURNS trigger LANGUAGE plpgsql AS $$ declare - oldnew text[]; - relid text; + oldnew text[]; + relid text; argstr text; begin - relid := TG_relid::regclass; - argstr := ''; - for i in 0 .. TG_nargs - 1 loop - if i > 0 then - argstr := argstr || ', '; - end if; - argstr := argstr || TG_argv[i]; - end loop; + relid := TG_relid::regclass; + argstr := ''; + for i in 0 .. TG_nargs - 1 loop + if i > 0 then + argstr := argstr || ', '; + end if; + argstr := argstr || TG_argv[i]; + end loop; - RAISE NOTICE '%(%) % % % ON %', - tg_name, argstr, TG_when, TG_level, TG_OP, relid; + insert into tglog(context) values( + format('%s(%s) %s %s %s ON %s', + tg_name, argstr, TG_when, TG_level, TG_OP, relid)); oldnew := '{}'::text[]; - if TG_OP != 'INSERT' then - oldnew := array_append(oldnew, format('OLD: %s', OLD)); - end if; + if TG_OP != 'INSERT' then + oldnew := array_append(oldnew, format('OLD: %s', OLD)); + end if; - if TG_OP != 'DELETE' then - oldnew := array_append(oldnew, format('NEW: %s', NEW)); - end if; + if TG_OP != 'DELETE' then + oldnew := array_append(oldnew, format('NEW: %s', NEW)); + end if; - RAISE NOTICE '%', array_to_string(oldnew, ','); + insert into tglog(context) values( + format('%s', + array_to_string(oldnew, ','))); - if TG_OP = 'DELETE' then - return OLD; - else - return NEW; - end if; + if TG_OP = 'DELETE' then + return OLD; + else + return NEW; + end if; end; $$; -- Test basic functionality CREATE TRIGGER trig_row_before -BEFORE INSERT OR UPDATE OR DELETE ON rem1 +BEFORE INSERT OR UPDATE OR DELETE ON loc1 FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); CREATE TRIGGER trig_row_after -AFTER INSERT OR UPDATE OR DELETE ON rem1 +AFTER INSERT OR UPDATE OR DELETE ON loc1 FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); delete from rem1; -NOTICE: trigger_func() called: action = DELETE, when = BEFORE, level = STATEMENT -NOTICE: trig_row_before(23, skidoo) BEFORE ROW DELETE ON rem1 -NOTICE: OLD: (1,hi) -NOTICE: trig_row_before(23, skidoo) BEFORE ROW DELETE ON rem1 -NOTICE: OLD: (10,"hi remote") -NOTICE: trig_row_before(23, skidoo) BEFORE ROW DELETE ON rem1 -NOTICE: OLD: (2,bye) -NOTICE: trig_row_before(23, skidoo) BEFORE ROW DELETE ON rem1 -NOTICE: OLD: (11,"bye remote") -NOTICE: trig_row_after(23, skidoo) AFTER ROW DELETE ON rem1 -NOTICE: OLD: (1,hi) -NOTICE: trig_row_after(23, skidoo) AFTER ROW DELETE ON rem1 -NOTICE: OLD: (10,"hi remote") -NOTICE: trig_row_after(23, skidoo) AFTER ROW DELETE ON rem1 -NOTICE: OLD: (2,bye) -NOTICE: trig_row_after(23, skidoo) AFTER ROW DELETE ON rem1 -NOTICE: OLD: (11,"bye remote") -NOTICE: trigger_func() called: action = DELETE, when = AFTER, level = STATEMENT +select * from showtrigger(); + id | context +----+-------------------------------------------------------------------------- + 1 | trigger_func() called: action = DELETE, when = BEFORE, level = STATEMENT + 2 | trig_row_before(23, skidoo) BEFORE ROW DELETE ON loc1 + 3 | OLD: (1,hi) + 4 | trig_row_after(23, skidoo) AFTER ROW DELETE ON loc1 + 5 | OLD: (1,hi) + 6 | trigger_func() called: action = DELETE, when = AFTER, level = STATEMENT + 7 | trigger_func() called: action = DELETE, when = BEFORE, level = STATEMENT + 8 | trig_row_before(23, skidoo) BEFORE ROW DELETE ON loc1 + 9 | OLD: (10,"hi remote") + 10 | trig_row_after(23, skidoo) AFTER ROW DELETE ON loc1 + 11 | OLD: (10,"hi remote") + 12 | trigger_func() called: action = DELETE, when = AFTER, level = STATEMENT + 13 | trigger_func() called: action = DELETE, when = BEFORE, level = STATEMENT + 14 | trig_row_before(23, skidoo) BEFORE ROW DELETE ON loc1 + 15 | OLD: (2,bye) + 16 | trig_row_after(23, skidoo) AFTER ROW DELETE ON loc1 + 17 | OLD: (2,bye) + 18 | trigger_func() called: action = DELETE, when = AFTER, level = STATEMENT + 19 | trigger_func() called: action = DELETE, when = BEFORE, level = STATEMENT + 20 | trig_row_before(23, skidoo) BEFORE ROW DELETE ON loc1 + 21 | OLD: (11,"bye remote") + 22 | trig_row_after(23, skidoo) AFTER ROW DELETE ON loc1 + 23 | OLD: (11,"bye remote") + 24 | trigger_func() called: action = DELETE, when = AFTER, level = STATEMENT +(24 rows) + insert into rem1 values(1,'insert'); -NOTICE: trigger_func() called: action = INSERT, when = BEFORE, level = STATEMENT -NOTICE: trig_row_before(23, skidoo) BEFORE ROW INSERT ON rem1 -NOTICE: NEW: (1,insert) -NOTICE: trig_row_after(23, skidoo) AFTER ROW INSERT ON rem1 -NOTICE: NEW: (1,insert) -NOTICE: trigger_func() called: action = INSERT, when = AFTER, level = STATEMENT +select * from showtrigger(); + id | context +----+-------------------------------------------------------------------------- + 25 | trigger_func() called: action = INSERT, when = BEFORE, level = STATEMENT + 26 | trig_row_before(23, skidoo) BEFORE ROW INSERT ON loc1 + 27 | NEW: (1,insert) + 28 | trig_row_after(23, skidoo) AFTER ROW INSERT ON loc1 + 29 | NEW: (1,insert) + 30 | trigger_func() called: action = INSERT, when = AFTER, level = STATEMENT +(6 rows) + update rem1 set f2 = 'update' where f1 = 1; -NOTICE: trigger_func() called: action = UPDATE, when = BEFORE, level = STATEMENT -NOTICE: trig_row_before(23, skidoo) BEFORE ROW UPDATE ON rem1 -NOTICE: OLD: (1,insert),NEW: (1,update) -NOTICE: trig_row_after(23, skidoo) AFTER ROW UPDATE ON rem1 -NOTICE: OLD: (1,insert),NEW: (1,update) -NOTICE: trigger_func() called: action = UPDATE, when = AFTER, level = STATEMENT +select * from showtrigger(); + id | context +----+-------------------------------------------------------------------------- + 31 | trigger_func() called: action = UPDATE, when = BEFORE, level = STATEMENT + 32 | trig_row_before(23, skidoo) BEFORE ROW UPDATE ON loc1 + 33 | OLD: (1,insert),NEW: (1,update) + 34 | trig_row_after(23, skidoo) AFTER ROW UPDATE ON loc1 + 35 | OLD: (1,insert),NEW: (1,update) + 36 | trigger_func() called: action = UPDATE, when = AFTER, level = STATEMENT +(6 rows) + update rem1 set f2 = f2 || f2; -NOTICE: trigger_func() called: action = UPDATE, when = BEFORE, level = STATEMENT -NOTICE: trig_row_before(23, skidoo) BEFORE ROW UPDATE ON rem1 -NOTICE: OLD: (1,update),NEW: (1,updateupdate) -NOTICE: trig_row_after(23, skidoo) AFTER ROW UPDATE ON rem1 -NOTICE: OLD: (1,update),NEW: (1,updateupdate) -NOTICE: trigger_func() called: action = UPDATE, when = AFTER, level = STATEMENT +select * from showtrigger(); + id | context +----+-------------------------------------------------------------------------- + 37 | trigger_func() called: action = UPDATE, when = BEFORE, level = STATEMENT + 38 | trig_row_before(23, skidoo) BEFORE ROW UPDATE ON loc1 + 39 | OLD: (1,update),NEW: (1,updateupdate) + 40 | trig_row_after(23, skidoo) AFTER ROW UPDATE ON loc1 + 41 | OLD: (1,update),NEW: (1,updateupdate) + 42 | trigger_func() called: action = UPDATE, when = AFTER, level = STATEMENT +(6 rows) + -- cleanup -DROP TRIGGER trig_row_before ON rem1; -DROP TRIGGER trig_row_after ON rem1; -DROP TRIGGER trig_stmt_before ON rem1; -DROP TRIGGER trig_stmt_after ON rem1; +DROP TRIGGER trig_row_before ON loc1; +DROP TRIGGER trig_row_after ON loc1; +DROP TRIGGER trig_stmt_before ON loc1; +DROP TRIGGER trig_stmt_after ON loc1; DELETE from rem1; +select * from showtrigger(); + id | context +----+--------- +(0 rows) + +-- Test multiple AFTER ROW triggers on a foreign table +CREATE TRIGGER trig_row_after1 +AFTER INSERT OR UPDATE OR DELETE ON loc1 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); +CREATE TRIGGER trig_row_after2 +AFTER INSERT OR UPDATE OR DELETE ON loc1 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); +insert into rem1 values(1,'insert'); +select * from showtrigger(); + id | context +----+------------------------------------------------------ + 43 | trig_row_after1(23, skidoo) AFTER ROW INSERT ON loc1 + 44 | NEW: (1,insert) + 45 | trig_row_after2(23, skidoo) AFTER ROW INSERT ON loc1 + 46 | NEW: (1,insert) +(4 rows) + +update rem1 set f2 = 'update' where f1 = 1; +select * from showtrigger(); + id | context +----+------------------------------------------------------ + 47 | trig_row_after1(23, skidoo) AFTER ROW UPDATE ON loc1 + 48 | OLD: (1,insert),NEW: (1,update) + 49 | trig_row_after2(23, skidoo) AFTER ROW UPDATE ON loc1 + 50 | OLD: (1,insert),NEW: (1,update) +(4 rows) + +update rem1 set f2 = f2 || f2; +select * from showtrigger(); + id | context +----+------------------------------------------------------ + 51 | trig_row_after1(23, skidoo) AFTER ROW UPDATE ON loc1 + 52 | OLD: (1,update),NEW: (1,updateupdate) + 53 | trig_row_after2(23, skidoo) AFTER ROW UPDATE ON loc1 + 54 | OLD: (1,update),NEW: (1,updateupdate) +(4 rows) + +delete from rem1; +select * from showtrigger(); + id | context +----+------------------------------------------------------ + 55 | trig_row_after1(23, skidoo) AFTER ROW DELETE ON loc1 + 56 | OLD: (1,updateupdate) + 57 | trig_row_after2(23, skidoo) AFTER ROW DELETE ON loc1 + 58 | OLD: (1,updateupdate) +(4 rows) + +-- cleanup +DROP TRIGGER trig_row_after1 ON loc1; +DROP TRIGGER trig_row_after2 ON loc1; -- Test WHEN conditions CREATE TRIGGER trig_row_before_insupd -BEFORE INSERT OR UPDATE ON rem1 +BEFORE INSERT OR UPDATE ON loc1 FOR EACH ROW WHEN (NEW.f2 like '%update%') EXECUTE PROCEDURE trigger_data(23,'skidoo'); CREATE TRIGGER trig_row_after_insupd -AFTER INSERT OR UPDATE ON rem1 +AFTER INSERT OR UPDATE ON loc1 FOR EACH ROW WHEN (NEW.f2 like '%update%') EXECUTE PROCEDURE trigger_data(23,'skidoo'); -- Insert or update not matching: nothing happens INSERT INTO rem1 values(1, 'insert'); +select * from showtrigger(); + id | context +----+--------- +(0 rows) + UPDATE rem1 set f2 = 'test'; +select * from showtrigger(); + id | context +----+--------- +(0 rows) + -- Insert or update matching: triggers are fired INSERT INTO rem1 values(2, 'update'); -NOTICE: trig_row_before_insupd(23, skidoo) BEFORE ROW INSERT ON rem1 -NOTICE: NEW: (2,update) -NOTICE: trig_row_after_insupd(23, skidoo) AFTER ROW INSERT ON rem1 -NOTICE: NEW: (2,update) +select * from showtrigger(); + id | context +----+-------------------------------------------------------------- + 59 | trig_row_before_insupd(23, skidoo) BEFORE ROW INSERT ON loc1 + 60 | NEW: (2,update) + 61 | trig_row_after_insupd(23, skidoo) AFTER ROW INSERT ON loc1 + 62 | NEW: (2,update) +(4 rows) + UPDATE rem1 set f2 = 'update update' where f1 = '2'; -NOTICE: trig_row_before_insupd(23, skidoo) BEFORE ROW UPDATE ON rem1 -NOTICE: OLD: (2,update),NEW: (2,"update update") -NOTICE: trig_row_after_insupd(23, skidoo) AFTER ROW UPDATE ON rem1 -NOTICE: OLD: (2,update),NEW: (2,"update update") +select * from showtrigger(); + id | context +----+-------------------------------------------------------------- + 63 | trig_row_before_insupd(23, skidoo) BEFORE ROW UPDATE ON loc1 + 64 | OLD: (2,update),NEW: (2,"update update") + 65 | trig_row_after_insupd(23, skidoo) AFTER ROW UPDATE ON loc1 + 66 | OLD: (2,update),NEW: (2,"update update") +(4 rows) + CREATE TRIGGER trig_row_before_delete -BEFORE DELETE ON rem1 +BEFORE DELETE ON loc1 FOR EACH ROW WHEN (OLD.f2 like '%update%') EXECUTE PROCEDURE trigger_data(23,'skidoo'); CREATE TRIGGER trig_row_after_delete -AFTER DELETE ON rem1 +AFTER DELETE ON loc1 FOR EACH ROW WHEN (OLD.f2 like '%update%') EXECUTE PROCEDURE trigger_data(23,'skidoo'); -- Trigger is fired for f1=2, not for f1=1 DELETE FROM rem1; -NOTICE: trig_row_before_delete(23, skidoo) BEFORE ROW DELETE ON rem1 -NOTICE: OLD: (2,"update update") -NOTICE: trig_row_after_delete(23, skidoo) AFTER ROW DELETE ON rem1 -NOTICE: OLD: (2,"update update") +select * from showtrigger(); + id | context +----+-------------------------------------------------------------- + 67 | trig_row_before_delete(23, skidoo) BEFORE ROW DELETE ON loc1 + 68 | OLD: (2,"update update") + 69 | trig_row_after_delete(23, skidoo) AFTER ROW DELETE ON loc1 + 70 | OLD: (2,"update update") +(4 rows) + -- cleanup -DROP TRIGGER trig_row_before_insupd ON rem1; -DROP TRIGGER trig_row_after_insupd ON rem1; -DROP TRIGGER trig_row_before_delete ON rem1; -DROP TRIGGER trig_row_after_delete ON rem1; +DROP TRIGGER trig_row_before_insupd ON loc1; +DROP TRIGGER trig_row_after_insupd ON loc1; +DROP TRIGGER trig_row_before_delete ON loc1; +DROP TRIGGER trig_row_after_delete ON loc1; -- Test various RETURN statements in BEFORE triggers. CREATE FUNCTION trig_row_before_insupdate() RETURNS TRIGGER AS $$ BEGIN @@ -3018,10 +8841,15 @@ CREATE FUNCTION trig_row_before_insupdate() RETURNS TRIGGER AS $$ END $$ language plpgsql; CREATE TRIGGER trig_row_before_insupd -BEFORE INSERT OR UPDATE ON rem1 +BEFORE INSERT OR UPDATE ON loc1 FOR EACH ROW EXECUTE PROCEDURE trig_row_before_insupdate(); -- The new values should have 'triggered' appended INSERT INTO rem1 values(1, 'insert'); +select * from showtrigger(); + id | context +----+--------- +(0 rows) + SELECT * from loc1; f1 | f2 ----+-------------------- @@ -3034,6 +8862,11 @@ INSERT INTO rem1 values(2, 'insert') RETURNING f2; insert triggered ! (1 row) +select * from showtrigger(); + id | context +----+--------- +(0 rows) + SELECT * from loc1; f1 | f2 ----+-------------------- @@ -3042,6 +8875,11 @@ SELECT * from loc1; (2 rows) UPDATE rem1 set f2 = ''; +select * from showtrigger(); + id | context +----+--------- +(0 rows) + SELECT * from loc1; f1 | f2 ----+-------------- @@ -3056,6 +8894,11 @@ UPDATE rem1 set f2 = 'skidoo' RETURNING f2; skidoo triggered ! (2 rows) +select * from showtrigger(); + id | context +----+--------- +(0 rows) + SELECT * from loc1; f1 | f2 ----+-------------------- @@ -3065,16 +8908,34 @@ SELECT * from loc1; EXPLAIN (verbose, costs off) UPDATE rem1 set f1 = 10; -- all columns should be transmitted - QUERY PLAN ------------------------------------------------------------------------ + QUERY PLAN +------------------------------------------------------------------------------------------------ Update on public.rem1 - Remote SQL: UPDATE public.loc1 SET f1 = $2, f2 = $3 WHERE ctid = $1 -> Foreign Scan on public.rem1 - Output: 10, f2, ctid, rem1.* - Remote SQL: SELECT f1, f2, ctid FROM public.loc1 FOR UPDATE -(5 rows) + Output: 10, f2, ctid, tableoid + Node ID: 1 + Remote SQL: SELECT f2, ctid, tableoid FROM public.loc1 FOR UPDATE + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT f2, ctid, tableoid FROM public.loc1 FOR UPDATE + LockRows + Output: f2, ctid, tableoid, ctid + -> Seq Scan on public.loc1 + Output: f2, ctid, tableoid, ctid + +(13 rows) + +select * from showtrigger(); + id | context +----+--------- +(0 rows) UPDATE rem1 set f1 = 10; +select * from showtrigger(); + id | context +----+--------- +(0 rows) + SELECT * from loc1; f1 | f2 ----+-------------------------------- @@ -3083,12 +8944,22 @@ SELECT * from loc1; (2 rows) DELETE FROM rem1; +select * from showtrigger(); + id | context +----+--------- +(0 rows) + -- Add a second trigger, to check that the changes are propagated correctly -- from trigger to trigger CREATE TRIGGER trig_row_before_insupd2 -BEFORE INSERT OR UPDATE ON rem1 +BEFORE INSERT OR UPDATE ON loc1 FOR EACH ROW EXECUTE PROCEDURE trig_row_before_insupdate(); INSERT INTO rem1 values(1, 'insert'); +select * from showtrigger(); + id | context +----+--------- +(0 rows) + SELECT * from loc1; f1 | f2 ----+-------------------------------- @@ -3101,6 +8972,11 @@ INSERT INTO rem1 values(2, 'insert') RETURNING f2; insert triggered ! triggered ! (1 row) +select * from showtrigger(); + id | context +----+--------- +(0 rows) + SELECT * from loc1; f1 | f2 ----+-------------------------------- @@ -3109,6 +8985,11 @@ SELECT * from loc1; (2 rows) UPDATE rem1 set f2 = ''; +select * from showtrigger(); + id | context +----+--------- +(0 rows) + SELECT * from loc1; f1 | f2 ----+-------------------------- @@ -3123,6 +9004,11 @@ UPDATE rem1 set f2 = 'skidoo' RETURNING f2; skidoo triggered ! triggered ! (2 rows) +select * from showtrigger(); + id | context +----+--------- +(0 rows) + SELECT * from loc1; f1 | f2 ----+-------------------------------- @@ -3130,10 +9016,20 @@ SELECT * from loc1; 2 | skidoo triggered ! triggered ! (2 rows) -DROP TRIGGER trig_row_before_insupd ON rem1; -DROP TRIGGER trig_row_before_insupd2 ON rem1; +DROP TRIGGER trig_row_before_insupd ON loc1; +DROP TRIGGER trig_row_before_insupd2 ON loc1; DELETE from rem1; +select * from showtrigger(); + id | context +----+--------- +(0 rows) + INSERT INTO rem1 VALUES (1, 'test'); +select * from showtrigger(); + id | context +----+--------- +(0 rows) + -- Test with a trigger returning NULL CREATE FUNCTION trig_null() RETURNS TRIGGER AS $$ BEGIN @@ -3141,10 +9037,15 @@ CREATE FUNCTION trig_null() RETURNS TRIGGER AS $$ END $$ language plpgsql; CREATE TRIGGER trig_null -BEFORE INSERT OR UPDATE OR DELETE ON rem1 +BEFORE INSERT OR UPDATE OR DELETE ON loc1 FOR EACH ROW EXECUTE PROCEDURE trig_null(); -- Nothing should have changed. INSERT INTO rem1 VALUES (2, 'test2'); +select * from showtrigger(); + id | context +----+--------- +(0 rows) + SELECT * from loc1; f1 | f2 ----+------ @@ -3152,6 +9053,11 @@ SELECT * from loc1; (1 row) UPDATE rem1 SET f2 = 'test2'; +select * from showtrigger(); + id | context +----+--------- +(0 rows) + SELECT * from loc1; f1 | f2 ----+------ @@ -3159,41 +9065,1160 @@ SELECT * from loc1; (1 row) DELETE from rem1; +select * from showtrigger(); + id | context +----+--------- +(0 rows) + SELECT * from loc1; f1 | f2 ----+------ 1 | test (1 row) -DROP TRIGGER trig_null ON rem1; +DROP TRIGGER trig_null ON loc1; DELETE from rem1; +select * from showtrigger(); + id | context +----+--------- +(0 rows) + -- Test a combination of local and remote triggers CREATE TRIGGER trig_row_before -BEFORE INSERT OR UPDATE OR DELETE ON rem1 +BEFORE INSERT OR UPDATE OR DELETE ON loc1 FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); CREATE TRIGGER trig_row_after -AFTER INSERT OR UPDATE OR DELETE ON rem1 +AFTER INSERT OR UPDATE OR DELETE ON loc1 FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); CREATE TRIGGER trig_local_before BEFORE INSERT OR UPDATE ON loc1 FOR EACH ROW EXECUTE PROCEDURE trig_row_before_insupdate(); INSERT INTO rem1(f2) VALUES ('test'); -NOTICE: trig_row_before(23, skidoo) BEFORE ROW INSERT ON rem1 -NOTICE: NEW: (12,test) -NOTICE: trig_row_after(23, skidoo) AFTER ROW INSERT ON rem1 -NOTICE: NEW: (12,"test triggered !") +select * from showtrigger(); + id | context +----+------------------------------------------------------- + 71 | trig_row_before(23, skidoo) BEFORE ROW INSERT ON loc1 + 72 | NEW: (12,"test triggered !") + 73 | trig_row_after(23, skidoo) AFTER ROW INSERT ON loc1 + 74 | NEW: (12,"test triggered !") +(4 rows) + UPDATE rem1 SET f2 = 'testo'; -NOTICE: trig_row_before(23, skidoo) BEFORE ROW UPDATE ON rem1 -NOTICE: OLD: (12,"test triggered !"),NEW: (12,testo) -NOTICE: trig_row_after(23, skidoo) AFTER ROW UPDATE ON rem1 -NOTICE: OLD: (12,"test triggered !"),NEW: (12,"testo triggered !") +select * from showtrigger(); + id | context +----+------------------------------------------------------------ + 75 | trig_row_before(23, skidoo) BEFORE ROW UPDATE ON loc1 + 76 | OLD: (12,"test triggered !"),NEW: (12,"testo triggered !") + 77 | trig_row_after(23, skidoo) AFTER ROW UPDATE ON loc1 + 78 | OLD: (12,"test triggered !"),NEW: (12,"testo triggered !") +(4 rows) + -- Test returning a system attribute INSERT INTO rem1(f2) VALUES ('test') RETURNING ctid; -NOTICE: trig_row_before(23, skidoo) BEFORE ROW INSERT ON rem1 -NOTICE: NEW: (13,test) -NOTICE: trig_row_after(23, skidoo) AFTER ROW INSERT ON rem1 -NOTICE: NEW: (13,"test triggered !") - ctid --------- - (0,29) +ERROR: column "ctid" does not exist +LINE 1: INSERT INTO rem1(f2) VALUES ('test') RETURNING ctid; + ^ +CONTEXT: referenced column: ctid +select * from showtrigger(); + id | context +----+--------- +(0 rows) + +-- cleanup +DROP TRIGGER trig_row_before ON loc1; +DROP TRIGGER trig_row_after ON loc1; +DROP TRIGGER trig_local_before ON loc1; +-- Test direct foreign table modification functionality +EXPLAIN (verbose, costs off) +DELETE FROM rem1; -- can be pushed down + QUERY PLAN +-------------------------------------------------------------------------------------------- + Delete on public.rem1 + -> Foreign Scan on public.rem1 + Output: ctid, tableoid + Node ID: 1 + Remote SQL: SELECT ctid, tableoid FROM public.loc1 FOR UPDATE + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT ctid, tableoid FROM public.loc1 FOR UPDATE + LockRows + Output: ctid, tableoid, ctid + -> Seq Scan on public.loc1 + Output: ctid, tableoid, ctid + +(13 rows) + +EXPLAIN (verbose, costs off) +DELETE FROM rem1 WHERE false; -- currently can't be pushed down + QUERY PLAN +-------------------------------------------------------------------------------------------- + Delete on public.rem1 + -> Result + Output: ctid, tableoid + One-Time Filter: false + -> Foreign Scan on public.rem1 + Output: ctid, tableoid + Node ID: 1 + Remote SQL: SELECT ctid, tableoid FROM public.loc1 FOR UPDATE + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT ctid, tableoid FROM public.loc1 FOR UPDATE + LockRows + Output: ctid, tableoid, ctid + -> Seq Scan on public.loc1 + Output: ctid, tableoid, ctid + +(16 rows) + +-- Test with statement-level triggers +CREATE TRIGGER trig_stmt_before + BEFORE DELETE OR INSERT OR UPDATE ON loc1 + FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func(); +EXPLAIN (verbose, costs off) +UPDATE rem1 set f2 = ''; -- can be pushed down + QUERY PLAN +------------------------------------------------------------------------------------------------ + Update on public.rem1 + -> Foreign Scan on public.rem1 + Output: f1, NULL::text, ctid, tableoid + Node ID: 1 + Remote SQL: SELECT f1, ctid, tableoid FROM public.loc1 FOR UPDATE + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT f1, ctid, tableoid FROM public.loc1 FOR UPDATE + LockRows + Output: f1, ctid, tableoid, ctid + -> Seq Scan on public.loc1 + Output: f1, ctid, tableoid, ctid + +(13 rows) + +EXPLAIN (verbose, costs off) +DELETE FROM rem1; -- can be pushed down + QUERY PLAN +-------------------------------------------------------------------------------------------- + Delete on public.rem1 + -> Foreign Scan on public.rem1 + Output: ctid, tableoid + Node ID: 1 + Remote SQL: SELECT ctid, tableoid FROM public.loc1 FOR UPDATE + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT ctid, tableoid FROM public.loc1 FOR UPDATE + LockRows + Output: ctid, tableoid, ctid + -> Seq Scan on public.loc1 + Output: ctid, tableoid, ctid + +(13 rows) + +DROP TRIGGER trig_stmt_before ON loc1; +CREATE TRIGGER trig_stmt_after + AFTER DELETE OR INSERT OR UPDATE ON loc1 + FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func(); +EXPLAIN (verbose, costs off) +UPDATE rem1 set f2 = ''; -- can be pushed down + QUERY PLAN +------------------------------------------------------------------------------------------------ + Update on public.rem1 + -> Foreign Scan on public.rem1 + Output: f1, NULL::text, ctid, tableoid + Node ID: 1 + Remote SQL: SELECT f1, ctid, tableoid FROM public.loc1 FOR UPDATE + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT f1, ctid, tableoid FROM public.loc1 FOR UPDATE + LockRows + Output: f1, ctid, tableoid, ctid + -> Seq Scan on public.loc1 + Output: f1, ctid, tableoid, ctid + +(13 rows) + +EXPLAIN (verbose, costs off) +DELETE FROM rem1; -- can be pushed down + QUERY PLAN +-------------------------------------------------------------------------------------------- + Delete on public.rem1 + -> Foreign Scan on public.rem1 + Output: ctid, tableoid + Node ID: 1 + Remote SQL: SELECT ctid, tableoid FROM public.loc1 FOR UPDATE + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT ctid, tableoid FROM public.loc1 FOR UPDATE + LockRows + Output: ctid, tableoid, ctid + -> Seq Scan on public.loc1 + Output: ctid, tableoid, ctid + +(13 rows) + +DROP TRIGGER trig_stmt_after ON loc1; +-- Test with row-level ON INSERT triggers +CREATE TRIGGER trig_row_before_insert +BEFORE INSERT ON loc1 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); +EXPLAIN (verbose, costs off) +UPDATE rem1 set f2 = ''; -- can be pushed down + QUERY PLAN +------------------------------------------------------------------------------------------------ + Update on public.rem1 + -> Foreign Scan on public.rem1 + Output: f1, NULL::text, ctid, tableoid + Node ID: 1 + Remote SQL: SELECT f1, ctid, tableoid FROM public.loc1 FOR UPDATE + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT f1, ctid, tableoid FROM public.loc1 FOR UPDATE + LockRows + Output: f1, ctid, tableoid, ctid + -> Seq Scan on public.loc1 + Output: f1, ctid, tableoid, ctid + +(13 rows) + +EXPLAIN (verbose, costs off) +DELETE FROM rem1; -- can be pushed down + QUERY PLAN +-------------------------------------------------------------------------------------------- + Delete on public.rem1 + -> Foreign Scan on public.rem1 + Output: ctid, tableoid + Node ID: 1 + Remote SQL: SELECT ctid, tableoid FROM public.loc1 FOR UPDATE + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT ctid, tableoid FROM public.loc1 FOR UPDATE + LockRows + Output: ctid, tableoid, ctid + -> Seq Scan on public.loc1 + Output: ctid, tableoid, ctid + +(13 rows) + +DROP TRIGGER trig_row_before_insert ON loc1; +CREATE TRIGGER trig_row_after_insert +AFTER INSERT ON loc1 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); +EXPLAIN (verbose, costs off) +UPDATE rem1 set f2 = ''; -- can be pushed down + QUERY PLAN +------------------------------------------------------------------------------------------------ + Update on public.rem1 + -> Foreign Scan on public.rem1 + Output: f1, NULL::text, ctid, tableoid + Node ID: 1 + Remote SQL: SELECT f1, ctid, tableoid FROM public.loc1 FOR UPDATE + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT f1, ctid, tableoid FROM public.loc1 FOR UPDATE + LockRows + Output: f1, ctid, tableoid, ctid + -> Seq Scan on public.loc1 + Output: f1, ctid, tableoid, ctid + +(13 rows) + +EXPLAIN (verbose, costs off) +DELETE FROM rem1; -- can be pushed down + QUERY PLAN +-------------------------------------------------------------------------------------------- + Delete on public.rem1 + -> Foreign Scan on public.rem1 + Output: ctid, tableoid + Node ID: 1 + Remote SQL: SELECT ctid, tableoid FROM public.loc1 FOR UPDATE + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT ctid, tableoid FROM public.loc1 FOR UPDATE + LockRows + Output: ctid, tableoid, ctid + -> Seq Scan on public.loc1 + Output: ctid, tableoid, ctid + +(13 rows) + +DROP TRIGGER trig_row_after_insert ON loc1; +-- Test with row-level ON UPDATE triggers +CREATE TRIGGER trig_row_before_update +BEFORE UPDATE ON loc1 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); +EXPLAIN (verbose, costs off) +UPDATE rem1 set f2 = ''; -- can't be pushed down + QUERY PLAN +------------------------------------------------------------------------------------------------ + Update on public.rem1 + -> Foreign Scan on public.rem1 + Output: f1, NULL::text, ctid, tableoid + Node ID: 1 + Remote SQL: SELECT f1, ctid, tableoid FROM public.loc1 FOR UPDATE + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT f1, ctid, tableoid FROM public.loc1 FOR UPDATE + LockRows + Output: f1, ctid, tableoid, ctid + -> Seq Scan on public.loc1 + Output: f1, ctid, tableoid, ctid + +(13 rows) + +EXPLAIN (verbose, costs off) +DELETE FROM rem1; -- can be pushed down + QUERY PLAN +-------------------------------------------------------------------------------------------- + Delete on public.rem1 + -> Foreign Scan on public.rem1 + Output: ctid, tableoid + Node ID: 1 + Remote SQL: SELECT ctid, tableoid FROM public.loc1 FOR UPDATE + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT ctid, tableoid FROM public.loc1 FOR UPDATE + LockRows + Output: ctid, tableoid, ctid + -> Seq Scan on public.loc1 + Output: ctid, tableoid, ctid + +(13 rows) + +DROP TRIGGER trig_row_before_update ON loc1; +CREATE TRIGGER trig_row_after_update +AFTER UPDATE ON loc1 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); +EXPLAIN (verbose, costs off) +UPDATE rem1 set f2 = ''; -- can't be pushed down + QUERY PLAN +------------------------------------------------------------------------------------------------ + Update on public.rem1 + -> Foreign Scan on public.rem1 + Output: f1, NULL::text, ctid, tableoid + Node ID: 1 + Remote SQL: SELECT f1, ctid, tableoid FROM public.loc1 FOR UPDATE + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT f1, ctid, tableoid FROM public.loc1 FOR UPDATE + LockRows + Output: f1, ctid, tableoid, ctid + -> Seq Scan on public.loc1 + Output: f1, ctid, tableoid, ctid + +(13 rows) + +EXPLAIN (verbose, costs off) +DELETE FROM rem1; -- can be pushed down + QUERY PLAN +-------------------------------------------------------------------------------------------- + Delete on public.rem1 + -> Foreign Scan on public.rem1 + Output: ctid, tableoid + Node ID: 1 + Remote SQL: SELECT ctid, tableoid FROM public.loc1 FOR UPDATE + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT ctid, tableoid FROM public.loc1 FOR UPDATE + LockRows + Output: ctid, tableoid, ctid + -> Seq Scan on public.loc1 + Output: ctid, tableoid, ctid + +(13 rows) + +DROP TRIGGER trig_row_after_update ON loc1; +-- Test with row-level ON DELETE triggers +CREATE TRIGGER trig_row_before_delete +BEFORE DELETE ON loc1 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); +EXPLAIN (verbose, costs off) +UPDATE rem1 set f2 = ''; -- can be pushed down + QUERY PLAN +------------------------------------------------------------------------------------------------ + Update on public.rem1 + -> Foreign Scan on public.rem1 + Output: f1, NULL::text, ctid, tableoid + Node ID: 1 + Remote SQL: SELECT f1, ctid, tableoid FROM public.loc1 FOR UPDATE + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT f1, ctid, tableoid FROM public.loc1 FOR UPDATE + LockRows + Output: f1, ctid, tableoid, ctid + -> Seq Scan on public.loc1 + Output: f1, ctid, tableoid, ctid + +(13 rows) + +EXPLAIN (verbose, costs off) +DELETE FROM rem1; -- can't be pushed down + QUERY PLAN +-------------------------------------------------------------------------------------------- + Delete on public.rem1 + -> Foreign Scan on public.rem1 + Output: ctid, tableoid + Node ID: 1 + Remote SQL: SELECT ctid, tableoid FROM public.loc1 FOR UPDATE + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT ctid, tableoid FROM public.loc1 FOR UPDATE + LockRows + Output: ctid, tableoid, ctid + -> Seq Scan on public.loc1 + Output: ctid, tableoid, ctid + +(13 rows) + +DROP TRIGGER trig_row_before_delete ON loc1; +CREATE TRIGGER trig_row_after_delete +AFTER DELETE ON loc1 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); +EXPLAIN (verbose, costs off) +UPDATE rem1 set f2 = ''; -- can be pushed down + QUERY PLAN +------------------------------------------------------------------------------------------------ + Update on public.rem1 + -> Foreign Scan on public.rem1 + Output: f1, NULL::text, ctid, tableoid + Node ID: 1 + Remote SQL: SELECT f1, ctid, tableoid FROM public.loc1 FOR UPDATE + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT f1, ctid, tableoid FROM public.loc1 FOR UPDATE + LockRows + Output: f1, ctid, tableoid, ctid + -> Seq Scan on public.loc1 + Output: f1, ctid, tableoid, ctid + +(13 rows) + +EXPLAIN (verbose, costs off) +DELETE FROM rem1; -- can't be pushed down + QUERY PLAN +-------------------------------------------------------------------------------------------- + Delete on public.rem1 + -> Foreign Scan on public.rem1 + Output: ctid, tableoid + Node ID: 1 + Remote SQL: SELECT ctid, tableoid FROM public.loc1 FOR UPDATE + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT ctid, tableoid FROM public.loc1 FOR UPDATE + LockRows + Output: ctid, tableoid, ctid + -> Seq Scan on public.loc1 + Output: ctid, tableoid, ctid + +(13 rows) + +DROP TRIGGER trig_row_after_delete ON loc1; +-- ====================================================================================================================================== +-- TEST-MODULE: test inheritance features +-- -------------------------------------- +-- openGauss not support this feature. and cannot fix, remove all test case. +-- ====================================================================================================================================== +-- ====================================================================================================================================== +-- TEST-MODULE: test tuple routing for foreign-table partitions +-- -------------------------------------- +-- partition table is different between openGauss and pg, and cannot fix these test cases, remove all. +-- ====================================================================================================================================== +-- ====================================================================================================================================== +-- TEST-MODULE: test COPY FROM +-- -------------------------------------- +-- ====================================================================================================================================== +create table loc2 (f1 int, f2 text); +alter table loc2 set (autovacuum_enabled = 'false'); +create foreign table rem2 (f1 int, f2 text) server loopback options(table_name 'loc2'); +-- Test basic functionality +copy rem2 from stdin; +select * from rem2; + f1 | f2 +----+----- + 1 | foo + 2 | bar +(2 rows) + +delete from rem2; +-- Test check constraints +alter table loc2 add constraint loc2_f1positive check (f1 >= 0); +--nspt alter foreign table rem2 add constraint rem2_f1positive check (f1 >= 0); +-- check constraint is enforced on the remote side, not locally +copy rem2 from stdin; +copy rem2 from stdin; -- ERROR +ERROR: new row for relation "loc2" violates check constraint "loc2_f1positive" +DETAIL: N/A +CONTEXT: Remote SQL command: INSERT INTO public.loc2(f1, f2) VALUES ($1, $2) +COPY rem2, line 1: "-1 xyzzy" +select * from rem2; + f1 | f2 +----+----- + 1 | foo + 2 | bar +(2 rows) + +--nspt alter foreign table rem2 drop constraint rem2_f1positive; +alter table loc2 drop constraint loc2_f1positive; +delete from rem2; +-- Test local triggers +create trigger trig_stmt_before before insert on loc2 + for each statement execute procedure trigger_func(); +create trigger trig_stmt_after after insert on loc2 + for each statement execute procedure trigger_func(); +create trigger trig_row_before before insert on loc2 + for each row execute procedure trigger_data(23,'skidoo'); +create trigger trig_row_after after insert on loc2 + for each row execute procedure trigger_data(23,'skidoo'); +copy rem2 from stdin; +select * from showtrigger(); + id | context +----+-------------------------------------------------------------------------- + 79 | trigger_func() called: action = INSERT, when = BEFORE, level = STATEMENT + 80 | trig_row_before(23, skidoo) BEFORE ROW INSERT ON loc2 + 81 | NEW: (1,foo) + 82 | trig_row_after(23, skidoo) AFTER ROW INSERT ON loc2 + 83 | NEW: (1,foo) + 84 | trigger_func() called: action = INSERT, when = AFTER, level = STATEMENT + 85 | trigger_func() called: action = INSERT, when = BEFORE, level = STATEMENT + 86 | trig_row_before(23, skidoo) BEFORE ROW INSERT ON loc2 + 87 | NEW: (2,bar) + 88 | trig_row_after(23, skidoo) AFTER ROW INSERT ON loc2 + 89 | NEW: (2,bar) + 90 | trigger_func() called: action = INSERT, when = AFTER, level = STATEMENT +(12 rows) + +select * from rem2; + f1 | f2 +----+----- + 1 | foo + 2 | bar +(2 rows) + +drop trigger trig_row_before on loc2; +drop trigger trig_row_after on loc2; +drop trigger trig_stmt_before on loc2; +drop trigger trig_stmt_after on loc2; +delete from rem2; +select * from showtrigger(); + id | context +----+--------- +(0 rows) + +create trigger trig_row_before_insert before insert on loc2 + for each row execute procedure trig_row_before_insupdate(); +-- The new values are concatenated with ' triggered !' +copy rem2 from stdin; +select * from showtrigger(); + id | context +----+--------- +(0 rows) + +select * from rem2; + f1 | f2 +----+----------------- + 1 | foo triggered ! + 2 | bar triggered ! +(2 rows) + +drop trigger trig_row_before_insert on loc2; +delete from rem2; +create trigger trig_null before insert on loc2 + for each row execute procedure trig_null(); +-- Nothing happens +copy rem2 from stdin; +select * from showtrigger(); + id | context +----+--------- +(0 rows) + +select * from rem2; + f1 | f2 +----+---- +(0 rows) + +drop trigger trig_null on loc2; +delete from rem2; +-- Test remote triggers +create trigger trig_row_before_insert before insert on loc2 + for each row execute procedure trig_row_before_insupdate(); +-- The new values are concatenated with ' triggered !' +copy rem2 from stdin; +select * from showtrigger(); + id | context +----+--------- +(0 rows) + +select * from rem2; + f1 | f2 +----+----------------- + 1 | foo triggered ! + 2 | bar triggered ! +(2 rows) + +drop trigger trig_row_before_insert on loc2; +delete from rem2; +create trigger trig_null before insert on loc2 + for each row execute procedure trig_null(); +-- Nothing happens +copy rem2 from stdin; +select * from showtrigger(); + id | context +----+--------- +(0 rows) + +select * from rem2; + f1 | f2 +----+---- +(0 rows) + +drop trigger trig_null on loc2; +delete from rem2; +-- Test a combination of local and remote triggers +create trigger rem2_trig_row_before before insert on loc2 + for each row execute procedure trigger_data(23,'skidoo'); +create trigger rem2_trig_row_after after insert on loc2 + for each row execute procedure trigger_data(23,'skidoo'); +create trigger loc2_trig_row_before_insert before insert on loc2 + for each row execute procedure trig_row_before_insupdate(); +copy rem2 from stdin; +select * from showtrigger(); + id | context +----+------------------------------------------------------------ + 91 | rem2_trig_row_before(23, skidoo) BEFORE ROW INSERT ON loc2 + 92 | NEW: (1,"foo triggered !") + 93 | rem2_trig_row_after(23, skidoo) AFTER ROW INSERT ON loc2 + 94 | NEW: (1,"foo triggered !") + 95 | rem2_trig_row_before(23, skidoo) BEFORE ROW INSERT ON loc2 + 96 | NEW: (2,"bar triggered !") + 97 | rem2_trig_row_after(23, skidoo) AFTER ROW INSERT ON loc2 + 98 | NEW: (2,"bar triggered !") +(8 rows) + +select * from rem2; + f1 | f2 +----+----------------- + 1 | foo triggered ! + 2 | bar triggered ! +(2 rows) + +drop trigger rem2_trig_row_before on loc2; +drop trigger rem2_trig_row_after on loc2; +drop trigger loc2_trig_row_before_insert on loc2; +delete from rem2; +-- test COPY FROM with foreign table created in the same transaction +create table loc3 (f1 int, f2 text); +begin; +create foreign table rem3 (f1 int, f2 text) + server loopback options(table_name 'loc3'); +copy rem3 from stdin; +select * from showtrigger(); + id | context +----+--------- +(0 rows) + +commit; +select * from rem3; + f1 | f2 +----+----- + 1 | foo + 2 | bar +(2 rows) + +drop foreign table rem3; +drop table loc3; +-- ====================================================================================================================================== +-- TEST-MODULE: test for TRUNCATE +-- -------------------------------------- +-- openGauss not support this feature. However, some cases are still left for maintenance. +-- ====================================================================================================================================== +CREATE TABLE tru_rtable0 (id int primary key); +NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "tru_rtable0_pkey" for table "tru_rtable0" +CREATE FOREIGN TABLE tru_ftable (id int) + SERVER loopback OPTIONS (table_name 'tru_rtable0'); +INSERT INTO tru_rtable0 (SELECT x FROM generate_series(1,10) x); +CREATE TABLE tru_ptable (id int) PARTITION BY HASH(id)( + partition tru_ptable__p0, + partition tru_ptable__p1 +); +CREATE FOREIGN TABLE tru_p_ftable (id int) + SERVER loopback OPTIONS (table_name 'tru_ptable'); +INSERT INTO tru_ptable (SELECT x FROM generate_series(11,20) x); +CREATE TABLE tru_pk_table(id int primary key); +NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "tru_pk_table_pkey" for table "tru_pk_table" +CREATE TABLE tru_fk_table(fkey int references tru_pk_table(id)); +INSERT INTO tru_pk_table (SELECT x FROM generate_series(1,10) x); +INSERT INTO tru_fk_table (SELECT x % 10 + 1 FROM generate_series(5,25) x); +CREATE FOREIGN TABLE tru_pk_ftable (id int) + SERVER loopback OPTIONS (table_name 'tru_pk_table'); +-- normal truncate +SELECT sum(id) FROM tru_ftable; -- 55 + sum +----- + 55 (1 row) +TRUNCATE tru_ftable; -- nspt +ERROR: It is not supported to truncate foreign table "tru_ftable". +SELECT count(*) FROM tru_rtable0; -- 10 + count +------- + 10 +(1 row) + +SELECT count(*) FROM tru_ftable; -- 10 + count +------- + 10 +(1 row) + +-- partitioned table with both local and foreign tables as partitions +SELECT sum(id) FROM tru_p_ftable; -- 155 +ERROR: could not operate foreign table on partitioned table +TRUNCATE tru_p_ftable; +ERROR: It is not supported to truncate foreign table "tru_p_ftable". +SELECT count(*) FROM tru_p_ftable; -- 0 +ERROR: could not operate foreign table on partitioned table +SELECT count(*) FROM tru_p_ftable partition(tru_ptable__p0); -- 0 +ERROR: relation "tru_p_ftable" is not partitioned table +DETAIL: N/A. +SELECT count(*) FROM tru_ptable partition(tru_ftable__p1); -- 0 +ERROR: partition "tru_ftable__p1" of relation "tru_ptable" does not exist +SELECT count(*) FROM tru_ptable; -- 0 + count +------- + 10 +(1 row) + +-- 'CASCADE' option +SELECT sum(id) FROM tru_pk_ftable; -- 55 + sum +----- + 55 +(1 row) + +TRUNCATE tru_pk_ftable; -- nspt +ERROR: It is not supported to truncate foreign table "tru_pk_ftable". +TRUNCATE tru_pk_ftable CASCADE; +ERROR: It is not supported to truncate foreign table "tru_pk_ftable". +SELECT count(*) FROM tru_pk_ftable; -- 10 + count +------- + 10 +(1 row) + +SELECT count(*) FROM tru_fk_table; -- 21 + count +------- + 21 +(1 row) + +-- truncate two tables at a command +INSERT INTO tru_ftable (SELECT x FROM generate_series(1,8) x); +ERROR: duplicate key value violates unique constraint "tru_rtable0_pkey" +DETAIL: Key (id)=(1) already exists. +CONTEXT: Remote SQL command: INSERT INTO public.tru_rtable0(id) VALUES ($1) +INSERT INTO tru_pk_ftable (SELECT x FROM generate_series(3,10) x); +ERROR: duplicate key value violates unique constraint "tru_pk_table_pkey" +DETAIL: Key (id)=(3) already exists. +CONTEXT: Remote SQL command: INSERT INTO public.tru_pk_table(id) VALUES ($1) +SELECT count(*) from tru_ftable; -- 8 + count +------- + 10 +(1 row) + +SELECT count(*) from tru_pk_ftable; -- 8 + count +------- + 10 +(1 row) + +TRUNCATE tru_ftable, tru_pk_ftable CASCADE; +ERROR: It is not supported to truncate foreign table "tru_ftable". +SELECT count(*) from tru_ftable; -- 0 + count +------- + 10 +(1 row) + +SELECT count(*) from tru_pk_ftable; -- 0 + count +------- + 10 +(1 row) + +-- cleanup +DROP FOREIGN TABLE tru_pk_ftable,tru_ftable__p1,tru_ftable; +ERROR: foreign table "tru_ftable__p1" does not exist +DROP TABLE tru_rtable0, tru_ptable, tru_pk_table, tru_fk_table; +-- ====================================================================================================================================== +-- TEST-MODULE: test IMPORT FOREIGN SCHEMA +-- -------------------------------------- +-- openGauss not support this feature. However, some cases are still left for maintenance. +-- ====================================================================================================================================== +CREATE SCHEMA import_source; +CREATE TABLE import_source.t1 (c1 int, c2 varchar NOT NULL); +CREATE SCHEMA import_dest1; +IMPORT FOREIGN SCHEMA import_source FROM SERVER loopback INTO import_dest1; +ERROR: syntax error at or near "IMPORT" +LINE 1: IMPORT FOREIGN SCHEMA import_source FROM SERVER loopback INT... + ^ +\det+ import_dest1.* + List of foreign tables + Schema | Table | Server | FDW Options | Description +--------+-------+--------+-------------+------------- +(0 rows) + +\d import_dest1.* +-- ====================================================================================================================================== +-- TEST-MODULE: test partitionwise joins +-- TEST-MODULE: test partitionwise aggregates +-- -------------------------------------- +-- partition table is different between openGauss and pg, and cannot fix these test cases, remove all. +-- ====================================================================================================================================== +-- nspt +-- ====================================================================================================================================== +-- TEST-MODULE: access rights and superuser +-- -------------------------------------- +-- some privileges is different between og and pg +-- ====================================================================================================================================== +-- Non-superuser cannot create a FDW without a password in the connstr +CREATE ROLE regress_nosuper with password 'qwer@1234'; +GRANT USAGE ON FOREIGN DATA WRAPPER postgres_fdw TO regress_nosuper; +grant all on schema public to regress_nosuper; +SET SESSION AUTHORIZATION regress_nosuper PASSWORD 'qwer@1234'; +SHOW is_superuser; --nspt +ERROR: unrecognized configuration parameter "is_superuser" +-- This will be OK, we can create the FDW +DO $d$ + BEGIN + EXECUTE $$CREATE SERVER loopback_nopw FOREIGN DATA WRAPPER postgres_fdw + OPTIONS (dbname '$$||current_database()||$$', + port '$$||current_setting('port')||$$' + )$$; + END; +$d$; +-- But creation of user mappings for non-superusers should fail +CREATE USER MAPPING FOR public SERVER loopback_nopw; +CREATE USER MAPPING FOR CURRENT_USER SERVER loopback_nopw; +CREATE FOREIGN TABLE ft1_nopw ( + c1 int NOT NULL, + c2 int NOT NULL, + c3 text, + c4 timestamptz, + c5 timestamp, + c6 varchar(10), + c7 char(10) default 'ft1', + c8 text +) SERVER loopback_nopw OPTIONS (schema_name 'public', table_name 'ft1'); +SELECT 1 FROM ft1_nopw LIMIT 1; +ERROR: password is required +DETAIL: Non-superusers must provide a password in the user mapping. +-- If we add a password to the connstr it'll fail, because we don't allow passwords +-- in connstrs only in user mappings. +DO $d$ + BEGIN + EXECUTE $$ALTER SERVER loopback_nopw OPTIONS (ADD password 'qwer@1234')$$; + END; +$d$; +ERROR: invalid option "password" +HINT: Valid options in this context are: service, connect_timeout, dbname, host, remote_nodename, hostaddr, port, localhost, localport, application_name, fencedUdfRPCMode, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, rw_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, krbsrvname, prototype, connection_info, connectionExtraInfo, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, updatable +CONTEXT: SQL statement "ALTER SERVER loopback_nopw OPTIONS (ADD password '********')" +PL/pgSQL function inline_code_block line 3 at EXECUTE statement +-- If we add a password for our user mapping instead, we should get a different +-- error because the password wasn't actually *used* when we run with trust auth. +-- +-- This won't work with installcheck, but neither will most of the FDW checks. +ALTER USER MAPPING FOR CURRENT_USER SERVER loopback_nopw OPTIONS (ADD password 'qwer@1234'); +SELECT 1 FROM ft1_nopw LIMIT 1; +ERROR: password is required +DETAIL: Non-superuser cannot connect if the server does not request a password. +HINT: Target server's authentication method must be changed. +-- Unpriv user cannot make the mapping passwordless +ALTER USER MAPPING FOR CURRENT_USER SERVER loopback_nopw OPTIONS (ADD password_required 'false'); +ERROR: invalid option "password_required" +HINT: Valid options in this context are: user, password +SELECT 1 FROM ft1_nopw LIMIT 1; +ERROR: password is required +DETAIL: Non-superuser cannot connect if the server does not request a password. +HINT: Target server's authentication method must be changed. +RESET ROLE; +-- But the superuser can +ALTER USER MAPPING FOR regress_nosuper SERVER loopback_nopw OPTIONS (ADD password_required 'false'); +ERROR: invalid option "password_required" +HINT: Valid options in this context are: user, password +SET ROLE regress_nosuper; +ERROR: Invalid username/password,set role denied. +-- Should finally work now +SELECT 1 FROM ft1_nopw LIMIT 1; +ERROR: password is required +DETAIL: Non-superuser cannot connect if the server does not request a password. +HINT: Target server's authentication method must be changed. +-- unpriv user also cannot set sslcert / sslkey on the user mapping +-- first set password_required so we see the right error messages +ALTER USER MAPPING FOR CURRENT_USER SERVER loopback_nopw OPTIONS (SET password_required 'true'); +ERROR: option "password_required" not found +ALTER USER MAPPING FOR CURRENT_USER SERVER loopback_nopw OPTIONS (ADD sslcert 'foo.crt'); +ERROR: invalid option "sslcert" +HINT: Valid options in this context are: user, password +ALTER USER MAPPING FOR CURRENT_USER SERVER loopback_nopw OPTIONS (ADD sslkey 'foo.key'); +ERROR: invalid option "sslkey" +HINT: Valid options in this context are: user, password +-- We're done with the role named after a specific user and need to check the +-- changes to the public mapping. +DROP USER MAPPING FOR CURRENT_USER SERVER loopback_nopw; +-- This will fail again as it'll resolve the user mapping for public, which +-- lacks password_required=false +SELECT 1 FROM ft1_nopw LIMIT 1; +ERROR: password is required +DETAIL: Non-superusers must provide a password in the user mapping. +\c +-- The user mapping for public is passwordless and lacks the password_required=false +-- mapping option, but will work because the current user is a superuser. +SELECT 1 FROM ft1_nopw LIMIT 1; + ?column? +---------- + 1 +(1 row) + +-- cleanup +DROP USER MAPPING FOR public SERVER loopback_nopw; +DROP OWNED BY regress_nosuper; +DROP ROLE regress_nosuper; +-- Clean-up +RESET enable_partitionwise_aggregate; +ERROR: unrecognized configuration parameter "enable_partitionwise_aggregate" +-- Two-phase transactions are not supported. +BEGIN; +SELECT count(*) FROM ft1; + count +------- + 822 +(1 row) + +-- error here +PREPARE TRANSACTION 'fdw_tpc'; +ERROR: missed cleaning up connection during pre-commit +ROLLBACK; +NOTICE: there is no transaction in progress +-- ====================================================================================================================================== +-- TEST-MODULE: reestablish new connection +-- TEST-MODULE: test connection invalidation cases and postgres_fdw_get_connections function +-- TEST-MODULE: test postgres_fdw_disconnect and postgres_fdw_disconnect_all functions +-- TEST-MODULE: test case for having multiple cached connections for a foreign server +-- TEST-MODULE: Test foreign server level option keep_connections +-- -------------------------------------- +-- openGauss not have the functions following: +-- postgres_fdw_abs +-- postgres_fdw_disconnect +-- postgres_fdw_disconnect_all +-- postgres_fdw_get_connections +-- Therefore, these test modules are temporarily unavailable. +-- ====================================================================================================================================== +\df postgres_fdw_abs + List of functions + Schema | Name | Result data type | Argument data types | Type | fencedmode | propackage | prokind +--------+------------------+------------------+---------------------+--------+------------+------------+--------- + public | postgres_fdw_abs | integer | integer | normal | f | f | f +(1 row) + +\df postgres_fdw_disconnect + List of functions + Schema | Name | Result data type | Argument data types | Type | fencedmode | propackage | prokind +--------+------+------------------+---------------------+------+------------+------------+--------- +(0 rows) + +\df postgres_fdw_disconnect_all + List of functions + Schema | Name | Result data type | Argument data types | Type | fencedmode | propackage | prokind +--------+------+------------------+---------------------+------+------------+------------+--------- +(0 rows) + +\df postgres_fdw_get_connections + List of functions + Schema | Name | Result data type | Argument data types | Type | fencedmode | propackage | prokind +--------+------+------------------+---------------------+------+------------+------------+--------- +(0 rows) + +\df postgres_fdw_handler + List of functions + Schema | Name | Result data type | Argument data types | Type | fencedmode | propackage | prokind +--------+----------------------+------------------+---------------------+--------+------------+------------+--------- + public | postgres_fdw_handler | fdw_handler | | normal | f | f | f +(1 row) + +\df postgres_fdw_validator + List of functions + Schema | Name | Result data type | Argument data types | Type | fencedmode | propackage | prokind +--------+------------------------+------------------+---------------------+--------+------------+------------+--------- + public | postgres_fdw_validator | void | text[], oid | normal | f | f | f +(1 row) + +-- ====================================================================================================================================== +-- TEST-MODULE: batch insert +-- -------------------------------------- +-- openGauss not support this feature, it will run as normal +-- ====================================================================================================================================== +BEGIN; +CREATE SERVER batch10 FOREIGN DATA WRAPPER postgres_fdw OPTIONS( batch_size '10' ); +ERROR: invalid option "batch_size" +HINT: Valid options in this context are: service, connect_timeout, dbname, host, remote_nodename, hostaddr, port, localhost, localport, application_name, fencedUdfRPCMode, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, rw_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, krbsrvname, prototype, connection_info, connectionExtraInfo, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, updatable +SELECT count(*) +FROM pg_foreign_server +WHERE srvname = 'batch10' +AND srvoptions @> array['batch_size=10']; +ERROR: current transaction is aborted, commands ignored until end of transaction block, firstChar[Q] +ALTER SERVER batch10 OPTIONS( SET batch_size '20' ); +ERROR: current transaction is aborted, commands ignored until end of transaction block, firstChar[Q] +SELECT count(*) +FROM pg_foreign_server +WHERE srvname = 'batch10' +AND srvoptions @> array['batch_size=10']; +ERROR: current transaction is aborted, commands ignored until end of transaction block, firstChar[Q] +SELECT count(*) +FROM pg_foreign_server +WHERE srvname = 'batch10' +AND srvoptions @> array['batch_size=20']; +ERROR: current transaction is aborted, commands ignored until end of transaction block, firstChar[Q] +CREATE FOREIGN TABLE table30 ( x int ) SERVER batch10 OPTIONS ( batch_size '30' ); +ERROR: current transaction is aborted, commands ignored until end of transaction block, firstChar[Q] +SELECT COUNT(*) +FROM pg_foreign_table +WHERE ftrelid = 'table30'::regclass +AND ftoptions @> array['batch_size=30']; +ERROR: current transaction is aborted, commands ignored until end of transaction block, firstChar[Q] +ALTER FOREIGN TABLE table30 OPTIONS ( SET batch_size '40'); +ERROR: current transaction is aborted, commands ignored until end of transaction block, firstChar[Q] +SELECT COUNT(*) +FROM pg_foreign_table +WHERE ftrelid = 'table30'::regclass +AND ftoptions @> array['batch_size=30']; +ERROR: current transaction is aborted, commands ignored until end of transaction block, firstChar[Q] +SELECT COUNT(*) +FROM pg_foreign_table +WHERE ftrelid = 'table30'::regclass +AND ftoptions @> array['batch_size=40']; +ERROR: current transaction is aborted, commands ignored until end of transaction block, firstChar[Q] +ROLLBACK; +CREATE TABLE batch_table ( x int ); +CREATE FOREIGN TABLE ftable ( x int ) SERVER loopback OPTIONS ( table_name 'batch_table'/*, batch_size '10' */); +EXPLAIN (VERBOSE, COSTS OFF) INSERT INTO ftable SELECT * FROM generate_series(1, 10) i; + QUERY PLAN +----------------------------------------------------- + Insert on public.ftable + -> Function Scan on pg_catalog.generate_series i + Output: i.i + Function Call: generate_series(1, 10) +(4 rows) + +INSERT INTO ftable SELECT * FROM generate_series(1, 10) i; +INSERT INTO ftable SELECT * FROM generate_series(11, 31) i; +INSERT INTO ftable VALUES (32); +INSERT INTO ftable VALUES (33), (34); +SELECT COUNT(*) FROM ftable; + count +------- + 34 +(1 row) + +TRUNCATE batch_table; +DROP FOREIGN TABLE ftable; +-- try if large batches exceed max number of bind parameters +CREATE FOREIGN TABLE ftable ( x int ) SERVER loopback OPTIONS ( table_name 'batch_table'/*, batch_size '100000'*/ ); +INSERT INTO ftable SELECT * FROM generate_series(1, 70000) i; +SELECT COUNT(*) FROM ftable; + count +------- + 70000 +(1 row) + +TRUNCATE batch_table; +DROP FOREIGN TABLE ftable; +-- Disable batch insert +CREATE FOREIGN TABLE ftable ( x int ) SERVER loopback OPTIONS ( table_name 'batch_table'/*, batch_size '1'*/ ); +EXPLAIN (VERBOSE, COSTS OFF) INSERT INTO ftable VALUES (1), (2); + QUERY PLAN +------------------------------------ + Insert on public.ftable + -> Values Scan on "*VALUES*" + Output: "*VALUES*".column1 +(3 rows) + +INSERT INTO ftable VALUES (1), (2); +SELECT COUNT(*) FROM ftable; + count +------- + 2 +(1 row) + +-- Disable batch inserting into foreign tables with BEFORE ROW INSERT triggers +-- even if the batch_size option is enabled. +ALTER FOREIGN TABLE ftable OPTIONS ( SET batch_size '10' ); +ERROR: option "batch_size" not found +CREATE TRIGGER trig_row_before BEFORE INSERT ON ftable +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); +ERROR: "ftable" is not a table or view +EXPLAIN (VERBOSE, COSTS OFF) INSERT INTO ftable VALUES (3), (4); + QUERY PLAN +------------------------------------ + Insert on public.ftable + -> Values Scan on "*VALUES*" + Output: "*VALUES*".column1 +(3 rows) + +INSERT INTO ftable VALUES (3), (4); +SELECT COUNT(*) FROM ftable; + count +------- + 4 +(1 row) + +-- Clean up +DROP TRIGGER trig_row_before ON ftable; +ERROR: trigger "trig_row_before" for table "ftable" does not exist +DROP FOREIGN TABLE ftable; +DROP TABLE batch_table; +-- Use partitioning +-- nspt remove it all +ALTER SERVER loopback OPTIONS (DROP batch_size); +ERROR: option "batch_size" not found +-- ====================================================================================================================================== +-- TEST-MODULE: test asynchronous execution +-- -------------------------------------- +-- openGauss not support this feature, therefore, some cases that can run properly after modification are retained, +-- and some cases that cannot be supported are directly deleted. +-- If you want to restore the test case later, refer to README in the file header. +-- ====================================================================================================================================== +ALTER SERVER loopback OPTIONS (DROP extensions); --nspt +ERROR: option "extensions" not found +ALTER SERVER loopback OPTIONS (ADD async_capable 'true'); --nspt +ERROR: invalid option "async_capable" +HINT: Valid options in this context are: service, connect_timeout, dbname, host, remote_nodename, hostaddr, port, localhost, localport, application_name, fencedUdfRPCMode, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, rw_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, krbsrvname, prototype, connection_info, connectionExtraInfo, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, updatable +ALTER SERVER loopback2 OPTIONS (ADD async_capable 'true'); --nspt +ERROR: invalid option "async_capable" +HINT: Valid options in this context are: service, connect_timeout, dbname, host, remote_nodename, hostaddr, port, localhost, localport, application_name, fencedUdfRPCMode, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, rw_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, krbsrvname, prototype, connection_info, connectionExtraInfo, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, updatable +-- ====================================================================================================================================== +-- TEST-MODULE: test invalid server and foreign table options +-- -------------------------------------- +-- ====================================================================================================================================== +-- Invalid fdw_startup_cost option +CREATE SERVER inv_scst FOREIGN DATA WRAPPER postgres_fdw + OPTIONS(fdw_startup_cost '100$%$#$#'); +ERROR: fdw_startup_cost requires a non-negative numeric value +-- Invalid fdw_tuple_cost option +CREATE SERVER inv_scst FOREIGN DATA WRAPPER postgres_fdw + OPTIONS(fdw_tuple_cost '100$%$#$#'); +ERROR: fdw_tuple_cost requires a non-negative numeric value +-- Invalid fetch_size option +CREATE FOREIGN TABLE inv_fsz (c1 int ) + SERVER loopback OPTIONS (fetch_size '100$%$#$#'); +ERROR: invalid option "fetch_size" +HINT: Valid options in this context are: schema_name, table_name, use_remote_estimate, updatable +-- Invalid batch_size option +CREATE FOREIGN TABLE inv_bsz (c1 int ) + SERVER loopback OPTIONS (batch_size '100$%$#$#'); +ERROR: invalid option "batch_size" +HINT: Valid options in this context are: schema_name, table_name, use_remote_estimate, updatable +-- ====================================================================================================================================== +-- TEST-MODULE: clean up all the test data +-- -------------------------------------- +-- heihei! +-- ====================================================================================================================================== +\c regression +drop database postgresfdw_test_db; diff --git a/contrib/postgres_fdw/expected/postgres_fdw_cstore.out b/contrib/postgres_fdw/expected/postgres_fdw_cstore.out new file mode 100644 index 000000000..bef4b6415 --- /dev/null +++ b/contrib/postgres_fdw/expected/postgres_fdw_cstore.out @@ -0,0 +1,7269 @@ +-- ====================================================================================================================================== +-- README: For details, see "postgres_fdw.sql". +-- This test case was written by us. Test for cstore. +-- ====================================================================================================================================== +create database postgresfdw_test_db_cstore; +\c postgresfdw_test_db_cstore +set show_fdw_remote_plan = on; +-- ====================================================================================================================================== +-- TEST-MODULE: create FDW objects +-- -------------------------------------- +-- ====================================================================================================================================== +CREATE EXTENSION postgres_fdw; +CREATE SERVER testserver1 FOREIGN DATA WRAPPER postgres_fdw; +DO $d$ + BEGIN + EXECUTE $$CREATE SERVER loopback FOREIGN DATA WRAPPER postgres_fdw + OPTIONS (dbname '$$||current_database()||$$', + port '$$||current_setting('port')||$$' + )$$; + EXECUTE $$CREATE SERVER loopback2 FOREIGN DATA WRAPPER postgres_fdw + OPTIONS (dbname '$$||current_database()||$$', + port '$$||current_setting('port')||$$' + )$$; + EXECUTE $$CREATE SERVER loopback3 FOREIGN DATA WRAPPER postgres_fdw + OPTIONS (dbname '$$||current_database()||$$', + port '$$||current_setting('port')||$$' + )$$; + END; +$d$; +CREATE USER MAPPING FOR public SERVER testserver1 + OPTIONS (user 'value', password 'value'); +CREATE USER MAPPING FOR CURRENT_USER SERVER loopback; +CREATE USER MAPPING FOR CURRENT_USER SERVER loopback2; +CREATE USER MAPPING FOR public SERVER loopback3; +-- ====================================================================================================================================== +-- TEST-MODULE: create objects used through FDW loopback server +-- -------------------------------------- +-- ====================================================================================================================================== +CREATE TYPE user_enum AS ENUM ('foo', 'bar', 'buz'); +CREATE SCHEMA "S 1"; +CREATE TABLE "S 1"."T 1" ( + "C 1" int NOT NULL, + c2 int NOT NULL, + c3 text, + c4 timestamptz, + c5 timestamp, + c6 varchar(10), + c7 char(10), + c8 text, + CONSTRAINT t1_pkey PRIMARY KEY ("C 1") +) with (orientation=column); +NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "t1_pkey" for table "T 1" +CREATE TABLE "S 1"."T 2" ( + c1 int NOT NULL, + c2 text, + CONSTRAINT t2_pkey PRIMARY KEY (c1) +) with (orientation=column); +NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "t2_pkey" for table "T 2" +CREATE TABLE "S 1"."T 3" ( + c1 int NOT NULL, + c2 int NOT NULL, + c3 text, + CONSTRAINT t3_pkey PRIMARY KEY (c1) +) with (orientation=column); +NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "t3_pkey" for table "T 3" +CREATE TABLE "S 1"."T 4" ( + c1 int NOT NULL, + c2 int NOT NULL, + c3 text, + CONSTRAINT t4_pkey PRIMARY KEY (c1) +) with (orientation=column); +NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "t4_pkey" for table "T 4" +-- Disable autovacuum for these tables to avoid unexpected effects of that +ALTER TABLE "S 1"."T 1" SET (autovacuum_enabled = 'false'); +ALTER TABLE "S 1"."T 2" SET (autovacuum_enabled = 'false'); +ALTER TABLE "S 1"."T 3" SET (autovacuum_enabled = 'false'); +ALTER TABLE "S 1"."T 4" SET (autovacuum_enabled = 'false'); +INSERT INTO "S 1"."T 1" + SELECT id, + id % 10, + to_char(id, 'FM00000'), + '1970-01-01'::timestamptz + ((id % 100) || ' days')::interval, + '1970-01-01'::timestamp + ((id % 100) || ' days')::interval, + id % 10, + id % 10, + 'foo'::user_enum + FROM generate_series(1, 1000) id; +INSERT INTO "S 1"."T 2" + SELECT id, + 'AAA' || to_char(id, 'FM000') + FROM generate_series(1, 100) id; +INSERT INTO "S 1"."T 3" + SELECT id, + id + 1, + 'AAA' || to_char(id, 'FM000') + FROM generate_series(1, 100) id; +DELETE FROM "S 1"."T 3" WHERE c1 % 2 != 0; -- delete for outer join tests +INSERT INTO "S 1"."T 4" + SELECT id, + id + 1, + 'AAA' || to_char(id, 'FM000') + FROM generate_series(1, 100) id; +DELETE FROM "S 1"."T 4" WHERE c1 % 3 != 0; -- delete for outer join tests +ANALYZE "S 1"."T 1"; +ANALYZE "S 1"."T 2"; +ANALYZE "S 1"."T 3"; +ANALYZE "S 1"."T 4"; +-- ====================================================================================================================================== +-- TEST-MODULE: create foreign tables +-- -------------------------------------- +-- +-- ====================================================================================================================================== +CREATE FOREIGN TABLE ft1 ( + c0 int, + c1 int NOT NULL, + c2 int NOT NULL, + c3 text, + c4 timestamptz, + c5 timestamp, + c6 varchar(10), + c7 char(10) default 'ft1', + c8 user_enum +) SERVER loopback; +ALTER FOREIGN TABLE ft1 DROP COLUMN c0; +CREATE FOREIGN TABLE ft2 ( + c1 int NOT NULL, + c2 int NOT NULL, + cx int, + c3 text, + c4 timestamptz, + c5 timestamp, + c6 varchar(10), + c7 char(10) default 'ft2', + c8 user_enum +) SERVER loopback; +ALTER FOREIGN TABLE ft2 DROP COLUMN cx; +CREATE FOREIGN TABLE ft4 ( + c1 int NOT NULL, + c2 int NOT NULL, + c3 text +) SERVER loopback OPTIONS (schema_name 'S 1', table_name 'T 3'); +CREATE FOREIGN TABLE ft5 ( + c1 int NOT NULL, + c2 int NOT NULL, + c3 text +) SERVER loopback OPTIONS (schema_name 'S 1', table_name 'T 4'); +CREATE FOREIGN TABLE ft6 ( + c1 int NOT NULL, + c2 int NOT NULL, + c3 text +) SERVER loopback2 OPTIONS (schema_name 'S 1', table_name 'T 4'); +CREATE FOREIGN TABLE ft7 ( + c1 int NOT NULL, + c2 int NOT NULL, + c3 text +) SERVER loopback3 OPTIONS (schema_name 'S 1', table_name 'T 4'); +-- ====================================================================================================================================== +-- TEST-MODULE: tests for validator +-- -------------------------------------- +-- ====================================================================================================================================== +-- requiressl and some other parameters are omitted because +-- valid values for them depend on configure options +ALTER SERVER testserver1 OPTIONS ( + use_remote_estimate 'false', + updatable 'true', + fdw_startup_cost '123.456', + fdw_tuple_cost '0.123', + service 'value', + connect_timeout 'value', + dbname 'value', + host 'value', + hostaddr 'value', + port 'value', + application_name 'value', + keepalives 'value', + keepalives_idle 'value', + keepalives_interval 'value', + sslcompression 'value', + sslmode 'value', + sslcert 'value', + sslkey 'value', + sslrootcert 'value', + sslcrl 'value', + krbsrvname 'value' +); +ALTER FOREIGN TABLE ft1 OPTIONS (schema_name 'S 1', table_name 'T 1'); +ALTER FOREIGN TABLE ft2 OPTIONS (schema_name 'S 1', table_name 'T 1'); +ALTER FOREIGN TABLE ft1 ALTER COLUMN c1 OPTIONS (column_name 'C 1'); +ALTER FOREIGN TABLE ft2 ALTER COLUMN c1 OPTIONS (column_name 'C 1'); +\det+ + List of foreign tables + Schema | Table | Server | FDW Options | Description +--------+-------+-----------+---------------------------------------+------------- + public | ft1 | loopback | (schema_name 'S 1', table_name 'T 1') | + public | ft2 | loopback | (schema_name 'S 1', table_name 'T 1') | + public | ft4 | loopback | (schema_name 'S 1', table_name 'T 3') | + public | ft5 | loopback | (schema_name 'S 1', table_name 'T 4') | + public | ft6 | loopback2 | (schema_name 'S 1', table_name 'T 4') | + public | ft7 | loopback3 | (schema_name 'S 1', table_name 'T 4') | +(6 rows) + +-- Now we should be able to run ANALYZE. +-- To exercise multiple code paths, we use local stats on ft1 +-- and remote-estimate mode on ft2. +ANALYZE ft1; +ALTER FOREIGN TABLE ft2 OPTIONS (use_remote_estimate 'true'); +-- ====================================================================================================================================== +-- TEST-MODULE: simple queries +-- -------------------------------------- +-- ====================================================================================================================================== +-- single table without alias +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 ORDER BY c3, c1 OFFSET 100 LIMIT 10; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" ORDER BY c3 ASC NULLS LAST, "C 1" ASC NULLS LAST LIMIT 10::bigint OFFSET 100::bigint + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" ORDER BY c3 ASC NULLS LAST, "C 1" ASC NULLS LAST LIMIT 10::bigint OFFSET 100::bigint + Row Adapter + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + -> Vector Limit + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + -> Vector Sort + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Sort Key: "T 1".c3, "T 1"."C 1" + -> CStore Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + +(17 rows) + +SELECT * FROM ft1 ORDER BY c3, c1 OFFSET 100 LIMIT 10; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +-----+----+-------+------------------------------+--------------------------+----+------------+----- + 101 | 1 | 00101 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1 | 1 | foo + 102 | 2 | 00102 | Sat Jan 03 00:00:00 1970 PST | Sat Jan 03 00:00:00 1970 | 2 | 2 | foo + 103 | 3 | 00103 | Sun Jan 04 00:00:00 1970 PST | Sun Jan 04 00:00:00 1970 | 3 | 3 | foo + 104 | 4 | 00104 | Mon Jan 05 00:00:00 1970 PST | Mon Jan 05 00:00:00 1970 | 4 | 4 | foo + 105 | 5 | 00105 | Tue Jan 06 00:00:00 1970 PST | Tue Jan 06 00:00:00 1970 | 5 | 5 | foo + 106 | 6 | 00106 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo + 107 | 7 | 00107 | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7 | 7 | foo + 108 | 8 | 00108 | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8 | 8 | foo + 109 | 9 | 00109 | Sat Jan 10 00:00:00 1970 PST | Sat Jan 10 00:00:00 1970 | 9 | 9 | foo + 110 | 0 | 00110 | Sun Jan 11 00:00:00 1970 PST | Sun Jan 11 00:00:00 1970 | 0 | 0 | foo +(10 rows) + +-- whole-row reference +EXPLAIN (VERBOSE, COSTS OFF) SELECT t1 FROM ft1 t1 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: t1.*, c3, c1 + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" ORDER BY c3 ASC NULLS LAST, "C 1" ASC NULLS LAST LIMIT 10::bigint OFFSET 100::bigint + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" ORDER BY c3 ASC NULLS LAST, "C 1" ASC NULLS LAST LIMIT 10::bigint OFFSET 100::bigint + Row Adapter + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + -> Vector Limit + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + -> Vector Sort + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Sort Key: "T 1".c3, "T 1"."C 1" + -> CStore Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + +(17 rows) + +SELECT t1 FROM ft1 t1 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; + t1 +-------------------------------------------------------------------------------------------- + (101,1,00101,"Fri Jan 02 00:00:00 1970 PST","Fri Jan 02 00:00:00 1970",1,"1 ",foo) + (102,2,00102,"Sat Jan 03 00:00:00 1970 PST","Sat Jan 03 00:00:00 1970",2,"2 ",foo) + (103,3,00103,"Sun Jan 04 00:00:00 1970 PST","Sun Jan 04 00:00:00 1970",3,"3 ",foo) + (104,4,00104,"Mon Jan 05 00:00:00 1970 PST","Mon Jan 05 00:00:00 1970",4,"4 ",foo) + (105,5,00105,"Tue Jan 06 00:00:00 1970 PST","Tue Jan 06 00:00:00 1970",5,"5 ",foo) + (106,6,00106,"Wed Jan 07 00:00:00 1970 PST","Wed Jan 07 00:00:00 1970",6,"6 ",foo) + (107,7,00107,"Thu Jan 08 00:00:00 1970 PST","Thu Jan 08 00:00:00 1970",7,"7 ",foo) + (108,8,00108,"Fri Jan 09 00:00:00 1970 PST","Fri Jan 09 00:00:00 1970",8,"8 ",foo) + (109,9,00109,"Sat Jan 10 00:00:00 1970 PST","Sat Jan 10 00:00:00 1970",9,"9 ",foo) + (110,0,00110,"Sun Jan 11 00:00:00 1970 PST","Sun Jan 11 00:00:00 1970",0,"0 ",foo) +(10 rows) + +-- empty result +SELECT * FROM ft1 WHERE false; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +----+----+----+----+----+----+----+---- +(0 rows) + +-- with WHERE clause +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE t1.c1 = 101 AND t1.c6 = '1' AND t1.c7 >= '1'; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((c7 >= '1'::bpchar)) AND (("C 1" = 101)) AND ((c6 = '1'::text)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((c7 >= '1'::bpchar)) AND (("C 1" = 101)) AND ((c6 = '1'::text)) + Row Adapter + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + -> CStore Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Filter: (("T 1".c7 >= '1'::bpchar) AND ("T 1"."C 1" = 101) AND (("T 1".c6)::text = '1'::text)) + +(13 rows) + +SELECT * FROM ft1 t1 WHERE t1.c1 = 101 AND t1.c6 = '1' AND t1.c7 >= '1'; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +-----+----+-------+------------------------------+--------------------------+----+------------+----- + 101 | 1 | 00101 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1 | 1 | foo +(1 row) + +-- with FOR UPDATE/SHARE +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 = 101 FOR UPDATE; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8, t1.* + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 101)) FOR UPDATE + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 101)) FOR UPDATE + failed to get remote plan, error massage is: + SELECT FOR UPDATE/SHARE/NO KEY UPDATE/KEY SHARE cannot be used with column table "T 1" + +(10 rows) + +SELECT * FROM ft1 t1 WHERE c1 = 101 FOR UPDATE; +ERROR: SELECT FOR UPDATE/SHARE/NO KEY UPDATE/KEY SHARE cannot be used with column table "T 1" +CONTEXT: Remote SQL command: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 101)) FOR UPDATE +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 = 102 FOR SHARE; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------ + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8, t1.* + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 102)) FOR SHARE + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 102)) FOR SHARE + failed to get remote plan, error massage is: + SELECT FOR UPDATE/SHARE/NO KEY UPDATE/KEY SHARE cannot be used with column table "T 1" + +(10 rows) + +SELECT * FROM ft1 t1 WHERE c1 = 102 FOR SHARE; +ERROR: SELECT FOR UPDATE/SHARE/NO KEY UPDATE/KEY SHARE cannot be used with column table "T 1" +CONTEXT: Remote SQL command: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 102)) FOR SHARE +-- aggregate +SELECT COUNT(*) FROM ft1 t1; + count +------- + 1000 +(1 row) + +-- subquery +SELECT * FROM ft1 t1 WHERE t1.c3 IN (SELECT c3 FROM ft2 t2 WHERE c1 <= 10) ORDER BY c1; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +----+----+-------+------------------------------+--------------------------+----+------------+----- + 1 | 1 | 00001 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1 | 1 | foo + 2 | 2 | 00002 | Sat Jan 03 00:00:00 1970 PST | Sat Jan 03 00:00:00 1970 | 2 | 2 | foo + 3 | 3 | 00003 | Sun Jan 04 00:00:00 1970 PST | Sun Jan 04 00:00:00 1970 | 3 | 3 | foo + 4 | 4 | 00004 | Mon Jan 05 00:00:00 1970 PST | Mon Jan 05 00:00:00 1970 | 4 | 4 | foo + 5 | 5 | 00005 | Tue Jan 06 00:00:00 1970 PST | Tue Jan 06 00:00:00 1970 | 5 | 5 | foo + 6 | 6 | 00006 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo + 7 | 7 | 00007 | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7 | 7 | foo + 8 | 8 | 00008 | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8 | 8 | foo + 9 | 9 | 00009 | Sat Jan 10 00:00:00 1970 PST | Sat Jan 10 00:00:00 1970 | 9 | 9 | foo + 10 | 0 | 00010 | Sun Jan 11 00:00:00 1970 PST | Sun Jan 11 00:00:00 1970 | 0 | 0 | foo +(10 rows) + +-- subquery+MAX +SELECT * FROM ft1 t1 WHERE t1.c3 = (SELECT MAX(c3) FROM ft2 t2) ORDER BY c1; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +------+----+-------+------------------------------+--------------------------+----+------------+----- + 1000 | 0 | 01000 | Thu Jan 01 00:00:00 1970 PST | Thu Jan 01 00:00:00 1970 | 0 | 0 | foo +(1 row) + +-- used in CTE +WITH t1 AS (SELECT * FROM ft1 WHERE c1 <= 10) SELECT t2.c1, t2.c2, t2.c3, t2.c4 FROM t1, ft2 t2 WHERE t1.c1 = t2.c1 ORDER BY t1.c1; + c1 | c2 | c3 | c4 +----+----+-------+------------------------------ + 1 | 1 | 00001 | Fri Jan 02 00:00:00 1970 PST + 2 | 2 | 00002 | Sat Jan 03 00:00:00 1970 PST + 3 | 3 | 00003 | Sun Jan 04 00:00:00 1970 PST + 4 | 4 | 00004 | Mon Jan 05 00:00:00 1970 PST + 5 | 5 | 00005 | Tue Jan 06 00:00:00 1970 PST + 6 | 6 | 00006 | Wed Jan 07 00:00:00 1970 PST + 7 | 7 | 00007 | Thu Jan 08 00:00:00 1970 PST + 8 | 8 | 00008 | Fri Jan 09 00:00:00 1970 PST + 9 | 9 | 00009 | Sat Jan 10 00:00:00 1970 PST + 10 | 0 | 00010 | Sun Jan 11 00:00:00 1970 PST +(10 rows) + +-- fixed values +SELECT 'fixed', NULL FROM ft1 t1 WHERE c1 = 1; + ?column? | ?column? +----------+---------- + fixed | +(1 row) + +-- Test forcing the remote server to produce sorted data for a merge join. +SET enable_hashjoin TO false; +SET enable_nestloop TO false; +-- inner join; expressions in the clauses appear in the equivalence class list +EXPLAIN (VERBOSE, COSTS OFF) + SELECT t1.c1, t2."C 1" FROM ft2 t1 JOIN "S 1"."T 1" t2 ON (t1.c1 = t2."C 1") OFFSET 100 LIMIT 10; + QUERY PLAN +------------------------------------------------------------------------------------------------------ + Limit + Output: t1.c1, t2."C 1" + -> Merge Join + Output: t1.c1, t2."C 1" + Merge Cond: (t1.c1 = t2."C 1") + -> Foreign Scan on public.ft2 t1 + Output: t1.c1 + Node ID: 1 + Remote SQL: SELECT "C 1" FROM "S 1"."T 1" ORDER BY "C 1" ASC NULLS LAST + -> Row Adapter + Output: t2."C 1" + -> Vector Sort + Output: t2."C 1" + Sort Key: t2."C 1" + -> CStore Scan on "S 1"."T 1" t2 + Output: t2."C 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1" FROM "S 1"."T 1" ORDER BY "C 1" ASC NULLS LAST + Row Adapter + Output: "C 1" + -> Vector Sort + Output: "C 1" + Sort Key: "T 1"."C 1" + -> CStore Scan on "S 1"."T 1" + Output: "C 1" + +(27 rows) + +SELECT t1.c1, t2."C 1" FROM ft2 t1 JOIN "S 1"."T 1" t2 ON (t1.c1 = t2."C 1") OFFSET 100 LIMIT 10; + c1 | C 1 +-----+----- + 101 | 101 + 102 | 102 + 103 | 103 + 104 | 104 + 105 | 105 + 106 | 106 + 107 | 107 + 108 | 108 + 109 | 109 + 110 | 110 +(10 rows) + +-- outer join; expressions in the clauses do not appear in equivalence class +-- list but no output change as compared to the previous query +EXPLAIN (VERBOSE, COSTS OFF) + SELECT t1.c1, t2."C 1" FROM ft2 t1 LEFT JOIN "S 1"."T 1" t2 ON (t1.c1 = t2."C 1") OFFSET 100 LIMIT 10; + QUERY PLAN +------------------------------------------------------------------------------------------------------ + Limit + Output: t1.c1, t2."C 1" + -> Merge Left Join + Output: t1.c1, t2."C 1" + Merge Cond: (t1.c1 = t2."C 1") + -> Foreign Scan on public.ft2 t1 + Output: t1.c1 + Node ID: 1 + Remote SQL: SELECT "C 1" FROM "S 1"."T 1" ORDER BY "C 1" ASC NULLS LAST + -> Row Adapter + Output: t2."C 1" + -> Vector Sort + Output: t2."C 1" + Sort Key: t2."C 1" + -> CStore Scan on "S 1"."T 1" t2 + Output: t2."C 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1" FROM "S 1"."T 1" ORDER BY "C 1" ASC NULLS LAST + Row Adapter + Output: "C 1" + -> Vector Sort + Output: "C 1" + Sort Key: "T 1"."C 1" + -> CStore Scan on "S 1"."T 1" + Output: "C 1" + +(27 rows) + +SELECT t1.c1, t2."C 1" FROM ft2 t1 LEFT JOIN "S 1"."T 1" t2 ON (t1.c1 = t2."C 1") OFFSET 100 LIMIT 10; + c1 | C 1 +-----+----- + 101 | 101 + 102 | 102 + 103 | 103 + 104 | 104 + 105 | 105 + 106 | 106 + 107 | 107 + 108 | 108 + 109 | 109 + 110 | 110 +(10 rows) + +-- A join between local table and foreign join. ORDER BY clause is added to the +-- foreign join so that the local table can be joined using merge join strategy. +EXPLAIN (VERBOSE, COSTS OFF) + SELECT t1."C 1" FROM "S 1"."T 1" t1 left join ft1 t2 join ft2 t3 on (t2.c1 = t3.c1) on (t3.c1 = t1."C 1") OFFSET 100 LIMIT 10; + QUERY PLAN +------------------------------------------------------------------------------------------------------ + Limit + Output: t1."C 1" + -> Merge Right Join + Output: t1."C 1" + Merge Cond: (t3.c1 = t1."C 1") + -> Merge Join + Output: t3.c1 + Merge Cond: (t3.c1 = t2.c1) + -> Foreign Scan on public.ft2 t3 + Output: t3.c1 + Node ID: 1 + Remote SQL: SELECT "C 1" FROM "S 1"."T 1" ORDER BY "C 1" ASC NULLS LAST + -> Sort + Output: t2.c1 + Sort Key: t2.c1 + -> Foreign Scan on public.ft1 t2 + Output: t2.c1 + Node ID: 2 + Remote SQL: SELECT "C 1" FROM "S 1"."T 1" + -> Row Adapter + Output: t1."C 1" + -> Vector Sort + Output: t1."C 1" + Sort Key: t1."C 1" + -> CStore Scan on "S 1"."T 1" t1 + Output: t1."C 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1" FROM "S 1"."T 1" ORDER BY "C 1" ASC NULLS LAST + Row Adapter + Output: "C 1" + -> Vector Sort + Output: "C 1" + Sort Key: "T 1"."C 1" + -> CStore Scan on "S 1"."T 1" + Output: "C 1" + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1" FROM "S 1"."T 1" + Row Adapter + Output: "C 1" + -> CStore Scan on "S 1"."T 1" + Output: "C 1" + +(42 rows) + +SELECT t1."C 1" FROM "S 1"."T 1" t1 left join ft1 t2 join ft2 t3 on (t2.c1 = t3.c1) on (t3.c1 = t1."C 1") OFFSET 100 LIMIT 10; + C 1 +----- + 101 + 102 + 103 + 104 + 105 + 106 + 107 + 108 + 109 + 110 +(10 rows) + +-- Test similar to above, except that the full join prevents any equivalence +-- classes from being merged. This produces single relation equivalence classes +-- included in join restrictions. +EXPLAIN (VERBOSE, COSTS OFF) + SELECT t1."C 1", t2.c1, t3.c1 FROM "S 1"."T 1" t1 left join ft1 t2 full join ft2 t3 on (t2.c1 = t3.c1) on (t3.c1 = t1."C 1") OFFSET 100 LIMIT 10; + QUERY PLAN +------------------------------------------------------------------------------------------------------ + Limit + Output: t1."C 1", t2.c1, t3.c1 + -> Merge Right Join + Output: t1."C 1", t2.c1, t3.c1 + Merge Cond: (t3.c1 = t1."C 1") + -> Merge Left Join + Output: t3.c1, t2.c1 + Merge Cond: (t3.c1 = t2.c1) + -> Foreign Scan on public.ft2 t3 + Output: t3.c1 + Node ID: 1 + Remote SQL: SELECT "C 1" FROM "S 1"."T 1" ORDER BY "C 1" ASC NULLS LAST + -> Sort + Output: t2.c1 + Sort Key: t2.c1 + -> Foreign Scan on public.ft1 t2 + Output: t2.c1 + Node ID: 2 + Remote SQL: SELECT "C 1" FROM "S 1"."T 1" + -> Row Adapter + Output: t1."C 1" + -> Vector Sort + Output: t1."C 1" + Sort Key: t1."C 1" + -> CStore Scan on "S 1"."T 1" t1 + Output: t1."C 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1" FROM "S 1"."T 1" ORDER BY "C 1" ASC NULLS LAST + Row Adapter + Output: "C 1" + -> Vector Sort + Output: "C 1" + Sort Key: "T 1"."C 1" + -> CStore Scan on "S 1"."T 1" + Output: "C 1" + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1" FROM "S 1"."T 1" + Row Adapter + Output: "C 1" + -> CStore Scan on "S 1"."T 1" + Output: "C 1" + +(42 rows) + +SELECT t1."C 1", t2.c1, t3.c1 FROM "S 1"."T 1" t1 left join ft1 t2 full join ft2 t3 on (t2.c1 = t3.c1) on (t3.c1 = t1."C 1") OFFSET 100 LIMIT 10; + C 1 | c1 | c1 +-----+-----+----- + 101 | 101 | 101 + 102 | 102 | 102 + 103 | 103 | 103 + 104 | 104 | 104 + 105 | 105 | 105 + 106 | 106 | 106 + 107 | 107 | 107 + 108 | 108 | 108 + 109 | 109 | 109 + 110 | 110 | 110 +(10 rows) + +-- Test similar to above with all full outer joins +EXPLAIN (VERBOSE, COSTS OFF) + SELECT t1."C 1", t2.c1, t3.c1 FROM "S 1"."T 1" t1 full join ft1 t2 full join ft2 t3 on (t2.c1 = t3.c1) on (t3.c1 = t1."C 1") OFFSET 100 LIMIT 10; + QUERY PLAN +------------------------------------------------------------------------------------------------------ + Limit + Output: t1."C 1", t2.c1, t3.c1 + -> Merge Full Join + Output: t1."C 1", t2.c1, t3.c1 + Merge Cond: (t1."C 1" = t3.c1) + -> Row Adapter + Output: t1."C 1" + -> Vector Sort + Output: t1."C 1" + Sort Key: t1."C 1" + -> CStore Scan on "S 1"."T 1" t1 + Output: t1."C 1" + -> Sort + Output: t2.c1, t3.c1 + Sort Key: t3.c1 + -> Merge Full Join + Output: t2.c1, t3.c1 + Merge Cond: (t3.c1 = t2.c1) + -> Foreign Scan on public.ft2 t3 + Output: t3.c1 + Node ID: 1 + Remote SQL: SELECT "C 1" FROM "S 1"."T 1" ORDER BY "C 1" ASC NULLS LAST + -> Sort + Output: t2.c1 + Sort Key: t2.c1 + -> Foreign Scan on public.ft1 t2 + Output: t2.c1 + Node ID: 2 + Remote SQL: SELECT "C 1" FROM "S 1"."T 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1" FROM "S 1"."T 1" ORDER BY "C 1" ASC NULLS LAST + Row Adapter + Output: "C 1" + -> Vector Sort + Output: "C 1" + Sort Key: "T 1"."C 1" + -> CStore Scan on "S 1"."T 1" + Output: "C 1" + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1" FROM "S 1"."T 1" + Row Adapter + Output: "C 1" + -> CStore Scan on "S 1"."T 1" + Output: "C 1" + +(45 rows) + +SELECT t1."C 1", t2.c1, t3.c1 FROM "S 1"."T 1" t1 full join ft1 t2 full join ft2 t3 on (t2.c1 = t3.c1) on (t3.c1 = t1."C 1") OFFSET 100 LIMIT 10; + C 1 | c1 | c1 +-----+-----+----- + 101 | 101 | 101 + 102 | 102 | 102 + 103 | 103 | 103 + 104 | 104 | 104 + 105 | 105 | 105 + 106 | 106 | 106 + 107 | 107 | 107 + 108 | 108 | 108 + 109 | 109 | 109 + 110 | 110 | 110 +(10 rows) + +RESET enable_hashjoin; +RESET enable_nestloop; +-- Test executing assertion in estimate_path_cost_size() that makes sure that +-- retrieved_rows for foreign rel re-used to cost pre-sorted foreign paths is +-- a sensible value even when the rel has tuples=0 +CREATE TABLE loct_empty (c1 int NOT NULL, c2 text) with (orientation=column); +CREATE FOREIGN TABLE ft_empty (c1 int NOT NULL, c2 text) + SERVER loopback OPTIONS (table_name 'loct_empty'); +INSERT INTO loct_empty + SELECT id, 'AAA' || to_char(id, 'FM000') FROM generate_series(1, 100) id; +DELETE FROM loct_empty; +ANALYZE ft_empty; +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft_empty ORDER BY c1; + QUERY PLAN +------------------------------------------------------------------------------- + Sort + Output: c1, c2 + Sort Key: ft_empty.c1 + -> Foreign Scan on public.ft_empty + Output: c1, c2 + Node ID: 1 + Remote SQL: SELECT c1, c2 FROM public.loct_empty + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c1, c2 FROM public.loct_empty + Row Adapter + Output: c1, c2 + -> CStore Scan on public.loct_empty + Output: c1, c2 + +(15 rows) + +-- ====================================================================================================================================== +-- TEST-MODULE: WHERE with remotely-executable conditions +-- -------------------------------------- +-- ====================================================================================================================================== +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE t1.c1 = 1; -- Var, OpExpr(b), Const + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------ + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 1)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 1)) + Row Adapter + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + -> CStore Index Scan using t1_pkey on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Index Cond: ("T 1"."C 1" = 1) + +(13 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE t1.c1 = 100 AND t1.c2 = 0; -- BoolExpr + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 100)) AND ((c2 = 0)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 100)) AND ((c2 = 0)) + Row Adapter + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + -> CStore Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Filter: (("T 1"."C 1" = 100) AND ("T 1".c2 = 0)) + +(13 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 IS NULL; -- NullTest + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" IS NULL)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" IS NULL)) + Row Adapter + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + -> Vector Result + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + One-Time Filter: false + -> CStore Index Scan using t1_pkey on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Index Cond: ("T 1"."C 1" IS NULL) + +(16 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 IS NOT NULL; -- NullTest + QUERY PLAN +---------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" + Row Adapter + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + -> CStore Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + +(12 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE round(abs(c1), 0) = 1; -- FuncExpr + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------ + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((round(abs("C 1"), 0) = 1::numeric)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((round(abs("C 1"), 0) = 1::numeric)) + Row Adapter + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + -> CStore Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Filter: (round((abs("T 1"."C 1"))::numeric, 0) = 1::numeric) + +(13 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 = -c1; -- OpExpr(l) + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = (- "C 1"))) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = (- "C 1"))) + Row Adapter + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + -> CStore Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Filter: ("T 1"."C 1" = (- "T 1"."C 1")) + +(13 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE (c1 IS NOT NULL) IS DISTINCT FROM (c1 IS NOT NULL); -- DistinctExpr + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((("C 1" IS NOT NULL) IS DISTINCT FROM ("C 1" IS NOT NULL))) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((("C 1" IS NOT NULL) IS DISTINCT FROM ("C 1" IS NOT NULL))) + Row Adapter + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + -> CStore Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Filter: (("T 1"."C 1" IS NOT NULL) IS DISTINCT FROM ("T 1"."C 1" IS NOT NULL)) + +(13 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 = ANY(ARRAY[c2, 1, c1 + 0]); -- ScalarArrayOpExpr + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------ + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = ANY (ARRAY[c2, 1, ("C 1" + 0)]))) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = ANY (ARRAY[c2, 1, ("C 1" + 0)]))) + Result + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Filter: ("T 1"."C 1" = ANY (ARRAY["T 1".c2, 1, ("T 1"."C 1" + 0)])) + -> Row Adapter + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + -> CStore Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + +(15 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 = (ARRAY[c1,c2,3])[1]; -- SubscriptingRef + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = ((ARRAY["C 1", c2, 3])[1]))) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = ((ARRAY["C 1", c2, 3])[1]))) + Result + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Filter: ("T 1"."C 1" = (ARRAY["T 1"."C 1", "T 1".c2, 3])[1]) + -> Row Adapter + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + -> CStore Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + +(15 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c6 = E'foo''s\\bar'; -- check special chars + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((c6 = E'foo''s\\bar'::text)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((c6 = E'foo''s\\bar'::text)) + Row Adapter + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + -> CStore Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Filter: (("T 1".c6)::text = 'foo''s\bar'::text) + +(13 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c8 = 'foo'; -- can't be sent to remote + QUERY PLAN +---------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Filter: (t1.c8 = 'foo'::user_enum) + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" + Row Adapter + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + -> CStore Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + +(13 rows) + +-- parameterized remote path for foreign table +EXPLAIN (VERBOSE, COSTS OFF) + SELECT * FROM "S 1"."T 1" a, ft2 b WHERE a."C 1" = 47 AND b.c1 = a.c2; + QUERY PLAN +------------------------------------------------------------------------------------------------------------- + Nested Loop + Output: a."C 1", a.c2, a.c3, a.c4, a.c5, a.c6, a.c7, a.c8, b.c1, b.c2, b.c3, b.c4, b.c5, b.c6, b.c7, b.c8 + Join Filter: (a.c2 = b.c1) + -> Row Adapter + Output: a."C 1", a.c2, a.c3, a.c4, a.c5, a.c6, a.c7, a.c8 + -> CStore Index Scan using t1_pkey on "S 1"."T 1" a + Output: a."C 1", a.c2, a.c3, a.c4, a.c5, a.c6, a.c7, a.c8 + Index Cond: (a."C 1" = 47) + -> Foreign Scan on public.ft2 b + Output: b.c1, b.c2, b.c3, b.c4, b.c5, b.c6, b.c7, b.c8 + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" + Row Adapter + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + -> CStore Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + +(20 rows) + +SELECT * FROM ft2 a, ft2 b WHERE a.c1 = 47 AND b.c1 = a.c2; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 | c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +----+----+-------+------------------------------+--------------------------+----+------------+-----+----+----+-------+------------------------------+--------------------------+----+------------+----- + 47 | 7 | 00047 | Tue Feb 17 00:00:00 1970 PST | Tue Feb 17 00:00:00 1970 | 7 | 7 | foo | 7 | 7 | 00007 | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7 | 7 | foo +(1 row) + +-- check both safe and unsafe join conditions +EXPLAIN (VERBOSE, COSTS OFF) + SELECT * FROM ft2 a, ft2 b + WHERE a.c2 = 6 AND b.c1 = a.c1 AND a.c8 = 'foo' AND b.c7 = upper(a.c7); + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------------------------- + Hash Join + Output: a.c1, a.c2, a.c3, a.c4, a.c5, a.c6, a.c7, a.c8, b.c1, b.c2, b.c3, b.c4, b.c5, b.c6, b.c7, b.c8 + Hash Cond: ((b.c1 = a.c1) AND ((b.c7)::text = upper((a.c7)::text))) + -> Foreign Scan on public.ft2 b + Output: b.c1, b.c2, b.c3, b.c4, b.c5, b.c6, b.c7, b.c8 + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" + -> Hash + Output: a.c1, a.c2, a.c3, a.c4, a.c5, a.c6, a.c7, a.c8 + -> Foreign Scan on public.ft2 a + Output: a.c1, a.c2, a.c3, a.c4, a.c5, a.c6, a.c7, a.c8 + Filter: (a.c8 = 'foo'::user_enum) + Node ID: 2 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((c2 = 6)) ORDER BY "C 1" ASC NULLS LAST + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" + Row Adapter + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + -> CStore Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((c2 = 6)) ORDER BY "C 1" ASC NULLS LAST + Row Adapter + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + -> Vector Sort + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Sort Key: "T 1"."C 1" + -> CStore Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Filter: ("T 1".c2 = 6) + +(31 rows) + +SELECT * FROM ft2 a, ft2 b +WHERE a.c2 = 6 AND b.c1 = a.c1 AND a.c8 = 'foo' AND b.c7 = upper(a.c7); + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 | c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +-----+----+-------+------------------------------+--------------------------+----+------------+-----+-----+----+-------+------------------------------+--------------------------+----+------------+----- + 6 | 6 | 00006 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo | 6 | 6 | 00006 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo + 16 | 6 | 00016 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo | 16 | 6 | 00016 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo + 26 | 6 | 00026 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo | 26 | 6 | 00026 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo + 36 | 6 | 00036 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo | 36 | 6 | 00036 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo + 46 | 6 | 00046 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo | 46 | 6 | 00046 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo + 56 | 6 | 00056 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo | 56 | 6 | 00056 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo + 66 | 6 | 00066 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo | 66 | 6 | 00066 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo + 76 | 6 | 00076 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo | 76 | 6 | 00076 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo + 86 | 6 | 00086 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo | 86 | 6 | 00086 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo + 96 | 6 | 00096 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo | 96 | 6 | 00096 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo + 106 | 6 | 00106 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo | 106 | 6 | 00106 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo + 116 | 6 | 00116 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo | 116 | 6 | 00116 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo + 126 | 6 | 00126 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo | 126 | 6 | 00126 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo + 136 | 6 | 00136 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo | 136 | 6 | 00136 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo + 146 | 6 | 00146 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo | 146 | 6 | 00146 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo + 156 | 6 | 00156 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo | 156 | 6 | 00156 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo + 166 | 6 | 00166 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo | 166 | 6 | 00166 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo + 176 | 6 | 00176 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo | 176 | 6 | 00176 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo + 186 | 6 | 00186 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo | 186 | 6 | 00186 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo + 196 | 6 | 00196 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo | 196 | 6 | 00196 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo + 206 | 6 | 00206 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo | 206 | 6 | 00206 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo + 216 | 6 | 00216 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo | 216 | 6 | 00216 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo + 226 | 6 | 00226 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo | 226 | 6 | 00226 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo + 236 | 6 | 00236 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo | 236 | 6 | 00236 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo + 246 | 6 | 00246 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo | 246 | 6 | 00246 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo + 256 | 6 | 00256 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo | 256 | 6 | 00256 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo + 266 | 6 | 00266 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo | 266 | 6 | 00266 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo + 276 | 6 | 00276 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo | 276 | 6 | 00276 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo + 286 | 6 | 00286 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo | 286 | 6 | 00286 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo + 296 | 6 | 00296 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo | 296 | 6 | 00296 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo + 306 | 6 | 00306 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo | 306 | 6 | 00306 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo + 316 | 6 | 00316 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo | 316 | 6 | 00316 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo + 326 | 6 | 00326 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo | 326 | 6 | 00326 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo + 336 | 6 | 00336 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo | 336 | 6 | 00336 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo + 346 | 6 | 00346 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo | 346 | 6 | 00346 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo + 356 | 6 | 00356 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo | 356 | 6 | 00356 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo + 366 | 6 | 00366 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo | 366 | 6 | 00366 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo + 376 | 6 | 00376 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo | 376 | 6 | 00376 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo + 386 | 6 | 00386 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo | 386 | 6 | 00386 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo + 396 | 6 | 00396 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo | 396 | 6 | 00396 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo + 406 | 6 | 00406 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo | 406 | 6 | 00406 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo + 416 | 6 | 00416 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo | 416 | 6 | 00416 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo + 426 | 6 | 00426 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo | 426 | 6 | 00426 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo + 436 | 6 | 00436 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo | 436 | 6 | 00436 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo + 446 | 6 | 00446 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo | 446 | 6 | 00446 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo + 456 | 6 | 00456 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo | 456 | 6 | 00456 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo + 466 | 6 | 00466 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo | 466 | 6 | 00466 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo + 476 | 6 | 00476 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo | 476 | 6 | 00476 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo + 486 | 6 | 00486 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo | 486 | 6 | 00486 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo + 496 | 6 | 00496 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo | 496 | 6 | 00496 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo + 506 | 6 | 00506 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo | 506 | 6 | 00506 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo + 516 | 6 | 00516 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo | 516 | 6 | 00516 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo + 526 | 6 | 00526 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo | 526 | 6 | 00526 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo + 536 | 6 | 00536 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo | 536 | 6 | 00536 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo + 546 | 6 | 00546 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo | 546 | 6 | 00546 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo + 556 | 6 | 00556 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo | 556 | 6 | 00556 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo + 566 | 6 | 00566 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo | 566 | 6 | 00566 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo + 576 | 6 | 00576 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo | 576 | 6 | 00576 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo + 586 | 6 | 00586 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo | 586 | 6 | 00586 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo + 596 | 6 | 00596 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo | 596 | 6 | 00596 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo + 606 | 6 | 00606 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo | 606 | 6 | 00606 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo + 616 | 6 | 00616 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo | 616 | 6 | 00616 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo + 626 | 6 | 00626 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo | 626 | 6 | 00626 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo + 636 | 6 | 00636 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo | 636 | 6 | 00636 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo + 646 | 6 | 00646 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo | 646 | 6 | 00646 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo + 656 | 6 | 00656 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo | 656 | 6 | 00656 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo + 666 | 6 | 00666 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo | 666 | 6 | 00666 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo + 676 | 6 | 00676 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo | 676 | 6 | 00676 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo + 686 | 6 | 00686 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo | 686 | 6 | 00686 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo + 696 | 6 | 00696 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo | 696 | 6 | 00696 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo + 706 | 6 | 00706 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo | 706 | 6 | 00706 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo + 716 | 6 | 00716 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo | 716 | 6 | 00716 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo + 726 | 6 | 00726 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo | 726 | 6 | 00726 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo + 736 | 6 | 00736 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo | 736 | 6 | 00736 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo + 746 | 6 | 00746 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo | 746 | 6 | 00746 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo + 756 | 6 | 00756 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo | 756 | 6 | 00756 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo + 766 | 6 | 00766 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo | 766 | 6 | 00766 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo + 776 | 6 | 00776 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo | 776 | 6 | 00776 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo + 786 | 6 | 00786 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo | 786 | 6 | 00786 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo + 796 | 6 | 00796 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo | 796 | 6 | 00796 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo + 806 | 6 | 00806 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo | 806 | 6 | 00806 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo + 816 | 6 | 00816 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo | 816 | 6 | 00816 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo + 826 | 6 | 00826 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo | 826 | 6 | 00826 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo + 836 | 6 | 00836 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo | 836 | 6 | 00836 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo + 846 | 6 | 00846 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo | 846 | 6 | 00846 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo + 856 | 6 | 00856 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo | 856 | 6 | 00856 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo + 866 | 6 | 00866 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo | 866 | 6 | 00866 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo + 876 | 6 | 00876 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo | 876 | 6 | 00876 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo + 886 | 6 | 00886 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo | 886 | 6 | 00886 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo + 896 | 6 | 00896 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo | 896 | 6 | 00896 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo + 906 | 6 | 00906 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo | 906 | 6 | 00906 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo + 916 | 6 | 00916 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo | 916 | 6 | 00916 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo + 926 | 6 | 00926 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo | 926 | 6 | 00926 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo + 936 | 6 | 00936 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo | 936 | 6 | 00936 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo + 946 | 6 | 00946 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo | 946 | 6 | 00946 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo + 956 | 6 | 00956 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo | 956 | 6 | 00956 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo + 966 | 6 | 00966 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo | 966 | 6 | 00966 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo + 976 | 6 | 00976 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo | 976 | 6 | 00976 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo + 986 | 6 | 00986 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo | 986 | 6 | 00986 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo + 996 | 6 | 00996 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo | 996 | 6 | 00996 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo +(100 rows) + +-- bug before 9.3.5 due to sloppy handling of remote-estimate parameters +SELECT * FROM ft1 WHERE c1 = ANY (ARRAY(SELECT c1 FROM ft2 WHERE c1 < 5)); + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +----+----+-------+------------------------------+--------------------------+----+------------+----- + 1 | 1 | 00001 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1 | 1 | foo + 2 | 2 | 00002 | Sat Jan 03 00:00:00 1970 PST | Sat Jan 03 00:00:00 1970 | 2 | 2 | foo + 3 | 3 | 00003 | Sun Jan 04 00:00:00 1970 PST | Sun Jan 04 00:00:00 1970 | 3 | 3 | foo + 4 | 4 | 00004 | Mon Jan 05 00:00:00 1970 PST | Mon Jan 05 00:00:00 1970 | 4 | 4 | foo +(4 rows) + +SELECT * FROM ft2 WHERE c1 = ANY (ARRAY(SELECT c1 FROM ft1 WHERE c1 < 5)); + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +----+----+-------+------------------------------+--------------------------+----+------------+----- + 1 | 1 | 00001 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1 | 1 | foo + 2 | 2 | 00002 | Sat Jan 03 00:00:00 1970 PST | Sat Jan 03 00:00:00 1970 | 2 | 2 | foo + 3 | 3 | 00003 | Sun Jan 04 00:00:00 1970 PST | Sun Jan 04 00:00:00 1970 | 3 | 3 | foo + 4 | 4 | 00004 | Mon Jan 05 00:00:00 1970 PST | Mon Jan 05 00:00:00 1970 | 4 | 4 | foo +(4 rows) + +-- we should not push order by clause with volatile expressions or unsafe +-- collations +EXPLAIN (VERBOSE, COSTS OFF) + SELECT * FROM ft2 ORDER BY ft2.c1, random(); + QUERY PLAN +---------------------------------------------------------------------------------------------------- + Sort + Output: c1, c2, c3, c4, c5, c6, c7, c8, (random()) + Sort Key: ft2.c1, (random()) + -> Foreign Scan on public.ft2 + Output: c1, c2, c3, c4, c5, c6, c7, c8, random() + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" + Row Adapter + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + -> CStore Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + +(15 rows) + +EXPLAIN (VERBOSE, COSTS OFF) + SELECT * FROM ft2 ORDER BY ft2.c1, ft2.c3 collate "C"; + QUERY PLAN +---------------------------------------------------------------------------------------------------- + Sort + Output: c1, c2, c3, c4, c5, c6, c7, c8, ((c3)::text) + Sort Key: ft2.c1, ft2.c3 COLLATE "C" + -> Foreign Scan on public.ft2 + Output: c1, c2, c3, c4, c5, c6, c7, c8, (c3)::text + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" + Row Adapter + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + -> CStore Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + +(15 rows) + +-- user-defined operator/function +CREATE FUNCTION postgres_fdw_abs(int) RETURNS int AS $$ +BEGIN +RETURN abs($1); +END +$$ LANGUAGE plpgsql IMMUTABLE; +CREATE OPERATOR === ( + LEFTARG = int, + RIGHTARG = int, + PROCEDURE = int4eq, + COMMUTATOR = === +); +-- built-in operators and functions can be shipped for remote execution +EXPLAIN (VERBOSE, COSTS OFF) + SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = abs(t1.c2); + QUERY PLAN +------------------------------------------------------------------------------------------------------ + Foreign Scan + Output: (count(c3)) + Node ID: 1 + Relations: Aggregate on (public.ft1 t1) + Remote SQL: SELECT count(c3) FROM "S 1"."T 1" WHERE (("C 1" = abs(c2))) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT count(c3) FROM "S 1"."T 1" WHERE (("C 1" = abs(c2))) + Row Adapter + Output: (count(c3)) + -> Vector Aggregate + Output: count(c3) + -> CStore Scan on "S 1"."T 1" + Output: c3, "C 1", c2 + Filter: ("T 1"."C 1" = abs("T 1".c2)) + +(16 rows) + +SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = abs(t1.c2); + count +------- + 9 +(1 row) + +EXPLAIN (VERBOSE, COSTS OFF) + SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = t1.c2; + QUERY PLAN +------------------------------------------------------------------------------------------------- + Foreign Scan + Output: (count(c3)) + Node ID: 1 + Relations: Aggregate on (public.ft1 t1) + Remote SQL: SELECT count(c3) FROM "S 1"."T 1" WHERE (("C 1" = c2)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT count(c3) FROM "S 1"."T 1" WHERE (("C 1" = c2)) + Row Adapter + Output: (count(c3)) + -> Vector Aggregate + Output: count(c3) + -> CStore Scan on "S 1"."T 1" + Output: c3, "C 1", c2 + Filter: ("T 1"."C 1" = "T 1".c2) + +(16 rows) + +SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = t1.c2; + count +------- + 9 +(1 row) + +-- by default, user-defined ones cannot +EXPLAIN (VERBOSE, COSTS OFF) + SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = postgres_fdw_abs(t1.c2); + QUERY PLAN +-------------------------------------------------------------------------------- + Aggregate + Output: count(c3) + -> Foreign Scan on public.ft1 t1 + Output: c3, c1, c2 + Filter: (t1.c1 = postgres_fdw_abs(t1.c2)) + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3 FROM "S 1"."T 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3 FROM "S 1"."T 1" + Row Adapter + Output: "C 1", c2, c3 + -> CStore Scan on "S 1"."T 1" + Output: "C 1", c2, c3 + +(15 rows) + +SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = postgres_fdw_abs(t1.c2); + count +------- + 9 +(1 row) + +EXPLAIN (VERBOSE, COSTS OFF) + SELECT count(c3) FROM ft1 t1 WHERE t1.c1 === t1.c2; + QUERY PLAN +-------------------------------------------------------------------------------- + Aggregate + Output: count(c3) + -> Foreign Scan on public.ft1 t1 + Output: c3 + Filter: (t1.c1 === t1.c2) + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3 FROM "S 1"."T 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3 FROM "S 1"."T 1" + Row Adapter + Output: "C 1", c2, c3 + -> CStore Scan on "S 1"."T 1" + Output: "C 1", c2, c3 + +(15 rows) + +SELECT count(c3) FROM ft1 t1 WHERE t1.c1 === t1.c2; + count +------- + 9 +(1 row) + +-- ORDER BY can be shipped, though +EXPLAIN (VERBOSE, COSTS OFF) + SELECT * FROM ft1 t1 WHERE t1.c1 === t1.c2 order by t1.c2 limit 1; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------- + Limit + Output: c1, c2, c3, c4, c5, c6, c7, c8 + -> Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Filter: (t1.c1 === t1.c2) + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" ORDER BY c2 ASC NULLS LAST + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" ORDER BY c2 ASC NULLS LAST + Row Adapter + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + -> Vector Sort + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Sort Key: "T 1".c2 + -> CStore Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + +(18 rows) + +SELECT * FROM ft1 t1 WHERE t1.c1 === t1.c2 order by t1.c2 limit 1; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +----+----+-------+------------------------------+--------------------------+----+------------+----- + 1 | 1 | 00001 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1 | 1 | foo +(1 row) + +-- but let's put them in an extension ... +ALTER EXTENSION postgres_fdw ADD FUNCTION postgres_fdw_abs(int); +ALTER EXTENSION postgres_fdw ADD OPERATOR === (int, int); +SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = postgres_fdw_abs(t1.c2); + count +------- + 9 +(1 row) + +SELECT count(c3) FROM ft1 t1 WHERE t1.c1 === t1.c2; + count +------- + 9 +(1 row) + +-- and both ORDER BY and LIMIT can be shipped +SELECT * FROM ft1 t1 WHERE t1.c1 === t1.c2 order by t1.c2 limit 1; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +----+----+-------+------------------------------+--------------------------+----+------------+----- + 1 | 1 | 00001 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1 | 1 | foo +(1 row) + +-- case when expr +explain (verbose, costs off) select * from ft1 where case c1 when 1 then 0 end = 0; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (((CASE "C 1" WHEN 1 THEN 0::numeric ELSE NULL::numeric END) = 0::numeric)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (((CASE "C 1" WHEN 1 THEN 0::numeric ELSE NULL::numeric END) = 0::numeric)) + Row Adapter + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + -> CStore Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Filter: (CASE "T 1"."C 1" WHEN 1 THEN 0::numeric ELSE NULL::numeric END = 0::numeric) + +(13 rows) + +select * from ft1 where case c1 when 1 then 0 end = 0; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +----+----+-------+------------------------------+--------------------------+----+------------+----- + 1 | 1 | 00001 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1 | 1 | foo +(1 row) + +-- ====================================================================================================================================== +-- TEST-MODULE: JOIN queries +-- -------------------------------------- +-- ====================================================================================================================================== +-- Analyze ft4 and ft5 so that we have better statistics. These tables do not +-- have use_remote_estimate set. +ANALYZE ft4; +ANALYZE ft5; +-- join two tables +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; -- Inaccurate + QUERY PLAN +---------------------------------------------------------------------------- + Limit + Output: t1.c1, t2.c1, t1.c3 + -> Sort + Output: t1.c1, t2.c1, t1.c3 + Sort Key: t1.c3, t1.c1 + -> Hash Join + Output: t1.c1, t2.c1, t1.c3 + Hash Cond: (t1.c1 = t2.c1) + -> Foreign Scan on public.ft1 t1 + Output: t1.c1, t1.c3 + Node ID: 1 + Remote SQL: SELECT "C 1", c3 FROM "S 1"."T 1" + -> Hash + Output: t2.c1 + -> Foreign Scan on public.ft2 t2 + Output: t2.c1 + Node ID: 2 + Remote SQL: SELECT "C 1" FROM "S 1"."T 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c3 FROM "S 1"."T 1" + Row Adapter + Output: "C 1", c3 + -> CStore Scan on "S 1"."T 1" + Output: "C 1", c3 + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1" FROM "S 1"."T 1" + Row Adapter + Output: "C 1" + -> CStore Scan on "S 1"."T 1" + Output: "C 1" + +(31 rows) + +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; + c1 | c1 +-----+----- + 101 | 101 + 102 | 102 + 103 | 103 + 104 | 104 + 105 | 105 + 106 | 106 + 107 | 107 + 108 | 108 + 109 | 109 + 110 | 110 +(10 rows) + +-- join three tables +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) JOIN ft4 t3 ON (t3.c1 = t1.c1) ORDER BY t1.c3, t1.c1 OFFSET 10 LIMIT 10; -- Inaccurate + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + Limit + Output: t1.c1, t2.c2, t3.c3, t1.c3 + -> Sort + Output: t1.c1, t2.c2, t3.c3, t1.c3 + Sort Key: t1.c3, t1.c1 + -> Hash Join + Output: t1.c1, t2.c2, t3.c3, t1.c3 + Hash Cond: (t1.c1 = t2.c1) + -> Foreign Scan on public.ft1 t1 + Output: t1.c1, t1.c3 + Node ID: 1 + Remote SQL: SELECT "C 1", c3 FROM "S 1"."T 1" + -> Hash + Output: t2.c2, t2.c1, t3.c3, t3.c1 + -> Foreign Scan + Output: t2.c2, t2.c1, t3.c3, t3.c1 + Node ID: 2 + Relations: (public.ft2 t2) INNER JOIN (public.ft4 t3) + Remote SQL: SELECT r2.c2, r2."C 1", r4.c3, r4.c1 FROM ("S 1"."T 1" r2 INNER JOIN "S 1"."T 3" r4 ON (((r2."C 1" = r4.c1)))) ORDER BY r2."C 1" ASC NULLS LAST + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c3 FROM "S 1"."T 1" + Row Adapter + Output: "C 1", c3 + -> CStore Scan on "S 1"."T 1" + Output: "C 1", c3 + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT r2.c2, r2."C 1", r4.c3, r4.c1 FROM ("S 1"."T 1" r2 INNER JOIN "S 1"."T 3" r4 ON (((r2."C 1" = r4.c1)))) ORDER BY r2."C 1" ASC NULLS LAST + Row Adapter + Output: r2.c2, r2."C 1", r4.c3, r4.c1 + -> Vector Sort + Output: r2.c2, r2."C 1", r4.c3, r4.c1 + Sort Key: r2."C 1" + -> Vector Sonic Hash Join + Output: r2.c2, r2."C 1", r4.c3, r4.c1 + Hash Cond: (r2."C 1" = r4.c1) + -> CStore Scan on "S 1"."T 1" r2 + Output: r2.c2, r2."C 1" + -> CStore Scan on "S 1"."T 3" r4 + Output: r4.c3, r4.c1 + +(40 rows) + +SELECT t1.c1, t2.c2, t3.c3 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) JOIN ft4 t3 ON (t3.c1 = t1.c1) ORDER BY t1.c3, t1.c1 OFFSET 10 LIMIT 10; + c1 | c2 | c3 +----+----+-------- + 22 | 2 | AAA022 + 24 | 4 | AAA024 + 26 | 6 | AAA026 + 28 | 8 | AAA028 + 30 | 0 | AAA030 + 32 | 2 | AAA032 + 34 | 4 | AAA034 + 36 | 6 | AAA036 + 38 | 8 | AAA038 + 40 | 0 | AAA040 +(10 rows) + +-- left outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: t1.c1, t2.c1 + Node ID: 1 + Relations: (public.ft4 t1) LEFT JOIN (public.ft5 t2) + Remote SQL: SELECT r1.c1, r2.c1 FROM ("S 1"."T 3" r1 LEFT JOIN "S 1"."T 4" r2 ON (((r1.c1 = r2.c1)))) ORDER BY r1.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT r1.c1, r2.c1 FROM ("S 1"."T 3" r1 LEFT JOIN "S 1"."T 4" r2 ON (((r1.c1 = r2.c1)))) ORDER BY r1.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint + Row Adapter + Output: r1.c1, r2.c1 + -> Vector Limit + Output: r1.c1, r2.c1 + -> Vector Sort + Output: r1.c1, r2.c1 + Sort Key: r1.c1, r2.c1 + -> Vector Hash Left Join + Output: r1.c1, r2.c1 + Hash Cond: (r1.c1 = r2.c1) + -> CStore Scan on "S 1"."T 3" r1 + Output: r1.c1 + -> CStore Scan on "S 1"."T 4" r2 + Output: r2.c1 + +(23 rows) + +SELECT t1.c1, t2.c1 FROM ft4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; + c1 | c1 +----+---- + 22 | + 24 | 24 + 26 | + 28 | + 30 | 30 + 32 | + 34 | + 36 | 36 + 38 | + 40 | +(10 rows) + +-- left outer join three tables +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) LEFT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: t1.c1, t2.c2, t3.c3 + Node ID: 1 + Relations: ((public.ft2 t1) LEFT JOIN (public.ft2 t2)) LEFT JOIN (public.ft4 t3) + Remote SQL: SELECT r1."C 1", r2.c2, r4.c3 FROM (("S 1"."T 1" r1 LEFT JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) LEFT JOIN "S 1"."T 3" r4 ON (((r2."C 1" = r4.c1)))) LIMIT 10::bigint OFFSET 10::bigint + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT r1."C 1", r2.c2, r4.c3 FROM (("S 1"."T 1" r1 LEFT JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) LEFT JOIN "S 1"."T 3" r4 ON (((r2."C 1" = r4.c1)))) LIMIT 10::bigint OFFSET 10::bigint + Row Adapter + Output: r1."C 1", r2.c2, r4.c3 + -> Vector Limit + Output: r1."C 1", r2.c2, r4.c3 + -> Vector Nest Loop Left Join + Output: r1."C 1", r2.c2, r4.c3 + Join Filter: (r2."C 1" = r4.c1) + -> Vector Nest Loop Left Join + Output: r1."C 1", r2.c2, r2."C 1" + Join Filter: (r1."C 1" = r2."C 1") + -> CStore Scan on "S 1"."T 1" r1 + Output: r1."C 1" + -> Vector Materialize + Output: r2.c2, r2."C 1" + -> CStore Scan on "S 1"."T 1" r2 + Output: r2.c2, r2."C 1" + -> Vector Materialize + Output: r4.c3, r4.c1 + -> CStore Scan on "S 1"."T 3" r4 + Output: r4.c3, r4.c1 + +(29 rows) + +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) LEFT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + c1 | c2 | c3 +----+----+-------- + 11 | 1 | + 12 | 2 | AAA012 + 13 | 3 | + 14 | 4 | AAA014 + 15 | 5 | + 16 | 6 | AAA016 + 17 | 7 | + 18 | 8 | AAA018 + 19 | 9 | + 20 | 0 | AAA020 +(10 rows) + +-- left outer join + placement of clauses. +-- clauses within the nullable side are not pulled up, but top level clause on +-- non-nullable side is pushed into non-nullable side +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t1.c2, t2.c1, t2.c2 FROM ft4 t1 LEFT JOIN (SELECT * FROM ft5 WHERE c1 < 10) t2 ON (t1.c1 = t2.c1) WHERE t1.c1 < 10; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + Foreign Scan + Output: t1.c1, t1.c2, ft5.c1, ft5.c2 + Node ID: 1 + Relations: (public.ft4 t1) LEFT JOIN (public.ft5) + Remote SQL: SELECT r1.c1, r1.c2, r4.c1, r4.c2 FROM ("S 1"."T 3" r1 LEFT JOIN "S 1"."T 4" r4 ON (((r1.c1 = r4.c1)) AND ((r4.c1 < 10)))) WHERE ((r1.c1 < 10)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT r1.c1, r1.c2, r4.c1, r4.c2 FROM ("S 1"."T 3" r1 LEFT JOIN "S 1"."T 4" r4 ON (((r1.c1 = r4.c1)) AND ((r4.c1 < 10)))) WHERE ((r1.c1 < 10)) + Row Adapter + Output: r1.c1, r1.c2, r4.c1, r4.c2 + -> Vector Nest Loop Left Join + Output: r1.c1, r1.c2, r4.c1, r4.c2 + Join Filter: (r1.c1 = r4.c1) + -> CStore Scan on "S 1"."T 3" r1 + Output: r1.c1, r1.c2 + Filter: (r1.c1 < 10) + -> Vector Materialize + Output: r4.c1, r4.c2 + -> CStore Scan on "S 1"."T 4" r4 + Output: r4.c1, r4.c2 + Filter: (r4.c1 < 10) + +(22 rows) + +SELECT t1.c1, t1.c2, t2.c1, t2.c2 FROM ft4 t1 LEFT JOIN (SELECT * FROM ft5 WHERE c1 < 10) t2 ON (t1.c1 = t2.c1) WHERE t1.c1 < 10; + c1 | c2 | c1 | c2 +----+----+----+---- + 2 | 3 | | + 4 | 5 | | + 6 | 7 | 6 | 7 + 8 | 9 | | +(4 rows) + +-- clauses within the nullable side are not pulled up, but the top level clause +-- on nullable side is not pushed down into nullable side +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t1.c2, t2.c1, t2.c2 FROM ft4 t1 LEFT JOIN (SELECT * FROM ft5 WHERE c1 < 10) t2 ON (t1.c1 = t2.c1) + WHERE (t2.c1 < 10 OR t2.c1 IS NULL) AND t1.c1 < 10; + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: t1.c1, t1.c2, ft5.c1, ft5.c2 + Node ID: 1 + Relations: (public.ft4 t1) LEFT JOIN (public.ft5) + Remote SQL: SELECT r1.c1, r1.c2, r4.c1, r4.c2 FROM ("S 1"."T 3" r1 LEFT JOIN "S 1"."T 4" r4 ON (((r1.c1 = r4.c1)) AND ((r4.c1 < 10)))) WHERE (((r4.c1 < 10) OR (r4.c1 IS NULL))) AND ((r1.c1 < 10)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT r1.c1, r1.c2, r4.c1, r4.c2 FROM ("S 1"."T 3" r1 LEFT JOIN "S 1"."T 4" r4 ON (((r1.c1 = r4.c1)) AND ((r4.c1 < 10)))) WHERE (((r4.c1 < 10) OR (r4.c1 IS NULL))) AND ((r1.c1 < 10)) + Row Adapter + Output: r1.c1, r1.c2, r4.c1, r4.c2 + -> Vector Nest Loop Left Join + Output: r1.c1, r1.c2, r4.c1, r4.c2 + Join Filter: (r1.c1 = r4.c1) + Filter: ((r4.c1 < 10) OR (r4.c1 IS NULL)) + -> CStore Scan on "S 1"."T 3" r1 + Output: r1.c1, r1.c2 + Filter: (r1.c1 < 10) + -> Vector Materialize + Output: r4.c1, r4.c2 + -> CStore Scan on "S 1"."T 4" r4 + Output: r4.c1, r4.c2 + Filter: (r4.c1 < 10) + +(23 rows) + +SELECT t1.c1, t1.c2, t2.c1, t2.c2 FROM ft4 t1 LEFT JOIN (SELECT * FROM ft5 WHERE c1 < 10) t2 ON (t1.c1 = t2.c1) + WHERE (t2.c1 < 10 OR t2.c1 IS NULL) AND t1.c1 < 10; + c1 | c2 | c1 | c2 +----+----+----+---- + 2 | 3 | | + 4 | 5 | | + 6 | 7 | 6 | 7 + 8 | 9 | | +(4 rows) + +-- right outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft5 t1 RIGHT JOIN ft4 t2 ON (t1.c1 = t2.c1) ORDER BY t2.c1, t1.c1 OFFSET 10 LIMIT 10; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: t1.c1, t2.c1 + Node ID: 1 + Relations: (public.ft4 t2) LEFT JOIN (public.ft5 t1) + Remote SQL: SELECT r1.c1, r2.c1 FROM ("S 1"."T 3" r2 LEFT JOIN "S 1"."T 4" r1 ON (((r1.c1 = r2.c1)))) ORDER BY r2.c1 ASC NULLS LAST, r1.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT r1.c1, r2.c1 FROM ("S 1"."T 3" r2 LEFT JOIN "S 1"."T 4" r1 ON (((r1.c1 = r2.c1)))) ORDER BY r2.c1 ASC NULLS LAST, r1.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint + Row Adapter + Output: r1.c1, r2.c1 + -> Vector Limit + Output: r1.c1, r2.c1 + -> Vector Sort + Output: r1.c1, r2.c1 + Sort Key: r2.c1, r1.c1 + -> Vector Hash Left Join + Output: r1.c1, r2.c1 + Hash Cond: (r2.c1 = r1.c1) + -> CStore Scan on "S 1"."T 3" r2 + Output: r2.c1 + -> CStore Scan on "S 1"."T 4" r1 + Output: r1.c1 + +(23 rows) + +SELECT t1.c1, t2.c1 FROM ft5 t1 RIGHT JOIN ft4 t2 ON (t1.c1 = t2.c1) ORDER BY t2.c1, t1.c1 OFFSET 10 LIMIT 10; + c1 | c1 +----+---- + | 22 + 24 | 24 + | 26 + | 28 + 30 | 30 + | 32 + | 34 + 36 | 36 + | 38 + | 40 +(10 rows) + +-- right outer join three tables +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 RIGHT JOIN ft2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: t1.c1, t2.c2, t3.c3 + Node ID: 1 + Relations: ((public.ft4 t3) LEFT JOIN (public.ft2 t2)) LEFT JOIN (public.ft2 t1) + Remote SQL: SELECT r1."C 1", r2.c2, r4.c3 FROM (("S 1"."T 3" r4 LEFT JOIN "S 1"."T 1" r2 ON (((r2."C 1" = r4.c1)))) LEFT JOIN "S 1"."T 1" r1 ON (((r1."C 1" = r2."C 1")))) LIMIT 10::bigint OFFSET 10::bigint + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT r1."C 1", r2.c2, r4.c3 FROM (("S 1"."T 3" r4 LEFT JOIN "S 1"."T 1" r2 ON (((r2."C 1" = r4.c1)))) LEFT JOIN "S 1"."T 1" r1 ON (((r1."C 1" = r2."C 1")))) LIMIT 10::bigint OFFSET 10::bigint + Row Adapter + Output: r1."C 1", r2.c2, r4.c3 + -> Vector Limit + Output: r1."C 1", r2.c2, r4.c3 + -> Vector Hash Right Join + Output: r1."C 1", r2.c2, r4.c3 + Hash Cond: (r1."C 1" = r2."C 1") + -> CStore Scan on "S 1"."T 1" r1 + Output: r1."C 1" + -> Vector Hash Right Join + Output: r4.c3, r2.c2, r2."C 1" + Hash Cond: (r2."C 1" = r4.c1) + -> CStore Scan on "S 1"."T 1" r2 + Output: r2.c2, r2."C 1" + -> CStore Scan on "S 1"."T 3" r4 + Output: r4.c3, r4.c1 + +(25 rows) + +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 RIGHT JOIN ft2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + c1 | c2 | c3 +----+----+-------- + 22 | 2 | AAA022 + 24 | 4 | AAA024 + 26 | 6 | AAA026 + 28 | 8 | AAA028 + 30 | 0 | AAA030 + 32 | 2 | AAA032 + 34 | 4 | AAA034 + 36 | 6 | AAA036 + 38 | 8 | AAA038 + 40 | 0 | AAA040 +(10 rows) + +-- full outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft4 t1 FULL JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 45 LIMIT 10; -- Inaccurate + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------ + Limit + Output: t1.c1, t2.c1 + -> Sort + Output: t1.c1, t2.c1 + Sort Key: t1.c1, t2.c1 + -> Foreign Scan + Output: t1.c1, t2.c1 + Node ID: 1 + Relations: (public.ft4 t1) FULL JOIN (public.ft5 t2) + Remote SQL: SELECT r1.c1, r2.c1 FROM ("S 1"."T 3" r1 FULL JOIN "S 1"."T 4" r2 ON (((r1.c1 = r2.c1)))) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT r1.c1, r2.c1 FROM ("S 1"."T 3" r1 FULL JOIN "S 1"."T 4" r2 ON (((r1.c1 = r2.c1)))) + Hash Full Join + Output: r1.c1, r2.c1 + Hash Cond: (r1.c1 = r2.c1) + -> Row Adapter + Output: r1.c1 + -> CStore Scan on "S 1"."T 3" r1 + Output: r1.c1 + -> Hash + Output: r2.c1 + -> Row Adapter + Output: r2.c1 + -> CStore Scan on "S 1"."T 4" r2 + Output: r2.c1 + +(27 rows) + +SELECT t1.c1, t2.c1 FROM ft4 t1 FULL JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 45 LIMIT 10; + c1 | c1 +-----+---- + 92 | + 94 | + 96 | 96 + 98 | + 100 | + | 3 + | 9 + | 15 + | 21 + | 27 +(10 rows) + +-- full outer join with restrictions on the joining relations +-- a. the joining relations are both base relations +EXPLAIN (VERBOSE, COSTS OFF) -- Inaccurate +SELECT t1.c1, t2.c1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + Sort + Output: ft4.c1, ft5.c1 + Sort Key: ft4.c1, ft5.c1 + -> Foreign Scan + Output: ft4.c1, ft5.c1 + Node ID: 1 + Relations: (public.ft4) FULL JOIN (public.ft5) + Remote SQL: SELECT s4.c1, s5.c1 FROM ((SELECT c1 FROM "S 1"."T 3" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s4(c1) FULL JOIN (SELECT c1 FROM "S 1"."T 4" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s5(c1) ON (((s4.c1 = s5.c1)))) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT s4.c1, s5.c1 FROM ((SELECT c1 FROM "S 1"."T 3" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s4(c1) FULL JOIN (SELECT c1 FROM "S 1"."T 4" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s5(c1) ON (((s4.c1 = s5.c1)))) + Hash Full Join + Output: "T 3".c1, "T 4".c1 + Hash Cond: ("T 3".c1 = "T 4".c1) + -> Row Adapter + Output: "T 3".c1 + -> CStore Scan on "S 1"."T 3" + Output: "T 3".c1 + Filter: (("T 3".c1 >= 50) AND ("T 3".c1 <= 60)) + -> Hash + Output: "T 4".c1 + -> Row Adapter + Output: "T 4".c1 + -> CStore Scan on "S 1"."T 4" + Output: "T 4".c1 + Filter: (("T 4".c1 >= 50) AND ("T 4".c1 <= 60)) + +(27 rows) + +SELECT t1.c1, t2.c1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1; + c1 | c1 +----+---- + 50 | + 52 | + 54 | 54 + 56 | + 58 | + 60 | 60 + | 51 + | 57 +(8 rows) + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT 1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t2 ON (TRUE) OFFSET 10 LIMIT 10; + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: 1 + Node ID: 1 + Relations: (public.ft4) FULL JOIN (public.ft5) + Remote SQL: SELECT NULL FROM ((SELECT NULL FROM "S 1"."T 3" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s4 FULL JOIN (SELECT NULL FROM "S 1"."T 4" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s5 ON (TRUE)) LIMIT 10::bigint OFFSET 10::bigint + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT NULL FROM ((SELECT NULL FROM "S 1"."T 3" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s4 FULL JOIN (SELECT NULL FROM "S 1"."T 4" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s5 ON (TRUE)) LIMIT 10::bigint OFFSET 10::bigint + Row Adapter + Output: (NULL::text) + -> Vector Limit + Output: (NULL::text) + -> Vector Subquery Scan on subquery + Output: NULL::text + -> Vector Result + Output: (NULL::text), (NULL::text) + -> Vector Append + -> Vector Nest Loop Left Join + Output: (NULL::text), (NULL::text) + -> CStore Scan on "S 1"."T 3" + Output: NULL::text + Filter: (("S 1"."T 3".c1 >= 50) AND ("S 1"."T 3".c1 <= 60)) + -> Vector Materialize + Output: (NULL::text) + -> CStore Scan on "S 1"."T 4" + Output: NULL::text + Filter: (("S 1"."T 4".c1 >= 50) AND ("S 1"."T 4".c1 <= 60)) + -> Vector Nest Loop Left Anti Full Join + Output: (NULL::text), (NULL::text) + -> CStore Scan on "S 1"."T 4" + Output: NULL::text + Filter: (("S 1"."T 4".c1 >= 50) AND ("S 1"."T 4".c1 <= 60)) + -> Vector Materialize + Output: (NULL::text) + -> CStore Scan on "S 1"."T 3" + Output: NULL::text + Filter: (("S 1"."T 3".c1 >= 50) AND ("S 1"."T 3".c1 <= 60)) + +(38 rows) + +SELECT 1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t2 ON (TRUE) OFFSET 10 LIMIT 10; + ?column? +---------- + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 +(10 rows) + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT count(*) FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t2 ON (TRUE) OFFSET 10 LIMIT 2; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Limit + Output: (count(*)) + -> Aggregate + Output: count(*) + -> Foreign Scan + Node ID: 1 + Relations: (public.ft4) FULL JOIN (public.ft5) + Remote SQL: SELECT NULL FROM ((SELECT NULL FROM "S 1"."T 3" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s4 FULL JOIN (SELECT NULL FROM "S 1"."T 4" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s5 ON (TRUE)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT NULL FROM ((SELECT NULL FROM "S 1"."T 3" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s4 FULL JOIN (SELECT NULL FROM "S 1"."T 4" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s5 ON (TRUE)) + Row Adapter + Output: (NULL::text) + -> Vector Subquery Scan on subquery + Output: NULL::text + -> Vector Result + Output: (NULL::text), (NULL::text) + -> Vector Append + -> Vector Nest Loop Left Join + Output: (NULL::text), (NULL::text) + -> CStore Scan on "S 1"."T 3" + Output: NULL::text + Filter: (("S 1"."T 3".c1 >= 50) AND ("S 1"."T 3".c1 <= 60)) + -> Vector Materialize + Output: (NULL::text) + -> CStore Scan on "S 1"."T 4" + Output: NULL::text + Filter: (("S 1"."T 4".c1 >= 50) AND ("S 1"."T 4".c1 <= 60)) + -> Vector Nest Loop Left Anti Full Join + Output: (NULL::text), (NULL::text) + -> CStore Scan on "S 1"."T 4" + Output: NULL::text + Filter: (("S 1"."T 4".c1 >= 50) AND ("S 1"."T 4".c1 <= 60)) + -> Vector Materialize + Output: (NULL::text) + -> CStore Scan on "S 1"."T 3" + Output: NULL::text + Filter: (("S 1"."T 3".c1 >= 50) AND ("S 1"."T 3".c1 <= 60)) + +(39 rows) + +SELECT count(*) FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t2 ON (TRUE) OFFSET 10 LIMIT 2; + count +------- +(0 rows) + +-- b. one of the joining relations is a base relation and the other is a join +-- relation +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, ss.a, ss.b FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT t2.c1, t3.c1 FROM ft4 t2 LEFT JOIN ft5 t3 ON (t2.c1 = t3.c1) WHERE (t2.c1 between 50 and 60)) ss(a, b) ON (t1.c1 = ss.a) ORDER BY t1.c1, ss.a, ss.b; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Sort + Output: ft4.c1, t2.c1, t3.c1 + Sort Key: ft4.c1, t2.c1, t3.c1 + -> Foreign Scan + Output: ft4.c1, t2.c1, t3.c1 + Node ID: 1 + Relations: (public.ft4) FULL JOIN ((public.ft4 t2) LEFT JOIN (public.ft5 t3)) + Remote SQL: SELECT s4.c1, s8.c1, s8.c2 FROM ((SELECT c1 FROM "S 1"."T 3" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s4(c1) FULL JOIN (SELECT r5.c1, r6.c1 FROM ("S 1"."T 3" r5 LEFT JOIN "S 1"."T 4" r6 ON (((r5.c1 = r6.c1)))) WHERE ((r5.c1 >= 50)) AND ((r5.c1 <= 60))) s8(c1, c2) ON (((s4.c1 = s8.c1)))) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT s4.c1, s8.c1, s8.c2 FROM ((SELECT c1 FROM "S 1"."T 3" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s4(c1) FULL JOIN (SELECT r5.c1, r6.c1 FROM ("S 1"."T 3" r5 LEFT JOIN "S 1"."T 4" r6 ON (((r5.c1 = r6.c1)))) WHERE ((r5.c1 >= 50)) AND ((r5.c1 <= 60))) s8(c1, c2) ON (((s4.c1 = s8.c1)))) + Hash Full Join + Output: "T 3".c1, r5.c1, r6.c1 + Hash Cond: ("T 3".c1 = r5.c1) + -> Row Adapter + Output: "T 3".c1 + -> CStore Scan on "S 1"."T 3" + Output: "T 3".c1 + Filter: (("T 3".c1 >= 50) AND ("T 3".c1 <= 60)) + -> Hash + Output: r5.c1, r6.c1 + -> Hash Left Join + Output: r5.c1, r6.c1 + Hash Cond: (r5.c1 = r6.c1) + -> Row Adapter + Output: r5.c1 + -> CStore Scan on "S 1"."T 3" r5 + Output: r5.c1 + Filter: ((r5.c1 >= 50) AND (r5.c1 <= 60)) + -> Hash + Output: r6.c1 + -> Row Adapter + Output: r6.c1 + -> CStore Scan on "S 1"."T 4" r6 + Output: r6.c1 + +(36 rows) + +SELECT t1.c1, ss.a, ss.b FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT t2.c1, t3.c1 FROM ft4 t2 LEFT JOIN ft5 t3 ON (t2.c1 = t3.c1) WHERE (t2.c1 between 50 and 60)) ss(a, b) ON (t1.c1 = ss.a) ORDER BY t1.c1, ss.a, ss.b; + c1 | a | b +----+----+---- + 50 | 50 | + 52 | 52 | + 54 | 54 | 54 + 56 | 56 | + 58 | 58 | + 60 | 60 | 60 +(6 rows) + +-- c. test deparsing the remote query as nested subqueries +EXPLAIN (VERBOSE, COSTS OFF) -- Inaccurate +SELECT t1.c1, ss.a, ss.b FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT t2.c1, t3.c1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t2 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t3 ON (t2.c1 = t3.c1) WHERE t2.c1 IS NULL OR t2.c1 IS NOT NULL) ss(a, b) ON (t1.c1 = ss.a) ORDER BY t1.c1, ss.a, ss.b; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Sort + Output: public.ft4.c1, public.ft4.c1, ft5.c1 + Sort Key: public.ft4.c1, public.ft4.c1, ft5.c1 + -> Foreign Scan + Output: public.ft4.c1, public.ft4.c1, ft5.c1 + Node ID: 1 + Relations: (public.ft4) FULL JOIN ((public.ft4) FULL JOIN (public.ft5)) + Remote SQL: SELECT s4.c1, s10.c1, s10.c2 FROM ((SELECT c1 FROM "S 1"."T 3" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s4(c1) FULL JOIN (SELECT s8.c1, s9.c1 FROM ((SELECT c1 FROM "S 1"."T 3" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s8(c1) FULL JOIN (SELECT c1 FROM "S 1"."T 4" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s9(c1) ON (((s8.c1 = s9.c1)))) WHERE (((s8.c1 IS NULL) OR (s8.c1 IS NOT NULL)))) s10(c1, c2) ON (((s4.c1 = s10.c1)))) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT s4.c1, s10.c1, s10.c2 FROM ((SELECT c1 FROM "S 1"."T 3" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s4(c1) FULL JOIN (SELECT s8.c1, s9.c1 FROM ((SELECT c1 FROM "S 1"."T 3" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s8(c1) FULL JOIN (SELECT c1 FROM "S 1"."T 4" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s9(c1) ON (((s8.c1 = s9.c1)))) WHERE (((s8.c1 IS NULL) OR (s8.c1 IS NOT NULL)))) s10(c1, c2) ON (((s4.c1 = s10.c1)))) + Hash Full Join + Output: "S 1"."T 3".c1, "S 1"."T 3".c1, "T 4".c1 + Hash Cond: ("S 1"."T 3".c1 = "S 1"."T 3".c1) + -> Row Adapter + Output: "S 1"."T 3".c1 + -> CStore Scan on "S 1"."T 3" + Output: "S 1"."T 3".c1 + Filter: (("S 1"."T 3".c1 >= 50) AND ("S 1"."T 3".c1 <= 60)) + -> Hash + Output: "S 1"."T 3".c1, "T 4".c1 + -> Hash Full Join + Output: "S 1"."T 3".c1, "T 4".c1 + Hash Cond: ("S 1"."T 3".c1 = "T 4".c1) + Filter: (("S 1"."T 3".c1 IS NULL) OR ("S 1"."T 3".c1 IS NOT NULL)) + -> Row Adapter + Output: "S 1"."T 3".c1 + -> CStore Scan on "S 1"."T 3" + Output: "S 1"."T 3".c1 + Filter: (("S 1"."T 3".c1 >= 50) AND ("S 1"."T 3".c1 <= 60)) + -> Hash + Output: "T 4".c1 + -> Row Adapter + Output: "T 4".c1 + -> CStore Scan on "S 1"."T 4" + Output: "T 4".c1 + Filter: (("T 4".c1 >= 50) AND ("T 4".c1 <= 60)) + +(38 rows) + +SELECT t1.c1, ss.a, ss.b FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT t2.c1, t3.c1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t2 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t3 ON (t2.c1 = t3.c1) WHERE t2.c1 IS NULL OR t2.c1 IS NOT NULL) ss(a, b) ON (t1.c1 = ss.a) ORDER BY t1.c1, ss.a, ss.b; + c1 | a | b +----+----+---- + 50 | 50 | + 52 | 52 | + 54 | 54 | 54 + 56 | 56 | + 58 | 58 | + 60 | 60 | 60 + | | 51 + | | 57 +(8 rows) + +-- d. test deparsing rowmarked relations as subqueries +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, ss.a, ss.b FROM (SELECT c1 FROM "S 1"."T 3" WHERE c1 = 50) t1 INNER JOIN (SELECT t2.c1, t3.c1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t2 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t3 ON (t2.c1 = t3.c1) WHERE t2.c1 IS NULL OR t2.c1 IS NOT NULL) ss(a, b) ON (TRUE) ORDER BY t1.c1, ss.a, ss.b FOR UPDATE OF t1; +ERROR: SELECT FOR UPDATE/SHARE/NO KEY UPDATE/KEY SHARE cannot be used with column table "T 3" +SELECT t1.c1, ss.a, ss.b FROM (SELECT c1 FROM "S 1"."T 3" WHERE c1 = 50) t1 INNER JOIN (SELECT t2.c1, t3.c1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t2 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t3 ON (t2.c1 = t3.c1) WHERE t2.c1 IS NULL OR t2.c1 IS NOT NULL) ss(a, b) ON (TRUE) ORDER BY t1.c1, ss.a, ss.b FOR UPDATE OF t1; +ERROR: SELECT FOR UPDATE/SHARE/NO KEY UPDATE/KEY SHARE cannot be used with column table "T 3" +-- full outer join + inner join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1, t3.c1 FROM ft4 t1 INNER JOIN ft5 t2 ON (t1.c1 = t2.c1 + 1 and t1.c1 between 50 and 60) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) ORDER BY t1.c1, t2.c1, t3.c1 LIMIT 10; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: t1.c1, t2.c1, t3.c1 + Node ID: 1 + Relations: ((public.ft4 t1) INNER JOIN (public.ft5 t2)) FULL JOIN (public.ft4 t3) + Remote SQL: SELECT r1.c1, r2.c1, r4.c1 FROM (("S 1"."T 3" r1 INNER JOIN "S 1"."T 4" r2 ON (((r1.c1 = (r2.c1 + 1))) AND (((r2.c1 + 1) >= 50)) AND (((r2.c1 + 1) <= 60)) AND ((r1.c1 >= 50)) AND ((r1.c1 <= 60)))) FULL JOIN "S 1"."T 3" r4 ON (((r2.c1 = r4.c1)))) ORDER BY r1.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST, r4.c1 ASC NULLS LAST LIMIT 10::bigint + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT r1.c1, r2.c1, r4.c1 FROM (("S 1"."T 3" r1 INNER JOIN "S 1"."T 4" r2 ON (((r1.c1 = (r2.c1 + 1))) AND (((r2.c1 + 1) >= 50)) AND (((r2.c1 + 1) <= 60)) AND ((r1.c1 >= 50)) AND ((r1.c1 <= 60)))) FULL JOIN "S 1"."T 3" r4 ON (((r2.c1 = r4.c1)))) ORDER BY r1.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST, r4.c1 ASC NULLS LAST LIMIT 10::bigint + Limit + Output: r1.c1, r2.c1, r4.c1 + -> Sort + Output: r1.c1, r2.c1, r4.c1 + Sort Key: r1.c1, r2.c1, r4.c1 + -> Hash Full Join + Output: r1.c1, r2.c1, r4.c1 + Hash Cond: (r2.c1 = r4.c1) + -> Nested Loop + Output: r1.c1, r2.c1 + Join Filter: (r1.c1 = (r2.c1 + 1)) + -> Row Adapter + Output: r2.c1 + -> CStore Scan on "S 1"."T 4" r2 + Output: r2.c1 + Filter: (((r2.c1 + 1) >= 50) AND ((r2.c1 + 1) <= 60)) + -> Row Adapter + Output: r1.c1 + -> CStore Scan on "S 1"."T 3" r1 + Output: r1.c1 + Filter: ((r1.c1 >= 50) AND (r1.c1 <= 60)) + -> Hash + Output: r4.c1 + -> Row Adapter + Output: r4.c1 + -> CStore Scan on "S 1"."T 3" r4 + Output: r4.c1 + +(36 rows) + +SELECT t1.c1, t2.c1, t3.c1 FROM ft4 t1 INNER JOIN ft5 t2 ON (t1.c1 = t2.c1 + 1 and t1.c1 between 50 and 60) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) ORDER BY t1.c1, t2.c1, t3.c1 LIMIT 10; + c1 | c1 | c1 +----+----+---- + 52 | 51 | + 58 | 57 | + | | 2 + | | 4 + | | 6 + | | 8 + | | 10 + | | 12 + | | 14 + | | 16 +(10 rows) + +-- full outer join three tables +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: t1.c1, t2.c2, t3.c3 + Node ID: 1 + Relations: ((public.ft2 t1) FULL JOIN (public.ft2 t2)) FULL JOIN (public.ft4 t3) + Remote SQL: SELECT r1."C 1", r2.c2, r4.c3 FROM (("S 1"."T 1" r1 FULL JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) FULL JOIN "S 1"."T 3" r4 ON (((r2."C 1" = r4.c1)))) LIMIT 10::bigint OFFSET 10::bigint + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT r1."C 1", r2.c2, r4.c3 FROM (("S 1"."T 1" r1 FULL JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) FULL JOIN "S 1"."T 3" r4 ON (((r2."C 1" = r4.c1)))) LIMIT 10::bigint OFFSET 10::bigint + Limit + Output: r1."C 1", r2.c2, r4.c3 + -> Hash Full Join + Output: r1."C 1", r2.c2, r4.c3 + Hash Cond: (r2."C 1" = r4.c1) + -> Hash Full Join + Output: r1."C 1", r2.c2, r2."C 1" + Hash Cond: (r1."C 1" = r2."C 1") + -> Row Adapter + Output: r1."C 1" + -> CStore Scan on "S 1"."T 1" r1 + Output: r1."C 1" + -> Hash + Output: r2.c2, r2."C 1" + -> Row Adapter + Output: r2.c2, r2."C 1" + -> CStore Scan on "S 1"."T 1" r2 + Output: r2.c2, r2."C 1" + -> Hash + Output: r4.c3, r4.c1 + -> Row Adapter + Output: r4.c3, r4.c1 + -> CStore Scan on "S 1"."T 3" r4 + Output: r4.c3, r4.c1 + +(33 rows) + +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + c1 | c2 | c3 +----+----+-------- + 11 | 1 | + 12 | 2 | AAA012 + 13 | 3 | + 14 | 4 | AAA014 + 15 | 5 | + 16 | 6 | AAA016 + 17 | 7 | + 18 | 8 | AAA018 + 19 | 9 | + 20 | 0 | AAA020 +(10 rows) + +-- full outer join + right outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: t1.c1, t2.c2, t3.c3 + Node ID: 1 + Relations: ((public.ft4 t3) LEFT JOIN (public.ft2 t2)) LEFT JOIN (public.ft2 t1) + Remote SQL: SELECT r1."C 1", r2.c2, r4.c3 FROM (("S 1"."T 3" r4 LEFT JOIN "S 1"."T 1" r2 ON (((r2."C 1" = r4.c1)))) LEFT JOIN "S 1"."T 1" r1 ON (((r1."C 1" = r2."C 1")))) LIMIT 10::bigint OFFSET 10::bigint + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT r1."C 1", r2.c2, r4.c3 FROM (("S 1"."T 3" r4 LEFT JOIN "S 1"."T 1" r2 ON (((r2."C 1" = r4.c1)))) LEFT JOIN "S 1"."T 1" r1 ON (((r1."C 1" = r2."C 1")))) LIMIT 10::bigint OFFSET 10::bigint + Row Adapter + Output: r1."C 1", r2.c2, r4.c3 + -> Vector Limit + Output: r1."C 1", r2.c2, r4.c3 + -> Vector Hash Right Join + Output: r1."C 1", r2.c2, r4.c3 + Hash Cond: (r1."C 1" = r2."C 1") + -> CStore Scan on "S 1"."T 1" r1 + Output: r1."C 1" + -> Vector Hash Right Join + Output: r4.c3, r2.c2, r2."C 1" + Hash Cond: (r2."C 1" = r4.c1) + -> CStore Scan on "S 1"."T 1" r2 + Output: r2.c2, r2."C 1" + -> CStore Scan on "S 1"."T 3" r4 + Output: r4.c3, r4.c1 + +(25 rows) + +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + c1 | c2 | c3 +----+----+-------- + 22 | 2 | AAA022 + 24 | 4 | AAA024 + 26 | 6 | AAA026 + 28 | 8 | AAA028 + 30 | 0 | AAA030 + 32 | 2 | AAA032 + 34 | 4 | AAA034 + 36 | 6 | AAA036 + 38 | 8 | AAA038 + 40 | 0 | AAA040 +(10 rows) + +-- right outer join + full outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 RIGHT JOIN ft2 t2 ON (t1.c1 = t2.c1) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: t1.c1, t2.c2, t3.c3 + Node ID: 1 + Relations: ((public.ft2 t2) LEFT JOIN (public.ft2 t1)) FULL JOIN (public.ft4 t3) + Remote SQL: SELECT r1."C 1", r2.c2, r4.c3 FROM (("S 1"."T 1" r2 LEFT JOIN "S 1"."T 1" r1 ON (((r1."C 1" = r2."C 1")))) FULL JOIN "S 1"."T 3" r4 ON (((r2."C 1" = r4.c1)))) LIMIT 10::bigint OFFSET 10::bigint + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT r1."C 1", r2.c2, r4.c3 FROM (("S 1"."T 1" r2 LEFT JOIN "S 1"."T 1" r1 ON (((r1."C 1" = r2."C 1")))) FULL JOIN "S 1"."T 3" r4 ON (((r2."C 1" = r4.c1)))) LIMIT 10::bigint OFFSET 10::bigint + Limit + Output: r1."C 1", r2.c2, r4.c3 + -> Hash Full Join + Output: r1."C 1", r2.c2, r4.c3 + Hash Cond: (r2."C 1" = r4.c1) + -> Nested Loop Left Join + Output: r2.c2, r2."C 1", r1."C 1" + Join Filter: (r1."C 1" = r2."C 1") + -> Row Adapter + Output: r2.c2, r2."C 1" + -> CStore Scan on "S 1"."T 1" r2 + Output: r2.c2, r2."C 1" + -> Materialize + Output: r1."C 1" + -> Row Adapter + Output: r1."C 1" + -> CStore Scan on "S 1"."T 1" r1 + Output: r1."C 1" + -> Hash + Output: r4.c3, r4.c1 + -> Row Adapter + Output: r4.c3, r4.c1 + -> CStore Scan on "S 1"."T 3" r4 + Output: r4.c3, r4.c1 + +(33 rows) + +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 RIGHT JOIN ft2 t2 ON (t1.c1 = t2.c1) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + c1 | c2 | c3 +----+----+-------- + 11 | 1 | + 12 | 2 | AAA012 + 13 | 3 | + 14 | 4 | AAA014 + 15 | 5 | + 16 | 6 | AAA016 + 17 | 7 | + 18 | 8 | AAA018 + 19 | 9 | + 20 | 0 | AAA020 +(10 rows) + +-- full outer join + left outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) LEFT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: t1.c1, t2.c2, t3.c3 + Node ID: 1 + Relations: ((public.ft2 t1) FULL JOIN (public.ft2 t2)) LEFT JOIN (public.ft4 t3) + Remote SQL: SELECT r1."C 1", r2.c2, r4.c3 FROM (("S 1"."T 1" r1 FULL JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) LEFT JOIN "S 1"."T 3" r4 ON (((r2."C 1" = r4.c1)))) LIMIT 10::bigint OFFSET 10::bigint + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT r1."C 1", r2.c2, r4.c3 FROM (("S 1"."T 1" r1 FULL JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) LEFT JOIN "S 1"."T 3" r4 ON (((r2."C 1" = r4.c1)))) LIMIT 10::bigint OFFSET 10::bigint + Limit + Output: r1."C 1", r2.c2, r4.c3 + -> Nested Loop Left Join + Output: r1."C 1", r2.c2, r4.c3 + Join Filter: (r2."C 1" = r4.c1) + -> Hash Full Join + Output: r1."C 1", r2.c2, r2."C 1" + Hash Cond: (r1."C 1" = r2."C 1") + -> Row Adapter + Output: r1."C 1" + -> CStore Scan on "S 1"."T 1" r1 + Output: r1."C 1" + -> Hash + Output: r2.c2, r2."C 1" + -> Row Adapter + Output: r2.c2, r2."C 1" + -> CStore Scan on "S 1"."T 1" r2 + Output: r2.c2, r2."C 1" + -> Materialize + Output: r4.c3, r4.c1 + -> Row Adapter + Output: r4.c3, r4.c1 + -> CStore Scan on "S 1"."T 3" r4 + Output: r4.c3, r4.c1 + +(33 rows) + +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) LEFT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + c1 | c2 | c3 +----+----+-------- + 11 | 1 | + 12 | 2 | AAA012 + 13 | 3 | + 14 | 4 | AAA014 + 15 | 5 | + 16 | 6 | AAA016 + 17 | 7 | + 18 | 8 | AAA018 + 19 | 9 | + 20 | 0 | AAA020 +(10 rows) + +-- left outer join + full outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: t1.c1, t2.c2, t3.c3 + Node ID: 1 + Relations: ((public.ft2 t1) LEFT JOIN (public.ft2 t2)) FULL JOIN (public.ft4 t3) + Remote SQL: SELECT r1."C 1", r2.c2, r4.c3 FROM (("S 1"."T 1" r1 LEFT JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) FULL JOIN "S 1"."T 3" r4 ON (((r2."C 1" = r4.c1)))) LIMIT 10::bigint OFFSET 10::bigint + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT r1."C 1", r2.c2, r4.c3 FROM (("S 1"."T 1" r1 LEFT JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) FULL JOIN "S 1"."T 3" r4 ON (((r2."C 1" = r4.c1)))) LIMIT 10::bigint OFFSET 10::bigint + Limit + Output: r1."C 1", r2.c2, r4.c3 + -> Hash Full Join + Output: r1."C 1", r2.c2, r4.c3 + Hash Cond: (r2."C 1" = r4.c1) + -> Nested Loop Left Join + Output: r1."C 1", r2.c2, r2."C 1" + Join Filter: (r1."C 1" = r2."C 1") + -> Row Adapter + Output: r1."C 1" + -> CStore Scan on "S 1"."T 1" r1 + Output: r1."C 1" + -> Materialize + Output: r2.c2, r2."C 1" + -> Row Adapter + Output: r2.c2, r2."C 1" + -> CStore Scan on "S 1"."T 1" r2 + Output: r2.c2, r2."C 1" + -> Hash + Output: r4.c3, r4.c1 + -> Row Adapter + Output: r4.c3, r4.c1 + -> CStore Scan on "S 1"."T 3" r4 + Output: r4.c3, r4.c1 + +(33 rows) + +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + c1 | c2 | c3 +----+----+-------- + 11 | 1 | + 12 | 2 | AAA012 + 13 | 3 | + 14 | 4 | AAA014 + 15 | 5 | + 16 | 6 | AAA016 + 17 | 7 | + 18 | 8 | AAA018 + 19 | 9 | + 20 | 0 | AAA020 +(10 rows) + +-- right outer join + left outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 RIGHT JOIN ft2 t2 ON (t1.c1 = t2.c1) LEFT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: t1.c1, t2.c2, t3.c3 + Node ID: 1 + Relations: ((public.ft2 t2) LEFT JOIN (public.ft2 t1)) LEFT JOIN (public.ft4 t3) + Remote SQL: SELECT r1."C 1", r2.c2, r4.c3 FROM (("S 1"."T 1" r2 LEFT JOIN "S 1"."T 1" r1 ON (((r1."C 1" = r2."C 1")))) LEFT JOIN "S 1"."T 3" r4 ON (((r2."C 1" = r4.c1)))) LIMIT 10::bigint OFFSET 10::bigint + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT r1."C 1", r2.c2, r4.c3 FROM (("S 1"."T 1" r2 LEFT JOIN "S 1"."T 1" r1 ON (((r1."C 1" = r2."C 1")))) LEFT JOIN "S 1"."T 3" r4 ON (((r2."C 1" = r4.c1)))) LIMIT 10::bigint OFFSET 10::bigint + Row Adapter + Output: r1."C 1", r2.c2, r4.c3 + -> Vector Limit + Output: r1."C 1", r2.c2, r4.c3 + -> Vector Nest Loop Left Join + Output: r1."C 1", r2.c2, r4.c3 + Join Filter: (r2."C 1" = r4.c1) + -> Vector Nest Loop Left Join + Output: r2.c2, r2."C 1", r1."C 1" + Join Filter: (r1."C 1" = r2."C 1") + -> CStore Scan on "S 1"."T 1" r2 + Output: r2.c2, r2."C 1" + -> Vector Materialize + Output: r1."C 1" + -> CStore Scan on "S 1"."T 1" r1 + Output: r1."C 1" + -> Vector Materialize + Output: r4.c3, r4.c1 + -> CStore Scan on "S 1"."T 3" r4 + Output: r4.c3, r4.c1 + +(29 rows) + +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 RIGHT JOIN ft2 t2 ON (t1.c1 = t2.c1) LEFT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + c1 | c2 | c3 +----+----+-------- + 11 | 1 | + 12 | 2 | AAA012 + 13 | 3 | + 14 | 4 | AAA014 + 15 | 5 | + 16 | 6 | AAA016 + 17 | 7 | + 18 | 8 | AAA018 + 19 | 9 | + 20 | 0 | AAA020 +(10 rows) + +-- left outer join + right outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: t1.c1, t2.c2, t3.c3 + Node ID: 1 + Relations: (public.ft4 t3) LEFT JOIN ((public.ft2 t1) INNER JOIN (public.ft2 t2)) + Remote SQL: SELECT r1."C 1", r2.c2, r4.c3 FROM ("S 1"."T 3" r4 LEFT JOIN ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) ON (((r2."C 1" = r4.c1)))) LIMIT 10::bigint OFFSET 10::bigint + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT r1."C 1", r2.c2, r4.c3 FROM ("S 1"."T 3" r4 LEFT JOIN ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) ON (((r2."C 1" = r4.c1)))) LIMIT 10::bigint OFFSET 10::bigint + Row Adapter + Output: r1."C 1", r2.c2, r4.c3 + -> Vector Limit + Output: r1."C 1", r2.c2, r4.c3 + -> Vector Hash Right Join + Output: r1."C 1", r2.c2, r4.c3 + Hash Cond: (r2."C 1" = r4.c1) + -> Vector Sonic Hash Join + Output: r1."C 1", r2.c2, r2."C 1" + Hash Cond: (r1."C 1" = r2."C 1") + -> CStore Scan on "S 1"."T 1" r1 + Output: r1."C 1" + -> CStore Scan on "S 1"."T 1" r2 + Output: r2.c2, r2."C 1" + -> CStore Scan on "S 1"."T 3" r4 + Output: r4.c3, r4.c1 + +(25 rows) + +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + c1 | c2 | c3 +----+----+-------- + 56 | 6 | AAA056 + 58 | 8 | AAA058 + 60 | 0 | AAA060 + 64 | 4 | AAA064 + 68 | 8 | AAA068 + 70 | 0 | AAA070 + 84 | 4 | AAA084 + 12 | 2 | AAA012 + 22 | 2 | AAA022 + 30 | 0 | AAA030 +(10 rows) + +-- full outer join + WHERE clause, only matched rows +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft4 t1 FULL JOIN ft5 t2 ON (t1.c1 = t2.c1) WHERE (t1.c1 = t2.c1 OR t1.c1 IS NULL) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Limit + Output: t1.c1, t2.c1 + -> Sort + Output: t1.c1, t2.c1 + Sort Key: t1.c1, t2.c1 + -> Foreign Scan + Output: t1.c1, t2.c1 + Node ID: 1 + Relations: (public.ft4 t1) FULL JOIN (public.ft5 t2) + Remote SQL: SELECT r1.c1, r2.c1 FROM ("S 1"."T 3" r1 FULL JOIN "S 1"."T 4" r2 ON (((r1.c1 = r2.c1)))) WHERE (((r1.c1 = r2.c1) OR (r1.c1 IS NULL))) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT r1.c1, r2.c1 FROM ("S 1"."T 3" r1 FULL JOIN "S 1"."T 4" r2 ON (((r1.c1 = r2.c1)))) WHERE (((r1.c1 = r2.c1) OR (r1.c1 IS NULL))) + Hash Full Join + Output: r1.c1, r2.c1 + Hash Cond: (r1.c1 = r2.c1) + Filter: ((r1.c1 = r2.c1) OR (r1.c1 IS NULL)) + -> Row Adapter + Output: r1.c1 + -> CStore Scan on "S 1"."T 3" r1 + Output: r1.c1 + -> Hash + Output: r2.c1 + -> Row Adapter + Output: r2.c1 + -> CStore Scan on "S 1"."T 4" r2 + Output: r2.c1 + +(28 rows) + +SELECT t1.c1, t2.c1 FROM ft4 t1 FULL JOIN ft5 t2 ON (t1.c1 = t2.c1) WHERE (t1.c1 = t2.c1 OR t1.c1 IS NULL) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; + c1 | c1 +----+---- + 66 | 66 + 72 | 72 + 78 | 78 + 84 | 84 + 90 | 90 + 96 | 96 + | 3 + | 9 + | 15 + | 21 +(10 rows) + +-- full outer join + WHERE clause with shippable extensions set +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t1.c3 FROM ft1 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE postgres_fdw_abs(t1.c1) > 0 OFFSET 10 LIMIT 10; + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------------- + Limit + Output: t1.c1, t2.c2, t1.c3 + -> Foreign Scan + Output: t1.c1, t2.c2, t1.c3 + Filter: (postgres_fdw_abs(t1.c1) > 0) + Node ID: 1 + Relations: (public.ft1 t1) FULL JOIN (public.ft2 t2) + Remote SQL: SELECT r1."C 1", r1.c3, r2.c2 FROM ("S 1"."T 1" r1 FULL JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT r1."C 1", r1.c3, r2.c2 FROM ("S 1"."T 1" r1 FULL JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) + Hash Full Join + Output: r1."C 1", r1.c3, r2.c2 + Hash Cond: (r1."C 1" = r2."C 1") + -> Row Adapter + Output: r1."C 1", r1.c3 + -> CStore Scan on "S 1"."T 1" r1 + Output: r1."C 1", r1.c3 + -> Hash + Output: r2.c2, r2."C 1" + -> Row Adapter + Output: r2.c2, r2."C 1" + -> CStore Scan on "S 1"."T 1" r2 + Output: r2.c2, r2."C 1" + +(25 rows) + +-- full outer join + WHERE clause with shippable extensions not set +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t1.c3 FROM ft1 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE postgres_fdw_abs(t1.c1) > 0 OFFSET 10 LIMIT 10; + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------------- + Limit + Output: t1.c1, t2.c2, t1.c3 + -> Foreign Scan + Output: t1.c1, t2.c2, t1.c3 + Filter: (postgres_fdw_abs(t1.c1) > 0) + Node ID: 1 + Relations: (public.ft1 t1) FULL JOIN (public.ft2 t2) + Remote SQL: SELECT r1."C 1", r1.c3, r2.c2 FROM ("S 1"."T 1" r1 FULL JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT r1."C 1", r1.c3, r2.c2 FROM ("S 1"."T 1" r1 FULL JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) + Hash Full Join + Output: r1."C 1", r1.c3, r2.c2 + Hash Cond: (r1."C 1" = r2."C 1") + -> Row Adapter + Output: r1."C 1", r1.c3 + -> CStore Scan on "S 1"."T 1" r1 + Output: r1."C 1", r1.c3 + -> Hash + Output: r2.c2, r2."C 1" + -> Row Adapter + Output: r2.c2, r2."C 1" + -> CStore Scan on "S 1"."T 1" r2 + Output: r2.c2, r2."C 1" + +(25 rows) + +-- join two tables with FOR UPDATE clause +-- tests whole-row reference for row marks +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR UPDATE OF t1; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------- + Limit + Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* + -> Sort + Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* + Sort Key: t1.c3, t1.c1 + -> LockRows + Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* + -> Hash Join + Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* + Hash Cond: (t1.c1 = t2.c1) + -> Foreign Scan on public.ft1 t1 + Output: t1.c1, t1.c3, t1.* + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" FOR UPDATE + -> Hash + Output: t2.c1, t2.* + -> Foreign Scan on public.ft2 t2 + Output: t2.c1, t2.* + Node ID: 2 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" FOR UPDATE + failed to get remote plan, error massage is: + SELECT FOR UPDATE/SHARE/NO KEY UPDATE/KEY SHARE cannot be used with column table "T 1" + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" + failed to get remote plan, error massage is: + current transaction is aborted, commands ignored until end of transaction block, firstChar[Q] + +(29 rows) + +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR UPDATE OF t1; +ERROR: SELECT FOR UPDATE/SHARE/NO KEY UPDATE/KEY SHARE cannot be used with column table "T 1" +CONTEXT: Remote SQL command: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" FOR UPDATE +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR UPDATE; +ERROR: SELECT FOR UPDATE/SHARE/NO KEY UPDATE/KEY SHARE cannot be used with column table "T 1" +CONTEXT: Remote SQL command: EXPLAIN SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" FOR UPDATE +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR UPDATE; +ERROR: SELECT FOR UPDATE/SHARE/NO KEY UPDATE/KEY SHARE cannot be used with column table "T 1" +CONTEXT: Remote SQL command: EXPLAIN SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" FOR UPDATE +-- join two tables with FOR SHARE clause +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR SHARE OF t1; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------- + Limit + Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* + -> Sort + Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* + Sort Key: t1.c3, t1.c1 + -> LockRows + Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* + -> Hash Join + Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* + Hash Cond: (t1.c1 = t2.c1) + -> Foreign Scan on public.ft1 t1 + Output: t1.c1, t1.c3, t1.* + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" FOR SHARE + -> Hash + Output: t2.c1, t2.* + -> Foreign Scan on public.ft2 t2 + Output: t2.c1, t2.* + Node ID: 2 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" FOR SHARE + failed to get remote plan, error massage is: + SELECT FOR UPDATE/SHARE/NO KEY UPDATE/KEY SHARE cannot be used with column table "T 1" + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" + failed to get remote plan, error massage is: + current transaction is aborted, commands ignored until end of transaction block, firstChar[Q] + +(29 rows) + +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR SHARE OF t1; +ERROR: SELECT FOR UPDATE/SHARE/NO KEY UPDATE/KEY SHARE cannot be used with column table "T 1" +CONTEXT: Remote SQL command: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" FOR SHARE +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR SHARE; +ERROR: SELECT FOR UPDATE/SHARE/NO KEY UPDATE/KEY SHARE cannot be used with column table "T 1" +CONTEXT: Remote SQL command: EXPLAIN SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" FOR SHARE +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR SHARE; +ERROR: SELECT FOR UPDATE/SHARE/NO KEY UPDATE/KEY SHARE cannot be used with column table "T 1" +CONTEXT: Remote SQL command: EXPLAIN SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" FOR SHARE +-- join in CTE +EXPLAIN (VERBOSE, COSTS OFF) -- Inaccurate +WITH t (c1_1, c1_3, c2_1) AS MATERIALIZED (SELECT t1.c1, t1.c3, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1)) SELECT c1_1, c2_1 FROM t ORDER BY c1_3, c1_1 OFFSET 100 LIMIT 10; + QUERY PLAN +---------------------------------------------------------------------------- + Limit + Output: t.c1_1, t.c2_1, t.c1_3 + CTE t + -> Hash Join + Output: t1.c1, t1.c3, t2.c1 + Hash Cond: (t1.c1 = t2.c1) + -> Foreign Scan on public.ft1 t1 + Output: t1.c1, t1.c3 + Node ID: 1 + Remote SQL: SELECT "C 1", c3 FROM "S 1"."T 1" + -> Hash + Output: t2.c1 + -> Foreign Scan on public.ft2 t2 + Output: t2.c1 + Node ID: 2 + Remote SQL: SELECT "C 1" FROM "S 1"."T 1" + -> Sort + Output: t.c1_1, t.c2_1, t.c1_3 + Sort Key: t.c1_3, t.c1_1 + -> CTE Scan on t + Output: t.c1_1, t.c2_1, t.c1_3 + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c3 FROM "S 1"."T 1" + Row Adapter + Output: "C 1", c3 + -> CStore Scan on "S 1"."T 1" + Output: "C 1", c3 + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1" FROM "S 1"."T 1" + Row Adapter + Output: "C 1" + -> CStore Scan on "S 1"."T 1" + Output: "C 1" + +(34 rows) + +WITH t (c1_1, c1_3, c2_1) AS MATERIALIZED (SELECT t1.c1, t1.c3, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1)) SELECT c1_1, c2_1 FROM t ORDER BY c1_3, c1_1 OFFSET 100 LIMIT 10; + c1_1 | c2_1 +------+------ + 101 | 101 + 102 | 102 + 103 | 103 + 104 | 104 + 105 | 105 + 106 | 106 + 107 | 107 + 108 | 108 + 109 | 109 + 110 | 110 +(10 rows) + +-- ctid with whole-row reference, currently, does not support system column +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.ctid, t1, t2, t1.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; +ERROR: column t1.ctid does not exist +LINE 2: SELECT t1.ctid, t1, t2, t1.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1... + ^ +CONTEXT: referenced column: ctid +-- SEMI JOIN, not pushed down +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1 FROM ft1 t1 WHERE EXISTS (SELECT 1 FROM ft2 t2 WHERE t1.c1 = t2.c1) ORDER BY t1.c1 OFFSET 100 LIMIT 10; + QUERY PLAN +------------------------------------------------------------------------ + Limit + Output: t1.c1 + -> Sort + Output: t1.c1 + Sort Key: t1.c1 + -> Hash Semi Join + Output: t1.c1 + Hash Cond: (t1.c1 = t2.c1) + -> Foreign Scan on public.ft1 t1 + Output: t1.c1 + Node ID: 1 + Remote SQL: SELECT "C 1" FROM "S 1"."T 1" + -> Hash + Output: t2.c1 + -> Foreign Scan on public.ft2 t2 + Output: t2.c1 + Node ID: 2 + Remote SQL: SELECT "C 1" FROM "S 1"."T 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1" FROM "S 1"."T 1" + Row Adapter + Output: "C 1" + -> CStore Scan on "S 1"."T 1" + Output: "C 1" + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1" FROM "S 1"."T 1" + Row Adapter + Output: "C 1" + -> CStore Scan on "S 1"."T 1" + Output: "C 1" + +(31 rows) + +SELECT t1.c1 FROM ft1 t1 WHERE EXISTS (SELECT 1 FROM ft2 t2 WHERE t1.c1 = t2.c1) ORDER BY t1.c1 OFFSET 100 LIMIT 10; + c1 +----- + 101 + 102 + 103 + 104 + 105 + 106 + 107 + 108 + 109 + 110 +(10 rows) + +-- ANTI JOIN, not pushed down +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1 FROM ft1 t1 WHERE NOT EXISTS (SELECT 1 FROM ft2 t2 WHERE t1.c1 = t2.c2) ORDER BY t1.c1 OFFSET 100 LIMIT 10; + QUERY PLAN +------------------------------------------------------------------------------------------------------ + Limit + Output: t1.c1 + -> Merge Anti Join + Output: t1.c1 + Merge Cond: (t1.c1 = t2.c2) + -> Foreign Scan on public.ft1 t1 + Output: t1.c1 + Node ID: 1 + Remote SQL: SELECT "C 1" FROM "S 1"."T 1" ORDER BY "C 1" ASC NULLS LAST + -> Materialize + Output: t2.c2 + -> Foreign Scan on public.ft2 t2 + Output: t2.c2 + Node ID: 2 + Remote SQL: SELECT c2 FROM "S 1"."T 1" ORDER BY c2 ASC NULLS LAST + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1" FROM "S 1"."T 1" ORDER BY "C 1" ASC NULLS LAST + Row Adapter + Output: "C 1" + -> Vector Sort + Output: "C 1" + Sort Key: "T 1"."C 1" + -> CStore Scan on "S 1"."T 1" + Output: "C 1" + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c2 FROM "S 1"."T 1" ORDER BY c2 ASC NULLS LAST + Row Adapter + Output: c2 + -> Vector Sort + Output: c2 + Sort Key: "T 1".c2 + -> CStore Scan on "S 1"."T 1" + Output: c2 + +(34 rows) + +SELECT t1.c1 FROM ft1 t1 WHERE NOT EXISTS (SELECT 1 FROM ft2 t2 WHERE t1.c1 = t2.c2) ORDER BY t1.c1 OFFSET 100 LIMIT 10; + c1 +----- + 110 + 111 + 112 + 113 + 114 + 115 + 116 + 117 + 118 + 119 +(10 rows) + +-- CROSS JOIN can be pushed down +EXPLAIN (VERBOSE, COSTS OFF) -- Inaccurate +SELECT t1.c1, t2.c1 FROM ft1 t1 CROSS JOIN ft2 t2 ORDER BY t1.c1, t2.c1 OFFSET 100 LIMIT 10; + QUERY PLAN +------------------------------------------------------------------------ + Limit + Output: t1.c1, t2.c1 + -> Sort + Output: t1.c1, t2.c1 + Sort Key: t1.c1, t2.c1 + -> Nested Loop + Output: t1.c1, t2.c1 + -> Foreign Scan on public.ft1 t1 + Output: t1.c1 + Node ID: 1 + Remote SQL: SELECT "C 1" FROM "S 1"."T 1" + -> Materialize + Output: t2.c1 + -> Foreign Scan on public.ft2 t2 + Output: t2.c1 + Node ID: 2 + Remote SQL: SELECT "C 1" FROM "S 1"."T 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1" FROM "S 1"."T 1" + Row Adapter + Output: "C 1" + -> CStore Scan on "S 1"."T 1" + Output: "C 1" + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1" FROM "S 1"."T 1" + Row Adapter + Output: "C 1" + -> CStore Scan on "S 1"."T 1" + Output: "C 1" + +(30 rows) + +SELECT t1.c1, t2.c1 FROM ft1 t1 CROSS JOIN ft2 t2 ORDER BY t1.c1, t2.c1 OFFSET 100 LIMIT 10; + c1 | c1 +----+----- + 1 | 101 + 1 | 102 + 1 | 103 + 1 | 104 + 1 | 105 + 1 | 106 + 1 | 107 + 1 | 108 + 1 | 109 + 1 | 110 +(10 rows) + +-- different server, not pushed down. No result expected. +EXPLAIN (VERBOSE, COSTS OFF) -- Inaccurate +SELECT t1.c1, t2.c1 FROM ft5 t1 JOIN ft6 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 100 LIMIT 10; + QUERY PLAN +------------------------------------------------------------------------------------------------ + Limit + Output: t1.c1, t2.c1 + -> Nested Loop + Output: t1.c1, t2.c1 + Join Filter: (t1.c1 = t2.c1) + -> Foreign Scan on public.ft6 t2 + Output: t2.c1 + Node ID: 1 + Remote SQL: SELECT c1 FROM "S 1"."T 4" ORDER BY c1 ASC NULLS LAST + -> Foreign Scan on public.ft5 t1 + Output: t1.c1 + Node ID: 2 + Remote SQL: SELECT c1 FROM "S 1"."T 4" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c1 FROM "S 1"."T 4" ORDER BY c1 ASC NULLS LAST + Row Adapter + Output: c1 + -> Vector Sort + Output: c1 + Sort Key: "T 4".c1 + -> CStore Scan on "S 1"."T 4" + Output: c1 + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c1 FROM "S 1"."T 4" + Row Adapter + Output: c1 + -> CStore Scan on "S 1"."T 4" + Output: c1 + +(29 rows) + +SELECT t1.c1, t2.c1 FROM ft5 t1 JOIN ft6 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 100 LIMIT 10; + c1 | c1 +----+---- +(0 rows) + +-- unsafe join conditions (c8 has a UDT), not pushed down. Practically a CROSS +-- JOIN since c8 in both tables has same value. +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 LEFT JOIN ft2 t2 ON (t1.c8 = t2.c8) ORDER BY t1.c1, t2.c1 OFFSET 100 LIMIT 10; + QUERY PLAN +---------------------------------------------------------------------------- + Limit + Output: t1.c1, t2.c1 + -> Sort + Output: t1.c1, t2.c1 + Sort Key: t1.c1, t2.c1 + -> Hash Left Join + Output: t1.c1, t2.c1 + Hash Cond: (t1.c8 = t2.c8) + -> Foreign Scan on public.ft1 t1 + Output: t1.c1, t1.c8 + Node ID: 1 + Remote SQL: SELECT "C 1", c8 FROM "S 1"."T 1" + -> Hash + Output: t2.c1, t2.c8 + -> Foreign Scan on public.ft2 t2 + Output: t2.c1, t2.c8 + Node ID: 2 + Remote SQL: SELECT "C 1", c8 FROM "S 1"."T 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c8 FROM "S 1"."T 1" + Row Adapter + Output: "C 1", c8 + -> CStore Scan on "S 1"."T 1" + Output: "C 1", c8 + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c8 FROM "S 1"."T 1" + Row Adapter + Output: "C 1", c8 + -> CStore Scan on "S 1"."T 1" + Output: "C 1", c8 + +(31 rows) + +SELECT t1.c1, t2.c1 FROM ft1 t1 LEFT JOIN ft2 t2 ON (t1.c8 = t2.c8) ORDER BY t1.c1, t2.c1 OFFSET 100 LIMIT 10; + c1 | c1 +----+----- + 1 | 101 + 1 | 102 + 1 | 103 + 1 | 104 + 1 | 105 + 1 | 106 + 1 | 107 + 1 | 108 + 1 | 109 + 1 | 110 +(10 rows) + +-- unsafe conditions on one side (c8 has a UDT), not pushed down. +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE t1.c8 = 'foo' ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; + QUERY PLAN +-------------------------------------------------------------------------------- + Limit + Output: t1.c1, t2.c1, t1.c3 + -> Sort + Output: t1.c1, t2.c1, t1.c3 + Sort Key: t1.c3, t1.c1 + -> Hash Left Join + Output: t1.c1, t2.c1, t1.c3 + Hash Cond: (t1.c1 = t2.c1) + -> Foreign Scan on public.ft1 t1 + Output: t1.c1, t1.c3 + Filter: (t1.c8 = 'foo'::user_enum) + Node ID: 1 + Remote SQL: SELECT "C 1", c3, c8 FROM "S 1"."T 1" + -> Hash + Output: t2.c1 + -> Foreign Scan on public.ft2 t2 + Output: t2.c1 + Node ID: 2 + Remote SQL: SELECT "C 1" FROM "S 1"."T 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c3, c8 FROM "S 1"."T 1" + Row Adapter + Output: "C 1", c3, c8 + -> CStore Scan on "S 1"."T 1" + Output: "C 1", c3, c8 + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1" FROM "S 1"."T 1" + Row Adapter + Output: "C 1" + -> CStore Scan on "S 1"."T 1" + Output: "C 1" + +(32 rows) + +SELECT t1.c1, t2.c1 FROM ft1 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE t1.c8 = 'foo' ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; + c1 | c1 +-----+----- + 101 | 101 + 102 | 102 + 103 | 103 + 104 | 104 + 105 | 105 + 106 | 106 + 107 | 107 + 108 | 108 + 109 | 109 + 110 | 110 +(10 rows) + +-- join where unsafe to pushdown condition in WHERE clause has a column not +-- in the SELECT clause. In this test unsafe clause needs to have column +-- references from both joining sides so that the clause is not pushed down +-- into one of the joining sides. +EXPLAIN (VERBOSE, COSTS OFF) -- Inaccurate +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE t1.c8 = t2.c8 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; + QUERY PLAN +-------------------------------------------------------------------------------- + Limit + Output: t1.c1, t2.c1, t1.c3 + -> Sort + Output: t1.c1, t2.c1, t1.c3 + Sort Key: t1.c3, t1.c1 + -> Hash Join + Output: t1.c1, t2.c1, t1.c3 + Hash Cond: ((t1.c1 = t2.c1) AND (t1.c8 = t2.c8)) + -> Foreign Scan on public.ft1 t1 + Output: t1.c1, t1.c3, t1.c8 + Node ID: 1 + Remote SQL: SELECT "C 1", c3, c8 FROM "S 1"."T 1" + -> Hash + Output: t2.c1, t2.c8 + -> Foreign Scan on public.ft2 t2 + Output: t2.c1, t2.c8 + Node ID: 2 + Remote SQL: SELECT "C 1", c8 FROM "S 1"."T 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c3, c8 FROM "S 1"."T 1" + Row Adapter + Output: "C 1", c3, c8 + -> CStore Scan on "S 1"."T 1" + Output: "C 1", c3, c8 + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c8 FROM "S 1"."T 1" + Row Adapter + Output: "C 1", c8 + -> CStore Scan on "S 1"."T 1" + Output: "C 1", c8 + +(31 rows) + +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE t1.c8 = t2.c8 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; + c1 | c1 +-----+----- + 101 | 101 + 102 | 102 + 103 | 103 + 104 | 104 + 105 | 105 + 106 | 106 + 107 | 107 + 108 | 108 + 109 | 109 + 110 | 110 +(10 rows) + +-- Aggregate after UNION, for testing setrefs +EXPLAIN (VERBOSE, COSTS OFF) -- Inaccurate +SELECT t1c1, avg(t1c1 + t2c1) FROM (SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) UNION SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1)) AS t (t1c1, t2c1) GROUP BY t1c1 ORDER BY t1c1 OFFSET 100 LIMIT 10; + QUERY PLAN +--------------------------------------------------------------------------------------- + Limit + Output: t1.c1, (avg((t1.c1 + t2.c1))) + -> Sort + Output: t1.c1, (avg((t1.c1 + t2.c1))) + Sort Key: t1.c1 + -> HashAggregate + Output: t1.c1, avg((t1.c1 + t2.c1)) + Group By Key: t1.c1 + -> HashAggregate + Output: t1.c1, t2.c1 + Group By Key: t1.c1, t2.c1 + -> Append + -> Hash Join + Output: t1.c1, t2.c1 + Hash Cond: (t1.c1 = t2.c1) + -> Foreign Scan on public.ft1 t1 + Output: t1.c1 + Node ID: 1 + Remote SQL: SELECT "C 1" FROM "S 1"."T 1" + -> Hash + Output: t2.c1 + -> Foreign Scan on public.ft2 t2 + Output: t2.c1 + Node ID: 2 + Remote SQL: SELECT "C 1" FROM "S 1"."T 1" + -> Hash Join + Output: t1.c1, t2.c1 + Hash Cond: (t1.c1 = t2.c1) + -> Foreign Scan on public.ft1 t1 + Output: t1.c1 + Node ID: 3 + Remote SQL: SELECT "C 1" FROM "S 1"."T 1" + -> Hash + Output: t2.c1 + -> Foreign Scan on public.ft2 t2 + Output: t2.c1 + Node ID: 4 + Remote SQL: SELECT "C 1" FROM "S 1"."T 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1" FROM "S 1"."T 1" + Row Adapter + Output: "C 1" + -> CStore Scan on "S 1"."T 1" + Output: "C 1" + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1" FROM "S 1"."T 1" + Row Adapter + Output: "C 1" + -> CStore Scan on "S 1"."T 1" + Output: "C 1" + Node 3: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1" FROM "S 1"."T 1" + Row Adapter + Output: "C 1" + -> CStore Scan on "S 1"."T 1" + Output: "C 1" + Node 4: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1" FROM "S 1"."T 1" + Row Adapter + Output: "C 1" + -> CStore Scan on "S 1"."T 1" + Output: "C 1" + +(61 rows) + +SELECT t1c1, avg(t1c1 + t2c1) FROM (SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) UNION SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1)) AS t (t1c1, t2c1) GROUP BY t1c1 ORDER BY t1c1 OFFSET 100 LIMIT 10; + t1c1 | avg +------+---------------------- + 101 | 202.0000000000000000 + 102 | 204.0000000000000000 + 103 | 206.0000000000000000 + 104 | 208.0000000000000000 + 105 | 210.0000000000000000 + 106 | 212.0000000000000000 + 107 | 214.0000000000000000 + 108 | 216.0000000000000000 + 109 | 218.0000000000000000 + 110 | 220.0000000000000000 +(10 rows) + +-- non-Var items in targetlist of the nullable rel of a join preventing +-- push-down in some cases +-- unable to push {ft1, ft2} +EXPLAIN (VERBOSE, COSTS OFF) +SELECT q.a, ft2.c1 FROM (SELECT 13 FROM ft1 WHERE c1 = 13) q(a) RIGHT JOIN ft2 ON (q.a = ft2.c1) WHERE ft2.c1 BETWEEN 10 AND 15; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------ + Nested Loop Left Join + Output: (13), ft2.c1 + Join Filter: (13 = ft2.c1) + -> Foreign Scan on public.ft2 + Output: ft2.c1 + Node ID: 1 + Remote SQL: SELECT "C 1" FROM "S 1"."T 1" WHERE (("C 1" >= 10)) AND (("C 1" <= 15)) ORDER BY "C 1" ASC NULLS LAST + -> Materialize + Output: (13) + -> Foreign Scan on public.ft1 + Output: 13 + Node ID: 2 + Remote SQL: SELECT NULL FROM "S 1"."T 1" WHERE (("C 1" = 13)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1" FROM "S 1"."T 1" WHERE (("C 1" >= 10)) AND (("C 1" <= 15)) ORDER BY "C 1" ASC NULLS LAST + Row Adapter + Output: "C 1" + -> Vector Sort + Output: "C 1" + Sort Key: "T 1"."C 1" + -> CStore Scan on "S 1"."T 1" + Output: "C 1" + Filter: (("T 1"."C 1" >= 10) AND ("T 1"."C 1" <= 15)) + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT NULL FROM "S 1"."T 1" WHERE (("C 1" = 13)) + Row Adapter + Output: (NULL::text) + -> CStore Index Only Scan using t1_pkey on "S 1"."T 1" + Output: NULL::text + Index Cond: ("T 1"."C 1" = 13) + +(31 rows) + +SELECT q.a, ft2.c1 FROM (SELECT 13 FROM ft1 WHERE c1 = 13) q(a) RIGHT JOIN ft2 ON (q.a = ft2.c1) WHERE ft2.c1 BETWEEN 10 AND 15; + a | c1 +----+---- + | 10 + | 11 + | 12 + 13 | 13 + | 14 + | 15 +(6 rows) + +-- ok to push {ft1, ft2} but not {ft1, ft2, ft4} +EXPLAIN (VERBOSE, COSTS OFF) -- Inaccurate +SELECT ft4.c1, q.* FROM ft4 LEFT JOIN (SELECT 13, ft1.c1, ft2.c1 FROM ft1 RIGHT JOIN ft2 ON (ft1.c1 = ft2.c1) WHERE ft1.c1 = 12) q(a, b, c) ON (ft4.c1 = q.b) WHERE ft4.c1 BETWEEN 10 AND 15; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------- + Nested Loop Left Join + Output: ft4.c1, (13), ft1.c1, ft2.c1 + Join Filter: (ft4.c1 = ft1.c1) + -> Foreign Scan on public.ft4 + Output: ft4.c1 + Node ID: 1 + Remote SQL: SELECT c1 FROM "S 1"."T 3" WHERE ((c1 >= 10)) AND ((c1 <= 15)) + -> Materialize + Output: ft1.c1, ft2.c1, (13) + -> Nested Loop + Output: ft1.c1, ft2.c1, 13 + -> Foreign Scan on public.ft2 + Output: ft2.c1 + Node ID: 2 + Remote SQL: SELECT "C 1" FROM "S 1"."T 1" WHERE (("C 1" = 12)) ORDER BY "C 1" ASC NULLS LAST + -> Foreign Scan on public.ft1 + Output: ft1.c1 + Node ID: 3 + Remote SQL: SELECT "C 1" FROM "S 1"."T 1" WHERE (("C 1" = 12)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c1 FROM "S 1"."T 3" WHERE ((c1 >= 10)) AND ((c1 <= 15)) + Row Adapter + Output: c1 + -> CStore Scan on "S 1"."T 3" + Output: c1 + Filter: (("T 3".c1 >= 10) AND ("T 3".c1 <= 15)) + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1" FROM "S 1"."T 1" WHERE (("C 1" = 12)) ORDER BY "C 1" ASC NULLS LAST + Row Adapter + Output: "C 1" + -> CStore Index Only Scan using t1_pkey on "S 1"."T 1" + Output: "C 1" + Index Cond: ("T 1"."C 1" = 12) + Node 3: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1" FROM "S 1"."T 1" WHERE (("C 1" = 12)) + Row Adapter + Output: "C 1" + -> CStore Index Only Scan using t1_pkey on "S 1"."T 1" + Output: "C 1" + Index Cond: ("T 1"."C 1" = 12) + +(40 rows) + +SELECT ft4.c1, q.* FROM ft4 LEFT JOIN (SELECT 13, ft1.c1, ft2.c1 FROM ft1 RIGHT JOIN ft2 ON (ft1.c1 = ft2.c1) WHERE ft1.c1 = 12) q(a, b, c) ON (ft4.c1 = q.b) WHERE ft4.c1 BETWEEN 10 AND 15; + c1 | a | b | c +----+----+----+---- + 10 | | | + 12 | 13 | 12 | 12 + 14 | | | +(3 rows) + +-- join with nullable side with some columns with null values +UPDATE ft5 SET c3 = null where c1 % 9 = 0; +ERROR: SELECT FOR UPDATE/SHARE/NO KEY UPDATE/KEY SHARE cannot be used with column table "T 4" +CONTEXT: Remote SQL command: SELECT c1, c2, ctid, tableoid FROM "S 1"."T 4" WHERE (((c1 % 9) = 0)) FOR UPDATE +UPDATE "S 1"."T 4" SET c3 = null where c1 % 9 = 0; +EXPLAIN (VERBOSE, COSTS OFF) -- Inaccurate +SELECT ft5, ft5.c1, ft5.c2, ft5.c3, ft4.c1, ft4.c2 FROM ft5 left join ft4 on ft5.c1 = ft4.c1 WHERE ft4.c1 BETWEEN 10 and 30 ORDER BY ft5.c1, ft4.c1; + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Sort + Output: ft5.*, ft5.c1, ft5.c2, ft5.c3, ft4.c1, ft4.c2 + Sort Key: ft5.c1 + -> Foreign Scan + Output: ft5.*, ft5.c1, ft5.c2, ft5.c3, ft4.c1, ft4.c2 + Node ID: 1 + Relations: (public.ft5) INNER JOIN (public.ft4) + Remote SQL: SELECT CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1.c1, r1.c2, r1.c3) END, r1.c1, r1.c2, r1.c3, r2.c1, r2.c2 FROM ("S 1"."T 4" r1 INNER JOIN "S 1"."T 3" r2 ON (((r1.c1 = r2.c1)) AND ((r2.c1 >= 10)) AND ((r2.c1 <= 30)) AND ((r1.c1 >= 10)) AND ((r1.c1 <= 30)))) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1.c1, r1.c2, r1.c3) END, r1.c1, r1.c2, r1.c3, r2.c1, r2.c2 FROM ("S 1"."T 4" r1 INNER JOIN "S 1"."T 3" r2 ON (((r1.c1 = r2.c1)) AND ((r2.c1 >= 10)) AND ((r2.c1 <= 30)) AND ((r1.c1 >= 10)) AND ((r1.c1 <= 30)))) + Hash Join + Output: CASE WHEN ((ROW(r1.c1, r1.c2, r1.c3))::text IS NOT NULL) THEN ROW(r1.c1, r1.c2, r1.c3) ELSE NULL::record END, r1.c1, r1.c2, r1.c3, r2.c1, r2.c2 + Hash Cond: (r2.c1 = r1.c1) + -> Row Adapter + Output: r2.c1, r2.c2 + -> CStore Scan on "S 1"."T 3" r2 + Output: r2.c1, r2.c2 + Filter: ((r2.c1 >= 10) AND (r2.c1 <= 30)) + -> Hash + Output: r1.c1, r1.c2, r1.c3 + -> Row Adapter + Output: r1.c1, r1.c2, r1.c3 + -> CStore Scan on "S 1"."T 4" r1 + Output: r1.c1, r1.c2, r1.c3 + Filter: ((r1.c1 >= 10) AND (r1.c1 <= 30)) + +(27 rows) + +SELECT ft5, ft5.c1, ft5.c2, ft5.c3, ft4.c1, ft4.c2 FROM ft5 left join ft4 on ft5.c1 = ft4.c1 WHERE ft4.c1 BETWEEN 10 and 30 ORDER BY ft5.c1, ft4.c1; + ft5 | c1 | c2 | c3 | c1 | c2 +----------------+----+----+--------+----+---- + (12,13,AAA012) | 12 | 13 | AAA012 | 12 | 13 + (18,19,) | 18 | 19 | | 18 | 19 + (24,25,AAA024) | 24 | 25 | AAA024 | 24 | 25 + (30,31,AAA030) | 30 | 31 | AAA030 | 30 | 31 +(4 rows) + +-- multi-way join involving multiple merge joins +-- (this case used to have EPQ-related planning problems) +CREATE TABLE local_tbl (c1 int NOT NULL, c2 int NOT NULL, c3 text, CONSTRAINT local_tbl_pkey PRIMARY KEY (c1)) with (orientation=column); +NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "local_tbl_pkey" for table "local_tbl" +INSERT INTO local_tbl SELECT id, id % 10, to_char(id, 'FM0000') FROM generate_series(1, 1000) id; +ANALYZE local_tbl; +SET enable_nestloop TO false; +SET enable_hashjoin TO false; +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM ft1, ft2, ft4, ft5, local_tbl WHERE ft1.c1 = ft2.c1 AND ft1.c2 = ft4.c1 + AND ft1.c2 = ft5.c1 AND ft1.c2 = local_tbl.c1 AND ft1.c1 < 100 AND ft2.c1 < 100 FOR UPDATE; +ERROR: SELECT FOR UPDATE/SHARE/NO KEY UPDATE/KEY SHARE cannot be used with column table "local_tbl" +SELECT * FROM ft1, ft2, ft4, ft5, local_tbl WHERE ft1.c1 = ft2.c1 AND ft1.c2 = ft4.c1 + AND ft1.c2 = ft5.c1 AND ft1.c2 = local_tbl.c1 AND ft1.c1 < 100 AND ft2.c1 < 100 FOR UPDATE; +ERROR: SELECT FOR UPDATE/SHARE/NO KEY UPDATE/KEY SHARE cannot be used with column table "local_tbl" +RESET enable_nestloop; +RESET enable_hashjoin; +DROP TABLE local_tbl; +-- check join pushdown in situations where multiple userids are involved +CREATE ROLE regress_view_owner sysadmin password 'QWERT@12345'; +CREATE USER MAPPING FOR regress_view_owner SERVER loopback; +GRANT SELECT ON ft4 TO regress_view_owner; +GRANT SELECT ON ft5 TO regress_view_owner; +CREATE VIEW v4 AS SELECT * FROM ft4; +CREATE VIEW v5 AS SELECT * FROM ft5; +ALTER VIEW v5 OWNER TO regress_view_owner; +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; -- can't be pushed down, different view owners + QUERY PLAN +------------------------------------------------------------------------- + Limit + Output: ft4.c1, ft5.c2, ft5.c1 + -> Sort + Output: ft4.c1, ft5.c2, ft5.c1 + Sort Key: ft4.c1, ft5.c1 + -> Hash Left Join + Output: ft4.c1, ft5.c2, ft5.c1 + Hash Cond: (ft4.c1 = ft5.c1) + -> Foreign Scan on public.ft4 + Output: ft4.c1 + Node ID: 1 + Remote SQL: SELECT c1 FROM "S 1"."T 3" + -> Hash + Output: ft5.c2, ft5.c1 + -> Foreign Scan on public.ft5 + Output: ft5.c2, ft5.c1 + Node ID: 2 + Remote SQL: SELECT c1, c2 FROM "S 1"."T 4" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c1 FROM "S 1"."T 3" + Row Adapter + Output: c1 + -> CStore Scan on "S 1"."T 3" + Output: c1 + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c1, c2 FROM "S 1"."T 4" + Row Adapter + Output: c1, c2 + -> CStore Scan on "S 1"."T 4" + Output: c1, c2 + +(31 rows) + +SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; + c1 | c2 +----+---- + 22 | + 24 | 25 + 26 | + 28 | + 30 | 31 + 32 | + 34 | + 36 | 37 + 38 | + 40 | +(10 rows) + +ALTER VIEW v4 OWNER TO regress_view_owner; +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; -- can be pushed down + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: ft4.c1, ft5.c2, ft5.c1 + Node ID: 1 + Relations: (public.ft4) LEFT JOIN (public.ft5) + Remote SQL: SELECT r6.c1, r9.c2, r9.c1 FROM ("S 1"."T 3" r6 LEFT JOIN "S 1"."T 4" r9 ON (((r6.c1 = r9.c1)))) ORDER BY r6.c1 ASC NULLS LAST, r9.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT r6.c1, r9.c2, r9.c1 FROM ("S 1"."T 3" r6 LEFT JOIN "S 1"."T 4" r9 ON (((r6.c1 = r9.c1)))) ORDER BY r6.c1 ASC NULLS LAST, r9.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint + Row Adapter + Output: r6.c1, r9.c2, r9.c1 + -> Vector Limit + Output: r6.c1, r9.c2, r9.c1 + -> Vector Sort + Output: r6.c1, r9.c2, r9.c1 + Sort Key: r6.c1, r9.c1 + -> Vector Hash Left Join + Output: r6.c1, r9.c2, r9.c1 + Hash Cond: (r6.c1 = r9.c1) + -> CStore Scan on "S 1"."T 3" r6 + Output: r6.c1 + -> CStore Scan on "S 1"."T 4" r9 + Output: r9.c2, r9.c1 + +(23 rows) + +SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; + c1 | c2 +----+---- + 22 | + 24 | 25 + 26 | + 28 | + 30 | 31 + 32 | + 34 | + 36 | 37 + 38 | + 40 | +(10 rows) + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; -- can't be pushed down, view owner not current user + QUERY PLAN +------------------------------------------------------------------------- + Limit + Output: ft4.c1, t2.c2, t2.c1 + -> Sort + Output: ft4.c1, t2.c2, t2.c1 + Sort Key: ft4.c1, t2.c1 + -> Hash Left Join + Output: ft4.c1, t2.c2, t2.c1 + Hash Cond: (ft4.c1 = t2.c1) + -> Foreign Scan on public.ft4 + Output: ft4.c1 + Node ID: 1 + Remote SQL: SELECT c1 FROM "S 1"."T 3" + -> Hash + Output: t2.c2, t2.c1 + -> Foreign Scan on public.ft5 t2 + Output: t2.c2, t2.c1 + Node ID: 2 + Remote SQL: SELECT c1, c2 FROM "S 1"."T 4" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c1 FROM "S 1"."T 3" + Row Adapter + Output: c1 + -> CStore Scan on "S 1"."T 3" + Output: c1 + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c1, c2 FROM "S 1"."T 4" + Row Adapter + Output: c1, c2 + -> CStore Scan on "S 1"."T 4" + Output: c1, c2 + +(31 rows) + +SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; + c1 | c2 +----+---- + 22 | + 24 | 25 + 26 | + 28 | + 30 | 31 + 32 | + 34 | + 36 | 37 + 38 | + 40 | +(10 rows) + +ALTER VIEW v4 OWNER TO regress_view_owner; +-- PlaceHolder +explain(verbose, costs off) select * from ft1 t1 left join (select c1, case when c2 is not null then 1 else 0 end as cc from ft2) t2 on t1.c1 = t2.c1; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------ + Hash Left Join + Output: t1.c1, t1.c2, t1.c3, t1.c4, t1.c5, t1.c6, t1.c7, t1.c8, ft2.c1, (CASE WHEN (ft2.c2 IS NOT NULL) THEN 1 ELSE 0 END) + Hash Cond: (t1.c1 = ft2.c1) + -> Foreign Scan on public.ft1 t1 + Output: t1.c1, t1.c2, t1.c3, t1.c4, t1.c5, t1.c6, t1.c7, t1.c8 + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" + -> Hash + Output: ft2.c1, (CASE WHEN (ft2.c2 IS NOT NULL) THEN 1 ELSE 0 END) + -> Foreign Scan on public.ft2 + Output: ft2.c1, CASE WHEN (ft2.c2 IS NOT NULL) THEN 1 ELSE 0 END + Node ID: 2 + Remote SQL: SELECT "C 1", c2 FROM "S 1"."T 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" + Row Adapter + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + -> CStore Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2 FROM "S 1"."T 1" + Row Adapter + Output: "C 1", c2 + -> CStore Scan on "S 1"."T 1" + Output: "C 1", c2 + +(26 rows) + +-- cleanup +DROP OWNED BY regress_view_owner; +DROP ROLE regress_view_owner; +-- ====================================================================================================================================== +-- TEST-MODULE: Aggregate and grouping queries +-- -------------------------------------- +-- openGauss not support filter +-- ====================================================================================================================================== +-- Simple aggregates +explain (verbose, costs off) +select count(c6), sum(c1), avg(c1), min(c2), max(c1), stddev(c2), sum(c1) * (random() <= 1)::int as sum2 from ft1 where c2 < 5 group by c2 order by 1, 2; + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: (count(c6)), (sum(c1)), (avg(c1)), (min(c2)), (max(c1)), (stddev(c2)), ((sum(c1)) * ((random() <= 1::double precision))::integer), c2 + Node ID: 1 + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT count(c6), sum("C 1"), avg("C 1"), min(c2), max("C 1"), stddev(c2), c2 FROM "S 1"."T 1" WHERE ((c2 < 5)) GROUP BY 7 ORDER BY count(c6) ASC NULLS LAST, sum("C 1") ASC NULLS LAST + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT count(c6), sum("C 1"), avg("C 1"), min(c2), max("C 1"), stddev(c2), c2 FROM "S 1"."T 1" WHERE ((c2 < 5)) GROUP BY 7 ORDER BY count(c6) ASC NULLS LAST, sum("C 1") ASC NULLS LAST + Row Adapter + Output: (count(c6)), (sum("C 1")), (avg("C 1")), (min(c2)), (max("C 1")), (stddev(c2)), c2 + -> Vector Sort + Output: (count(c6)), (sum("C 1")), (avg("C 1")), (min(c2)), (max("C 1")), (stddev(c2)), c2 + Sort Key: (count("T 1".c6)), (sum("T 1"."C 1")) + -> Vector Hash Aggregate + Output: count(c6), sum("C 1"), avg("C 1"), min(c2), max("C 1"), stddev(c2), c2 + Group By Key: "T 1".c2 + -> CStore Scan on "S 1"."T 1" + Output: c2, c6, "C 1" + Filter: ("T 1".c2 < 5) + +(20 rows) + +select count(c6), sum(c1), avg(c1), min(c2), max(c1), stddev(c2), sum(c1) * (random() <= 1)::int as sum2 from ft1 where c2 < 5 group by c2 order by 1, 2; + count | sum | avg | min | max | stddev | sum2 +-------+-------+----------------------+-----+------+--------+------- + 100 | 49600 | 496.0000000000000000 | 1 | 991 | 0 | 49600 + 100 | 49700 | 497.0000000000000000 | 2 | 992 | 0 | 49700 + 100 | 49800 | 498.0000000000000000 | 3 | 993 | 0 | 49800 + 100 | 49900 | 499.0000000000000000 | 4 | 994 | 0 | 49900 + 100 | 50500 | 505.0000000000000000 | 0 | 1000 | 0 | 50500 +(5 rows) + +explain (verbose, costs off) +select count(c6), sum(c1), avg(c1), min(c2), max(c1), stddev(c2), sum(c1) * (random() <= 1)::int as sum2 from ft1 where c2 < 5 group by c2 order by 1, 2 limit 1; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: (count(c6)), (sum(c1)), (avg(c1)), (min(c2)), (max(c1)), (stddev(c2)), ((sum(c1)) * ((random() <= 1::double precision))::integer), c2 + Node ID: 1 + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT count(c6), sum("C 1"), avg("C 1"), min(c2), max("C 1"), stddev(c2), c2 FROM "S 1"."T 1" WHERE ((c2 < 5)) GROUP BY 7 ORDER BY count(c6) ASC NULLS LAST, sum("C 1") ASC NULLS LAST LIMIT 1::bigint + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT count(c6), sum("C 1"), avg("C 1"), min(c2), max("C 1"), stddev(c2), c2 FROM "S 1"."T 1" WHERE ((c2 < 5)) GROUP BY 7 ORDER BY count(c6) ASC NULLS LAST, sum("C 1") ASC NULLS LAST LIMIT 1::bigint + Row Adapter + Output: (count(c6)), (sum("C 1")), (avg("C 1")), (min(c2)), (max("C 1")), (stddev(c2)), c2 + -> Vector Limit + Output: (count(c6)), (sum("C 1")), (avg("C 1")), (min(c2)), (max("C 1")), (stddev(c2)), c2 + -> Vector Sort + Output: (count(c6)), (sum("C 1")), (avg("C 1")), (min(c2)), (max("C 1")), (stddev(c2)), c2 + Sort Key: (count("T 1".c6)), (sum("T 1"."C 1")) + -> Vector Hash Aggregate + Output: count(c6), sum("C 1"), avg("C 1"), min(c2), max("C 1"), stddev(c2), c2 + Group By Key: "T 1".c2 + -> CStore Scan on "S 1"."T 1" + Output: c2, c6, "C 1" + Filter: ("T 1".c2 < 5) + +(22 rows) + +select count(c6), sum(c1), avg(c1), min(c2), max(c1), stddev(c2), sum(c1) * (random() <= 1)::int as sum2 from ft1 where c2 < 5 group by c2 order by 1, 2 limit 1; + count | sum | avg | min | max | stddev | sum2 +-------+-------+----------------------+-----+-----+--------+------- + 100 | 49600 | 496.0000000000000000 | 1 | 991 | 0 | 49600 +(1 row) + +-- Aggregate is not pushed down as aggregation contains random() +explain (verbose, costs off) +select sum(c1 * (random() <= 1)::int) as sum, avg(c1) from ft1; + QUERY PLAN +----------------------------------------------------------------------------- + Aggregate + Output: sum((c1 * ((random() <= 1::double precision))::integer)), avg(c1) + -> Foreign Scan on public.ft1 + Output: c1 + Node ID: 1 + Remote SQL: SELECT "C 1" FROM "S 1"."T 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1" FROM "S 1"."T 1" + Row Adapter + Output: "C 1" + -> CStore Scan on "S 1"."T 1" + Output: "C 1" + +(14 rows) + +-- Aggregate over join query +explain (verbose, costs off) +select count(*), sum(t1.c1), avg(t2.c1) from ft1 t1 inner join ft1 t2 on (t1.c2 = t2.c2) where t1.c2 = 6; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: (count(*)), (sum(t1.c1)), (avg(t2.c1)) + Node ID: 1 + Relations: Aggregate on ((public.ft1 t1) INNER JOIN (public.ft1 t2)) + Remote SQL: SELECT count(*), sum(r1."C 1"), avg(r2."C 1") FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r2.c2 = 6)) AND ((r1.c2 = 6)))) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT count(*), sum(r1."C 1"), avg(r2."C 1") FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r2.c2 = 6)) AND ((r1.c2 = 6)))) + Row Adapter + Output: (count(*)), (sum(r1."C 1")), (avg(r2."C 1")) + -> Vector Aggregate + Output: count(*), sum(r1."C 1"), avg(r2."C 1") + -> Vector Nest Loop + Output: r1."C 1", r2."C 1" + -> CStore Scan on "S 1"."T 1" r1 + Output: r1."C 1" + Filter: (r1.c2 = 6) + -> Vector Materialize + Output: r2."C 1" + -> CStore Scan on "S 1"."T 1" r2 + Output: r2."C 1" + Filter: (r2.c2 = 6) + +(23 rows) + +select count(*), sum(t1.c1), avg(t2.c1) from ft1 t1 inner join ft1 t2 on (t1.c2 = t2.c2) where t1.c2 = 6; + count | sum | avg +-------+---------+---------------------- + 10000 | 5010000 | 501.0000000000000000 +(1 row) + +-- Not pushed down due to local conditions present in underneath input rel +explain (verbose, costs off) -- Inaccurate +select sum(t1.c1), count(t2.c1) from ft1 t1 inner join ft2 t2 on (t1.c1 = t2.c1) where ((t1.c1 * t2.c1)/(t1.c1 * t2.c1)) * random() <= 1; + QUERY PLAN +------------------------------------------------------------------------------------------------ + Aggregate + Output: sum(t1.c1), count(t2.c1) + -> Hash Join + Output: t1.c1, t2.c1 + Hash Cond: (t1.c1 = t2.c1) + Join Filter: ((((t1.c1 * t2.c1) / (t1.c1 * t2.c1)) * random()) <= 1::double precision) + -> Foreign Scan on public.ft1 t1 + Output: t1.c1 + Node ID: 1 + Remote SQL: SELECT "C 1" FROM "S 1"."T 1" + -> Hash + Output: t2.c1 + -> Foreign Scan on public.ft2 t2 + Output: t2.c1 + Node ID: 2 + Remote SQL: SELECT "C 1" FROM "S 1"."T 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1" FROM "S 1"."T 1" + Row Adapter + Output: "C 1" + -> CStore Scan on "S 1"."T 1" + Output: "C 1" + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1" FROM "S 1"."T 1" + Row Adapter + Output: "C 1" + -> CStore Scan on "S 1"."T 1" + Output: "C 1" + +(29 rows) + +-- GROUP BY clause having expressions +explain (verbose, costs off) +select c2/2, sum(c2) * (c2/2) from ft1 group by c2/2 order by c2/2; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: ((c2 / 2)), (((sum(c2))::double precision * (c2 / 2))) + Node ID: 1 + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT (c2 / 2), (sum(c2) * (c2 / 2)) FROM "S 1"."T 1" GROUP BY 1 ORDER BY (c2 / 2) ASC NULLS LAST + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT (c2 / 2), (sum(c2) * (c2 / 2)) FROM "S 1"."T 1" GROUP BY 1 ORDER BY (c2 / 2) ASC NULLS LAST + Row Adapter + Output: ((c2 / 2)), (((sum(c2))::double precision * ((c2 / 2)))) + -> Vector Sort + Output: ((c2 / 2)), (((sum(c2))::double precision * ((c2 / 2)))) + Sort Key: (("T 1".c2 / 2)) + -> Vector Hash Aggregate + Output: ((c2 / 2)), ((sum(c2))::double precision * ((c2 / 2))) + Group By Key: ("T 1".c2 / 2) + -> CStore Scan on "S 1"."T 1" + Output: (c2 / 2), c2 + +(19 rows) + +select c2/2, sum(c2) * (c2/2) from ft1 group by c2/2 order by c2/2; + ?column? | ?column? +----------+---------- + 0 | 0 + .5 | 50 + 1 | 200 + 1.5 | 450 + 2 | 800 + 2.5 | 1250 + 3 | 1800 + 3.5 | 2450 + 4 | 3200 + 4.5 | 4050 +(10 rows) + +-- Aggregates in subquery are pushed down. +explain (verbose, costs off) +select count(x.a), sum(x.a) from (select c2 a, sum(c1) b from ft1 group by c2, sqrt(c1) order by 1, 2) x; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------ + Aggregate + Output: count(ft1.c2), sum(ft1.c2) + -> Foreign Scan + Output: ft1.c2, (sum(ft1.c1)), (sqrt((ft1.c1)::double precision)) + Node ID: 1 + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT c2, sum("C 1"), sqrt("C 1") FROM "S 1"."T 1" GROUP BY 1, 3 ORDER BY c2 ASC NULLS LAST, sum("C 1") ASC NULLS LAST + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c2, sum("C 1"), sqrt("C 1") FROM "S 1"."T 1" GROUP BY 1, 3 ORDER BY c2 ASC NULLS LAST, sum("C 1") ASC NULLS LAST + Row Adapter + Output: c2, (sum("C 1")), (sqrt(("C 1")::double precision)) + -> Vector Sort + Output: c2, (sum("C 1")), (sqrt(("C 1")::double precision)) + Sort Key: "T 1".c2, (sum("T 1"."C 1")) + -> Vector Hash Aggregate + Output: c2, sum("C 1"), (sqrt(("C 1")::double precision)) + Group By Key: "T 1".c2, sqrt(("T 1"."C 1")::double precision) + -> CStore Scan on "S 1"."T 1" + Output: c2, sqrt(("C 1")::double precision), "C 1" + +(21 rows) + +select count(x.a), sum(x.a) from (select c2 a, sum(c1) b from ft1 group by c2, sqrt(c1) order by 1, 2) x; + count | sum +-------+------ + 1000 | 4500 +(1 row) + +-- Aggregate is still pushed down by taking unshippable expression out +explain (verbose, costs off) +select c2 * (random() <= 1)::int as sum1, sum(c1) * c2 as sum2 from ft1 group by c2 order by 1, 2; + QUERY PLAN +------------------------------------------------------------------------------------------------- + Sort + Output: ((c2 * ((random() <= 1::double precision))::integer)), ((sum(c1) * c2)), c2 + Sort Key: ((ft1.c2 * ((random() <= 1::double precision))::integer)), ((sum(ft1.c1) * ft1.c2)) + -> HashAggregate + Output: (c2 * ((random() <= 1::double precision))::integer), (sum(c1) * c2), c2 + Group By Key: ft1.c2 + -> Foreign Scan on public.ft1 + Output: c2, c1 + Node ID: 1 + Remote SQL: SELECT "C 1", c2 FROM "S 1"."T 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2 FROM "S 1"."T 1" + Row Adapter + Output: "C 1", c2 + -> CStore Scan on "S 1"."T 1" + Output: "C 1", c2 + +(18 rows) + +select c2 * (random() <= 1)::int as sum1, sum(c1) * c2 as sum2 from ft1 group by c2 order by 1, 2; + sum1 | sum2 +------+-------- + 0 | 0 + 1 | 49600 + 2 | 99400 + 3 | 149400 + 4 | 199600 + 5 | 250000 + 6 | 300600 + 7 | 351400 + 8 | 402400 + 9 | 453600 +(10 rows) + +-- Aggregate with unshippable GROUP BY clause are not pushed +explain (verbose, costs off) +select c2 * (random() <= 1)::int as c2 from ft2 group by c2 * (random() <= 1)::int order by 1; + QUERY PLAN +----------------------------------------------------------------------------- + Group + Output: ((c2 * ((random() <= 1::double precision))::integer)) + Group By Key: ((ft2.c2 * ((random() <= 1::double precision))::integer)) + -> Sort + Output: ((c2 * ((random() <= 1::double precision))::integer)) + Sort Key: ((ft2.c2 * ((random() <= 1::double precision))::integer)) + -> Foreign Scan on public.ft2 + Output: (c2 * ((random() <= 1::double precision))::integer) + Node ID: 1 + Remote SQL: SELECT c2 FROM "S 1"."T 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c2 FROM "S 1"."T 1" + Row Adapter + Output: c2 + -> CStore Scan on "S 1"."T 1" + Output: c2 + +(18 rows) + +-- GROUP BY clause in various forms, cardinal, alias and constant expression +explain (verbose, costs off) +select count(c2) w, c2 x, 5 y, 7.0 z from ft1 group by 2, y, 9.0::int order by 2; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: (count(c2)), c2, (5), (7.0), (9) + Node ID: 1 + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT count(c2), c2, 5, 7.0, 9 FROM "S 1"."T 1" GROUP BY 2, 3, 5 ORDER BY c2 ASC NULLS LAST + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT count(c2), c2, 5, 7.0, 9 FROM "S 1"."T 1" GROUP BY 2, 3, 5 ORDER BY c2 ASC NULLS LAST + Row Adapter + Output: (count(c2)), c2, (5), (7.0), (9) + -> Vector Sort + Output: (count(c2)), c2, (5), (7.0), (9) + Sort Key: "T 1".c2 + -> Vector Sonic Hash Aggregate + Output: count(c2), c2, (5), 7.0, (9) + Group By Key: "T 1".c2, 5, 9 + -> CStore Scan on "S 1"."T 1" + Output: c2, 5, 9 + +(19 rows) + +select count(c2) w, c2 x, 5 y, 7.0 z from ft1 group by 2, y, 9.0::int order by 2; + w | x | y | z +-----+---+---+----- + 100 | 0 | 5 | 7.0 + 100 | 1 | 5 | 7.0 + 100 | 2 | 5 | 7.0 + 100 | 3 | 5 | 7.0 + 100 | 4 | 5 | 7.0 + 100 | 5 | 5 | 7.0 + 100 | 6 | 5 | 7.0 + 100 | 7 | 5 | 7.0 + 100 | 8 | 5 | 7.0 + 100 | 9 | 5 | 7.0 +(10 rows) + +-- GROUP BY clause referring to same column multiple times +-- Also, ORDER BY contains an aggregate function +explain (verbose, costs off) +select c2, c2 from ft1 where c2 > 6 group by 1, 2 order by sum(c1); + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: c2, c2, (sum(c1)) + Node ID: 1 + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT c2, c2, sum("C 1") FROM "S 1"."T 1" WHERE ((c2 > 6)) GROUP BY 1, 2 ORDER BY sum("C 1") ASC NULLS LAST + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c2, c2, sum("C 1") FROM "S 1"."T 1" WHERE ((c2 > 6)) GROUP BY 1, 2 ORDER BY sum("C 1") ASC NULLS LAST + Row Adapter + Output: c2, c2, (sum("C 1")) + -> Vector Sort + Output: c2, c2, (sum("C 1")) + Sort Key: (sum("T 1"."C 1")) + -> Vector Sonic Hash Aggregate + Output: c2, c2, sum("C 1") + Group By Key: "T 1".c2, "T 1".c2 + -> CStore Scan on "S 1"."T 1" + Output: c2, "C 1" + Filter: ("T 1".c2 > 6) + +(20 rows) + +select c2, c2 from ft1 where c2 > 6 group by 1, 2 order by sum(c1); + c2 | c2 +----+---- + 7 | 7 + 8 | 8 + 9 | 9 +(3 rows) + +-- Testing HAVING clause shippability +explain (verbose, costs off) +select c2, sum(c1) from ft2 group by c2 having avg(c1) < 500 and sum(c1) < 49800 order by c2; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: c2, (sum(c1)) + Node ID: 1 + Relations: Aggregate on (public.ft2) + Remote SQL: SELECT c2, sum("C 1") FROM "S 1"."T 1" GROUP BY 1 HAVING ((avg("C 1") < 500::numeric)) AND ((sum("C 1") < 49800)) ORDER BY c2 ASC NULLS LAST + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c2, sum("C 1") FROM "S 1"."T 1" GROUP BY 1 HAVING ((avg("C 1") < 500::numeric)) AND ((sum("C 1") < 49800)) ORDER BY c2 ASC NULLS LAST + Row Adapter + Output: c2, (sum("C 1")) + -> Vector Sort + Output: c2, (sum("C 1")) + Sort Key: "T 1".c2 + -> Vector Sonic Hash Aggregate + Output: c2, sum("C 1") + Group By Key: "T 1".c2 + Filter: ((avg("T 1"."C 1") < 500::numeric) AND (sum("T 1"."C 1") < 49800)) + -> CStore Scan on "S 1"."T 1" + Output: c2, "C 1" + +(20 rows) + +select c2, sum(c1) from ft2 group by c2 having avg(c1) < 500 and sum(c1) < 49800 order by c2; + c2 | sum +----+------- + 1 | 49600 + 2 | 49700 +(2 rows) + +-- Unshippable HAVING clause will be evaluated locally, and other qual in HAVING clause is pushed down +explain (verbose, costs off) +select count(*) from (select c5, count(c1) from ft1 group by c5, sqrt(c2) having (avg(c1) / avg(c1)) * random() <= 1 and avg(c1) < 500) x; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------ + Aggregate + Output: count(*) + -> Foreign Scan + Output: ft1.c5, (count(ft1.c1)), (sqrt((ft1.c2)::double precision)) + Filter: (((((avg(ft1.c1)) / (avg(ft1.c1))))::double precision * random()) <= 1::double precision) + Node ID: 1 + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT c5, count("C 1"), sqrt(c2), avg("C 1") FROM "S 1"."T 1" GROUP BY 1, 3 HAVING ((avg("C 1") < 500::numeric)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c5, count("C 1"), sqrt(c2), avg("C 1") FROM "S 1"."T 1" GROUP BY 1, 3 HAVING ((avg("C 1") < 500::numeric)) + Row Adapter + Output: c5, (count("C 1")), (sqrt((c2)::double precision)), (avg("C 1")) + -> Vector Hash Aggregate + Output: c5, count("C 1"), (sqrt((c2)::double precision)), avg("C 1") + Group By Key: "T 1".c5, sqrt(("T 1".c2)::double precision) + Filter: (avg("T 1"."C 1") < 500::numeric) + -> CStore Scan on "S 1"."T 1" + Output: c5, sqrt((c2)::double precision), "C 1" + +(20 rows) + +select count(*) from (select c5, count(c1) from ft1 group by c5, sqrt(c2) having (avg(c1) / avg(c1)) * random() <= 1 and avg(c1) < 500) x; + count +------- + 49 +(1 row) + +-- Aggregate in HAVING clause is not pushable, and thus aggregation is not pushed down +explain (verbose, costs off) +select sum(c1) from ft1 group by c2 having avg(c1 * (random() <= 1)::int) > 100 order by 1; + QUERY PLAN +----------------------------------------------------------------------------------------------- + Sort + Output: (sum(c1)), c2 + Sort Key: (sum(ft1.c1)) + -> HashAggregate + Output: sum(c1), c2 + Group By Key: ft1.c2 + Filter: (avg((ft1.c1 * ((random() <= 1::double precision))::integer)) > 100::numeric) + -> Foreign Scan on public.ft1 + Output: c2, c1 + Node ID: 1 + Remote SQL: SELECT "C 1", c2 FROM "S 1"."T 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2 FROM "S 1"."T 1" + Row Adapter + Output: "C 1", c2 + -> CStore Scan on "S 1"."T 1" + Output: "C 1", c2 + +(19 rows) + +-- Remote aggregate in combination with a local Param (for the output +-- of an initplan) can be trouble, per bug #15781 +explain (verbose, costs off) +select exists(select 1 from pg_enum), sum(c1) from ft1; + QUERY PLAN +----------------------------------------------------------------------------- + Foreign Scan + Output: $0, (sum(ft1.c1)) + Node ID: 1 + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT sum("C 1") FROM "S 1"."T 1" + InitPlan 1 (returns $0) + -> Seq Scan on pg_catalog.pg_enum + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT sum("C 1") FROM "S 1"."T 1" + Row Adapter + Output: (sum("C 1")) + -> Vector Aggregate + Output: sum("C 1") + -> CStore Scan on "S 1"."T 1" + Output: "C 1" + +(17 rows) + +select exists(select 1 from pg_enum), sum(c1) from ft1; + exists | sum +--------+-------- + t | 500500 +(1 row) + +explain (verbose, costs off) +select exists(select 1 from pg_enum), sum(c1) from ft1 group by 1; + QUERY PLAN +------------------------------------------------------------------------ + GroupAggregate + Output: ($0), sum(ft1.c1) + Group By Key: $0 + InitPlan 1 (returns $0) + -> Seq Scan on pg_catalog.pg_enum + -> Foreign Scan on public.ft1 + Output: $0, ft1.c1 + Node ID: 1 + Remote SQL: SELECT "C 1" FROM "S 1"."T 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1" FROM "S 1"."T 1" + Row Adapter + Output: "C 1" + -> CStore Scan on "S 1"."T 1" + Output: "C 1" + +(17 rows) + +select exists(select 1 from pg_enum), sum(c1) from ft1 group by 1; + exists | sum +--------+-------- + t | 500500 +(1 row) + +-- Testing ORDER BY, DISTINCT, FILTER, Ordered-sets and VARIADIC within aggregates +-- ORDER BY within aggregate, same column used to order +explain (verbose, costs off) +select array_agg(c1 order by c1) from ft1 where c1 < 100 group by c2 order by 1; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: (array_agg(c1 ORDER BY c1)), c2 + Node ID: 1 + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT array_agg("C 1" ORDER BY "C 1" ASC NULLS LAST), c2 FROM "S 1"."T 1" WHERE (("C 1" < 100)) GROUP BY 2 ORDER BY array_agg("C 1" ORDER BY "C 1" ASC NULLS LAST) ASC NULLS LAST + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT array_agg("C 1" ORDER BY "C 1" ASC NULLS LAST), c2 FROM "S 1"."T 1" WHERE (("C 1" < 100)) GROUP BY 2 ORDER BY array_agg("C 1" ORDER BY "C 1" ASC NULLS LAST) ASC NULLS LAST + Sort + Output: (array_agg("C 1" ORDER BY "C 1")), c2 + Sort Key: (array_agg("T 1"."C 1" ORDER BY "T 1"."C 1")) + -> GroupAggregate + Output: array_agg("C 1" ORDER BY "C 1"), c2 + Group By Key: "T 1".c2 + -> Sort + Output: c2, "C 1" + Sort Key: "T 1".c2 + -> Row Adapter + Output: c2, "C 1" + -> CStore Scan on "S 1"."T 1" + Output: c2, "C 1" + Filter: ("T 1"."C 1" < 100) + +(23 rows) + +select array_agg(c1 order by c1) from ft1 where c1 < 100 group by c2 order by 1; + array_agg +-------------------------------- + {1,11,21,31,41,51,61,71,81,91} + {2,12,22,32,42,52,62,72,82,92} + {3,13,23,33,43,53,63,73,83,93} + {4,14,24,34,44,54,64,74,84,94} + {5,15,25,35,45,55,65,75,85,95} + {6,16,26,36,46,56,66,76,86,96} + {7,17,27,37,47,57,67,77,87,97} + {8,18,28,38,48,58,68,78,88,98} + {9,19,29,39,49,59,69,79,89,99} + {10,20,30,40,50,60,70,80,90} +(10 rows) + +-- ORDER BY within aggregate, different column used to order also using DESC +explain (verbose, costs off) +select array_agg(c5 order by c1 desc) from ft2 where c2 = 6 and c1 < 50; + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: (array_agg(c5 ORDER BY c1 DESC)) + Node ID: 1 + Relations: Aggregate on (public.ft2) + Remote SQL: SELECT array_agg(c5 ORDER BY "C 1" DESC NULLS FIRST) FROM "S 1"."T 1" WHERE (("C 1" < 50)) AND ((c2 = 6)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT array_agg(c5 ORDER BY "C 1" DESC NULLS FIRST) FROM "S 1"."T 1" WHERE (("C 1" < 50)) AND ((c2 = 6)) + Aggregate + Output: array_agg(c5 ORDER BY "C 1" DESC) + -> Row Adapter + Output: c5, "C 1" + -> CStore Scan on "S 1"."T 1" + Output: c5, "C 1" + Filter: (("T 1"."C 1" < 50) AND ("T 1".c2 = 6)) + +(16 rows) + +select array_agg(c5 order by c1 desc) from ft2 where c2 = 6 and c1 < 50; + array_agg +------------------------------------------------------------------------------------------------------------------------------------------ + {"Mon Feb 16 00:00:00 1970","Fri Feb 06 00:00:00 1970","Tue Jan 27 00:00:00 1970","Sat Jan 17 00:00:00 1970","Wed Jan 07 00:00:00 1970"} +(1 row) + +-- DISTINCT within aggregate +explain (verbose, costs off) +select array_agg(distinct (t1.c1)%5) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: (array_agg(DISTINCT (t1.c1 % 5))), ((t2.c1 % 3)) + Node ID: 1 + Relations: Aggregate on ((public.ft4 t1) FULL JOIN (public.ft5 t2)) + Remote SQL: SELECT array_agg(DISTINCT (r1.c1 % 5)), (r2.c1 % 3) FROM ("S 1"."T 3" r1 FULL JOIN "S 1"."T 4" r2 ON (((r1.c1 = r2.c1)))) WHERE (((r1.c1 < 20) OR ((r1.c1 IS NULL) AND (r2.c1 < 5)))) GROUP BY 2 ORDER BY array_agg(DISTINCT (r1.c1 % 5)) ASC NULLS LAST + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT array_agg(DISTINCT (r1.c1 % 5)), (r2.c1 % 3) FROM ("S 1"."T 3" r1 FULL JOIN "S 1"."T 4" r2 ON (((r1.c1 = r2.c1)))) WHERE (((r1.c1 < 20) OR ((r1.c1 IS NULL) AND (r2.c1 < 5)))) GROUP BY 2 ORDER BY array_agg(DISTINCT (r1.c1 % 5)) ASC NULLS LAST + Sort + Output: (array_agg(DISTINCT (r1.c1 % 5))), ((r2.c1 % 3)) + Sort Key: (array_agg(DISTINCT (r1.c1 % 5))) + -> GroupAggregate + Output: array_agg(DISTINCT (r1.c1 % 5)), ((r2.c1 % 3)) + Group By Key: ((r2.c1 % 3)) + -> Sort + Output: ((r2.c1 % 3)), r1.c1 + Sort Key: ((r2.c1 % 3)) + -> Hash Full Join + Output: (r2.c1 % 3), r1.c1 + Hash Cond: (r1.c1 = r2.c1) + Filter: ((r1.c1 < 20) OR ((r1.c1 IS NULL) AND (r2.c1 < 5))) + -> Row Adapter + Output: r1.c1 + -> CStore Scan on "S 1"."T 3" r1 + Output: r1.c1 + -> Hash + Output: r2.c1 + -> Row Adapter + Output: r2.c1 + -> CStore Scan on "S 1"."T 4" r2 + Output: r2.c1 + +(32 rows) + +select array_agg(distinct (t1.c1)%5) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1; + array_agg +-------------- + {0,1,2,3,4} + {1,2,3,NULL} +(2 rows) + +-- DISTINCT combined with ORDER BY within aggregate +explain (verbose, costs off) +select array_agg(distinct (t1.c1)%5 order by (t1.c1)%5) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: (array_agg(DISTINCT (t1.c1 % 5) ORDER BY (t1.c1 % 5))), ((t2.c1 % 3)) + Node ID: 1 + Relations: Aggregate on ((public.ft4 t1) FULL JOIN (public.ft5 t2)) + Remote SQL: SELECT array_agg(DISTINCT (r1.c1 % 5) ORDER BY ((r1.c1 % 5)) ASC NULLS LAST), (r2.c1 % 3) FROM ("S 1"."T 3" r1 FULL JOIN "S 1"."T 4" r2 ON (((r1.c1 = r2.c1)))) WHERE (((r1.c1 < 20) OR ((r1.c1 IS NULL) AND (r2.c1 < 5)))) GROUP BY 2 ORDER BY array_agg(DISTINCT (r1.c1 % 5) ORDER BY ((r1.c1 % 5)) ASC NULLS LAST) ASC NULLS LAST + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT array_agg(DISTINCT (r1.c1 % 5) ORDER BY ((r1.c1 % 5)) ASC NULLS LAST), (r2.c1 % 3) FROM ("S 1"."T 3" r1 FULL JOIN "S 1"."T 4" r2 ON (((r1.c1 = r2.c1)))) WHERE (((r1.c1 < 20) OR ((r1.c1 IS NULL) AND (r2.c1 < 5)))) GROUP BY 2 ORDER BY array_agg(DISTINCT (r1.c1 % 5) ORDER BY ((r1.c1 % 5)) ASC NULLS LAST) ASC NULLS LAST + Sort + Output: (array_agg(DISTINCT (r1.c1 % 5) ORDER BY (r1.c1 % 5))), ((r2.c1 % 3)) + Sort Key: (array_agg(DISTINCT (r1.c1 % 5) ORDER BY (r1.c1 % 5))) + -> GroupAggregate + Output: array_agg(DISTINCT (r1.c1 % 5) ORDER BY (r1.c1 % 5)), ((r2.c1 % 3)) + Group By Key: ((r2.c1 % 3)) + -> Sort + Output: ((r2.c1 % 3)), r1.c1 + Sort Key: ((r2.c1 % 3)) + -> Hash Full Join + Output: (r2.c1 % 3), r1.c1 + Hash Cond: (r1.c1 = r2.c1) + Filter: ((r1.c1 < 20) OR ((r1.c1 IS NULL) AND (r2.c1 < 5))) + -> Row Adapter + Output: r1.c1 + -> CStore Scan on "S 1"."T 3" r1 + Output: r1.c1 + -> Hash + Output: r2.c1 + -> Row Adapter + Output: r2.c1 + -> CStore Scan on "S 1"."T 4" r2 + Output: r2.c1 + +(32 rows) + +select array_agg(distinct (t1.c1)%5 order by (t1.c1)%5) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1; + array_agg +-------------- + {0,1,2,3,4} + {1,2,3,NULL} +(2 rows) + +explain (verbose, costs off) +select array_agg(distinct (t1.c1)%5 order by (t1.c1)%5 desc nulls last) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: (array_agg(DISTINCT (t1.c1 % 5) ORDER BY (t1.c1 % 5) DESC NULLS LAST)), ((t2.c1 % 3)) + Node ID: 1 + Relations: Aggregate on ((public.ft4 t1) FULL JOIN (public.ft5 t2)) + Remote SQL: SELECT array_agg(DISTINCT (r1.c1 % 5) ORDER BY ((r1.c1 % 5)) DESC NULLS LAST), (r2.c1 % 3) FROM ("S 1"."T 3" r1 FULL JOIN "S 1"."T 4" r2 ON (((r1.c1 = r2.c1)))) WHERE (((r1.c1 < 20) OR ((r1.c1 IS NULL) AND (r2.c1 < 5)))) GROUP BY 2 ORDER BY array_agg(DISTINCT (r1.c1 % 5) ORDER BY ((r1.c1 % 5)) DESC NULLS LAST) ASC NULLS LAST + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT array_agg(DISTINCT (r1.c1 % 5) ORDER BY ((r1.c1 % 5)) DESC NULLS LAST), (r2.c1 % 3) FROM ("S 1"."T 3" r1 FULL JOIN "S 1"."T 4" r2 ON (((r1.c1 = r2.c1)))) WHERE (((r1.c1 < 20) OR ((r1.c1 IS NULL) AND (r2.c1 < 5)))) GROUP BY 2 ORDER BY array_agg(DISTINCT (r1.c1 % 5) ORDER BY ((r1.c1 % 5)) DESC NULLS LAST) ASC NULLS LAST + Sort + Output: (array_agg(DISTINCT (r1.c1 % 5) ORDER BY (r1.c1 % 5) DESC NULLS LAST)), ((r2.c1 % 3)) + Sort Key: (array_agg(DISTINCT (r1.c1 % 5) ORDER BY (r1.c1 % 5) DESC NULLS LAST)) + -> GroupAggregate + Output: array_agg(DISTINCT (r1.c1 % 5) ORDER BY (r1.c1 % 5) DESC NULLS LAST), ((r2.c1 % 3)) + Group By Key: ((r2.c1 % 3)) + -> Sort + Output: ((r2.c1 % 3)), r1.c1 + Sort Key: ((r2.c1 % 3)) + -> Hash Full Join + Output: (r2.c1 % 3), r1.c1 + Hash Cond: (r1.c1 = r2.c1) + Filter: ((r1.c1 < 20) OR ((r1.c1 IS NULL) AND (r2.c1 < 5))) + -> Row Adapter + Output: r1.c1 + -> CStore Scan on "S 1"."T 3" r1 + Output: r1.c1 + -> Hash + Output: r2.c1 + -> Row Adapter + Output: r2.c1 + -> CStore Scan on "S 1"."T 4" r2 + Output: r2.c1 + +(32 rows) + +select array_agg(distinct (t1.c1)%5 order by (t1.c1)%5 desc nulls last) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1; + array_agg +-------------- + {3,2,1,NULL} + {4,3,2,1,0} +(2 rows) + +-- Input relation to aggregate push down hook is not safe to pushdown and thus +-- the aggregate cannot be pushed down to foreign server. +explain (verbose, costs off) +select count(t1.c3) from ft2 t1 left join ft2 t2 on (t1.c1 = random() * t2.c2); + QUERY PLAN +------------------------------------------------------------------------------------------- + Aggregate + Output: count(t1.c3) + -> Nested Loop Left Join + Output: t1.c3 + Join Filter: ((t1.c1)::double precision = (random() * (t2.c2)::double precision)) + -> Foreign Scan on public.ft2 t1 + Output: t1.c3, t1.c1 + Node ID: 1 + Remote SQL: SELECT "C 1", c3 FROM "S 1"."T 1" + -> Materialize + Output: t2.c2 + -> Foreign Scan on public.ft2 t2 + Output: t2.c2 + Node ID: 2 + Remote SQL: SELECT c2 FROM "S 1"."T 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c3 FROM "S 1"."T 1" + Row Adapter + Output: "C 1", c3 + -> CStore Scan on "S 1"."T 1" + Output: "C 1", c3 + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c2 FROM "S 1"."T 1" + Row Adapter + Output: c2 + -> CStore Scan on "S 1"."T 1" + Output: c2 + +(28 rows) + +-- Subquery in FROM clause having aggregate +explain (verbose, costs off) +select count(*), x.b from ft1, (select c2 a, sum(c1) b from ft1 group by c2) x where ft1.c2 = x.a group by x.b order by 1, 2; + QUERY PLAN +----------------------------------------------------------------------------------------------- + Sort + Output: (count(*)), x.b + Sort Key: (count(*)), x.b + -> HashAggregate + Output: count(*), x.b + Group By Key: x.b + -> Hash Join + Output: x.b + Hash Cond: (public.ft1.c2 = x.a) + -> Foreign Scan on public.ft1 + Output: public.ft1.c2 + Node ID: 1 + Remote SQL: SELECT c2 FROM "S 1"."T 1" + -> Hash + Output: x.b, x.a + -> Subquery Scan on x + Output: x.b, x.a + -> Foreign Scan + Output: public.ft1.c2, (sum(public.ft1.c1)) + Node ID: 2 + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT c2, sum("C 1") FROM "S 1"."T 1" GROUP BY 1 + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c2 FROM "S 1"."T 1" + Row Adapter + Output: c2 + -> CStore Scan on "S 1"."T 1" + Output: c2 + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c2, sum("C 1") FROM "S 1"."T 1" GROUP BY 1 + Row Adapter + Output: c2, (sum("C 1")) + -> Vector Sonic Hash Aggregate + Output: c2, sum("C 1") + Group By Key: "T 1".c2 + -> CStore Scan on "S 1"."T 1" + Output: c2, "C 1" + +(38 rows) + +select count(*), x.b from ft1, (select c2 a, sum(c1) b from ft1 group by c2) x where ft1.c2 = x.a group by x.b order by 1, 2; + count | b +-------+------- + 100 | 49600 + 100 | 49700 + 100 | 49800 + 100 | 49900 + 100 | 50000 + 100 | 50100 + 100 | 50200 + 100 | 50300 + 100 | 50400 + 100 | 50500 +(10 rows) + +-- FULL join with IS NULL check in HAVING +explain (verbose, costs off) +select avg(t1.c1), sum(t2.c1) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) group by t2.c1 having (avg(t1.c1) is null and sum(t2.c1) < 10) or sum(t2.c1) is null order by 1 nulls last, 2; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: (avg(t1.c1)), (sum(t2.c1)), t2.c1 + Node ID: 1 + Relations: Aggregate on ((public.ft4 t1) FULL JOIN (public.ft5 t2)) + Remote SQL: SELECT avg(r1.c1), sum(r2.c1), r2.c1 FROM ("S 1"."T 3" r1 FULL JOIN "S 1"."T 4" r2 ON (((r1.c1 = r2.c1)))) GROUP BY 3 HAVING ((((avg(r1.c1) IS NULL) AND (sum(r2.c1) < 10)) OR (sum(r2.c1) IS NULL))) ORDER BY avg(r1.c1) ASC NULLS LAST, sum(r2.c1) ASC NULLS LAST + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT avg(r1.c1), sum(r2.c1), r2.c1 FROM ("S 1"."T 3" r1 FULL JOIN "S 1"."T 4" r2 ON (((r1.c1 = r2.c1)))) GROUP BY 3 HAVING ((((avg(r1.c1) IS NULL) AND (sum(r2.c1) < 10)) OR (sum(r2.c1) IS NULL))) ORDER BY avg(r1.c1) ASC NULLS LAST, sum(r2.c1) ASC NULLS LAST + Sort + Output: (avg(r1.c1)), (sum(r2.c1)), r2.c1 + Sort Key: (avg(r1.c1)), (sum(r2.c1)) + -> HashAggregate + Output: avg(r1.c1), sum(r2.c1), r2.c1 + Group By Key: r2.c1 + Filter: (((avg(r1.c1) IS NULL) AND (sum(r2.c1) < 10)) OR (sum(r2.c1) IS NULL)) + -> Hash Full Join + Output: r1.c1, r2.c1 + Hash Cond: (r1.c1 = r2.c1) + -> Row Adapter + Output: r1.c1 + -> CStore Scan on "S 1"."T 3" r1 + Output: r1.c1 + -> Hash + Output: r2.c1 + -> Row Adapter + Output: r2.c1 + -> CStore Scan on "S 1"."T 4" r2 + Output: r2.c1 + +(29 rows) + +select avg(t1.c1), sum(t2.c1) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) group by t2.c1 having (avg(t1.c1) is null and sum(t2.c1) < 10) or sum(t2.c1) is null order by 1 nulls last, 2; + avg | sum +---------------------+----- + 51.0000000000000000 | + | 3 + | 9 +(3 rows) + +-- Aggregate over FULL join needing to deparse the joining relations as +-- subqueries. +explain (verbose, costs off) +select count(*), sum(t1.c1), avg(t2.c1) from (select c1 from ft4 where c1 between 50 and 60) t1 full join (select c1 from ft5 where c1 between 50 and 60) t2 on (t1.c1 = t2.c1); + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: (count(*)), (sum(ft4.c1)), (avg(ft5.c1)) + Node ID: 1 + Relations: Aggregate on ((public.ft4) FULL JOIN (public.ft5)) + Remote SQL: SELECT count(*), sum(s4.c1), avg(s5.c1) FROM ((SELECT c1 FROM "S 1"."T 3" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s4(c1) FULL JOIN (SELECT c1 FROM "S 1"."T 4" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s5(c1) ON (((s4.c1 = s5.c1)))) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT count(*), sum(s4.c1), avg(s5.c1) FROM ((SELECT c1 FROM "S 1"."T 3" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s4(c1) FULL JOIN (SELECT c1 FROM "S 1"."T 4" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s5(c1) ON (((s4.c1 = s5.c1)))) + Aggregate + Output: count(*), sum("T 3".c1), avg("T 4".c1) + -> Hash Full Join + Output: "T 3".c1, "T 4".c1 + Hash Cond: ("T 3".c1 = "T 4".c1) + -> Row Adapter + Output: "T 3".c1 + -> CStore Scan on "S 1"."T 3" + Output: "T 3".c1 + Filter: (("T 3".c1 >= 50) AND ("T 3".c1 <= 60)) + -> Hash + Output: "T 4".c1 + -> Row Adapter + Output: "T 4".c1 + -> CStore Scan on "S 1"."T 4" + Output: "T 4".c1 + Filter: (("T 4".c1 >= 50) AND ("T 4".c1 <= 60)) + +(26 rows) + +select count(*), sum(t1.c1), avg(t2.c1) from (select c1 from ft4 where c1 between 50 and 60) t1 full join (select c1 from ft5 where c1 between 50 and 60) t2 on (t1.c1 = t2.c1); + count | sum | avg +-------+-----+--------------------- + 8 | 330 | 55.5000000000000000 +(1 row) + +-- ORDER BY expression is part of the target list but not pushed down to +-- foreign server. +explain (verbose, costs off) +select sum(c2) * (random() <= 1)::int as sum from ft1 order by 1; + QUERY PLAN +---------------------------------------------------------------------------- + Sort + Output: ((sum(c2) * ((random() <= 1::double precision))::integer)) + Sort Key: ((sum(ft1.c2) * ((random() <= 1::double precision))::integer)) + -> Aggregate + Output: (sum(c2) * ((random() <= 1::double precision))::integer) + -> Foreign Scan on public.ft1 + Output: c2 + Node ID: 1 + Remote SQL: SELECT c2 FROM "S 1"."T 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c2 FROM "S 1"."T 1" + Row Adapter + Output: c2 + -> CStore Scan on "S 1"."T 1" + Output: c2 + +(17 rows) + +select sum(c2) * (random() <= 1)::int as sum from ft1 order by 1; + sum +------ + 4500 +(1 row) + +-- Check with placeHolderVars +explain (verbose, costs off) +select sum(q.a), count(q.b) from ft4 left join (select 13, avg(ft1.c1), sum(ft2.c1) from ft1 right join ft2 on (ft1.c1 = ft2.c1)) q(a, b, c) on (ft4.c1 <= q.b); + QUERY PLAN +--------------------------------------------------------------------------------------- + Aggregate + Output: sum(q.a), count(q.b) + -> Nested Loop Left Join + Output: q.a, q.b + Join Filter: ((ft4.c1)::numeric <= q.b) + -> Foreign Scan on public.ft4 + Output: ft4.c1 + Node ID: 1 + Remote SQL: SELECT c1 FROM "S 1"."T 3" + -> Materialize + Output: q.a, q.b + -> Subquery Scan on q + Output: q.a, q.b + -> Aggregate + Output: 13, avg(ft1.c1), sum(ft2.c1) + -> Hash Right Join + Output: ft2.c1, ft1.c1 + Hash Cond: (ft1.c1 = ft2.c1) + -> Foreign Scan on public.ft1 + Output: ft1.c1 + Node ID: 2 + Remote SQL: SELECT "C 1" FROM "S 1"."T 1" + -> Hash + Output: ft2.c1 + -> Foreign Scan on public.ft2 + Output: ft2.c1 + Node ID: 3 + Remote SQL: SELECT "C 1" FROM "S 1"."T 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c1 FROM "S 1"."T 3" + Row Adapter + Output: c1 + -> CStore Scan on "S 1"."T 3" + Output: c1 + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1" FROM "S 1"."T 1" + Row Adapter + Output: "C 1" + -> CStore Scan on "S 1"."T 1" + Output: "C 1" + Node 3: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1" FROM "S 1"."T 1" + Row Adapter + Output: "C 1" + -> CStore Scan on "S 1"."T 1" + Output: "C 1" + +(46 rows) + +select sum(q.a), count(q.b) from ft4 left join (select 13, avg(ft1.c1), sum(ft2.c1) from ft1 right join ft2 on (ft1.c1 = ft2.c1)) q(a, b, c) on (ft4.c1 <= q.b); + sum | count +-----+------- + 650 | 50 +(1 row) + +-- Not supported cases +-- Grouping sets +explain (verbose, costs off) +select c2, sum(c1) from ft1 where c2 < 3 group by rollup(c2) order by 1 nulls last; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------ + Sort + Output: c2, (sum(c1)) + Sort Key: ft1.c2 + -> GroupAggregate + Output: c2, sum(c1) + Group By Key: ft1.c2 + Group By Key: () + -> Foreign Scan on public.ft1 + Output: c2, c1 + Node ID: 1 + Remote SQL: SELECT "C 1", c2 FROM "S 1"."T 1" WHERE ((c2 < 3)) ORDER BY c2 ASC NULLS LAST + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2 FROM "S 1"."T 1" WHERE ((c2 < 3)) ORDER BY c2 ASC NULLS LAST + Row Adapter + Output: "C 1", c2 + -> Vector Sort + Output: "C 1", c2 + Sort Key: "T 1".c2 + -> CStore Scan on "S 1"."T 1" + Output: "C 1", c2 + Filter: ("T 1".c2 < 3) + +(23 rows) + +select c2, sum(c1) from ft1 where c2 < 3 group by rollup(c2) order by 1 nulls last; + c2 | sum +----+-------- + 0 | 50500 + 1 | 49600 + 2 | 49700 + | 149800 +(4 rows) + +explain (verbose, costs off) +select c2, sum(c1) from ft1 where c2 < 3 group by cube(c2) order by 1 nulls last; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------ + Sort + Output: c2, (sum(c1)) + Sort Key: ft1.c2 + -> GroupAggregate + Output: c2, sum(c1) + Group By Key: ft1.c2 + Group By Key: () + -> Foreign Scan on public.ft1 + Output: c2, c1 + Node ID: 1 + Remote SQL: SELECT "C 1", c2 FROM "S 1"."T 1" WHERE ((c2 < 3)) ORDER BY c2 ASC NULLS LAST + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2 FROM "S 1"."T 1" WHERE ((c2 < 3)) ORDER BY c2 ASC NULLS LAST + Row Adapter + Output: "C 1", c2 + -> Vector Sort + Output: "C 1", c2 + Sort Key: "T 1".c2 + -> CStore Scan on "S 1"."T 1" + Output: "C 1", c2 + Filter: ("T 1".c2 < 3) + +(23 rows) + +select c2, sum(c1) from ft1 where c2 < 3 group by cube(c2) order by 1 nulls last; + c2 | sum +----+-------- + 0 | 50500 + 1 | 49600 + 2 | 49700 + | 149800 +(4 rows) + +explain (verbose, costs off) +select c2, c6, sum(c1) from ft1 where c2 < 3 group by grouping sets(c2, c6) order by 1 nulls last, 2 nulls last; + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------- + Sort + Output: c2, c6, (sum(c1)) + Sort Key: ft1.c2, ft1.c6 + -> GroupAggregate + Output: c2, c6, sum(c1) + Group By Key: ft1.c2 + Sort Key: ft1.c6 + Group By Key: ft1.c6 + -> Foreign Scan on public.ft1 + Output: c2, c6, c1 + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c6 FROM "S 1"."T 1" WHERE ((c2 < 3)) ORDER BY c2 ASC NULLS LAST + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c6 FROM "S 1"."T 1" WHERE ((c2 < 3)) ORDER BY c2 ASC NULLS LAST + Row Adapter + Output: "C 1", c2, c6 + -> Vector Sort + Output: "C 1", c2, c6 + Sort Key: "T 1".c2 + -> CStore Scan on "S 1"."T 1" + Output: "C 1", c2, c6 + Filter: ("T 1".c2 < 3) + +(24 rows) + +select c2, c6, sum(c1) from ft1 where c2 < 3 group by grouping sets(c2, c6) order by 1 nulls last, 2 nulls last; + c2 | c6 | sum +----+----+------- + 0 | | 50500 + 1 | | 49600 + 2 | | 49700 + | 0 | 50500 + | 1 | 49600 + | 2 | 49700 +(6 rows) + +explain (verbose, costs off) +select c2, sum(c1), grouping(c2) from ft1 where c2 < 3 group by c2 order by 1 nulls last; + QUERY PLAN +--------------------------------------------------------------------------------------------- + Sort + Output: c2, (sum(c1)), (GROUPING(c2)) + Sort Key: ft1.c2 + -> HashAggregate + Output: c2, sum(c1), GROUPING(c2) + Group By Key: ft1.c2 + -> Foreign Scan on public.ft1 + Output: c2, c1 + Node ID: 1 + Remote SQL: SELECT "C 1", c2 FROM "S 1"."T 1" WHERE ((c2 < 3)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2 FROM "S 1"."T 1" WHERE ((c2 < 3)) + Row Adapter + Output: "C 1", c2 + -> CStore Scan on "S 1"."T 1" + Output: "C 1", c2 + Filter: ("T 1".c2 < 3) + +(19 rows) + +select c2, sum(c1), grouping(c2) from ft1 where c2 < 3 group by c2 order by 1 nulls last; + c2 | sum | grouping +----+-------+---------- + 0 | 50500 | 0 + 1 | 49600 | 0 + 2 | 49700 | 0 +(3 rows) + +-- DISTINCT itself is not pushed down, whereas underneath aggregate is pushed +explain (verbose, costs off) +select distinct sum(c1)/1000 s from ft2 where c2 < 6 group by c2 order by 1; + QUERY PLAN +--------------------------------------------------------------------------------------------- + Unique + Output: ((sum(c1) / 1000)), c2 + -> Sort + Output: ((sum(c1) / 1000)), c2 + Sort Key: ((sum(ft2.c1) / 1000)) + -> HashAggregate + Output: (sum(c1) / 1000), c2 + Group By Key: ft2.c2 + -> Foreign Scan on public.ft2 + Output: c2, c1 + Node ID: 1 + Remote SQL: SELECT "C 1", c2 FROM "S 1"."T 1" WHERE ((c2 < 6)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2 FROM "S 1"."T 1" WHERE ((c2 < 6)) + Row Adapter + Output: "C 1", c2 + -> CStore Scan on "S 1"."T 1" + Output: "C 1", c2 + Filter: ("T 1".c2 < 6) + +(21 rows) + +select distinct sum(c1)/1000 s from ft2 where c2 < 6 group by c2 order by 1; + s +------ + 49.6 + 49.7 + 49.8 + 49.9 + 50 + 50.5 +(6 rows) + +-- WindowAgg +explain (verbose, costs off) +select c2, sum(c2), count(c2) over (partition by c2%2) from ft2 where c2 < 10 group by c2 order by 1; + QUERY PLAN +------------------------------------------------------------------------------------------ + Sort + Output: c2, (sum(c2)), (count(c2) OVER (PARTITION BY ((c2 % 2)))), ((c2 % 2)) + Sort Key: ft2.c2 + -> WindowAgg + Output: c2, (sum(c2)), count(c2) OVER (PARTITION BY ((c2 % 2))), ((c2 % 2)) + -> Sort + Output: c2, ((c2 % 2)), (sum(c2)) + Sort Key: ((ft2.c2 % 2)) + -> GroupAggregate + Output: c2, (c2 % 2), sum(c2) + Group By Key: ft2.c2 + -> Sort + Output: c2 + Sort Key: ft2.c2 + -> Foreign Scan on public.ft2 + Output: c2 + Node ID: 1 + Remote SQL: SELECT c2 FROM "S 1"."T 1" WHERE ((c2 < 10)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c2 FROM "S 1"."T 1" WHERE ((c2 < 10)) + Row Adapter + Output: c2 + -> CStore Scan on "S 1"."T 1" + Output: c2 + Filter: ("T 1".c2 < 10) + +(27 rows) + +select c2, sum(c2), count(c2) over (partition by c2%2) from ft2 where c2 < 10 group by c2 order by 1; + c2 | sum | count +----+-----+------- + 0 | 0 | 5 + 1 | 100 | 5 + 2 | 200 | 5 + 3 | 300 | 5 + 4 | 400 | 5 + 5 | 500 | 5 + 6 | 600 | 5 + 7 | 700 | 5 + 8 | 800 | 5 + 9 | 900 | 5 +(10 rows) + +explain (verbose, costs off) +select c2, array_agg(c2) over (partition by c2%2 order by c2 desc) from ft1 where c2 < 10 group by c2 order by 1; + QUERY PLAN +------------------------------------------------------------------------------------------------------------- + Sort + Output: c2, (array_agg(c2) OVER (PARTITION BY ((c2 % 2)) ORDER BY c2 USING = NULLS LAST)), ((c2 % 2)) + Sort Key: ft1.c2 + -> WindowAgg + Output: c2, array_agg(c2) OVER (PARTITION BY ((c2 % 2)) ORDER BY c2 USING = NULLS LAST), ((c2 % 2)) + -> Sort + Output: c2, ((c2 % 2)) + Sort Key: ((ft1.c2 % 2)), ft1.c2 DESC + -> HashAggregate + Output: c2, (c2 % 2) + Group By Key: ft1.c2 + -> Foreign Scan on public.ft1 + Output: c2 + Node ID: 1 + Remote SQL: SELECT c2 FROM "S 1"."T 1" WHERE ((c2 < 10)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c2 FROM "S 1"."T 1" WHERE ((c2 < 10)) + Row Adapter + Output: c2 + -> CStore Scan on "S 1"."T 1" + Output: c2 + Filter: ("T 1".c2 < 10) + +(24 rows) + +select c2, array_agg(c2) over (partition by c2%2 order by c2 desc) from ft1 where c2 < 10 group by c2 order by 1; + c2 | array_agg +----+------------- + 0 | {8,6,4,2,0} + 1 | {9,7,5,3,1} + 2 | {8,6,4,2} + 3 | {9,7,5,3} + 4 | {8,6,4} + 5 | {9,7,5} + 6 | {8,6} + 7 | {9,7} + 8 | {8} + 9 | {9} +(10 rows) + +explain (verbose, costs off) +select c2, array_agg(c2) over (partition by c2%2 order by c2 range between current row and unbounded following) from ft1 where c2 < 10 group by c2 order by 1; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------------------------------------- + Sort + Output: c2, (array_agg(c2) OVER (PARTITION BY ((c2 % 2)) ORDER BY c2 USING = NULLS LAST RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)), ((c2 % 2)) + Sort Key: ft1.c2 + -> WindowAgg + Output: c2, array_agg(c2) OVER (PARTITION BY ((c2 % 2)) ORDER BY c2 USING = NULLS LAST RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING), ((c2 % 2)) + -> Sort + Output: c2, ((c2 % 2)) + Sort Key: ((ft1.c2 % 2)), ft1.c2 + -> HashAggregate + Output: c2, (c2 % 2) + Group By Key: ft1.c2 + -> Foreign Scan on public.ft1 + Output: c2 + Node ID: 1 + Remote SQL: SELECT c2 FROM "S 1"."T 1" WHERE ((c2 < 10)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c2 FROM "S 1"."T 1" WHERE ((c2 < 10)) + Row Adapter + Output: c2 + -> CStore Scan on "S 1"."T 1" + Output: c2 + Filter: ("T 1".c2 < 10) + +(24 rows) + +select c2, array_agg(c2) over (partition by c2%2 order by c2 range between current row and unbounded following) from ft1 where c2 < 10 group by c2 order by 1; + c2 | array_agg +----+------------- + 0 | {0,2,4,6,8} + 1 | {1,3,5,7,9} + 2 | {2,4,6,8} + 3 | {3,5,7,9} + 4 | {4,6,8} + 5 | {5,7,9} + 6 | {6,8} + 7 | {7,9} + 8 | {8} + 9 | {9} +(10 rows) + +-- Same group by +explain verbose select c1,c1,c1,count(*) from ft1 group by 1,1,1; + QUERY PLAN +--------------------------------------------------------------------------- + HashAggregate (cost=147.00..157.00 rows=1000 width=12) + Output: c1, c1, c1, count(*) + Group By Key: ft1.c1 + -> Foreign Scan on public.ft1 (cost=100.00..142.00 rows=1000 width=4) + Output: c1 + Node ID: 1 + Remote SQL: SELECT "C 1" FROM "S 1"."T 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS ON) SELECT "C 1" FROM "S 1"."T 1" + Row Adapter (cost=345.00..345.00 rows=1000 width=4) + Output: "C 1" + -> CStore Scan on "S 1"."T 1" (cost=0.00..345.00 rows=1000 width=4) + Output: "C 1" + +(15 rows) + +set enable_hashagg = off; +explain verbose select c1,c1,c1,count(*) from ft1 group by 1,1,1; + QUERY PLAN +-------------------------------------------------------------------------------------------- + Foreign Scan (cost=105.00..157.00 rows=1000 width=0) + Output: c1, c1, c1, (count(*)) + Node ID: 1 + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT "C 1", count(*) FROM "S 1"."T 1" GROUP BY 1 + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS ON) SELECT "C 1", count(*) FROM "S 1"."T 1" GROUP BY 1 + Row Adapter (cost=360.00..360.00 rows=1000 width=12) + Output: "C 1", (count(*)) + -> Vector Sonic Hash Aggregate (cost=350.00..360.00 rows=1000 width=12) + Output: "C 1", count(*) + Group By Key: "T 1"."C 1" + -> CStore Scan on "S 1"."T 1" (cost=0.00..345.00 rows=1000 width=4) + Output: "C 1" + +(16 rows) + +set enable_hashagg = on; +-- ====================================================================================================================================== +-- TEST-MODULE: parameterized queries +-- -------------------------------------- +-- ====================================================================================================================================== +-- simple join +PREPARE st1(int, int) AS SELECT t1.c3, t2.c3 FROM ft1 t1, ft2 t2 WHERE t1.c1 = $1 AND t2.c1 = $2; +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st1(1, 2); + QUERY PLAN +--------------------------------------------------------------------------------------------------- + Nested Loop + Output: t1.c3, t2.c3 + -> Foreign Scan on public.ft1 t1 + Output: t1.c3 + Node ID: 1 + Remote SQL: SELECT c3 FROM "S 1"."T 1" WHERE (("C 1" = $1::integer)) + -> Foreign Scan on public.ft2 t2 + Output: t2.c3 + Node ID: 2 + Remote SQL: SELECT c3 FROM "S 1"."T 1" WHERE (("C 1" = $1::integer)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c3 FROM "S 1"."T 1" WHERE (("C 1" = $1::integer)) + failed to get remote plan, error massage is: + there is no parameter $1 + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c3 FROM "S 1"."T 1" WHERE (("C 1" = $1::integer)) + failed to get remote plan, error massage is: + current transaction is aborted, commands ignored until end of transaction block, firstChar[Q] + +(19 rows) + +EXECUTE st1(1, 1); + c3 | c3 +-------+------- + 00001 | 00001 +(1 row) + +EXECUTE st1(101, 101); + c3 | c3 +-------+------- + 00101 | 00101 +(1 row) + +-- subquery using stable function (can't be sent to remote) +PREPARE st2(int) AS SELECT * FROM ft1 t1 WHERE t1.c1 < $2 AND t1.c3 IN (SELECT c3 FROM ft2 t2 WHERE c1 > $1 AND "date"(c4) = '1970-01-17'::date) ORDER BY c1; +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st2(10, 20); + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------- + Sort + Output: t1.c1, t1.c2, t1.c3, t1.c4, t1.c5, t1.c6, t1.c7, t1.c8 + Sort Key: t1.c1 + -> Hash Semi Join + Output: t1.c1, t1.c2, t1.c3, t1.c4, t1.c5, t1.c6, t1.c7, t1.c8 + Hash Cond: (t1.c3 = t2.c3) + -> Foreign Scan on public.ft1 t1 + Output: t1.c1, t1.c2, t1.c3, t1.c4, t1.c5, t1.c6, t1.c7, t1.c8 + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" < $1::integer)) + -> Hash + Output: t2.c3 + -> Foreign Scan on public.ft2 t2 + Output: t2.c3 + Filter: ("date"(t2.c4) = 'Sat Jan 17 00:00:00 1970'::timestamp(0) without time zone) + Node ID: 2 + Remote SQL: SELECT c3, c4 FROM "S 1"."T 1" WHERE (("C 1" > $1::integer)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" < $1::integer)) + failed to get remote plan, error massage is: + there is no parameter $1 + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c3, c4 FROM "S 1"."T 1" WHERE (("C 1" > $1::integer)) + failed to get remote plan, error massage is: + current transaction is aborted, commands ignored until end of transaction block, firstChar[Q] + +(26 rows) + +EXECUTE st2(10, 20); + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +----+----+-------+------------------------------+--------------------------+----+------------+----- + 16 | 6 | 00016 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo +(1 row) + +EXECUTE st2(101, 121); + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +-----+----+-------+------------------------------+--------------------------+----+------------+----- + 116 | 6 | 00116 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo +(1 row) + +-- subquery using immutable function (can be sent to remote) +PREPARE st3(int) AS SELECT * FROM ft1 t1 WHERE t1.c1 < $2 AND t1.c3 IN (SELECT c3 FROM ft2 t2 WHERE c1 > $1 AND "date"(c5) = '1970-01-17'::date) ORDER BY c1; +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st3(10, 20); + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Sort + Output: t1.c1, t1.c2, t1.c3, t1.c4, t1.c5, t1.c6, t1.c7, t1.c8 + Sort Key: t1.c1 + -> Hash Semi Join + Output: t1.c1, t1.c2, t1.c3, t1.c4, t1.c5, t1.c6, t1.c7, t1.c8 + Hash Cond: (t1.c3 = t2.c3) + -> Foreign Scan on public.ft1 t1 + Output: t1.c1, t1.c2, t1.c3, t1.c4, t1.c5, t1.c6, t1.c7, t1.c8 + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" < $1::integer)) + -> Hash + Output: t2.c3 + -> Foreign Scan on public.ft2 t2 + Output: t2.c3 + Node ID: 2 + Remote SQL: SELECT c3 FROM "S 1"."T 1" WHERE (("C 1" > $1::integer)) AND (("date"(c5) = '1970-01-17 00:00:00'::timestamp(0) without time zone)) ORDER BY c3 ASC NULLS LAST + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" < $1::integer)) + failed to get remote plan, error massage is: + there is no parameter $1 + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c3 FROM "S 1"."T 1" WHERE (("C 1" > $1::integer)) AND (("date"(c5) = '1970-01-17 00:00:00'::timestamp(0) without time zone)) ORDER BY c3 ASC NULLS LAST + failed to get remote plan, error massage is: + current transaction is aborted, commands ignored until end of transaction block, firstChar[Q] + +(25 rows) + +EXECUTE st3(10, 20); + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +----+----+-------+------------------------------+--------------------------+----+------------+----- + 16 | 6 | 00016 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo +(1 row) + +EXECUTE st3(20, 30); + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +----+----+----+----+----+----+----+---- +(0 rows) + +-- custom plan should be chosen initially +PREPARE st4(int) AS SELECT * FROM ft1 t1 WHERE t1.c1 = $1; +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st4(1); + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = $1::integer)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = $1::integer)) + failed to get remote plan, error massage is: + there is no parameter $1 + +(10 rows) + +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st4(1); + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = $1::integer)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = $1::integer)) + failed to get remote plan, error massage is: + there is no parameter $1 + +(10 rows) + +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st4(1); + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = $1::integer)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = $1::integer)) + failed to get remote plan, error massage is: + there is no parameter $1 + +(10 rows) + +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st4(1); + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = $1::integer)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = $1::integer)) + failed to get remote plan, error massage is: + there is no parameter $1 + +(10 rows) + +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st4(1); + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = $1::integer)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = $1::integer)) + failed to get remote plan, error massage is: + there is no parameter $1 + +(10 rows) + +-- once we try it enough times, should switch to generic plan +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st4(1); + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = $1::integer)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = $1::integer)) + failed to get remote plan, error massage is: + there is no parameter $1 + +(10 rows) + +-- value of $1 should not be sent to remote +PREPARE st5(user_enum,int) AS SELECT * FROM ft1 t1 WHERE c8 = $1 and c1 = $2; +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st5('foo', 1); + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Filter: (t1.c8 = $1) + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = $1::integer)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = $1::integer)) + failed to get remote plan, error massage is: + there is no parameter $1 + +(11 rows) + +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st5('foo', 1); + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Filter: (t1.c8 = $1) + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = $1::integer)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = $1::integer)) + failed to get remote plan, error massage is: + there is no parameter $1 + +(11 rows) + +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st5('foo', 1); + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Filter: (t1.c8 = $1) + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = $1::integer)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = $1::integer)) + failed to get remote plan, error massage is: + there is no parameter $1 + +(11 rows) + +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st5('foo', 1); + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Filter: (t1.c8 = $1) + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = $1::integer)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = $1::integer)) + failed to get remote plan, error massage is: + there is no parameter $1 + +(11 rows) + +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st5('foo', 1); + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Filter: (t1.c8 = $1) + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = $1::integer)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = $1::integer)) + failed to get remote plan, error massage is: + there is no parameter $1 + +(11 rows) + +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st5('foo', 1); + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Filter: (t1.c8 = $1) + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = $1::integer)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = $1::integer)) + failed to get remote plan, error massage is: + there is no parameter $1 + +(11 rows) + +EXECUTE st5('foo', 1); + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +----+----+-------+------------------------------+--------------------------+----+------------+----- + 1 | 1 | 00001 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1 | 1 | foo +(1 row) + +-- altering FDW options requires replanning +PREPARE st6 AS SELECT * FROM ft1 t1 WHERE t1.c1 = t1.c2; +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st6; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = c2)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = c2)) + Row Adapter + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + -> CStore Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Filter: ("T 1"."C 1" = "T 1".c2) + +(13 rows) + +PREPARE st7 AS INSERT INTO ft1 (c1,c2,c3) VALUES (1001,101,'foo'); +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st7; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Insert on public.ft1 + -> Result + Output: NULL::integer, 1001, 101, 'foo'::text, NULL::timestamp with time zone, NULL::timestamp without time zone, NULL::character varying, 'ft1 '::character(10), NULL::user_enum +(3 rows) + +ALTER TABLE "S 1"."T 1" RENAME TO "T 0"; +ALTER FOREIGN TABLE ft1 OPTIONS (SET table_name 'T 0'); +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st6; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 0" WHERE (("C 1" = c2)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 0" WHERE (("C 1" = c2)) + Row Adapter + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + -> CStore Scan on "S 1"."T 0" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Filter: ("T 0"."C 1" = "T 0".c2) + +(13 rows) + +EXECUTE st6; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +----+----+-------+------------------------------+--------------------------+----+------------+----- + 1 | 1 | 00001 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1 | 1 | foo + 2 | 2 | 00002 | Sat Jan 03 00:00:00 1970 PST | Sat Jan 03 00:00:00 1970 | 2 | 2 | foo + 3 | 3 | 00003 | Sun Jan 04 00:00:00 1970 PST | Sun Jan 04 00:00:00 1970 | 3 | 3 | foo + 4 | 4 | 00004 | Mon Jan 05 00:00:00 1970 PST | Mon Jan 05 00:00:00 1970 | 4 | 4 | foo + 5 | 5 | 00005 | Tue Jan 06 00:00:00 1970 PST | Tue Jan 06 00:00:00 1970 | 5 | 5 | foo + 6 | 6 | 00006 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo + 7 | 7 | 00007 | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7 | 7 | foo + 8 | 8 | 00008 | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8 | 8 | foo + 9 | 9 | 00009 | Sat Jan 10 00:00:00 1970 PST | Sat Jan 10 00:00:00 1970 | 9 | 9 | foo +(9 rows) + +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st7; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Insert on public.ft1 + -> Result + Output: NULL::integer, 1001, 101, 'foo'::text, NULL::timestamp with time zone, NULL::timestamp without time zone, NULL::character varying, 'ft1 '::character(10), NULL::user_enum +(3 rows) + +ALTER TABLE "S 1"."T 0" RENAME TO "T 1"; +ALTER FOREIGN TABLE ft1 OPTIONS (SET table_name 'T 1'); +PREPARE st8 AS SELECT count(c3) FROM ft1 t1 WHERE t1.c1 === t1.c2; +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st8; + QUERY PLAN +-------------------------------------------------------------------------------- + Aggregate + Output: count(c3) + -> Foreign Scan on public.ft1 t1 + Output: c3 + Filter: (t1.c1 === t1.c2) + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3 FROM "S 1"."T 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3 FROM "S 1"."T 1" + Row Adapter + Output: "C 1", c2, c3 + -> CStore Scan on "S 1"."T 1" + Output: "C 1", c2, c3 + +(15 rows) + +ALTER SERVER loopback OPTIONS (DROP extensions); +ERROR: option "extensions" not found +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st8; + QUERY PLAN +-------------------------------------------------------------------------------- + Aggregate + Output: count(c3) + -> Foreign Scan on public.ft1 t1 + Output: c3 + Filter: (t1.c1 === t1.c2) + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3 FROM "S 1"."T 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3 FROM "S 1"."T 1" + Row Adapter + Output: "C 1", c2, c3 + -> CStore Scan on "S 1"."T 1" + Output: "C 1", c2, c3 + +(15 rows) + +EXECUTE st8; + count +------- + 9 +(1 row) + +ALTER SERVER loopback OPTIONS (ADD extensions 'postgres_fdw'); +ERROR: invalid option "extensions" +HINT: Valid options in this context are: service, connect_timeout, dbname, host, remote_nodename, hostaddr, port, localhost, localport, application_name, fencedUdfRPCMode, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, rw_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, krbsrvname, prototype, connection_info, connectionExtraInfo, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, updatable +-- cleanup +DEALLOCATE st1; +DEALLOCATE st2; +DEALLOCATE st3; +DEALLOCATE st4; +DEALLOCATE st5; +DEALLOCATE st6; +DEALLOCATE st7; +DEALLOCATE st8; +-- ====================================================================================================================================== +-- TEST-MODULE: used in PL/pgSQL function +-- -------------------------------------- +-- ====================================================================================================================================== +CREATE OR REPLACE FUNCTION f_test(p_c1 int) RETURNS int AS $$ +DECLARE + v_c1 int; +BEGIN + SELECT c1 INTO v_c1 FROM ft1 WHERE c1 = p_c1 LIMIT 1; + PERFORM c1 FROM ft1 WHERE c1 = p_c1 AND p_c1 = v_c1 LIMIT 1; + RETURN v_c1; +END; +$$ LANGUAGE plpgsql; +SELECT f_test(100); + f_test +-------- + 100 +(1 row) + +DROP FUNCTION f_test(int); +-- ====================================================================================================================================== +-- TEST-MODULE: REINDEX +-- -------------------------------------- +-- ====================================================================================================================================== +-- remote table is not created here +CREATE FOREIGN TABLE reindex_foreign (c1 int, c2 int) + SERVER loopback2 OPTIONS (table_name 'reindex_local'); +REINDEX TABLE reindex_foreign; -- error +ERROR: "reindex_foreign" is not a table or materialized view +DROP FOREIGN TABLE reindex_foreign; +-- partitions and foreign tables +CREATE TABLE reind_fdw_parent (c1 int) with (orientation=column) PARTITION BY RANGE (c1) ( + partition reind_fdw_0_10 values less than(11), + partition reind_fdw_10_20 values less than(21) +); +CREATE FOREIGN TABLE reind_fdw_10_20_fp(c1 int) + SERVER loopback OPTIONS (table_name 'reind_fdw_parent'); +CREATE FOREIGN TABLE reind_fdw_10_20_f1(c1 int) + SERVER loopback OPTIONS (table_name 'reind_fdw_parent partition(reind_fdw_10_20)'); +REINDEX TABLE reind_fdw_10_20_fp; -- error +ERROR: "reind_fdw_10_20_fp" is not a table or materialized view +REINDEX TABLE reind_fdw_10_20_f1; -- error +ERROR: "reind_fdw_10_20_f1" is not a table or materialized view +REINDEX TABLE reind_fdw_parent; -- ok +NOTICE: table "reind_fdw_parent" has no indexes +DROP TABLE reind_fdw_parent; +DROP FOREIGN TABLE reind_fdw_10_20_fp; +DROP FOREIGN TABLE reind_fdw_10_20_f1; +-- ====================================================================================================================================== +-- TEST-MODULE: conversion error +-- -------------------------------------- +-- ====================================================================================================================================== +ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 TYPE int; +SELECT * FROM ft1 ftx(x1,x2,x3,x4,x5,x6,x7,x8) WHERE x1 = 1; -- ERROR +ERROR: invalid input syntax for integer: "foo" +CONTEXT: column "x8" of foreign table "ftx" +EXPLAIN (VERBOSE, COSTS OFF) SELECT ftx.x1, ft2.c2, ftx.x8 FROM ft1 ftx(x1,x2,x3,x4,x5,x6,x7,x8), ft2 + WHERE ftx.x1 = ft2.c1 AND ftx.x1 = 1; + QUERY PLAN +------------------------------------------------------------------------------------------------ + Nested Loop + Output: ftx.x1, ft2.c2, ftx.x8 + -> Foreign Scan on public.ft1 ftx + Output: ftx.x1, ftx.x8 + Node ID: 1 + Remote SQL: SELECT "C 1", c8 FROM "S 1"."T 1" WHERE (("C 1" = 1)) + -> Foreign Scan on public.ft2 + Output: ft2.c2, ft2.c1 + Node ID: 2 + Remote SQL: SELECT "C 1", c2 FROM "S 1"."T 1" WHERE (("C 1" = 1)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c8 FROM "S 1"."T 1" WHERE (("C 1" = 1)) + Row Adapter + Output: "C 1", c8 + -> CStore Index Scan using t1_pkey on "S 1"."T 1" + Output: "C 1", c8 + Index Cond: ("T 1"."C 1" = 1) + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2 FROM "S 1"."T 1" WHERE (("C 1" = 1)) + Row Adapter + Output: "C 1", c2 + -> CStore Index Scan using t1_pkey on "S 1"."T 1" + Output: "C 1", c2 + Index Cond: ("T 1"."C 1" = 1) + +(25 rows) + +SELECT ftx.x1, ft2.c2, ftx.x8 FROM ft1 ftx(x1,x2,x3,x4,x5,x6,x7,x8), ft2 + WHERE ftx.x1 = ft2.c1 AND ftx.x1 = 1; -- ERROR +ERROR: invalid input syntax for integer: "foo" +CONTEXT: column "x8" of foreign table "ftx" +EXPLAIN (VERBOSE, COSTS OFF) SELECT ftx.x1, ft2.c2, ftx FROM ft1 ftx(x1,x2,x3,x4,x5,x6,x7,x8), ft2 + WHERE ftx.x1 = ft2.c1 AND ftx.x1 = 1; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------ + Nested Loop + Output: ftx.x1, ft2.c2, ftx.* + -> Foreign Scan on public.ft1 ftx + Output: ftx.x1, ftx.* + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 1)) + -> Foreign Scan on public.ft2 + Output: ft2.c2, ft2.c1 + Node ID: 2 + Remote SQL: SELECT "C 1", c2 FROM "S 1"."T 1" WHERE (("C 1" = 1)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 1)) + Row Adapter + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + -> CStore Index Scan using t1_pkey on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Index Cond: ("T 1"."C 1" = 1) + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2 FROM "S 1"."T 1" WHERE (("C 1" = 1)) + Row Adapter + Output: "C 1", c2 + -> CStore Index Scan using t1_pkey on "S 1"."T 1" + Output: "C 1", c2 + Index Cond: ("T 1"."C 1" = 1) + +(25 rows) + +SELECT ftx.x1, ft2.c2, ftx FROM ft1 ftx(x1,x2,x3,x4,x5,x6,x7,x8), ft2 + WHERE ftx.x1 = ft2.c1 AND ftx.x1 = 1; -- ERROR +ERROR: invalid input syntax for integer: "foo" +CONTEXT: column "x8" of foreign table "ftx" +SELECT sum(c2), array_agg(c8) FROM ft1 GROUP BY c8; -- ERROR +ERROR: invalid input syntax for integer: "foo" +CONTEXT: processing expression at position 2 in select list +ANALYZE ft1; -- ERROR +ERROR: invalid input syntax for integer: "foo" +CONTEXT: column "c8" of foreign table "ft1" +ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 TYPE user_enum; +-- ====================================================================================================================================== +-- TEST-MODULE: subtransaction +-- + local/remote error doesn't break cursor +-- -------------------------------------- +-- ====================================================================================================================================== +BEGIN; +DECLARE c CURSOR FOR SELECT * FROM ft1 ORDER BY c1; +FETCH c; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +----+----+-------+------------------------------+--------------------------+----+------------+----- + 1 | 1 | 00001 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1 | 1 | foo +(1 row) + +SAVEPOINT s; +ERROR OUT; -- ERROR +ERROR: syntax error at or near "ERROR" +LINE 1: ERROR OUT; + ^ +ROLLBACK TO s; +FETCH c; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +----+----+-------+------------------------------+--------------------------+----+------------+----- + 2 | 2 | 00002 | Sat Jan 03 00:00:00 1970 PST | Sat Jan 03 00:00:00 1970 | 2 | 2 | foo +(1 row) + +SAVEPOINT s; +SELECT * FROM ft1 WHERE 1 / (c1 - 1) > 0; -- ERROR +ERROR: division by zero +CONTEXT: Remote SQL command: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (((1 / ("C 1" - 1)) > 0::double precision)) +ROLLBACK TO s; +FETCH c; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +----+----+-------+------------------------------+--------------------------+----+------------+----- + 3 | 3 | 00003 | Sun Jan 04 00:00:00 1970 PST | Sun Jan 04 00:00:00 1970 | 3 | 3 | foo +(1 row) + +SELECT * FROM ft1 ORDER BY c1 LIMIT 1; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +----+----+-------+------------------------------+--------------------------+----+------------+----- + 1 | 1 | 00001 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1 | 1 | foo +(1 row) + +COMMIT; +-- ====================================================================================================================================== +-- TEST-MODULE: test handling of collations +-- -------------------------------------- +-- ====================================================================================================================================== +create table loct3 (f1 text collate "C" unique, f2 text, f3 varchar(10) unique) with (orientation=column); +NOTICE: CREATE TABLE / UNIQUE will create implicit index "loct3_f1_key" for table "loct3" +NOTICE: CREATE TABLE / UNIQUE will create implicit index "loct3_f3_key" for table "loct3" +create foreign table ft3 (f1 text collate "C", f2 text, f3 varchar(10)) + server loopback options (table_name 'loct3', use_remote_estimate 'true'); +-- can be sent to remote +explain (verbose, costs off) select * from ft3 where f1 = 'foo'; + QUERY PLAN +--------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft3 + Output: f1, f2, f3 + Node ID: 1 + Remote SQL: SELECT f1, f2, f3 FROM public.loct3 WHERE ((f1 = 'foo'::text)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT f1, f2, f3 FROM public.loct3 WHERE ((f1 = 'foo'::text)) + Row Adapter + Output: f1, f2, f3 + -> CStore Scan on public.loct3 + Output: f1, f2, f3 + Filter: (loct3.f1 = 'foo'::text) + +(13 rows) + +explain (verbose, costs off) select * from ft3 where f1 COLLATE "C" = 'foo'; + QUERY PLAN +--------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft3 + Output: f1, f2, f3 + Node ID: 1 + Remote SQL: SELECT f1, f2, f3 FROM public.loct3 WHERE ((f1 = 'foo'::text)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT f1, f2, f3 FROM public.loct3 WHERE ((f1 = 'foo'::text)) + Row Adapter + Output: f1, f2, f3 + -> CStore Scan on public.loct3 + Output: f1, f2, f3 + Filter: (loct3.f1 = 'foo'::text) + +(13 rows) + +explain (verbose, costs off) select * from ft3 where f2 = 'foo'; + QUERY PLAN +--------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft3 + Output: f1, f2, f3 + Node ID: 1 + Remote SQL: SELECT f1, f2, f3 FROM public.loct3 WHERE ((f2 = 'foo'::text)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT f1, f2, f3 FROM public.loct3 WHERE ((f2 = 'foo'::text)) + Row Adapter + Output: f1, f2, f3 + -> CStore Scan on public.loct3 + Output: f1, f2, f3 + Filter: (loct3.f2 = 'foo'::text) + +(13 rows) + +explain (verbose, costs off) select * from ft3 where f3 = 'foo'; + QUERY PLAN +--------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft3 + Output: f1, f2, f3 + Node ID: 1 + Remote SQL: SELECT f1, f2, f3 FROM public.loct3 WHERE ((f3 = 'foo'::text)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT f1, f2, f3 FROM public.loct3 WHERE ((f3 = 'foo'::text)) + Row Adapter + Output: f1, f2, f3 + -> CStore Scan on public.loct3 + Output: f1, f2, f3 + Filter: ((loct3.f3)::text = 'foo'::text) + +(13 rows) + +explain (verbose, costs off) select * from ft3 f, loct3 l + where f.f3 = l.f3 and l.f1 = 'foo'; + QUERY PLAN +------------------------------------------------------------------------------ + Nested Loop + Output: f.f1, f.f2, f.f3, l.f1, l.f2, l.f3 + Join Filter: ((f.f3)::text = (l.f3)::text) + -> Row Adapter + Output: l.f1, l.f2, l.f3 + -> CStore Scan on public.loct3 l + Output: l.f1, l.f2, l.f3 + Filter: (l.f1 = 'foo'::text) + -> Foreign Scan on public.ft3 f + Output: f.f1, f.f2, f.f3 + Node ID: 1 + Remote SQL: SELECT f1, f2, f3 FROM public.loct3 + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT f1, f2, f3 FROM public.loct3 + Row Adapter + Output: f1, f2, f3 + -> CStore Scan on public.loct3 + Output: f1, f2, f3 + +(20 rows) + +-- can't be sent to remote +explain (verbose, costs off) select * from ft3 where f1 COLLATE "POSIX" = 'foo'; + QUERY PLAN +------------------------------------------------------------------------------ + Foreign Scan on public.ft3 + Output: f1, f2, f3 + Filter: ((ft3.f1)::text = 'foo'::text) + Node ID: 1 + Remote SQL: SELECT f1, f2, f3 FROM public.loct3 + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT f1, f2, f3 FROM public.loct3 + Row Adapter + Output: f1, f2, f3 + -> CStore Scan on public.loct3 + Output: f1, f2, f3 + +(13 rows) + +explain (verbose, costs off) select * from ft3 where f1 = 'foo' COLLATE "C"; + QUERY PLAN +------------------------------------------------------------------------------ + Foreign Scan on public.ft3 + Output: f1, f2, f3 + Filter: (ft3.f1 = 'foo'::text COLLATE "C") + Node ID: 1 + Remote SQL: SELECT f1, f2, f3 FROM public.loct3 + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT f1, f2, f3 FROM public.loct3 + Row Adapter + Output: f1, f2, f3 + -> CStore Scan on public.loct3 + Output: f1, f2, f3 + +(13 rows) + +explain (verbose, costs off) select * from ft3 where f2 COLLATE "C" = 'foo'; + QUERY PLAN +------------------------------------------------------------------------------ + Foreign Scan on public.ft3 + Output: f1, f2, f3 + Filter: ((ft3.f2)::text = 'foo'::text) + Node ID: 1 + Remote SQL: SELECT f1, f2, f3 FROM public.loct3 + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT f1, f2, f3 FROM public.loct3 + Row Adapter + Output: f1, f2, f3 + -> CStore Scan on public.loct3 + Output: f1, f2, f3 + +(13 rows) + +explain (verbose, costs off) select * from ft3 where f2 = 'foo' COLLATE "C"; + QUERY PLAN +------------------------------------------------------------------------------ + Foreign Scan on public.ft3 + Output: f1, f2, f3 + Filter: (ft3.f2 = 'foo'::text COLLATE "C") + Node ID: 1 + Remote SQL: SELECT f1, f2, f3 FROM public.loct3 + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT f1, f2, f3 FROM public.loct3 + Row Adapter + Output: f1, f2, f3 + -> CStore Scan on public.loct3 + Output: f1, f2, f3 + +(13 rows) + +explain (verbose, costs off) select * from ft3 f, loct3 l + where f.f3 = l.f3 COLLATE "POSIX" and l.f1 = 'foo'; + QUERY PLAN +------------------------------------------------------------------------------ + Nested Loop + Output: f.f1, f.f2, f.f3, l.f1, l.f2, l.f3 + Join Filter: ((f.f3)::text = (l.f3)::text) + -> Row Adapter + Output: l.f1, l.f2, l.f3 + -> CStore Scan on public.loct3 l + Output: l.f1, l.f2, l.f3 + Filter: (l.f1 = 'foo'::text) + -> Foreign Scan on public.ft3 f + Output: f.f1, f.f2, f.f3 + Node ID: 1 + Remote SQL: SELECT f1, f2, f3 FROM public.loct3 + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT f1, f2, f3 FROM public.loct3 + Row Adapter + Output: f1, f2, f3 + -> CStore Scan on public.loct3 + Output: f1, f2, f3 + +(20 rows) + +-- ====================================================================================================================================== +-- TEST-MODULE: test writable foreign table stuff +-- -------------------------------------- +-- currently openGauss not support to update and delete from a foreign table built on a column table. +-- so it is unusefull. +-- ====================================================================================================================================== +EXPLAIN (verbose, costs off) +INSERT INTO ft2 (c1,c2,c3) SELECT c1+1000,c2+100, c3 || c3 FROM ft2 LIMIT 20; + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Insert on public.ft2 + -> Subquery Scan on "*SELECT*" + Output: "*SELECT*"."?column?", "*SELECT*"."?column?", NULL::integer, "*SELECT*"."?column?", NULL::timestamp with time zone, NULL::timestamp without time zone, NULL::character varying, 'ft2 '::character(10), NULL::user_enum + -> Foreign Scan on public.ft2 + Output: (public.ft2.c1 + 1000), (public.ft2.c2 + 100), (public.ft2.c3 || public.ft2.c3) + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3 FROM "S 1"."T 1" LIMIT 20::bigint + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3 FROM "S 1"."T 1" LIMIT 20::bigint + Row Adapter + Output: "C 1", c2, c3 + -> Vector Limit + Output: "C 1", c2, c3 + -> CStore Scan on "S 1"."T 1" + Output: "C 1", c2, c3 + +(17 rows) + +INSERT INTO ft2 (c1,c2,c3) SELECT c1+1000,c2+100, c3 || c3 FROM ft2 LIMIT 20; +INSERT INTO ft2 (c1,c2,c3) + VALUES (1101,201,'aaa'), (1102,202,'bbb'), (1103,203,'ccc') RETURNING *; +ERROR: Un-support feature +DETAIL: column stored relation doesn't support INSERT returning +CONTEXT: Remote SQL command: INSERT INTO "S 1"."T 1"("C 1", c2, c3, c4, c5, c6, c7, c8) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING "C 1", c2, c3, c4, c5, c6, c7, c8 +INSERT INTO ft2 (c1,c2,c3) VALUES (1104,204,'ddd'), (1105,205,'eee'); +EXPLAIN (verbose, costs off) +UPDATE ft2 SET c2 = c2 + 300, c3 = c3 || '_update3' WHERE c1 % 10 = 3; +ERROR: SELECT FOR UPDATE/SHARE/NO KEY UPDATE/KEY SHARE cannot be used with column table "T 1" +CONTEXT: Remote SQL command: EXPLAIN SELECT "C 1", c2, c3, c4, c5, c6, c7, c8, ctid, tableoid FROM "S 1"."T 1" WHERE ((("C 1" % 10) = 3)) FOR UPDATE +UPDATE ft2 SET c2 = c2 + 300, c3 = c3 || '_update3' WHERE c1 % 10 = 3; +ERROR: SELECT FOR UPDATE/SHARE/NO KEY UPDATE/KEY SHARE cannot be used with column table "T 1" +CONTEXT: Remote SQL command: EXPLAIN SELECT "C 1", c2, c3, c4, c5, c6, c7, c8, ctid, tableoid FROM "S 1"."T 1" WHERE ((("C 1" % 10) = 3)) FOR UPDATE +EXPLAIN (verbose, costs off) +UPDATE ft2 SET c2 = c2 + 400, c3 = c3 || '_update7' WHERE c1 % 10 = 7 RETURNING *; +ERROR: SELECT FOR UPDATE/SHARE/NO KEY UPDATE/KEY SHARE cannot be used with column table "T 1" +CONTEXT: Remote SQL command: EXPLAIN SELECT "C 1", c2, c3, c4, c5, c6, c7, c8, ctid, tableoid FROM "S 1"."T 1" WHERE ((("C 1" % 10) = 7)) FOR UPDATE +UPDATE ft2 SET c2 = c2 + 400, c3 = c3 || '_update7' WHERE c1 % 10 = 7 RETURNING *; +ERROR: SELECT FOR UPDATE/SHARE/NO KEY UPDATE/KEY SHARE cannot be used with column table "T 1" +CONTEXT: Remote SQL command: EXPLAIN SELECT "C 1", c2, c3, c4, c5, c6, c7, c8, ctid, tableoid FROM "S 1"."T 1" WHERE ((("C 1" % 10) = 7)) FOR UPDATE +EXPLAIN (verbose, costs off) +UPDATE ft2 SET c2 = ft2.c2 + 500, c3 = ft2.c3 || '_update9', c7 = DEFAULT + FROM ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 9; +ERROR: SELECT FOR UPDATE/SHARE/NO KEY UPDATE/KEY SHARE cannot be used with column table "T 1" +CONTEXT: Remote SQL command: EXPLAIN SELECT "C 1", c2, c3, c4, c5, c6, c8, ctid, tableoid FROM "S 1"."T 1" WHERE (((c2 % 10) = 9)) FOR UPDATE +UPDATE ft2 SET c2 = ft2.c2 + 500, c3 = ft2.c3 || '_update9', c7 = DEFAULT + FROM ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 9; +ERROR: SELECT FOR UPDATE/SHARE/NO KEY UPDATE/KEY SHARE cannot be used with column table "T 1" +CONTEXT: Remote SQL command: EXPLAIN SELECT "C 1", c2, c3, c4, c5, c6, c8, ctid, tableoid FROM "S 1"."T 1" WHERE (((c2 % 10) = 9)) FOR UPDATE +EXPLAIN (verbose, costs off) + DELETE FROM ft2 WHERE c1 % 10 = 5 RETURNING c1, c4; +ERROR: SELECT FOR UPDATE/SHARE/NO KEY UPDATE/KEY SHARE cannot be used with column table "T 1" +CONTEXT: Remote SQL command: EXPLAIN SELECT ctid, tableoid FROM "S 1"."T 1" WHERE ((("C 1" % 10) = 5)) FOR UPDATE +DELETE FROM ft2 WHERE c1 % 10 = 5 RETURNING c1, c4; +ERROR: SELECT FOR UPDATE/SHARE/NO KEY UPDATE/KEY SHARE cannot be used with column table "T 1" +CONTEXT: Remote SQL command: EXPLAIN SELECT ctid, tableoid FROM "S 1"."T 1" WHERE ((("C 1" % 10) = 5)) FOR UPDATE +EXPLAIN (verbose, costs off) +DELETE FROM ft2 USING ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 2; +ERROR: SELECT FOR UPDATE/SHARE/NO KEY UPDATE/KEY SHARE cannot be used with column table "T 1" +CONTEXT: Remote SQL command: EXPLAIN SELECT c2, ctid, tableoid FROM "S 1"."T 1" WHERE (((c2 % 10) = 2)) FOR UPDATE +DELETE FROM ft2 USING ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 2; +ERROR: SELECT FOR UPDATE/SHARE/NO KEY UPDATE/KEY SHARE cannot be used with column table "T 1" +CONTEXT: Remote SQL command: EXPLAIN SELECT c2, ctid, tableoid FROM "S 1"."T 1" WHERE (((c2 % 10) = 2)) FOR UPDATE +SELECT c1,c2,c3,c4 FROM ft2 ORDER BY c1; + c1 | c2 | c3 | c4 +------+-----+------------+------------------------------ + 1 | 1 | 00001 | Fri Jan 02 00:00:00 1970 PST + 2 | 2 | 00002 | Sat Jan 03 00:00:00 1970 PST + 3 | 3 | 00003 | Sun Jan 04 00:00:00 1970 PST + 4 | 4 | 00004 | Mon Jan 05 00:00:00 1970 PST + 5 | 5 | 00005 | Tue Jan 06 00:00:00 1970 PST + 6 | 6 | 00006 | Wed Jan 07 00:00:00 1970 PST + 7 | 7 | 00007 | Thu Jan 08 00:00:00 1970 PST + 8 | 8 | 00008 | Fri Jan 09 00:00:00 1970 PST + 9 | 9 | 00009 | Sat Jan 10 00:00:00 1970 PST + 10 | 0 | 00010 | Sun Jan 11 00:00:00 1970 PST + 11 | 1 | 00011 | Mon Jan 12 00:00:00 1970 PST + 12 | 2 | 00012 | Tue Jan 13 00:00:00 1970 PST + 13 | 3 | 00013 | Wed Jan 14 00:00:00 1970 PST + 14 | 4 | 00014 | Thu Jan 15 00:00:00 1970 PST + 15 | 5 | 00015 | Fri Jan 16 00:00:00 1970 PST + 16 | 6 | 00016 | Sat Jan 17 00:00:00 1970 PST + 17 | 7 | 00017 | Sun Jan 18 00:00:00 1970 PST + 18 | 8 | 00018 | Mon Jan 19 00:00:00 1970 PST + 19 | 9 | 00019 | Tue Jan 20 00:00:00 1970 PST + 20 | 0 | 00020 | Wed Jan 21 00:00:00 1970 PST + 21 | 1 | 00021 | Thu Jan 22 00:00:00 1970 PST + 22 | 2 | 00022 | Fri Jan 23 00:00:00 1970 PST + 23 | 3 | 00023 | Sat Jan 24 00:00:00 1970 PST + 24 | 4 | 00024 | Sun Jan 25 00:00:00 1970 PST + 25 | 5 | 00025 | Mon Jan 26 00:00:00 1970 PST + 26 | 6 | 00026 | Tue Jan 27 00:00:00 1970 PST + 27 | 7 | 00027 | Wed Jan 28 00:00:00 1970 PST + 28 | 8 | 00028 | Thu Jan 29 00:00:00 1970 PST + 29 | 9 | 00029 | Fri Jan 30 00:00:00 1970 PST + 30 | 0 | 00030 | Sat Jan 31 00:00:00 1970 PST + 31 | 1 | 00031 | Sun Feb 01 00:00:00 1970 PST + 32 | 2 | 00032 | Mon Feb 02 00:00:00 1970 PST + 33 | 3 | 00033 | Tue Feb 03 00:00:00 1970 PST + 34 | 4 | 00034 | Wed Feb 04 00:00:00 1970 PST + 35 | 5 | 00035 | Thu Feb 05 00:00:00 1970 PST + 36 | 6 | 00036 | Fri Feb 06 00:00:00 1970 PST + 37 | 7 | 00037 | Sat Feb 07 00:00:00 1970 PST + 38 | 8 | 00038 | Sun Feb 08 00:00:00 1970 PST + 39 | 9 | 00039 | Mon Feb 09 00:00:00 1970 PST + 40 | 0 | 00040 | Tue Feb 10 00:00:00 1970 PST + 41 | 1 | 00041 | Wed Feb 11 00:00:00 1970 PST + 42 | 2 | 00042 | Thu Feb 12 00:00:00 1970 PST + 43 | 3 | 00043 | Fri Feb 13 00:00:00 1970 PST + 44 | 4 | 00044 | Sat Feb 14 00:00:00 1970 PST + 45 | 5 | 00045 | Sun Feb 15 00:00:00 1970 PST + 46 | 6 | 00046 | Mon Feb 16 00:00:00 1970 PST + 47 | 7 | 00047 | Tue Feb 17 00:00:00 1970 PST + 48 | 8 | 00048 | Wed Feb 18 00:00:00 1970 PST + 49 | 9 | 00049 | Thu Feb 19 00:00:00 1970 PST + 50 | 0 | 00050 | Fri Feb 20 00:00:00 1970 PST + 51 | 1 | 00051 | Sat Feb 21 00:00:00 1970 PST + 52 | 2 | 00052 | Sun Feb 22 00:00:00 1970 PST + 53 | 3 | 00053 | Mon Feb 23 00:00:00 1970 PST + 54 | 4 | 00054 | Tue Feb 24 00:00:00 1970 PST + 55 | 5 | 00055 | Wed Feb 25 00:00:00 1970 PST + 56 | 6 | 00056 | Thu Feb 26 00:00:00 1970 PST + 57 | 7 | 00057 | Fri Feb 27 00:00:00 1970 PST + 58 | 8 | 00058 | Sat Feb 28 00:00:00 1970 PST + 59 | 9 | 00059 | Sun Mar 01 00:00:00 1970 PST + 60 | 0 | 00060 | Mon Mar 02 00:00:00 1970 PST + 61 | 1 | 00061 | Tue Mar 03 00:00:00 1970 PST + 62 | 2 | 00062 | Wed Mar 04 00:00:00 1970 PST + 63 | 3 | 00063 | Thu Mar 05 00:00:00 1970 PST + 64 | 4 | 00064 | Fri Mar 06 00:00:00 1970 PST + 65 | 5 | 00065 | Sat Mar 07 00:00:00 1970 PST + 66 | 6 | 00066 | Sun Mar 08 00:00:00 1970 PST + 67 | 7 | 00067 | Mon Mar 09 00:00:00 1970 PST + 68 | 8 | 00068 | Tue Mar 10 00:00:00 1970 PST + 69 | 9 | 00069 | Wed Mar 11 00:00:00 1970 PST + 70 | 0 | 00070 | Thu Mar 12 00:00:00 1970 PST + 71 | 1 | 00071 | Fri Mar 13 00:00:00 1970 PST + 72 | 2 | 00072 | Sat Mar 14 00:00:00 1970 PST + 73 | 3 | 00073 | Sun Mar 15 00:00:00 1970 PST + 74 | 4 | 00074 | Mon Mar 16 00:00:00 1970 PST + 75 | 5 | 00075 | Tue Mar 17 00:00:00 1970 PST + 76 | 6 | 00076 | Wed Mar 18 00:00:00 1970 PST + 77 | 7 | 00077 | Thu Mar 19 00:00:00 1970 PST + 78 | 8 | 00078 | Fri Mar 20 00:00:00 1970 PST + 79 | 9 | 00079 | Sat Mar 21 00:00:00 1970 PST + 80 | 0 | 00080 | Sun Mar 22 00:00:00 1970 PST + 81 | 1 | 00081 | Mon Mar 23 00:00:00 1970 PST + 82 | 2 | 00082 | Tue Mar 24 00:00:00 1970 PST + 83 | 3 | 00083 | Wed Mar 25 00:00:00 1970 PST + 84 | 4 | 00084 | Thu Mar 26 00:00:00 1970 PST + 85 | 5 | 00085 | Fri Mar 27 00:00:00 1970 PST + 86 | 6 | 00086 | Sat Mar 28 00:00:00 1970 PST + 87 | 7 | 00087 | Sun Mar 29 00:00:00 1970 PST + 88 | 8 | 00088 | Mon Mar 30 00:00:00 1970 PST + 89 | 9 | 00089 | Tue Mar 31 00:00:00 1970 PST + 90 | 0 | 00090 | Wed Apr 01 00:00:00 1970 PST + 91 | 1 | 00091 | Thu Apr 02 00:00:00 1970 PST + 92 | 2 | 00092 | Fri Apr 03 00:00:00 1970 PST + 93 | 3 | 00093 | Sat Apr 04 00:00:00 1970 PST + 94 | 4 | 00094 | Sun Apr 05 00:00:00 1970 PST + 95 | 5 | 00095 | Mon Apr 06 00:00:00 1970 PST + 96 | 6 | 00096 | Tue Apr 07 00:00:00 1970 PST + 97 | 7 | 00097 | Wed Apr 08 00:00:00 1970 PST + 98 | 8 | 00098 | Thu Apr 09 00:00:00 1970 PST + 99 | 9 | 00099 | Fri Apr 10 00:00:00 1970 PST + 100 | 0 | 00100 | Thu Jan 01 00:00:00 1970 PST + 101 | 1 | 00101 | Fri Jan 02 00:00:00 1970 PST + 102 | 2 | 00102 | Sat Jan 03 00:00:00 1970 PST + 103 | 3 | 00103 | Sun Jan 04 00:00:00 1970 PST + 104 | 4 | 00104 | Mon Jan 05 00:00:00 1970 PST + 105 | 5 | 00105 | Tue Jan 06 00:00:00 1970 PST + 106 | 6 | 00106 | Wed Jan 07 00:00:00 1970 PST + 107 | 7 | 00107 | Thu Jan 08 00:00:00 1970 PST + 108 | 8 | 00108 | Fri Jan 09 00:00:00 1970 PST + 109 | 9 | 00109 | Sat Jan 10 00:00:00 1970 PST + 110 | 0 | 00110 | Sun Jan 11 00:00:00 1970 PST + 111 | 1 | 00111 | Mon Jan 12 00:00:00 1970 PST + 112 | 2 | 00112 | Tue Jan 13 00:00:00 1970 PST + 113 | 3 | 00113 | Wed Jan 14 00:00:00 1970 PST + 114 | 4 | 00114 | Thu Jan 15 00:00:00 1970 PST + 115 | 5 | 00115 | Fri Jan 16 00:00:00 1970 PST + 116 | 6 | 00116 | Sat Jan 17 00:00:00 1970 PST + 117 | 7 | 00117 | Sun Jan 18 00:00:00 1970 PST + 118 | 8 | 00118 | Mon Jan 19 00:00:00 1970 PST + 119 | 9 | 00119 | Tue Jan 20 00:00:00 1970 PST + 120 | 0 | 00120 | Wed Jan 21 00:00:00 1970 PST + 121 | 1 | 00121 | Thu Jan 22 00:00:00 1970 PST + 122 | 2 | 00122 | Fri Jan 23 00:00:00 1970 PST + 123 | 3 | 00123 | Sat Jan 24 00:00:00 1970 PST + 124 | 4 | 00124 | Sun Jan 25 00:00:00 1970 PST + 125 | 5 | 00125 | Mon Jan 26 00:00:00 1970 PST + 126 | 6 | 00126 | Tue Jan 27 00:00:00 1970 PST + 127 | 7 | 00127 | Wed Jan 28 00:00:00 1970 PST + 128 | 8 | 00128 | Thu Jan 29 00:00:00 1970 PST + 129 | 9 | 00129 | Fri Jan 30 00:00:00 1970 PST + 130 | 0 | 00130 | Sat Jan 31 00:00:00 1970 PST + 131 | 1 | 00131 | Sun Feb 01 00:00:00 1970 PST + 132 | 2 | 00132 | Mon Feb 02 00:00:00 1970 PST + 133 | 3 | 00133 | Tue Feb 03 00:00:00 1970 PST + 134 | 4 | 00134 | Wed Feb 04 00:00:00 1970 PST + 135 | 5 | 00135 | Thu Feb 05 00:00:00 1970 PST + 136 | 6 | 00136 | Fri Feb 06 00:00:00 1970 PST + 137 | 7 | 00137 | Sat Feb 07 00:00:00 1970 PST + 138 | 8 | 00138 | Sun Feb 08 00:00:00 1970 PST + 139 | 9 | 00139 | Mon Feb 09 00:00:00 1970 PST + 140 | 0 | 00140 | Tue Feb 10 00:00:00 1970 PST + 141 | 1 | 00141 | Wed Feb 11 00:00:00 1970 PST + 142 | 2 | 00142 | Thu Feb 12 00:00:00 1970 PST + 143 | 3 | 00143 | Fri Feb 13 00:00:00 1970 PST + 144 | 4 | 00144 | Sat Feb 14 00:00:00 1970 PST + 145 | 5 | 00145 | Sun Feb 15 00:00:00 1970 PST + 146 | 6 | 00146 | Mon Feb 16 00:00:00 1970 PST + 147 | 7 | 00147 | Tue Feb 17 00:00:00 1970 PST + 148 | 8 | 00148 | Wed Feb 18 00:00:00 1970 PST + 149 | 9 | 00149 | Thu Feb 19 00:00:00 1970 PST + 150 | 0 | 00150 | Fri Feb 20 00:00:00 1970 PST + 151 | 1 | 00151 | Sat Feb 21 00:00:00 1970 PST + 152 | 2 | 00152 | Sun Feb 22 00:00:00 1970 PST + 153 | 3 | 00153 | Mon Feb 23 00:00:00 1970 PST + 154 | 4 | 00154 | Tue Feb 24 00:00:00 1970 PST + 155 | 5 | 00155 | Wed Feb 25 00:00:00 1970 PST + 156 | 6 | 00156 | Thu Feb 26 00:00:00 1970 PST + 157 | 7 | 00157 | Fri Feb 27 00:00:00 1970 PST + 158 | 8 | 00158 | Sat Feb 28 00:00:00 1970 PST + 159 | 9 | 00159 | Sun Mar 01 00:00:00 1970 PST + 160 | 0 | 00160 | Mon Mar 02 00:00:00 1970 PST + 161 | 1 | 00161 | Tue Mar 03 00:00:00 1970 PST + 162 | 2 | 00162 | Wed Mar 04 00:00:00 1970 PST + 163 | 3 | 00163 | Thu Mar 05 00:00:00 1970 PST + 164 | 4 | 00164 | Fri Mar 06 00:00:00 1970 PST + 165 | 5 | 00165 | Sat Mar 07 00:00:00 1970 PST + 166 | 6 | 00166 | Sun Mar 08 00:00:00 1970 PST + 167 | 7 | 00167 | Mon Mar 09 00:00:00 1970 PST + 168 | 8 | 00168 | Tue Mar 10 00:00:00 1970 PST + 169 | 9 | 00169 | Wed Mar 11 00:00:00 1970 PST + 170 | 0 | 00170 | Thu Mar 12 00:00:00 1970 PST + 171 | 1 | 00171 | Fri Mar 13 00:00:00 1970 PST + 172 | 2 | 00172 | Sat Mar 14 00:00:00 1970 PST + 173 | 3 | 00173 | Sun Mar 15 00:00:00 1970 PST + 174 | 4 | 00174 | Mon Mar 16 00:00:00 1970 PST + 175 | 5 | 00175 | Tue Mar 17 00:00:00 1970 PST + 176 | 6 | 00176 | Wed Mar 18 00:00:00 1970 PST + 177 | 7 | 00177 | Thu Mar 19 00:00:00 1970 PST + 178 | 8 | 00178 | Fri Mar 20 00:00:00 1970 PST + 179 | 9 | 00179 | Sat Mar 21 00:00:00 1970 PST + 180 | 0 | 00180 | Sun Mar 22 00:00:00 1970 PST + 181 | 1 | 00181 | Mon Mar 23 00:00:00 1970 PST + 182 | 2 | 00182 | Tue Mar 24 00:00:00 1970 PST + 183 | 3 | 00183 | Wed Mar 25 00:00:00 1970 PST + 184 | 4 | 00184 | Thu Mar 26 00:00:00 1970 PST + 185 | 5 | 00185 | Fri Mar 27 00:00:00 1970 PST + 186 | 6 | 00186 | Sat Mar 28 00:00:00 1970 PST + 187 | 7 | 00187 | Sun Mar 29 00:00:00 1970 PST + 188 | 8 | 00188 | Mon Mar 30 00:00:00 1970 PST + 189 | 9 | 00189 | Tue Mar 31 00:00:00 1970 PST + 190 | 0 | 00190 | Wed Apr 01 00:00:00 1970 PST + 191 | 1 | 00191 | Thu Apr 02 00:00:00 1970 PST + 192 | 2 | 00192 | Fri Apr 03 00:00:00 1970 PST + 193 | 3 | 00193 | Sat Apr 04 00:00:00 1970 PST + 194 | 4 | 00194 | Sun Apr 05 00:00:00 1970 PST + 195 | 5 | 00195 | Mon Apr 06 00:00:00 1970 PST + 196 | 6 | 00196 | Tue Apr 07 00:00:00 1970 PST + 197 | 7 | 00197 | Wed Apr 08 00:00:00 1970 PST + 198 | 8 | 00198 | Thu Apr 09 00:00:00 1970 PST + 199 | 9 | 00199 | Fri Apr 10 00:00:00 1970 PST + 200 | 0 | 00200 | Thu Jan 01 00:00:00 1970 PST + 201 | 1 | 00201 | Fri Jan 02 00:00:00 1970 PST + 202 | 2 | 00202 | Sat Jan 03 00:00:00 1970 PST + 203 | 3 | 00203 | Sun Jan 04 00:00:00 1970 PST + 204 | 4 | 00204 | Mon Jan 05 00:00:00 1970 PST + 205 | 5 | 00205 | Tue Jan 06 00:00:00 1970 PST + 206 | 6 | 00206 | Wed Jan 07 00:00:00 1970 PST + 207 | 7 | 00207 | Thu Jan 08 00:00:00 1970 PST + 208 | 8 | 00208 | Fri Jan 09 00:00:00 1970 PST + 209 | 9 | 00209 | Sat Jan 10 00:00:00 1970 PST + 210 | 0 | 00210 | Sun Jan 11 00:00:00 1970 PST + 211 | 1 | 00211 | Mon Jan 12 00:00:00 1970 PST + 212 | 2 | 00212 | Tue Jan 13 00:00:00 1970 PST + 213 | 3 | 00213 | Wed Jan 14 00:00:00 1970 PST + 214 | 4 | 00214 | Thu Jan 15 00:00:00 1970 PST + 215 | 5 | 00215 | Fri Jan 16 00:00:00 1970 PST + 216 | 6 | 00216 | Sat Jan 17 00:00:00 1970 PST + 217 | 7 | 00217 | Sun Jan 18 00:00:00 1970 PST + 218 | 8 | 00218 | Mon Jan 19 00:00:00 1970 PST + 219 | 9 | 00219 | Tue Jan 20 00:00:00 1970 PST + 220 | 0 | 00220 | Wed Jan 21 00:00:00 1970 PST + 221 | 1 | 00221 | Thu Jan 22 00:00:00 1970 PST + 222 | 2 | 00222 | Fri Jan 23 00:00:00 1970 PST + 223 | 3 | 00223 | Sat Jan 24 00:00:00 1970 PST + 224 | 4 | 00224 | Sun Jan 25 00:00:00 1970 PST + 225 | 5 | 00225 | Mon Jan 26 00:00:00 1970 PST + 226 | 6 | 00226 | Tue Jan 27 00:00:00 1970 PST + 227 | 7 | 00227 | Wed Jan 28 00:00:00 1970 PST + 228 | 8 | 00228 | Thu Jan 29 00:00:00 1970 PST + 229 | 9 | 00229 | Fri Jan 30 00:00:00 1970 PST + 230 | 0 | 00230 | Sat Jan 31 00:00:00 1970 PST + 231 | 1 | 00231 | Sun Feb 01 00:00:00 1970 PST + 232 | 2 | 00232 | Mon Feb 02 00:00:00 1970 PST + 233 | 3 | 00233 | Tue Feb 03 00:00:00 1970 PST + 234 | 4 | 00234 | Wed Feb 04 00:00:00 1970 PST + 235 | 5 | 00235 | Thu Feb 05 00:00:00 1970 PST + 236 | 6 | 00236 | Fri Feb 06 00:00:00 1970 PST + 237 | 7 | 00237 | Sat Feb 07 00:00:00 1970 PST + 238 | 8 | 00238 | Sun Feb 08 00:00:00 1970 PST + 239 | 9 | 00239 | Mon Feb 09 00:00:00 1970 PST + 240 | 0 | 00240 | Tue Feb 10 00:00:00 1970 PST + 241 | 1 | 00241 | Wed Feb 11 00:00:00 1970 PST + 242 | 2 | 00242 | Thu Feb 12 00:00:00 1970 PST + 243 | 3 | 00243 | Fri Feb 13 00:00:00 1970 PST + 244 | 4 | 00244 | Sat Feb 14 00:00:00 1970 PST + 245 | 5 | 00245 | Sun Feb 15 00:00:00 1970 PST + 246 | 6 | 00246 | Mon Feb 16 00:00:00 1970 PST + 247 | 7 | 00247 | Tue Feb 17 00:00:00 1970 PST + 248 | 8 | 00248 | Wed Feb 18 00:00:00 1970 PST + 249 | 9 | 00249 | Thu Feb 19 00:00:00 1970 PST + 250 | 0 | 00250 | Fri Feb 20 00:00:00 1970 PST + 251 | 1 | 00251 | Sat Feb 21 00:00:00 1970 PST + 252 | 2 | 00252 | Sun Feb 22 00:00:00 1970 PST + 253 | 3 | 00253 | Mon Feb 23 00:00:00 1970 PST + 254 | 4 | 00254 | Tue Feb 24 00:00:00 1970 PST + 255 | 5 | 00255 | Wed Feb 25 00:00:00 1970 PST + 256 | 6 | 00256 | Thu Feb 26 00:00:00 1970 PST + 257 | 7 | 00257 | Fri Feb 27 00:00:00 1970 PST + 258 | 8 | 00258 | Sat Feb 28 00:00:00 1970 PST + 259 | 9 | 00259 | Sun Mar 01 00:00:00 1970 PST + 260 | 0 | 00260 | Mon Mar 02 00:00:00 1970 PST + 261 | 1 | 00261 | Tue Mar 03 00:00:00 1970 PST + 262 | 2 | 00262 | Wed Mar 04 00:00:00 1970 PST + 263 | 3 | 00263 | Thu Mar 05 00:00:00 1970 PST + 264 | 4 | 00264 | Fri Mar 06 00:00:00 1970 PST + 265 | 5 | 00265 | Sat Mar 07 00:00:00 1970 PST + 266 | 6 | 00266 | Sun Mar 08 00:00:00 1970 PST + 267 | 7 | 00267 | Mon Mar 09 00:00:00 1970 PST + 268 | 8 | 00268 | Tue Mar 10 00:00:00 1970 PST + 269 | 9 | 00269 | Wed Mar 11 00:00:00 1970 PST + 270 | 0 | 00270 | Thu Mar 12 00:00:00 1970 PST + 271 | 1 | 00271 | Fri Mar 13 00:00:00 1970 PST + 272 | 2 | 00272 | Sat Mar 14 00:00:00 1970 PST + 273 | 3 | 00273 | Sun Mar 15 00:00:00 1970 PST + 274 | 4 | 00274 | Mon Mar 16 00:00:00 1970 PST + 275 | 5 | 00275 | Tue Mar 17 00:00:00 1970 PST + 276 | 6 | 00276 | Wed Mar 18 00:00:00 1970 PST + 277 | 7 | 00277 | Thu Mar 19 00:00:00 1970 PST + 278 | 8 | 00278 | Fri Mar 20 00:00:00 1970 PST + 279 | 9 | 00279 | Sat Mar 21 00:00:00 1970 PST + 280 | 0 | 00280 | Sun Mar 22 00:00:00 1970 PST + 281 | 1 | 00281 | Mon Mar 23 00:00:00 1970 PST + 282 | 2 | 00282 | Tue Mar 24 00:00:00 1970 PST + 283 | 3 | 00283 | Wed Mar 25 00:00:00 1970 PST + 284 | 4 | 00284 | Thu Mar 26 00:00:00 1970 PST + 285 | 5 | 00285 | Fri Mar 27 00:00:00 1970 PST + 286 | 6 | 00286 | Sat Mar 28 00:00:00 1970 PST + 287 | 7 | 00287 | Sun Mar 29 00:00:00 1970 PST + 288 | 8 | 00288 | Mon Mar 30 00:00:00 1970 PST + 289 | 9 | 00289 | Tue Mar 31 00:00:00 1970 PST + 290 | 0 | 00290 | Wed Apr 01 00:00:00 1970 PST + 291 | 1 | 00291 | Thu Apr 02 00:00:00 1970 PST + 292 | 2 | 00292 | Fri Apr 03 00:00:00 1970 PST + 293 | 3 | 00293 | Sat Apr 04 00:00:00 1970 PST + 294 | 4 | 00294 | Sun Apr 05 00:00:00 1970 PST + 295 | 5 | 00295 | Mon Apr 06 00:00:00 1970 PST + 296 | 6 | 00296 | Tue Apr 07 00:00:00 1970 PST + 297 | 7 | 00297 | Wed Apr 08 00:00:00 1970 PST + 298 | 8 | 00298 | Thu Apr 09 00:00:00 1970 PST + 299 | 9 | 00299 | Fri Apr 10 00:00:00 1970 PST + 300 | 0 | 00300 | Thu Jan 01 00:00:00 1970 PST + 301 | 1 | 00301 | Fri Jan 02 00:00:00 1970 PST + 302 | 2 | 00302 | Sat Jan 03 00:00:00 1970 PST + 303 | 3 | 00303 | Sun Jan 04 00:00:00 1970 PST + 304 | 4 | 00304 | Mon Jan 05 00:00:00 1970 PST + 305 | 5 | 00305 | Tue Jan 06 00:00:00 1970 PST + 306 | 6 | 00306 | Wed Jan 07 00:00:00 1970 PST + 307 | 7 | 00307 | Thu Jan 08 00:00:00 1970 PST + 308 | 8 | 00308 | Fri Jan 09 00:00:00 1970 PST + 309 | 9 | 00309 | Sat Jan 10 00:00:00 1970 PST + 310 | 0 | 00310 | Sun Jan 11 00:00:00 1970 PST + 311 | 1 | 00311 | Mon Jan 12 00:00:00 1970 PST + 312 | 2 | 00312 | Tue Jan 13 00:00:00 1970 PST + 313 | 3 | 00313 | Wed Jan 14 00:00:00 1970 PST + 314 | 4 | 00314 | Thu Jan 15 00:00:00 1970 PST + 315 | 5 | 00315 | Fri Jan 16 00:00:00 1970 PST + 316 | 6 | 00316 | Sat Jan 17 00:00:00 1970 PST + 317 | 7 | 00317 | Sun Jan 18 00:00:00 1970 PST + 318 | 8 | 00318 | Mon Jan 19 00:00:00 1970 PST + 319 | 9 | 00319 | Tue Jan 20 00:00:00 1970 PST + 320 | 0 | 00320 | Wed Jan 21 00:00:00 1970 PST + 321 | 1 | 00321 | Thu Jan 22 00:00:00 1970 PST + 322 | 2 | 00322 | Fri Jan 23 00:00:00 1970 PST + 323 | 3 | 00323 | Sat Jan 24 00:00:00 1970 PST + 324 | 4 | 00324 | Sun Jan 25 00:00:00 1970 PST + 325 | 5 | 00325 | Mon Jan 26 00:00:00 1970 PST + 326 | 6 | 00326 | Tue Jan 27 00:00:00 1970 PST + 327 | 7 | 00327 | Wed Jan 28 00:00:00 1970 PST + 328 | 8 | 00328 | Thu Jan 29 00:00:00 1970 PST + 329 | 9 | 00329 | Fri Jan 30 00:00:00 1970 PST + 330 | 0 | 00330 | Sat Jan 31 00:00:00 1970 PST + 331 | 1 | 00331 | Sun Feb 01 00:00:00 1970 PST + 332 | 2 | 00332 | Mon Feb 02 00:00:00 1970 PST + 333 | 3 | 00333 | Tue Feb 03 00:00:00 1970 PST + 334 | 4 | 00334 | Wed Feb 04 00:00:00 1970 PST + 335 | 5 | 00335 | Thu Feb 05 00:00:00 1970 PST + 336 | 6 | 00336 | Fri Feb 06 00:00:00 1970 PST + 337 | 7 | 00337 | Sat Feb 07 00:00:00 1970 PST + 338 | 8 | 00338 | Sun Feb 08 00:00:00 1970 PST + 339 | 9 | 00339 | Mon Feb 09 00:00:00 1970 PST + 340 | 0 | 00340 | Tue Feb 10 00:00:00 1970 PST + 341 | 1 | 00341 | Wed Feb 11 00:00:00 1970 PST + 342 | 2 | 00342 | Thu Feb 12 00:00:00 1970 PST + 343 | 3 | 00343 | Fri Feb 13 00:00:00 1970 PST + 344 | 4 | 00344 | Sat Feb 14 00:00:00 1970 PST + 345 | 5 | 00345 | Sun Feb 15 00:00:00 1970 PST + 346 | 6 | 00346 | Mon Feb 16 00:00:00 1970 PST + 347 | 7 | 00347 | Tue Feb 17 00:00:00 1970 PST + 348 | 8 | 00348 | Wed Feb 18 00:00:00 1970 PST + 349 | 9 | 00349 | Thu Feb 19 00:00:00 1970 PST + 350 | 0 | 00350 | Fri Feb 20 00:00:00 1970 PST + 351 | 1 | 00351 | Sat Feb 21 00:00:00 1970 PST + 352 | 2 | 00352 | Sun Feb 22 00:00:00 1970 PST + 353 | 3 | 00353 | Mon Feb 23 00:00:00 1970 PST + 354 | 4 | 00354 | Tue Feb 24 00:00:00 1970 PST + 355 | 5 | 00355 | Wed Feb 25 00:00:00 1970 PST + 356 | 6 | 00356 | Thu Feb 26 00:00:00 1970 PST + 357 | 7 | 00357 | Fri Feb 27 00:00:00 1970 PST + 358 | 8 | 00358 | Sat Feb 28 00:00:00 1970 PST + 359 | 9 | 00359 | Sun Mar 01 00:00:00 1970 PST + 360 | 0 | 00360 | Mon Mar 02 00:00:00 1970 PST + 361 | 1 | 00361 | Tue Mar 03 00:00:00 1970 PST + 362 | 2 | 00362 | Wed Mar 04 00:00:00 1970 PST + 363 | 3 | 00363 | Thu Mar 05 00:00:00 1970 PST + 364 | 4 | 00364 | Fri Mar 06 00:00:00 1970 PST + 365 | 5 | 00365 | Sat Mar 07 00:00:00 1970 PST + 366 | 6 | 00366 | Sun Mar 08 00:00:00 1970 PST + 367 | 7 | 00367 | Mon Mar 09 00:00:00 1970 PST + 368 | 8 | 00368 | Tue Mar 10 00:00:00 1970 PST + 369 | 9 | 00369 | Wed Mar 11 00:00:00 1970 PST + 370 | 0 | 00370 | Thu Mar 12 00:00:00 1970 PST + 371 | 1 | 00371 | Fri Mar 13 00:00:00 1970 PST + 372 | 2 | 00372 | Sat Mar 14 00:00:00 1970 PST + 373 | 3 | 00373 | Sun Mar 15 00:00:00 1970 PST + 374 | 4 | 00374 | Mon Mar 16 00:00:00 1970 PST + 375 | 5 | 00375 | Tue Mar 17 00:00:00 1970 PST + 376 | 6 | 00376 | Wed Mar 18 00:00:00 1970 PST + 377 | 7 | 00377 | Thu Mar 19 00:00:00 1970 PST + 378 | 8 | 00378 | Fri Mar 20 00:00:00 1970 PST + 379 | 9 | 00379 | Sat Mar 21 00:00:00 1970 PST + 380 | 0 | 00380 | Sun Mar 22 00:00:00 1970 PST + 381 | 1 | 00381 | Mon Mar 23 00:00:00 1970 PST + 382 | 2 | 00382 | Tue Mar 24 00:00:00 1970 PST + 383 | 3 | 00383 | Wed Mar 25 00:00:00 1970 PST + 384 | 4 | 00384 | Thu Mar 26 00:00:00 1970 PST + 385 | 5 | 00385 | Fri Mar 27 00:00:00 1970 PST + 386 | 6 | 00386 | Sat Mar 28 00:00:00 1970 PST + 387 | 7 | 00387 | Sun Mar 29 00:00:00 1970 PST + 388 | 8 | 00388 | Mon Mar 30 00:00:00 1970 PST + 389 | 9 | 00389 | Tue Mar 31 00:00:00 1970 PST + 390 | 0 | 00390 | Wed Apr 01 00:00:00 1970 PST + 391 | 1 | 00391 | Thu Apr 02 00:00:00 1970 PST + 392 | 2 | 00392 | Fri Apr 03 00:00:00 1970 PST + 393 | 3 | 00393 | Sat Apr 04 00:00:00 1970 PST + 394 | 4 | 00394 | Sun Apr 05 00:00:00 1970 PST + 395 | 5 | 00395 | Mon Apr 06 00:00:00 1970 PST + 396 | 6 | 00396 | Tue Apr 07 00:00:00 1970 PST + 397 | 7 | 00397 | Wed Apr 08 00:00:00 1970 PST + 398 | 8 | 00398 | Thu Apr 09 00:00:00 1970 PST + 399 | 9 | 00399 | Fri Apr 10 00:00:00 1970 PST + 400 | 0 | 00400 | Thu Jan 01 00:00:00 1970 PST + 401 | 1 | 00401 | Fri Jan 02 00:00:00 1970 PST + 402 | 2 | 00402 | Sat Jan 03 00:00:00 1970 PST + 403 | 3 | 00403 | Sun Jan 04 00:00:00 1970 PST + 404 | 4 | 00404 | Mon Jan 05 00:00:00 1970 PST + 405 | 5 | 00405 | Tue Jan 06 00:00:00 1970 PST + 406 | 6 | 00406 | Wed Jan 07 00:00:00 1970 PST + 407 | 7 | 00407 | Thu Jan 08 00:00:00 1970 PST + 408 | 8 | 00408 | Fri Jan 09 00:00:00 1970 PST + 409 | 9 | 00409 | Sat Jan 10 00:00:00 1970 PST + 410 | 0 | 00410 | Sun Jan 11 00:00:00 1970 PST + 411 | 1 | 00411 | Mon Jan 12 00:00:00 1970 PST + 412 | 2 | 00412 | Tue Jan 13 00:00:00 1970 PST + 413 | 3 | 00413 | Wed Jan 14 00:00:00 1970 PST + 414 | 4 | 00414 | Thu Jan 15 00:00:00 1970 PST + 415 | 5 | 00415 | Fri Jan 16 00:00:00 1970 PST + 416 | 6 | 00416 | Sat Jan 17 00:00:00 1970 PST + 417 | 7 | 00417 | Sun Jan 18 00:00:00 1970 PST + 418 | 8 | 00418 | Mon Jan 19 00:00:00 1970 PST + 419 | 9 | 00419 | Tue Jan 20 00:00:00 1970 PST + 420 | 0 | 00420 | Wed Jan 21 00:00:00 1970 PST + 421 | 1 | 00421 | Thu Jan 22 00:00:00 1970 PST + 422 | 2 | 00422 | Fri Jan 23 00:00:00 1970 PST + 423 | 3 | 00423 | Sat Jan 24 00:00:00 1970 PST + 424 | 4 | 00424 | Sun Jan 25 00:00:00 1970 PST + 425 | 5 | 00425 | Mon Jan 26 00:00:00 1970 PST + 426 | 6 | 00426 | Tue Jan 27 00:00:00 1970 PST + 427 | 7 | 00427 | Wed Jan 28 00:00:00 1970 PST + 428 | 8 | 00428 | Thu Jan 29 00:00:00 1970 PST + 429 | 9 | 00429 | Fri Jan 30 00:00:00 1970 PST + 430 | 0 | 00430 | Sat Jan 31 00:00:00 1970 PST + 431 | 1 | 00431 | Sun Feb 01 00:00:00 1970 PST + 432 | 2 | 00432 | Mon Feb 02 00:00:00 1970 PST + 433 | 3 | 00433 | Tue Feb 03 00:00:00 1970 PST + 434 | 4 | 00434 | Wed Feb 04 00:00:00 1970 PST + 435 | 5 | 00435 | Thu Feb 05 00:00:00 1970 PST + 436 | 6 | 00436 | Fri Feb 06 00:00:00 1970 PST + 437 | 7 | 00437 | Sat Feb 07 00:00:00 1970 PST + 438 | 8 | 00438 | Sun Feb 08 00:00:00 1970 PST + 439 | 9 | 00439 | Mon Feb 09 00:00:00 1970 PST + 440 | 0 | 00440 | Tue Feb 10 00:00:00 1970 PST + 441 | 1 | 00441 | Wed Feb 11 00:00:00 1970 PST + 442 | 2 | 00442 | Thu Feb 12 00:00:00 1970 PST + 443 | 3 | 00443 | Fri Feb 13 00:00:00 1970 PST + 444 | 4 | 00444 | Sat Feb 14 00:00:00 1970 PST + 445 | 5 | 00445 | Sun Feb 15 00:00:00 1970 PST + 446 | 6 | 00446 | Mon Feb 16 00:00:00 1970 PST + 447 | 7 | 00447 | Tue Feb 17 00:00:00 1970 PST + 448 | 8 | 00448 | Wed Feb 18 00:00:00 1970 PST + 449 | 9 | 00449 | Thu Feb 19 00:00:00 1970 PST + 450 | 0 | 00450 | Fri Feb 20 00:00:00 1970 PST + 451 | 1 | 00451 | Sat Feb 21 00:00:00 1970 PST + 452 | 2 | 00452 | Sun Feb 22 00:00:00 1970 PST + 453 | 3 | 00453 | Mon Feb 23 00:00:00 1970 PST + 454 | 4 | 00454 | Tue Feb 24 00:00:00 1970 PST + 455 | 5 | 00455 | Wed Feb 25 00:00:00 1970 PST + 456 | 6 | 00456 | Thu Feb 26 00:00:00 1970 PST + 457 | 7 | 00457 | Fri Feb 27 00:00:00 1970 PST + 458 | 8 | 00458 | Sat Feb 28 00:00:00 1970 PST + 459 | 9 | 00459 | Sun Mar 01 00:00:00 1970 PST + 460 | 0 | 00460 | Mon Mar 02 00:00:00 1970 PST + 461 | 1 | 00461 | Tue Mar 03 00:00:00 1970 PST + 462 | 2 | 00462 | Wed Mar 04 00:00:00 1970 PST + 463 | 3 | 00463 | Thu Mar 05 00:00:00 1970 PST + 464 | 4 | 00464 | Fri Mar 06 00:00:00 1970 PST + 465 | 5 | 00465 | Sat Mar 07 00:00:00 1970 PST + 466 | 6 | 00466 | Sun Mar 08 00:00:00 1970 PST + 467 | 7 | 00467 | Mon Mar 09 00:00:00 1970 PST + 468 | 8 | 00468 | Tue Mar 10 00:00:00 1970 PST + 469 | 9 | 00469 | Wed Mar 11 00:00:00 1970 PST + 470 | 0 | 00470 | Thu Mar 12 00:00:00 1970 PST + 471 | 1 | 00471 | Fri Mar 13 00:00:00 1970 PST + 472 | 2 | 00472 | Sat Mar 14 00:00:00 1970 PST + 473 | 3 | 00473 | Sun Mar 15 00:00:00 1970 PST + 474 | 4 | 00474 | Mon Mar 16 00:00:00 1970 PST + 475 | 5 | 00475 | Tue Mar 17 00:00:00 1970 PST + 476 | 6 | 00476 | Wed Mar 18 00:00:00 1970 PST + 477 | 7 | 00477 | Thu Mar 19 00:00:00 1970 PST + 478 | 8 | 00478 | Fri Mar 20 00:00:00 1970 PST + 479 | 9 | 00479 | Sat Mar 21 00:00:00 1970 PST + 480 | 0 | 00480 | Sun Mar 22 00:00:00 1970 PST + 481 | 1 | 00481 | Mon Mar 23 00:00:00 1970 PST + 482 | 2 | 00482 | Tue Mar 24 00:00:00 1970 PST + 483 | 3 | 00483 | Wed Mar 25 00:00:00 1970 PST + 484 | 4 | 00484 | Thu Mar 26 00:00:00 1970 PST + 485 | 5 | 00485 | Fri Mar 27 00:00:00 1970 PST + 486 | 6 | 00486 | Sat Mar 28 00:00:00 1970 PST + 487 | 7 | 00487 | Sun Mar 29 00:00:00 1970 PST + 488 | 8 | 00488 | Mon Mar 30 00:00:00 1970 PST + 489 | 9 | 00489 | Tue Mar 31 00:00:00 1970 PST + 490 | 0 | 00490 | Wed Apr 01 00:00:00 1970 PST + 491 | 1 | 00491 | Thu Apr 02 00:00:00 1970 PST + 492 | 2 | 00492 | Fri Apr 03 00:00:00 1970 PST + 493 | 3 | 00493 | Sat Apr 04 00:00:00 1970 PST + 494 | 4 | 00494 | Sun Apr 05 00:00:00 1970 PST + 495 | 5 | 00495 | Mon Apr 06 00:00:00 1970 PST + 496 | 6 | 00496 | Tue Apr 07 00:00:00 1970 PST + 497 | 7 | 00497 | Wed Apr 08 00:00:00 1970 PST + 498 | 8 | 00498 | Thu Apr 09 00:00:00 1970 PST + 499 | 9 | 00499 | Fri Apr 10 00:00:00 1970 PST + 500 | 0 | 00500 | Thu Jan 01 00:00:00 1970 PST + 501 | 1 | 00501 | Fri Jan 02 00:00:00 1970 PST + 502 | 2 | 00502 | Sat Jan 03 00:00:00 1970 PST + 503 | 3 | 00503 | Sun Jan 04 00:00:00 1970 PST + 504 | 4 | 00504 | Mon Jan 05 00:00:00 1970 PST + 505 | 5 | 00505 | Tue Jan 06 00:00:00 1970 PST + 506 | 6 | 00506 | Wed Jan 07 00:00:00 1970 PST + 507 | 7 | 00507 | Thu Jan 08 00:00:00 1970 PST + 508 | 8 | 00508 | Fri Jan 09 00:00:00 1970 PST + 509 | 9 | 00509 | Sat Jan 10 00:00:00 1970 PST + 510 | 0 | 00510 | Sun Jan 11 00:00:00 1970 PST + 511 | 1 | 00511 | Mon Jan 12 00:00:00 1970 PST + 512 | 2 | 00512 | Tue Jan 13 00:00:00 1970 PST + 513 | 3 | 00513 | Wed Jan 14 00:00:00 1970 PST + 514 | 4 | 00514 | Thu Jan 15 00:00:00 1970 PST + 515 | 5 | 00515 | Fri Jan 16 00:00:00 1970 PST + 516 | 6 | 00516 | Sat Jan 17 00:00:00 1970 PST + 517 | 7 | 00517 | Sun Jan 18 00:00:00 1970 PST + 518 | 8 | 00518 | Mon Jan 19 00:00:00 1970 PST + 519 | 9 | 00519 | Tue Jan 20 00:00:00 1970 PST + 520 | 0 | 00520 | Wed Jan 21 00:00:00 1970 PST + 521 | 1 | 00521 | Thu Jan 22 00:00:00 1970 PST + 522 | 2 | 00522 | Fri Jan 23 00:00:00 1970 PST + 523 | 3 | 00523 | Sat Jan 24 00:00:00 1970 PST + 524 | 4 | 00524 | Sun Jan 25 00:00:00 1970 PST + 525 | 5 | 00525 | Mon Jan 26 00:00:00 1970 PST + 526 | 6 | 00526 | Tue Jan 27 00:00:00 1970 PST + 527 | 7 | 00527 | Wed Jan 28 00:00:00 1970 PST + 528 | 8 | 00528 | Thu Jan 29 00:00:00 1970 PST + 529 | 9 | 00529 | Fri Jan 30 00:00:00 1970 PST + 530 | 0 | 00530 | Sat Jan 31 00:00:00 1970 PST + 531 | 1 | 00531 | Sun Feb 01 00:00:00 1970 PST + 532 | 2 | 00532 | Mon Feb 02 00:00:00 1970 PST + 533 | 3 | 00533 | Tue Feb 03 00:00:00 1970 PST + 534 | 4 | 00534 | Wed Feb 04 00:00:00 1970 PST + 535 | 5 | 00535 | Thu Feb 05 00:00:00 1970 PST + 536 | 6 | 00536 | Fri Feb 06 00:00:00 1970 PST + 537 | 7 | 00537 | Sat Feb 07 00:00:00 1970 PST + 538 | 8 | 00538 | Sun Feb 08 00:00:00 1970 PST + 539 | 9 | 00539 | Mon Feb 09 00:00:00 1970 PST + 540 | 0 | 00540 | Tue Feb 10 00:00:00 1970 PST + 541 | 1 | 00541 | Wed Feb 11 00:00:00 1970 PST + 542 | 2 | 00542 | Thu Feb 12 00:00:00 1970 PST + 543 | 3 | 00543 | Fri Feb 13 00:00:00 1970 PST + 544 | 4 | 00544 | Sat Feb 14 00:00:00 1970 PST + 545 | 5 | 00545 | Sun Feb 15 00:00:00 1970 PST + 546 | 6 | 00546 | Mon Feb 16 00:00:00 1970 PST + 547 | 7 | 00547 | Tue Feb 17 00:00:00 1970 PST + 548 | 8 | 00548 | Wed Feb 18 00:00:00 1970 PST + 549 | 9 | 00549 | Thu Feb 19 00:00:00 1970 PST + 550 | 0 | 00550 | Fri Feb 20 00:00:00 1970 PST + 551 | 1 | 00551 | Sat Feb 21 00:00:00 1970 PST + 552 | 2 | 00552 | Sun Feb 22 00:00:00 1970 PST + 553 | 3 | 00553 | Mon Feb 23 00:00:00 1970 PST + 554 | 4 | 00554 | Tue Feb 24 00:00:00 1970 PST + 555 | 5 | 00555 | Wed Feb 25 00:00:00 1970 PST + 556 | 6 | 00556 | Thu Feb 26 00:00:00 1970 PST + 557 | 7 | 00557 | Fri Feb 27 00:00:00 1970 PST + 558 | 8 | 00558 | Sat Feb 28 00:00:00 1970 PST + 559 | 9 | 00559 | Sun Mar 01 00:00:00 1970 PST + 560 | 0 | 00560 | Mon Mar 02 00:00:00 1970 PST + 561 | 1 | 00561 | Tue Mar 03 00:00:00 1970 PST + 562 | 2 | 00562 | Wed Mar 04 00:00:00 1970 PST + 563 | 3 | 00563 | Thu Mar 05 00:00:00 1970 PST + 564 | 4 | 00564 | Fri Mar 06 00:00:00 1970 PST + 565 | 5 | 00565 | Sat Mar 07 00:00:00 1970 PST + 566 | 6 | 00566 | Sun Mar 08 00:00:00 1970 PST + 567 | 7 | 00567 | Mon Mar 09 00:00:00 1970 PST + 568 | 8 | 00568 | Tue Mar 10 00:00:00 1970 PST + 569 | 9 | 00569 | Wed Mar 11 00:00:00 1970 PST + 570 | 0 | 00570 | Thu Mar 12 00:00:00 1970 PST + 571 | 1 | 00571 | Fri Mar 13 00:00:00 1970 PST + 572 | 2 | 00572 | Sat Mar 14 00:00:00 1970 PST + 573 | 3 | 00573 | Sun Mar 15 00:00:00 1970 PST + 574 | 4 | 00574 | Mon Mar 16 00:00:00 1970 PST + 575 | 5 | 00575 | Tue Mar 17 00:00:00 1970 PST + 576 | 6 | 00576 | Wed Mar 18 00:00:00 1970 PST + 577 | 7 | 00577 | Thu Mar 19 00:00:00 1970 PST + 578 | 8 | 00578 | Fri Mar 20 00:00:00 1970 PST + 579 | 9 | 00579 | Sat Mar 21 00:00:00 1970 PST + 580 | 0 | 00580 | Sun Mar 22 00:00:00 1970 PST + 581 | 1 | 00581 | Mon Mar 23 00:00:00 1970 PST + 582 | 2 | 00582 | Tue Mar 24 00:00:00 1970 PST + 583 | 3 | 00583 | Wed Mar 25 00:00:00 1970 PST + 584 | 4 | 00584 | Thu Mar 26 00:00:00 1970 PST + 585 | 5 | 00585 | Fri Mar 27 00:00:00 1970 PST + 586 | 6 | 00586 | Sat Mar 28 00:00:00 1970 PST + 587 | 7 | 00587 | Sun Mar 29 00:00:00 1970 PST + 588 | 8 | 00588 | Mon Mar 30 00:00:00 1970 PST + 589 | 9 | 00589 | Tue Mar 31 00:00:00 1970 PST + 590 | 0 | 00590 | Wed Apr 01 00:00:00 1970 PST + 591 | 1 | 00591 | Thu Apr 02 00:00:00 1970 PST + 592 | 2 | 00592 | Fri Apr 03 00:00:00 1970 PST + 593 | 3 | 00593 | Sat Apr 04 00:00:00 1970 PST + 594 | 4 | 00594 | Sun Apr 05 00:00:00 1970 PST + 595 | 5 | 00595 | Mon Apr 06 00:00:00 1970 PST + 596 | 6 | 00596 | Tue Apr 07 00:00:00 1970 PST + 597 | 7 | 00597 | Wed Apr 08 00:00:00 1970 PST + 598 | 8 | 00598 | Thu Apr 09 00:00:00 1970 PST + 599 | 9 | 00599 | Fri Apr 10 00:00:00 1970 PST + 600 | 0 | 00600 | Thu Jan 01 00:00:00 1970 PST + 601 | 1 | 00601 | Fri Jan 02 00:00:00 1970 PST + 602 | 2 | 00602 | Sat Jan 03 00:00:00 1970 PST + 603 | 3 | 00603 | Sun Jan 04 00:00:00 1970 PST + 604 | 4 | 00604 | Mon Jan 05 00:00:00 1970 PST + 605 | 5 | 00605 | Tue Jan 06 00:00:00 1970 PST + 606 | 6 | 00606 | Wed Jan 07 00:00:00 1970 PST + 607 | 7 | 00607 | Thu Jan 08 00:00:00 1970 PST + 608 | 8 | 00608 | Fri Jan 09 00:00:00 1970 PST + 609 | 9 | 00609 | Sat Jan 10 00:00:00 1970 PST + 610 | 0 | 00610 | Sun Jan 11 00:00:00 1970 PST + 611 | 1 | 00611 | Mon Jan 12 00:00:00 1970 PST + 612 | 2 | 00612 | Tue Jan 13 00:00:00 1970 PST + 613 | 3 | 00613 | Wed Jan 14 00:00:00 1970 PST + 614 | 4 | 00614 | Thu Jan 15 00:00:00 1970 PST + 615 | 5 | 00615 | Fri Jan 16 00:00:00 1970 PST + 616 | 6 | 00616 | Sat Jan 17 00:00:00 1970 PST + 617 | 7 | 00617 | Sun Jan 18 00:00:00 1970 PST + 618 | 8 | 00618 | Mon Jan 19 00:00:00 1970 PST + 619 | 9 | 00619 | Tue Jan 20 00:00:00 1970 PST + 620 | 0 | 00620 | Wed Jan 21 00:00:00 1970 PST + 621 | 1 | 00621 | Thu Jan 22 00:00:00 1970 PST + 622 | 2 | 00622 | Fri Jan 23 00:00:00 1970 PST + 623 | 3 | 00623 | Sat Jan 24 00:00:00 1970 PST + 624 | 4 | 00624 | Sun Jan 25 00:00:00 1970 PST + 625 | 5 | 00625 | Mon Jan 26 00:00:00 1970 PST + 626 | 6 | 00626 | Tue Jan 27 00:00:00 1970 PST + 627 | 7 | 00627 | Wed Jan 28 00:00:00 1970 PST + 628 | 8 | 00628 | Thu Jan 29 00:00:00 1970 PST + 629 | 9 | 00629 | Fri Jan 30 00:00:00 1970 PST + 630 | 0 | 00630 | Sat Jan 31 00:00:00 1970 PST + 631 | 1 | 00631 | Sun Feb 01 00:00:00 1970 PST + 632 | 2 | 00632 | Mon Feb 02 00:00:00 1970 PST + 633 | 3 | 00633 | Tue Feb 03 00:00:00 1970 PST + 634 | 4 | 00634 | Wed Feb 04 00:00:00 1970 PST + 635 | 5 | 00635 | Thu Feb 05 00:00:00 1970 PST + 636 | 6 | 00636 | Fri Feb 06 00:00:00 1970 PST + 637 | 7 | 00637 | Sat Feb 07 00:00:00 1970 PST + 638 | 8 | 00638 | Sun Feb 08 00:00:00 1970 PST + 639 | 9 | 00639 | Mon Feb 09 00:00:00 1970 PST + 640 | 0 | 00640 | Tue Feb 10 00:00:00 1970 PST + 641 | 1 | 00641 | Wed Feb 11 00:00:00 1970 PST + 642 | 2 | 00642 | Thu Feb 12 00:00:00 1970 PST + 643 | 3 | 00643 | Fri Feb 13 00:00:00 1970 PST + 644 | 4 | 00644 | Sat Feb 14 00:00:00 1970 PST + 645 | 5 | 00645 | Sun Feb 15 00:00:00 1970 PST + 646 | 6 | 00646 | Mon Feb 16 00:00:00 1970 PST + 647 | 7 | 00647 | Tue Feb 17 00:00:00 1970 PST + 648 | 8 | 00648 | Wed Feb 18 00:00:00 1970 PST + 649 | 9 | 00649 | Thu Feb 19 00:00:00 1970 PST + 650 | 0 | 00650 | Fri Feb 20 00:00:00 1970 PST + 651 | 1 | 00651 | Sat Feb 21 00:00:00 1970 PST + 652 | 2 | 00652 | Sun Feb 22 00:00:00 1970 PST + 653 | 3 | 00653 | Mon Feb 23 00:00:00 1970 PST + 654 | 4 | 00654 | Tue Feb 24 00:00:00 1970 PST + 655 | 5 | 00655 | Wed Feb 25 00:00:00 1970 PST + 656 | 6 | 00656 | Thu Feb 26 00:00:00 1970 PST + 657 | 7 | 00657 | Fri Feb 27 00:00:00 1970 PST + 658 | 8 | 00658 | Sat Feb 28 00:00:00 1970 PST + 659 | 9 | 00659 | Sun Mar 01 00:00:00 1970 PST + 660 | 0 | 00660 | Mon Mar 02 00:00:00 1970 PST + 661 | 1 | 00661 | Tue Mar 03 00:00:00 1970 PST + 662 | 2 | 00662 | Wed Mar 04 00:00:00 1970 PST + 663 | 3 | 00663 | Thu Mar 05 00:00:00 1970 PST + 664 | 4 | 00664 | Fri Mar 06 00:00:00 1970 PST + 665 | 5 | 00665 | Sat Mar 07 00:00:00 1970 PST + 666 | 6 | 00666 | Sun Mar 08 00:00:00 1970 PST + 667 | 7 | 00667 | Mon Mar 09 00:00:00 1970 PST + 668 | 8 | 00668 | Tue Mar 10 00:00:00 1970 PST + 669 | 9 | 00669 | Wed Mar 11 00:00:00 1970 PST + 670 | 0 | 00670 | Thu Mar 12 00:00:00 1970 PST + 671 | 1 | 00671 | Fri Mar 13 00:00:00 1970 PST + 672 | 2 | 00672 | Sat Mar 14 00:00:00 1970 PST + 673 | 3 | 00673 | Sun Mar 15 00:00:00 1970 PST + 674 | 4 | 00674 | Mon Mar 16 00:00:00 1970 PST + 675 | 5 | 00675 | Tue Mar 17 00:00:00 1970 PST + 676 | 6 | 00676 | Wed Mar 18 00:00:00 1970 PST + 677 | 7 | 00677 | Thu Mar 19 00:00:00 1970 PST + 678 | 8 | 00678 | Fri Mar 20 00:00:00 1970 PST + 679 | 9 | 00679 | Sat Mar 21 00:00:00 1970 PST + 680 | 0 | 00680 | Sun Mar 22 00:00:00 1970 PST + 681 | 1 | 00681 | Mon Mar 23 00:00:00 1970 PST + 682 | 2 | 00682 | Tue Mar 24 00:00:00 1970 PST + 683 | 3 | 00683 | Wed Mar 25 00:00:00 1970 PST + 684 | 4 | 00684 | Thu Mar 26 00:00:00 1970 PST + 685 | 5 | 00685 | Fri Mar 27 00:00:00 1970 PST + 686 | 6 | 00686 | Sat Mar 28 00:00:00 1970 PST + 687 | 7 | 00687 | Sun Mar 29 00:00:00 1970 PST + 688 | 8 | 00688 | Mon Mar 30 00:00:00 1970 PST + 689 | 9 | 00689 | Tue Mar 31 00:00:00 1970 PST + 690 | 0 | 00690 | Wed Apr 01 00:00:00 1970 PST + 691 | 1 | 00691 | Thu Apr 02 00:00:00 1970 PST + 692 | 2 | 00692 | Fri Apr 03 00:00:00 1970 PST + 693 | 3 | 00693 | Sat Apr 04 00:00:00 1970 PST + 694 | 4 | 00694 | Sun Apr 05 00:00:00 1970 PST + 695 | 5 | 00695 | Mon Apr 06 00:00:00 1970 PST + 696 | 6 | 00696 | Tue Apr 07 00:00:00 1970 PST + 697 | 7 | 00697 | Wed Apr 08 00:00:00 1970 PST + 698 | 8 | 00698 | Thu Apr 09 00:00:00 1970 PST + 699 | 9 | 00699 | Fri Apr 10 00:00:00 1970 PST + 700 | 0 | 00700 | Thu Jan 01 00:00:00 1970 PST + 701 | 1 | 00701 | Fri Jan 02 00:00:00 1970 PST + 702 | 2 | 00702 | Sat Jan 03 00:00:00 1970 PST + 703 | 3 | 00703 | Sun Jan 04 00:00:00 1970 PST + 704 | 4 | 00704 | Mon Jan 05 00:00:00 1970 PST + 705 | 5 | 00705 | Tue Jan 06 00:00:00 1970 PST + 706 | 6 | 00706 | Wed Jan 07 00:00:00 1970 PST + 707 | 7 | 00707 | Thu Jan 08 00:00:00 1970 PST + 708 | 8 | 00708 | Fri Jan 09 00:00:00 1970 PST + 709 | 9 | 00709 | Sat Jan 10 00:00:00 1970 PST + 710 | 0 | 00710 | Sun Jan 11 00:00:00 1970 PST + 711 | 1 | 00711 | Mon Jan 12 00:00:00 1970 PST + 712 | 2 | 00712 | Tue Jan 13 00:00:00 1970 PST + 713 | 3 | 00713 | Wed Jan 14 00:00:00 1970 PST + 714 | 4 | 00714 | Thu Jan 15 00:00:00 1970 PST + 715 | 5 | 00715 | Fri Jan 16 00:00:00 1970 PST + 716 | 6 | 00716 | Sat Jan 17 00:00:00 1970 PST + 717 | 7 | 00717 | Sun Jan 18 00:00:00 1970 PST + 718 | 8 | 00718 | Mon Jan 19 00:00:00 1970 PST + 719 | 9 | 00719 | Tue Jan 20 00:00:00 1970 PST + 720 | 0 | 00720 | Wed Jan 21 00:00:00 1970 PST + 721 | 1 | 00721 | Thu Jan 22 00:00:00 1970 PST + 722 | 2 | 00722 | Fri Jan 23 00:00:00 1970 PST + 723 | 3 | 00723 | Sat Jan 24 00:00:00 1970 PST + 724 | 4 | 00724 | Sun Jan 25 00:00:00 1970 PST + 725 | 5 | 00725 | Mon Jan 26 00:00:00 1970 PST + 726 | 6 | 00726 | Tue Jan 27 00:00:00 1970 PST + 727 | 7 | 00727 | Wed Jan 28 00:00:00 1970 PST + 728 | 8 | 00728 | Thu Jan 29 00:00:00 1970 PST + 729 | 9 | 00729 | Fri Jan 30 00:00:00 1970 PST + 730 | 0 | 00730 | Sat Jan 31 00:00:00 1970 PST + 731 | 1 | 00731 | Sun Feb 01 00:00:00 1970 PST + 732 | 2 | 00732 | Mon Feb 02 00:00:00 1970 PST + 733 | 3 | 00733 | Tue Feb 03 00:00:00 1970 PST + 734 | 4 | 00734 | Wed Feb 04 00:00:00 1970 PST + 735 | 5 | 00735 | Thu Feb 05 00:00:00 1970 PST + 736 | 6 | 00736 | Fri Feb 06 00:00:00 1970 PST + 737 | 7 | 00737 | Sat Feb 07 00:00:00 1970 PST + 738 | 8 | 00738 | Sun Feb 08 00:00:00 1970 PST + 739 | 9 | 00739 | Mon Feb 09 00:00:00 1970 PST + 740 | 0 | 00740 | Tue Feb 10 00:00:00 1970 PST + 741 | 1 | 00741 | Wed Feb 11 00:00:00 1970 PST + 742 | 2 | 00742 | Thu Feb 12 00:00:00 1970 PST + 743 | 3 | 00743 | Fri Feb 13 00:00:00 1970 PST + 744 | 4 | 00744 | Sat Feb 14 00:00:00 1970 PST + 745 | 5 | 00745 | Sun Feb 15 00:00:00 1970 PST + 746 | 6 | 00746 | Mon Feb 16 00:00:00 1970 PST + 747 | 7 | 00747 | Tue Feb 17 00:00:00 1970 PST + 748 | 8 | 00748 | Wed Feb 18 00:00:00 1970 PST + 749 | 9 | 00749 | Thu Feb 19 00:00:00 1970 PST + 750 | 0 | 00750 | Fri Feb 20 00:00:00 1970 PST + 751 | 1 | 00751 | Sat Feb 21 00:00:00 1970 PST + 752 | 2 | 00752 | Sun Feb 22 00:00:00 1970 PST + 753 | 3 | 00753 | Mon Feb 23 00:00:00 1970 PST + 754 | 4 | 00754 | Tue Feb 24 00:00:00 1970 PST + 755 | 5 | 00755 | Wed Feb 25 00:00:00 1970 PST + 756 | 6 | 00756 | Thu Feb 26 00:00:00 1970 PST + 757 | 7 | 00757 | Fri Feb 27 00:00:00 1970 PST + 758 | 8 | 00758 | Sat Feb 28 00:00:00 1970 PST + 759 | 9 | 00759 | Sun Mar 01 00:00:00 1970 PST + 760 | 0 | 00760 | Mon Mar 02 00:00:00 1970 PST + 761 | 1 | 00761 | Tue Mar 03 00:00:00 1970 PST + 762 | 2 | 00762 | Wed Mar 04 00:00:00 1970 PST + 763 | 3 | 00763 | Thu Mar 05 00:00:00 1970 PST + 764 | 4 | 00764 | Fri Mar 06 00:00:00 1970 PST + 765 | 5 | 00765 | Sat Mar 07 00:00:00 1970 PST + 766 | 6 | 00766 | Sun Mar 08 00:00:00 1970 PST + 767 | 7 | 00767 | Mon Mar 09 00:00:00 1970 PST + 768 | 8 | 00768 | Tue Mar 10 00:00:00 1970 PST + 769 | 9 | 00769 | Wed Mar 11 00:00:00 1970 PST + 770 | 0 | 00770 | Thu Mar 12 00:00:00 1970 PST + 771 | 1 | 00771 | Fri Mar 13 00:00:00 1970 PST + 772 | 2 | 00772 | Sat Mar 14 00:00:00 1970 PST + 773 | 3 | 00773 | Sun Mar 15 00:00:00 1970 PST + 774 | 4 | 00774 | Mon Mar 16 00:00:00 1970 PST + 775 | 5 | 00775 | Tue Mar 17 00:00:00 1970 PST + 776 | 6 | 00776 | Wed Mar 18 00:00:00 1970 PST + 777 | 7 | 00777 | Thu Mar 19 00:00:00 1970 PST + 778 | 8 | 00778 | Fri Mar 20 00:00:00 1970 PST + 779 | 9 | 00779 | Sat Mar 21 00:00:00 1970 PST + 780 | 0 | 00780 | Sun Mar 22 00:00:00 1970 PST + 781 | 1 | 00781 | Mon Mar 23 00:00:00 1970 PST + 782 | 2 | 00782 | Tue Mar 24 00:00:00 1970 PST + 783 | 3 | 00783 | Wed Mar 25 00:00:00 1970 PST + 784 | 4 | 00784 | Thu Mar 26 00:00:00 1970 PST + 785 | 5 | 00785 | Fri Mar 27 00:00:00 1970 PST + 786 | 6 | 00786 | Sat Mar 28 00:00:00 1970 PST + 787 | 7 | 00787 | Sun Mar 29 00:00:00 1970 PST + 788 | 8 | 00788 | Mon Mar 30 00:00:00 1970 PST + 789 | 9 | 00789 | Tue Mar 31 00:00:00 1970 PST + 790 | 0 | 00790 | Wed Apr 01 00:00:00 1970 PST + 791 | 1 | 00791 | Thu Apr 02 00:00:00 1970 PST + 792 | 2 | 00792 | Fri Apr 03 00:00:00 1970 PST + 793 | 3 | 00793 | Sat Apr 04 00:00:00 1970 PST + 794 | 4 | 00794 | Sun Apr 05 00:00:00 1970 PST + 795 | 5 | 00795 | Mon Apr 06 00:00:00 1970 PST + 796 | 6 | 00796 | Tue Apr 07 00:00:00 1970 PST + 797 | 7 | 00797 | Wed Apr 08 00:00:00 1970 PST + 798 | 8 | 00798 | Thu Apr 09 00:00:00 1970 PST + 799 | 9 | 00799 | Fri Apr 10 00:00:00 1970 PST + 800 | 0 | 00800 | Thu Jan 01 00:00:00 1970 PST + 801 | 1 | 00801 | Fri Jan 02 00:00:00 1970 PST + 802 | 2 | 00802 | Sat Jan 03 00:00:00 1970 PST + 803 | 3 | 00803 | Sun Jan 04 00:00:00 1970 PST + 804 | 4 | 00804 | Mon Jan 05 00:00:00 1970 PST + 805 | 5 | 00805 | Tue Jan 06 00:00:00 1970 PST + 806 | 6 | 00806 | Wed Jan 07 00:00:00 1970 PST + 807 | 7 | 00807 | Thu Jan 08 00:00:00 1970 PST + 808 | 8 | 00808 | Fri Jan 09 00:00:00 1970 PST + 809 | 9 | 00809 | Sat Jan 10 00:00:00 1970 PST + 810 | 0 | 00810 | Sun Jan 11 00:00:00 1970 PST + 811 | 1 | 00811 | Mon Jan 12 00:00:00 1970 PST + 812 | 2 | 00812 | Tue Jan 13 00:00:00 1970 PST + 813 | 3 | 00813 | Wed Jan 14 00:00:00 1970 PST + 814 | 4 | 00814 | Thu Jan 15 00:00:00 1970 PST + 815 | 5 | 00815 | Fri Jan 16 00:00:00 1970 PST + 816 | 6 | 00816 | Sat Jan 17 00:00:00 1970 PST + 817 | 7 | 00817 | Sun Jan 18 00:00:00 1970 PST + 818 | 8 | 00818 | Mon Jan 19 00:00:00 1970 PST + 819 | 9 | 00819 | Tue Jan 20 00:00:00 1970 PST + 820 | 0 | 00820 | Wed Jan 21 00:00:00 1970 PST + 821 | 1 | 00821 | Thu Jan 22 00:00:00 1970 PST + 822 | 2 | 00822 | Fri Jan 23 00:00:00 1970 PST + 823 | 3 | 00823 | Sat Jan 24 00:00:00 1970 PST + 824 | 4 | 00824 | Sun Jan 25 00:00:00 1970 PST + 825 | 5 | 00825 | Mon Jan 26 00:00:00 1970 PST + 826 | 6 | 00826 | Tue Jan 27 00:00:00 1970 PST + 827 | 7 | 00827 | Wed Jan 28 00:00:00 1970 PST + 828 | 8 | 00828 | Thu Jan 29 00:00:00 1970 PST + 829 | 9 | 00829 | Fri Jan 30 00:00:00 1970 PST + 830 | 0 | 00830 | Sat Jan 31 00:00:00 1970 PST + 831 | 1 | 00831 | Sun Feb 01 00:00:00 1970 PST + 832 | 2 | 00832 | Mon Feb 02 00:00:00 1970 PST + 833 | 3 | 00833 | Tue Feb 03 00:00:00 1970 PST + 834 | 4 | 00834 | Wed Feb 04 00:00:00 1970 PST + 835 | 5 | 00835 | Thu Feb 05 00:00:00 1970 PST + 836 | 6 | 00836 | Fri Feb 06 00:00:00 1970 PST + 837 | 7 | 00837 | Sat Feb 07 00:00:00 1970 PST + 838 | 8 | 00838 | Sun Feb 08 00:00:00 1970 PST + 839 | 9 | 00839 | Mon Feb 09 00:00:00 1970 PST + 840 | 0 | 00840 | Tue Feb 10 00:00:00 1970 PST + 841 | 1 | 00841 | Wed Feb 11 00:00:00 1970 PST + 842 | 2 | 00842 | Thu Feb 12 00:00:00 1970 PST + 843 | 3 | 00843 | Fri Feb 13 00:00:00 1970 PST + 844 | 4 | 00844 | Sat Feb 14 00:00:00 1970 PST + 845 | 5 | 00845 | Sun Feb 15 00:00:00 1970 PST + 846 | 6 | 00846 | Mon Feb 16 00:00:00 1970 PST + 847 | 7 | 00847 | Tue Feb 17 00:00:00 1970 PST + 848 | 8 | 00848 | Wed Feb 18 00:00:00 1970 PST + 849 | 9 | 00849 | Thu Feb 19 00:00:00 1970 PST + 850 | 0 | 00850 | Fri Feb 20 00:00:00 1970 PST + 851 | 1 | 00851 | Sat Feb 21 00:00:00 1970 PST + 852 | 2 | 00852 | Sun Feb 22 00:00:00 1970 PST + 853 | 3 | 00853 | Mon Feb 23 00:00:00 1970 PST + 854 | 4 | 00854 | Tue Feb 24 00:00:00 1970 PST + 855 | 5 | 00855 | Wed Feb 25 00:00:00 1970 PST + 856 | 6 | 00856 | Thu Feb 26 00:00:00 1970 PST + 857 | 7 | 00857 | Fri Feb 27 00:00:00 1970 PST + 858 | 8 | 00858 | Sat Feb 28 00:00:00 1970 PST + 859 | 9 | 00859 | Sun Mar 01 00:00:00 1970 PST + 860 | 0 | 00860 | Mon Mar 02 00:00:00 1970 PST + 861 | 1 | 00861 | Tue Mar 03 00:00:00 1970 PST + 862 | 2 | 00862 | Wed Mar 04 00:00:00 1970 PST + 863 | 3 | 00863 | Thu Mar 05 00:00:00 1970 PST + 864 | 4 | 00864 | Fri Mar 06 00:00:00 1970 PST + 865 | 5 | 00865 | Sat Mar 07 00:00:00 1970 PST + 866 | 6 | 00866 | Sun Mar 08 00:00:00 1970 PST + 867 | 7 | 00867 | Mon Mar 09 00:00:00 1970 PST + 868 | 8 | 00868 | Tue Mar 10 00:00:00 1970 PST + 869 | 9 | 00869 | Wed Mar 11 00:00:00 1970 PST + 870 | 0 | 00870 | Thu Mar 12 00:00:00 1970 PST + 871 | 1 | 00871 | Fri Mar 13 00:00:00 1970 PST + 872 | 2 | 00872 | Sat Mar 14 00:00:00 1970 PST + 873 | 3 | 00873 | Sun Mar 15 00:00:00 1970 PST + 874 | 4 | 00874 | Mon Mar 16 00:00:00 1970 PST + 875 | 5 | 00875 | Tue Mar 17 00:00:00 1970 PST + 876 | 6 | 00876 | Wed Mar 18 00:00:00 1970 PST + 877 | 7 | 00877 | Thu Mar 19 00:00:00 1970 PST + 878 | 8 | 00878 | Fri Mar 20 00:00:00 1970 PST + 879 | 9 | 00879 | Sat Mar 21 00:00:00 1970 PST + 880 | 0 | 00880 | Sun Mar 22 00:00:00 1970 PST + 881 | 1 | 00881 | Mon Mar 23 00:00:00 1970 PST + 882 | 2 | 00882 | Tue Mar 24 00:00:00 1970 PST + 883 | 3 | 00883 | Wed Mar 25 00:00:00 1970 PST + 884 | 4 | 00884 | Thu Mar 26 00:00:00 1970 PST + 885 | 5 | 00885 | Fri Mar 27 00:00:00 1970 PST + 886 | 6 | 00886 | Sat Mar 28 00:00:00 1970 PST + 887 | 7 | 00887 | Sun Mar 29 00:00:00 1970 PST + 888 | 8 | 00888 | Mon Mar 30 00:00:00 1970 PST + 889 | 9 | 00889 | Tue Mar 31 00:00:00 1970 PST + 890 | 0 | 00890 | Wed Apr 01 00:00:00 1970 PST + 891 | 1 | 00891 | Thu Apr 02 00:00:00 1970 PST + 892 | 2 | 00892 | Fri Apr 03 00:00:00 1970 PST + 893 | 3 | 00893 | Sat Apr 04 00:00:00 1970 PST + 894 | 4 | 00894 | Sun Apr 05 00:00:00 1970 PST + 895 | 5 | 00895 | Mon Apr 06 00:00:00 1970 PST + 896 | 6 | 00896 | Tue Apr 07 00:00:00 1970 PST + 897 | 7 | 00897 | Wed Apr 08 00:00:00 1970 PST + 898 | 8 | 00898 | Thu Apr 09 00:00:00 1970 PST + 899 | 9 | 00899 | Fri Apr 10 00:00:00 1970 PST + 900 | 0 | 00900 | Thu Jan 01 00:00:00 1970 PST + 901 | 1 | 00901 | Fri Jan 02 00:00:00 1970 PST + 902 | 2 | 00902 | Sat Jan 03 00:00:00 1970 PST + 903 | 3 | 00903 | Sun Jan 04 00:00:00 1970 PST + 904 | 4 | 00904 | Mon Jan 05 00:00:00 1970 PST + 905 | 5 | 00905 | Tue Jan 06 00:00:00 1970 PST + 906 | 6 | 00906 | Wed Jan 07 00:00:00 1970 PST + 907 | 7 | 00907 | Thu Jan 08 00:00:00 1970 PST + 908 | 8 | 00908 | Fri Jan 09 00:00:00 1970 PST + 909 | 9 | 00909 | Sat Jan 10 00:00:00 1970 PST + 910 | 0 | 00910 | Sun Jan 11 00:00:00 1970 PST + 911 | 1 | 00911 | Mon Jan 12 00:00:00 1970 PST + 912 | 2 | 00912 | Tue Jan 13 00:00:00 1970 PST + 913 | 3 | 00913 | Wed Jan 14 00:00:00 1970 PST + 914 | 4 | 00914 | Thu Jan 15 00:00:00 1970 PST + 915 | 5 | 00915 | Fri Jan 16 00:00:00 1970 PST + 916 | 6 | 00916 | Sat Jan 17 00:00:00 1970 PST + 917 | 7 | 00917 | Sun Jan 18 00:00:00 1970 PST + 918 | 8 | 00918 | Mon Jan 19 00:00:00 1970 PST + 919 | 9 | 00919 | Tue Jan 20 00:00:00 1970 PST + 920 | 0 | 00920 | Wed Jan 21 00:00:00 1970 PST + 921 | 1 | 00921 | Thu Jan 22 00:00:00 1970 PST + 922 | 2 | 00922 | Fri Jan 23 00:00:00 1970 PST + 923 | 3 | 00923 | Sat Jan 24 00:00:00 1970 PST + 924 | 4 | 00924 | Sun Jan 25 00:00:00 1970 PST + 925 | 5 | 00925 | Mon Jan 26 00:00:00 1970 PST + 926 | 6 | 00926 | Tue Jan 27 00:00:00 1970 PST + 927 | 7 | 00927 | Wed Jan 28 00:00:00 1970 PST + 928 | 8 | 00928 | Thu Jan 29 00:00:00 1970 PST + 929 | 9 | 00929 | Fri Jan 30 00:00:00 1970 PST + 930 | 0 | 00930 | Sat Jan 31 00:00:00 1970 PST + 931 | 1 | 00931 | Sun Feb 01 00:00:00 1970 PST + 932 | 2 | 00932 | Mon Feb 02 00:00:00 1970 PST + 933 | 3 | 00933 | Tue Feb 03 00:00:00 1970 PST + 934 | 4 | 00934 | Wed Feb 04 00:00:00 1970 PST + 935 | 5 | 00935 | Thu Feb 05 00:00:00 1970 PST + 936 | 6 | 00936 | Fri Feb 06 00:00:00 1970 PST + 937 | 7 | 00937 | Sat Feb 07 00:00:00 1970 PST + 938 | 8 | 00938 | Sun Feb 08 00:00:00 1970 PST + 939 | 9 | 00939 | Mon Feb 09 00:00:00 1970 PST + 940 | 0 | 00940 | Tue Feb 10 00:00:00 1970 PST + 941 | 1 | 00941 | Wed Feb 11 00:00:00 1970 PST + 942 | 2 | 00942 | Thu Feb 12 00:00:00 1970 PST + 943 | 3 | 00943 | Fri Feb 13 00:00:00 1970 PST + 944 | 4 | 00944 | Sat Feb 14 00:00:00 1970 PST + 945 | 5 | 00945 | Sun Feb 15 00:00:00 1970 PST + 946 | 6 | 00946 | Mon Feb 16 00:00:00 1970 PST + 947 | 7 | 00947 | Tue Feb 17 00:00:00 1970 PST + 948 | 8 | 00948 | Wed Feb 18 00:00:00 1970 PST + 949 | 9 | 00949 | Thu Feb 19 00:00:00 1970 PST + 950 | 0 | 00950 | Fri Feb 20 00:00:00 1970 PST + 951 | 1 | 00951 | Sat Feb 21 00:00:00 1970 PST + 952 | 2 | 00952 | Sun Feb 22 00:00:00 1970 PST + 953 | 3 | 00953 | Mon Feb 23 00:00:00 1970 PST + 954 | 4 | 00954 | Tue Feb 24 00:00:00 1970 PST + 955 | 5 | 00955 | Wed Feb 25 00:00:00 1970 PST + 956 | 6 | 00956 | Thu Feb 26 00:00:00 1970 PST + 957 | 7 | 00957 | Fri Feb 27 00:00:00 1970 PST + 958 | 8 | 00958 | Sat Feb 28 00:00:00 1970 PST + 959 | 9 | 00959 | Sun Mar 01 00:00:00 1970 PST + 960 | 0 | 00960 | Mon Mar 02 00:00:00 1970 PST + 961 | 1 | 00961 | Tue Mar 03 00:00:00 1970 PST + 962 | 2 | 00962 | Wed Mar 04 00:00:00 1970 PST + 963 | 3 | 00963 | Thu Mar 05 00:00:00 1970 PST + 964 | 4 | 00964 | Fri Mar 06 00:00:00 1970 PST + 965 | 5 | 00965 | Sat Mar 07 00:00:00 1970 PST + 966 | 6 | 00966 | Sun Mar 08 00:00:00 1970 PST + 967 | 7 | 00967 | Mon Mar 09 00:00:00 1970 PST + 968 | 8 | 00968 | Tue Mar 10 00:00:00 1970 PST + 969 | 9 | 00969 | Wed Mar 11 00:00:00 1970 PST + 970 | 0 | 00970 | Thu Mar 12 00:00:00 1970 PST + 971 | 1 | 00971 | Fri Mar 13 00:00:00 1970 PST + 972 | 2 | 00972 | Sat Mar 14 00:00:00 1970 PST + 973 | 3 | 00973 | Sun Mar 15 00:00:00 1970 PST + 974 | 4 | 00974 | Mon Mar 16 00:00:00 1970 PST + 975 | 5 | 00975 | Tue Mar 17 00:00:00 1970 PST + 976 | 6 | 00976 | Wed Mar 18 00:00:00 1970 PST + 977 | 7 | 00977 | Thu Mar 19 00:00:00 1970 PST + 978 | 8 | 00978 | Fri Mar 20 00:00:00 1970 PST + 979 | 9 | 00979 | Sat Mar 21 00:00:00 1970 PST + 980 | 0 | 00980 | Sun Mar 22 00:00:00 1970 PST + 981 | 1 | 00981 | Mon Mar 23 00:00:00 1970 PST + 982 | 2 | 00982 | Tue Mar 24 00:00:00 1970 PST + 983 | 3 | 00983 | Wed Mar 25 00:00:00 1970 PST + 984 | 4 | 00984 | Thu Mar 26 00:00:00 1970 PST + 985 | 5 | 00985 | Fri Mar 27 00:00:00 1970 PST + 986 | 6 | 00986 | Sat Mar 28 00:00:00 1970 PST + 987 | 7 | 00987 | Sun Mar 29 00:00:00 1970 PST + 988 | 8 | 00988 | Mon Mar 30 00:00:00 1970 PST + 989 | 9 | 00989 | Tue Mar 31 00:00:00 1970 PST + 990 | 0 | 00990 | Wed Apr 01 00:00:00 1970 PST + 991 | 1 | 00991 | Thu Apr 02 00:00:00 1970 PST + 992 | 2 | 00992 | Fri Apr 03 00:00:00 1970 PST + 993 | 3 | 00993 | Sat Apr 04 00:00:00 1970 PST + 994 | 4 | 00994 | Sun Apr 05 00:00:00 1970 PST + 995 | 5 | 00995 | Mon Apr 06 00:00:00 1970 PST + 996 | 6 | 00996 | Tue Apr 07 00:00:00 1970 PST + 997 | 7 | 00997 | Wed Apr 08 00:00:00 1970 PST + 998 | 8 | 00998 | Thu Apr 09 00:00:00 1970 PST + 999 | 9 | 00999 | Fri Apr 10 00:00:00 1970 PST + 1000 | 0 | 01000 | Thu Jan 01 00:00:00 1970 PST + 1001 | 101 | 0000100001 | + 1002 | 102 | 0000200002 | + 1003 | 103 | 0000300003 | + 1004 | 104 | 0000400004 | + 1005 | 105 | 0000500005 | + 1006 | 106 | 0000600006 | + 1007 | 107 | 0000700007 | + 1008 | 108 | 0000800008 | + 1009 | 109 | 0000900009 | + 1010 | 100 | 0001000010 | + 1011 | 101 | 0001100011 | + 1012 | 102 | 0001200012 | + 1013 | 103 | 0001300013 | + 1014 | 104 | 0001400014 | + 1015 | 105 | 0001500015 | + 1016 | 106 | 0001600016 | + 1017 | 107 | 0001700017 | + 1018 | 108 | 0001800018 | + 1019 | 109 | 0001900019 | + 1020 | 100 | 0002000020 | + 1104 | 204 | ddd | + 1105 | 205 | eee | +(1022 rows) + +-- ====================================================================================================================================== +-- TEST-MODULE: check constraints +-- -------------------------------------- +-- openGauss not support to "ALTER FOREIGN TABLE ft1 ADD CONSTRAINT", so this test module +-- is unuseful. +-- ====================================================================================================================================== +-- Consistent check constraints provide consistent results +ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c2positive CHECK (c2 >= 0); +ERROR: "ft1" is not a table +EXPLAIN (VERBOSE, COSTS OFF) SELECT count(*) FROM ft1 WHERE c2 < 0; + QUERY PLAN +---------------------------------------------------------------------------------------- + Aggregate + Output: count(*) + -> Foreign Scan on public.ft1 + Node ID: 1 + Remote SQL: SELECT NULL FROM "S 1"."T 1" WHERE ((c2 < 0)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT NULL FROM "S 1"."T 1" WHERE ((c2 < 0)) + Row Adapter + Output: (NULL::text) + -> CStore Scan on "S 1"."T 1" + Output: NULL::text + Filter: ("T 1".c2 < 0) + +(14 rows) + +SELECT count(*) FROM ft1 WHERE c2 < 0; + count +------- + 0 +(1 row) + +SET constraint_exclusion = 'on'; +EXPLAIN (VERBOSE, COSTS OFF) SELECT count(*) FROM ft1 WHERE c2 < 0; + QUERY PLAN +---------------------------------------------------------------------------------------- + Aggregate + Output: count(*) + -> Foreign Scan on public.ft1 + Node ID: 1 + Remote SQL: SELECT NULL FROM "S 1"."T 1" WHERE ((c2 < 0)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT NULL FROM "S 1"."T 1" WHERE ((c2 < 0)) + Row Adapter + Output: (NULL::text) + -> CStore Scan on "S 1"."T 1" + Output: NULL::text + Filter: ("T 1".c2 < 0) + +(14 rows) + +SELECT count(*) FROM ft1 WHERE c2 < 0; + count +------- + 0 +(1 row) + +RESET constraint_exclusion; +-- check constraint is enforced on the remote side, not locally +INSERT INTO ft1(c1, c2) VALUES(1111, -2); -- c2positive +UPDATE ft1 SET c2 = -c2 WHERE c1 = 1; -- c2positive +ERROR: SELECT FOR UPDATE/SHARE/NO KEY UPDATE/KEY SHARE cannot be used with column table "T 1" +CONTEXT: Remote SQL command: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8, ctid, tableoid FROM "S 1"."T 1" WHERE (("C 1" = 1)) FOR UPDATE +ALTER FOREIGN TABLE ft1 DROP CONSTRAINT ft1_c2positive; +ERROR: constraint "ft1_c2positive" of relation "ft1" does not exist +-- But inconsistent check constraints provide inconsistent results +ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c2negative CHECK (c2 < 0); +ERROR: "ft1" is not a table +EXPLAIN (VERBOSE, COSTS OFF) SELECT count(*) FROM ft1 WHERE c2 >= 0; + QUERY PLAN +--------------------------------------------------------------------------------------------- + Foreign Scan + Output: (count(*)) + Node ID: 1 + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT count(*) FROM "S 1"."T 1" WHERE ((c2 >= 0)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT count(*) FROM "S 1"."T 1" WHERE ((c2 >= 0)) + Row Adapter + Output: (count(*)) + -> Vector Aggregate + Output: count(*) + -> CStore Scan on "S 1"."T 1" + Output: 'Dummy' + Filter: ("T 1".c2 >= 0) + +(16 rows) + +SELECT count(*) FROM ft1 WHERE c2 >= 0; + count +------- + 1022 +(1 row) + +SET constraint_exclusion = 'on'; +EXPLAIN (VERBOSE, COSTS OFF) SELECT count(*) FROM ft1 WHERE c2 >= 0; + QUERY PLAN +--------------------------------------------------------------------------------------------- + Foreign Scan + Output: (count(*)) + Node ID: 1 + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT count(*) FROM "S 1"."T 1" WHERE ((c2 >= 0)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT count(*) FROM "S 1"."T 1" WHERE ((c2 >= 0)) + Row Adapter + Output: (count(*)) + -> Vector Aggregate + Output: count(*) + -> CStore Scan on "S 1"."T 1" + Output: 'Dummy' + Filter: ("T 1".c2 >= 0) + +(16 rows) + +SELECT count(*) FROM ft1 WHERE c2 >= 0; + count +------- + 1022 +(1 row) + +RESET constraint_exclusion; +-- local check constraint is not actually enforced +INSERT INTO ft1(c1, c2) VALUES(1111, 2); +ERROR: duplicate key value violates unique constraint "t1_pkey" +DETAIL: Key ("C 1")=(1111) already exists. +CONTEXT: Remote SQL command: INSERT INTO "S 1"."T 1"("C 1", c2, c3, c4, c5, c6, c7, c8) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) +UPDATE ft1 SET c2 = c2 + 1 WHERE c1 = 1; +ERROR: SELECT FOR UPDATE/SHARE/NO KEY UPDATE/KEY SHARE cannot be used with column table "T 1" +CONTEXT: Remote SQL command: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8, ctid, tableoid FROM "S 1"."T 1" WHERE (("C 1" = 1)) FOR UPDATE +ALTER FOREIGN TABLE ft1 DROP CONSTRAINT ft1_c2negative; +ERROR: constraint "ft1_c2negative" of relation "ft1" does not exist +-- ====================================================================================================================================== +-- TEST-MODULE: WITH CHECK OPTION constraints +-- -------------------------------------- +-- openGauss not support WITH CHECK OPTION, so this test module +-- is unuseful. +-- ====================================================================================================================================== +CREATE FUNCTION row_before_insupd_trigfunc() RETURNS trigger AS $$BEGIN NEW.a := NEW.a + 10; RETURN NEW; END$$ LANGUAGE plpgsql; +CREATE TABLE base_tbl (a int, b int) with (orientation=column); +ALTER TABLE base_tbl SET (autovacuum_enabled = 'false'); +CREATE TRIGGER row_before_insupd_trigger BEFORE INSERT OR UPDATE ON base_tbl FOR EACH ROW EXECUTE PROCEDURE row_before_insupd_trigfunc(); +ERROR: Only support CREATE TRIGGER on regular row table. +CREATE FOREIGN TABLE foreign_tbl (a int, b int) + SERVER loopback OPTIONS (table_name 'base_tbl'); +-- CREATE VIEW rw_view AS SELECT * FROM foreign_tbl +-- WHERE a < b WITH CHECK OPTION; +-- \d+ rw_view +DROP FOREIGN TABLE foreign_tbl CASCADE; +DROP TRIGGER row_before_insupd_trigger ON base_tbl; +ERROR: trigger "row_before_insupd_trigger" for table "base_tbl" does not exist +DROP TABLE base_tbl; +-- ====================================================================================================================================== +-- TEST-MODULE: test serial columns (ie, sequence-based defaults) +-- -------------------------------------- +-- ====================================================================================================================================== +create table loc1 (f1 serial, f2 text) with (orientation=column); +NOTICE: CREATE TABLE will create implicit sequence "loc1_f1_seq" for serial column "loc1.f1" +alter table loc1 set (autovacuum_enabled = 'false'); +create foreign table rem1 (f1 serial, f2 text) + server loopback options(table_name 'loc1'); +NOTICE: CREATE FOREIGN TABLE will create implicit sequence "rem1_f1_seq" for serial column "rem1.f1" +select pg_catalog.setval('rem1_f1_seq', 10, false); + setval +-------- + 10 +(1 row) + +insert into loc1(f2) values('hi'); +insert into rem1(f2) values('hi remote'); +insert into loc1(f2) values('bye'); +insert into rem1(f2) values('bye remote'); +select * from loc1; + f1 | f2 +----+------------ + 1 | hi + 10 | hi remote + 2 | bye + 11 | bye remote +(4 rows) + +select * from rem1; + f1 | f2 +----+------------ + 1 | hi + 10 | hi remote + 2 | bye + 11 | bye remote +(4 rows) + +-- ====================================================================================================================================== +-- TEST-MODULE: test generated columns +-- -------------------------------------- +-- openGauss not support generated columns in column table. +-- ====================================================================================================================================== +create table gloc1 ( + a int, + b int generated always as (a * 2) stored) +with (orientation=column); +ERROR: column/timeseries store unsupport constraint "GENERATED COL" +-- ====================================================================================================================================== +-- TEST-MODULE: test local triggers +-- -------------------------------------- +-- openGauss not support create trigger on foreign table and column table. +-- ====================================================================================================================================== +create table tglog(id serial, context text) with (orientation=column); +NOTICE: CREATE TABLE will create implicit sequence "tglog_id_seq" for serial column "tglog.id" +create table previd(a int) with (orientation=column); +insert into previd values(0); +create or replace function showtrigger(id out int, context out text) returns setof record LANGUAGE plpgsql as +$$ +DECLARE + r RECORD; + prev int; +BEGIN + select max(a) from previd into prev; + update previd set a = (select max(id) from tglog); + + FOR r IN SELECT * FROM tglog where id > prev ORDER BY id + LOOP + id := r.id; + context := r.context; + return next; + END LOOP; +END;$$; + +-- Trigger functions "borrowed" from triggers regress test. +CREATE FUNCTION trigger_func() RETURNS trigger LANGUAGE plpgsql AS $$ +BEGIN + insert into tglog(context) values( + format('trigger_func(%s) called: action = %s, when = %s, level = %s', + TG_ARGV[0], TG_OP, TG_WHEN, TG_LEVEL)); + RETURN NULL; +END;$$; +-- error, openGauss not support create trigger on foreign table +CREATE TRIGGER trig_stmt_before BEFORE DELETE OR INSERT OR UPDATE ON rem1 + FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func(); +ERROR: "rem1" is not a table or view +CREATE TRIGGER trig_stmt_after AFTER DELETE OR INSERT OR UPDATE ON rem1 + FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func(); +ERROR: "rem1" is not a table or view +-- success +CREATE TRIGGER trig_stmt_before BEFORE DELETE OR INSERT OR UPDATE ON loc1 --nspt + FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func(); +ERROR: Only support CREATE TRIGGER on regular row table. +CREATE TRIGGER trig_stmt_after AFTER DELETE OR INSERT OR UPDATE ON loc1 --nspt + FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func(); +ERROR: Only support CREATE TRIGGER on regular row table. +-- ====================================================================================================================================== +-- TEST-MODULE: test inheritance features +-- TEST-MODULE: test tuple routing for foreign-table partitions +-- -------------------------------------- +-- unsupport feature of openGauss +-- ====================================================================================================================================== +-- ====================================================================================================================================== +-- TEST-MODULE: test COPY FROM +-- -------------------------------------- +-- ====================================================================================================================================== +create table loc2 (f1 int primary key, f2 text) with (orientation=column); +NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "loc2_pkey" for table "loc2" +alter table loc2 set (autovacuum_enabled = 'false'); +create foreign table rem2 (f1 int, f2 text) server loopback options(table_name 'loc2'); +-- Test basic functionality +copy rem2 from stdin; +select * from rem2; + f1 | f2 +----+----- + 1 | foo + 2 | bar +(2 rows) + +delete from rem2; +ERROR: SELECT FOR UPDATE/SHARE/NO KEY UPDATE/KEY SHARE cannot be used with column table "loc2" +CONTEXT: Remote SQL command: SELECT ctid, tableoid FROM public.loc2 FOR UPDATE +-- check constraint is enforced on the remote side, not locally +copy rem2 from stdin; +ERROR: duplicate key value violates unique constraint "loc2_pkey" +DETAIL: Key (f1)=(2) already exists. +CONTEXT: Remote SQL command: INSERT INTO public.loc2(f1, f2) VALUES ($1, $2) +COPY rem2, line 2: "2 bar" +select * from rem2; + f1 | f2 +----+----- + 1 | foo + 2 | bar +(2 rows) + +drop foreign table rem2; +drop table loc2; +-- ====================================================================================================================================== +-- TEST-MODULE: test for TRUNCATE +-- TEST-MODULE: test IMPORT FOREIGN SCHEMA +-- TEST-MODULE: test partitionwise joins +-- TEST-MODULE: test partitionwise aggregates +-- TEST-MODULE: access rights and superuser +-- TEST-MODULE: reestablish new connection +-- TEST-MODULE: batch insert +-- TEST-MODULE: test asynchronous execution +-- TEST-MODULE: test invalid server and foreign table options +-- -------------------------------------- +-- openGauss not support this feature, therefore, some cases that can run properly after modification are retained, +-- and some cases that cannot be supported are directly deleted. +-- If you want to restore the test case later, refer to README in the file header. +-- ====================================================================================================================================== +-- ====================================================================================================================================== +-- TEST-MODULE: clean up all the test data +-- -------------------------------------- +-- heihei! +-- ====================================================================================================================================== +\c regression +drop database postgresfdw_test_db_cstore; diff --git a/contrib/postgres_fdw/expected/postgres_fdw_partition.out b/contrib/postgres_fdw/expected/postgres_fdw_partition.out new file mode 100644 index 000000000..76b84ca84 --- /dev/null +++ b/contrib/postgres_fdw/expected/postgres_fdw_partition.out @@ -0,0 +1,9265 @@ +-- ====================================================================================================================================== +-- README: +-- For details, see "postgres_fdw.sql". +-- ====================================================================================================================================== +create database postgresfdw_test_db_partition; +\c postgresfdw_test_db_partition +set show_fdw_remote_plan = on; +-- ====================================================================================================================================== +-- TEST-MODULE: create FDW objects +-- -------------------------------------- +-- ====================================================================================================================================== +CREATE EXTENSION postgres_fdw; +CREATE SERVER testserver1 FOREIGN DATA WRAPPER postgres_fdw; +DO $d$ + BEGIN + EXECUTE $$CREATE SERVER loopback FOREIGN DATA WRAPPER postgres_fdw + OPTIONS (dbname '$$||current_database()||$$', + port '$$||current_setting('port')||$$' + )$$; + EXECUTE $$CREATE SERVER loopback2 FOREIGN DATA WRAPPER postgres_fdw + OPTIONS (dbname '$$||current_database()||$$', + port '$$||current_setting('port')||$$' + )$$; + EXECUTE $$CREATE SERVER loopback3 FOREIGN DATA WRAPPER postgres_fdw + OPTIONS (dbname '$$||current_database()||$$', + port '$$||current_setting('port')||$$' + )$$; + END; +$d$; +CREATE USER MAPPING FOR public SERVER testserver1 + OPTIONS (user 'value', password 'value'); +CREATE USER MAPPING FOR CURRENT_USER SERVER loopback; +CREATE USER MAPPING FOR CURRENT_USER SERVER loopback2; +CREATE USER MAPPING FOR public SERVER loopback3; +-- ====================================================================================================================================== +-- TEST-MODULE: create objects used through FDW loopback server +-- -------------------------------------- +-- ====================================================================================================================================== +CREATE TYPE user_enum AS ENUM ('foo', 'bar', 'buz'); +CREATE SCHEMA "S 1"; +CREATE TABLE "S 1"."T 1" ( + "C 1" int NOT NULL, + c2 int NOT NULL, + c3 text, + c4 timestamptz, + c5 timestamp, + c6 varchar(10), + c7 char(10), + c8 user_enum, + CONSTRAINT t1_pkey PRIMARY KEY ("C 1") +) PARTITION BY RANGE ("C 1") ( + partition ptb1 values less than(500), + partition ptb2 values less than(maxvalue) +); +NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "t1_pkey" for table "T 1" +CREATE TABLE "S 1"."T 2" ( + c1 int NOT NULL, + c2 text, + CONSTRAINT t2_pkey PRIMARY KEY (c1) +) with (orientation=column) +PARTITION BY RANGE (c1) ( + partition ptb1 values less than(50), + partition ptb2 values less than(maxvalue) +); +NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "t2_pkey" for table "T 2" +CREATE TABLE "S 1"."T 3" ( + c1 int NOT NULL, + c2 int NOT NULL, + c3 text, + CONSTRAINT t3_pkey PRIMARY KEY (c1) +) PARTITION BY hash (c1) ( + partition ptb1, + partition ptb2 +); +NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "t3_pkey" for table "T 3" +CREATE TABLE "S 1"."T 4" ( + c1 int NOT NULL, + c2 int NOT NULL, + c3 text, + CONSTRAINT t4_pkey PRIMARY KEY (c1) +) PARTITION BY RANGE (c1) SUBPARTITION BY HASH (c2) ( + partition ptb1 values less than(50) + ( + subpartition ptb11, + subpartition ptb12 + ), + partition ptb2 values less than(maxvalue) + ( + subpartition ptb21, + subpartition ptb22 + ) +); +NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "t4_pkey" for table "T 4" +-- Disable autovacuum for these tables to avoid unexpected effects of that +ALTER TABLE "S 1"."T 1" SET (autovacuum_enabled = 'false'); +ALTER TABLE "S 1"."T 2" SET (autovacuum_enabled = 'false'); +ALTER TABLE "S 1"."T 3" SET (autovacuum_enabled = 'false'); +ALTER TABLE "S 1"."T 4" SET (autovacuum_enabled = 'false'); +INSERT INTO "S 1"."T 1" + SELECT id, + id % 10, + to_char(id, 'FM00000'), + '1970-01-01'::timestamptz + ((id % 100) || ' days')::interval, + '1970-01-01'::timestamp + ((id % 100) || ' days')::interval, + id % 10, + id % 10, + 'foo'::user_enum + FROM generate_series(1, 1000) id; +INSERT INTO "S 1"."T 2" + SELECT id, + 'AAA' || to_char(id, 'FM000') + FROM generate_series(1, 100) id; +INSERT INTO "S 1"."T 3" + SELECT id, + id + 1, + 'AAA' || to_char(id, 'FM000') + FROM generate_series(1, 100) id; +DELETE FROM "S 1"."T 3" WHERE c1 % 2 != 0; -- delete for outer join tests +INSERT INTO "S 1"."T 4" + SELECT id, + id + 1, + 'AAA' || to_char(id, 'FM000') + FROM generate_series(1, 100) id; +DELETE FROM "S 1"."T 4" WHERE c1 % 3 != 0; -- delete for outer join tests +ANALYZE "S 1"."T 1"; +ANALYZE "S 1"."T 2"; +ANALYZE "S 1"."T 3"; +ANALYZE "S 1"."T 4"; +-- ====================================================================================================================================== +-- TEST-MODULE: create foreign tables +-- -------------------------------------- +-- +-- ====================================================================================================================================== +CREATE FOREIGN TABLE ft1 ( + c0 int, + c1 int NOT NULL, + c2 int NOT NULL, + c3 text, + c4 timestamptz, + c5 timestamp, + c6 varchar(10), + c7 char(10) default 'ft1', + c8 user_enum +) SERVER loopback; +ALTER FOREIGN TABLE ft1 DROP COLUMN c0; +CREATE FOREIGN TABLE ft2 ( + c1 int NOT NULL, + c2 int NOT NULL, + cx int, + c3 text, + c4 timestamptz, + c5 timestamp, + c6 varchar(10), + c7 char(10) default 'ft2', + c8 user_enum +) SERVER loopback; +ALTER FOREIGN TABLE ft2 DROP COLUMN cx; +CREATE FOREIGN TABLE ft4 ( + c1 int NOT NULL, + c2 int NOT NULL, + c3 text +) SERVER loopback OPTIONS (schema_name 'S 1', table_name 'T 3'); +CREATE FOREIGN TABLE ft5 ( + c1 int NOT NULL, + c2 int NOT NULL, + c3 text +) SERVER loopback OPTIONS (schema_name 'S 1', table_name 'T 4'); +CREATE FOREIGN TABLE ft6 ( + c1 int NOT NULL, + c2 int NOT NULL, + c3 text +) SERVER loopback2 OPTIONS (schema_name 'S 1', table_name 'T 4'); +CREATE FOREIGN TABLE ft7 ( + c1 int NOT NULL, + c2 int NOT NULL, + c3 text +) SERVER loopback3 OPTIONS (schema_name 'S 1', table_name 'T 4'); +-- ====================================================================================================================================== +-- TEST-MODULE: tests for validator and prepare params +-- -------------------------------------- +-- ====================================================================================================================================== +-- requiressl and some other parameters are omitted because +-- valid values for them depend on configure options +ALTER SERVER testserver1 OPTIONS ( + use_remote_estimate 'false', + updatable 'true', + fdw_startup_cost '123.456', + fdw_tuple_cost '0.123', + service 'value', + connect_timeout 'value', + dbname 'value', + host 'value', + hostaddr 'value', + port 'value', + application_name 'value', + keepalives 'value', + keepalives_idle 'value', + keepalives_interval 'value', + sslcompression 'value', + sslmode 'value', + sslcert 'value', + sslkey 'value', + sslrootcert 'value', + sslcrl 'value', + krbsrvname 'value' +); +ALTER FOREIGN TABLE ft1 OPTIONS (schema_name 'S 1', table_name 'T 1'); +ALTER FOREIGN TABLE ft2 OPTIONS (schema_name 'S 1', table_name 'T 1'); +ALTER FOREIGN TABLE ft1 ALTER COLUMN c1 OPTIONS (column_name 'C 1'); +ALTER FOREIGN TABLE ft2 ALTER COLUMN c1 OPTIONS (column_name 'C 1'); +\det+ + List of foreign tables + Schema | Table | Server | FDW Options | Description +--------+-------+-----------+---------------------------------------+------------- + public | ft1 | loopback | (schema_name 'S 1', table_name 'T 1') | + public | ft2 | loopback | (schema_name 'S 1', table_name 'T 1') | + public | ft4 | loopback | (schema_name 'S 1', table_name 'T 3') | + public | ft5 | loopback | (schema_name 'S 1', table_name 'T 4') | + public | ft6 | loopback2 | (schema_name 'S 1', table_name 'T 4') | + public | ft7 | loopback3 | (schema_name 'S 1', table_name 'T 4') | +(6 rows) + +-- Now we should be able to run ANALYZE. +-- To exercise multiple code paths, we use local stats on ft1 +-- and remote-estimate mode on ft2. +ANALYZE ft1; +ALTER FOREIGN TABLE ft2 OPTIONS (use_remote_estimate 'true'); +-- now open the guc. +SHOW sql_beta_feature; + sql_beta_feature +------------------ + a_style_coerce +(1 row) + +SET sql_beta_feature TO 'partition_fdw_on'; +EXPLAIN (COSTS OFF) SELECT * FROM ft6; --err + QUERY PLAN +------------------------------------------------------------------------------ + Foreign Scan on ft6 + Node ID: 1 + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE OFF, COSTS OFF) SELECT c1, c2, c3 FROM "S 1"."T 4" + Partition Iterator + Iterations: 2, Sub Iterations: 4 + -> Partitioned Seq Scan on "T 4" + Selected Partitions: 1..2 + Selected Subpartitions: ALL + +(11 rows) + +SELECT * FROM ft6; + c1 | c2 | c3 +----+-----+-------- + 12 | 13 | AAA012 + 18 | 19 | AAA018 + 27 | 28 | AAA027 + 39 | 40 | AAA039 + 45 | 46 | AAA045 + 48 | 49 | AAA048 + 3 | 4 | AAA003 + 6 | 7 | AAA006 + 9 | 10 | AAA009 + 15 | 16 | AAA015 + 21 | 22 | AAA021 + 24 | 25 | AAA024 + 30 | 31 | AAA030 + 33 | 34 | AAA033 + 36 | 37 | AAA036 + 42 | 43 | AAA042 + 51 | 52 | AAA051 + 63 | 64 | AAA063 + 66 | 67 | AAA066 + 69 | 70 | AAA069 + 75 | 76 | AAA075 + 84 | 85 | AAA084 + 54 | 55 | AAA054 + 57 | 58 | AAA057 + 60 | 61 | AAA060 + 72 | 73 | AAA072 + 78 | 79 | AAA078 + 81 | 82 | AAA081 + 87 | 88 | AAA087 + 90 | 91 | AAA090 + 93 | 94 | AAA093 + 96 | 97 | AAA096 + 99 | 100 | AAA099 +(33 rows) + +SHOW sql_beta_feature; + sql_beta_feature +------------------ + partition_fdw_on +(1 row) + +SET sql_beta_feature TO 'partition_fdw_on'; +EXPLAIN (COSTS OFF) SELECT * FROM ft6; --suc + QUERY PLAN +------------------------------------------------------------------------------ + Foreign Scan on ft6 + Node ID: 1 + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE OFF, COSTS OFF) SELECT c1, c2, c3 FROM "S 1"."T 4" + Partition Iterator + Iterations: 2, Sub Iterations: 4 + -> Partitioned Seq Scan on "T 4" + Selected Partitions: 1..2 + Selected Subpartitions: ALL + +(11 rows) + +SELECT * FROM ft6; + c1 | c2 | c3 +----+-----+-------- + 12 | 13 | AAA012 + 18 | 19 | AAA018 + 27 | 28 | AAA027 + 39 | 40 | AAA039 + 45 | 46 | AAA045 + 48 | 49 | AAA048 + 3 | 4 | AAA003 + 6 | 7 | AAA006 + 9 | 10 | AAA009 + 15 | 16 | AAA015 + 21 | 22 | AAA021 + 24 | 25 | AAA024 + 30 | 31 | AAA030 + 33 | 34 | AAA033 + 36 | 37 | AAA036 + 42 | 43 | AAA042 + 51 | 52 | AAA051 + 63 | 64 | AAA063 + 66 | 67 | AAA066 + 69 | 70 | AAA069 + 75 | 76 | AAA075 + 84 | 85 | AAA084 + 54 | 55 | AAA054 + 57 | 58 | AAA057 + 60 | 61 | AAA060 + 72 | 73 | AAA072 + 78 | 79 | AAA078 + 81 | 82 | AAA081 + 87 | 88 | AAA087 + 90 | 91 | AAA090 + 93 | 94 | AAA093 + 96 | 97 | AAA096 + 99 | 100 | AAA099 +(33 rows) + +-- ====================================================================================================================================== +-- TEST-MODULE: simple queries +-- -------------------------------------- +-- +-- ====================================================================================================================================== +-- single table without alias +EXPLAIN (COSTS OFF) SELECT * FROM ft1 ORDER BY c3, c1 OFFSET 100 LIMIT 10; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + Foreign Scan on ft1 + Node ID: 1 + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE OFF, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" ORDER BY c3 ASC NULLS LAST, "C 1" ASC NULLS LAST LIMIT 10::bigint OFFSET 100::bigint + Limit + -> Sort + Sort Key: c3, "C 1" + -> Partition Iterator + Iterations: 2 + -> Partitioned Seq Scan on "T 1" + Selected Partitions: 1..2 + +(13 rows) + +SELECT * FROM ft1 ORDER BY c3, c1 OFFSET 100 LIMIT 10; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +-----+----+-------+------------------------------+--------------------------+----+------------+----- + 101 | 1 | 00101 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1 | 1 | foo + 102 | 2 | 00102 | Sat Jan 03 00:00:00 1970 PST | Sat Jan 03 00:00:00 1970 | 2 | 2 | foo + 103 | 3 | 00103 | Sun Jan 04 00:00:00 1970 PST | Sun Jan 04 00:00:00 1970 | 3 | 3 | foo + 104 | 4 | 00104 | Mon Jan 05 00:00:00 1970 PST | Mon Jan 05 00:00:00 1970 | 4 | 4 | foo + 105 | 5 | 00105 | Tue Jan 06 00:00:00 1970 PST | Tue Jan 06 00:00:00 1970 | 5 | 5 | foo + 106 | 6 | 00106 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo + 107 | 7 | 00107 | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7 | 7 | foo + 108 | 8 | 00108 | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8 | 8 | foo + 109 | 9 | 00109 | Sat Jan 10 00:00:00 1970 PST | Sat Jan 10 00:00:00 1970 | 9 | 9 | foo + 110 | 0 | 00110 | Sun Jan 11 00:00:00 1970 PST | Sun Jan 11 00:00:00 1970 | 0 | 0 | foo +(10 rows) + +--nspt single table with alias - also test that tableoid sort is not pushed to remote side +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 ORDER BY t1.c3, t1.c1, t1.tableoid OFFSET 100 LIMIT 10; +ERROR: column t1.tableoid does not exist +LINE 1: ... OFF) SELECT * FROM ft1 t1 ORDER BY t1.c3, t1.c1, t1.tableoi... + ^ +SELECT * FROM ft1 t1 ORDER BY t1.c3, t1.c1, t1.tableoid OFFSET 100 LIMIT 10; +ERROR: column t1.tableoid does not exist +LINE 1: SELECT * FROM ft1 t1 ORDER BY t1.c3, t1.c1, t1.tableoid OFFS... + ^ +-- whole-row reference +EXPLAIN (VERBOSE, COSTS OFF) SELECT t1 FROM ft1 t1 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: t1.*, c3, c1 + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" ORDER BY c3 ASC NULLS LAST, "C 1" ASC NULLS LAST LIMIT 10::bigint OFFSET 100::bigint + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" ORDER BY c3 ASC NULLS LAST, "C 1" ASC NULLS LAST LIMIT 10::bigint OFFSET 100::bigint + Limit + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + -> Sort + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Sort Key: "T 1".c3, "T 1"."C 1" + -> Partition Iterator + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Selected Partitions: 1..2 + +(19 rows) + +SELECT t1 FROM ft1 t1 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; + t1 +-------------------------------------------------------------------------------------------- + (101,1,00101,"Fri Jan 02 00:00:00 1970 PST","Fri Jan 02 00:00:00 1970",1,"1 ",foo) + (102,2,00102,"Sat Jan 03 00:00:00 1970 PST","Sat Jan 03 00:00:00 1970",2,"2 ",foo) + (103,3,00103,"Sun Jan 04 00:00:00 1970 PST","Sun Jan 04 00:00:00 1970",3,"3 ",foo) + (104,4,00104,"Mon Jan 05 00:00:00 1970 PST","Mon Jan 05 00:00:00 1970",4,"4 ",foo) + (105,5,00105,"Tue Jan 06 00:00:00 1970 PST","Tue Jan 06 00:00:00 1970",5,"5 ",foo) + (106,6,00106,"Wed Jan 07 00:00:00 1970 PST","Wed Jan 07 00:00:00 1970",6,"6 ",foo) + (107,7,00107,"Thu Jan 08 00:00:00 1970 PST","Thu Jan 08 00:00:00 1970",7,"7 ",foo) + (108,8,00108,"Fri Jan 09 00:00:00 1970 PST","Fri Jan 09 00:00:00 1970",8,"8 ",foo) + (109,9,00109,"Sat Jan 10 00:00:00 1970 PST","Sat Jan 10 00:00:00 1970",9,"9 ",foo) + (110,0,00110,"Sun Jan 11 00:00:00 1970 PST","Sun Jan 11 00:00:00 1970",0,"0 ",foo) +(10 rows) + +-- empty result +SELECT * FROM ft1 WHERE false; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +----+----+----+----+----+----+----+---- +(0 rows) + +-- with WHERE clause +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE t1.c1 = 101 AND t1.c6 = '1' AND t1.c7 >= '1'; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((c7 >= '1'::bpchar)) AND (("C 1" = 101)) AND ((c6 = '1'::text)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((c7 >= '1'::bpchar)) AND (("C 1" = 101)) AND ((c6 = '1'::text)) + Partitioned Index Scan using t1_pkey on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Index Cond: ("T 1"."C 1" = 101) + Filter: (("T 1".c7 >= '1'::bpchar) AND (("T 1".c6)::text = '1'::text)) + Selected Partitions: 1 + +(13 rows) + +SELECT * FROM ft1 t1 WHERE t1.c1 = 101 AND t1.c6 = '1' AND t1.c7 >= '1'; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +-----+----+-------+------------------------------+--------------------------+----+------------+----- + 101 | 1 | 00101 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1 | 1 | foo +(1 row) + +-- with FOR UPDATE/SHARE +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 = 101 FOR UPDATE; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8, t1.* + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 101)) FOR UPDATE + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 101)) FOR UPDATE + LockRows + Output: "C 1", c2, c3, c4, c5, c6, c7, c8, ctid, tableoid + -> Partitioned Index Scan using t1_pkey on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8, ctid, tableoid + Index Cond: ("T 1"."C 1" = 101) + Selected Partitions: 1 + +(14 rows) + +SELECT * FROM ft1 t1 WHERE c1 = 101 FOR UPDATE; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +-----+----+-------+------------------------------+--------------------------+----+------------+----- + 101 | 1 | 00101 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1 | 1 | foo +(1 row) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 = 102 FOR SHARE; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------ + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8, t1.* + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 102)) FOR SHARE + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 102)) FOR SHARE + LockRows + Output: "C 1", c2, c3, c4, c5, c6, c7, c8, ctid, tableoid + -> Partitioned Index Scan using t1_pkey on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8, ctid, tableoid + Index Cond: ("T 1"."C 1" = 102) + Selected Partitions: 1 + +(14 rows) + +SELECT * FROM ft1 t1 WHERE c1 = 102 FOR SHARE; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +-----+----+-------+------------------------------+--------------------------+----+------------+----- + 102 | 2 | 00102 | Sat Jan 03 00:00:00 1970 PST | Sat Jan 03 00:00:00 1970 | 2 | 2 | foo +(1 row) + +-- aggregate +SELECT COUNT(*) FROM ft1 t1; + count +------- + 1000 +(1 row) + +-- subquery +SELECT * FROM ft1 t1 WHERE t1.c3 IN (SELECT c3 FROM ft2 t2 WHERE c1 <= 10) ORDER BY c1; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +----+----+-------+------------------------------+--------------------------+----+------------+----- + 1 | 1 | 00001 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1 | 1 | foo + 2 | 2 | 00002 | Sat Jan 03 00:00:00 1970 PST | Sat Jan 03 00:00:00 1970 | 2 | 2 | foo + 3 | 3 | 00003 | Sun Jan 04 00:00:00 1970 PST | Sun Jan 04 00:00:00 1970 | 3 | 3 | foo + 4 | 4 | 00004 | Mon Jan 05 00:00:00 1970 PST | Mon Jan 05 00:00:00 1970 | 4 | 4 | foo + 5 | 5 | 00005 | Tue Jan 06 00:00:00 1970 PST | Tue Jan 06 00:00:00 1970 | 5 | 5 | foo + 6 | 6 | 00006 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo + 7 | 7 | 00007 | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7 | 7 | foo + 8 | 8 | 00008 | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8 | 8 | foo + 9 | 9 | 00009 | Sat Jan 10 00:00:00 1970 PST | Sat Jan 10 00:00:00 1970 | 9 | 9 | foo + 10 | 0 | 00010 | Sun Jan 11 00:00:00 1970 PST | Sun Jan 11 00:00:00 1970 | 0 | 0 | foo +(10 rows) + +-- subquery+MAX +SELECT * FROM ft1 t1 WHERE t1.c3 = (SELECT MAX(c3) FROM ft2 t2) ORDER BY c1; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +------+----+-------+------------------------------+--------------------------+----+------------+----- + 1000 | 0 | 01000 | Thu Jan 01 00:00:00 1970 PST | Thu Jan 01 00:00:00 1970 | 0 | 0 | foo +(1 row) + +-- used in CTE +WITH t1 AS (SELECT * FROM ft1 WHERE c1 <= 10) SELECT t2.c1, t2.c2, t2.c3, t2.c4 FROM t1, ft2 t2 WHERE t1.c1 = t2.c1 ORDER BY t1.c1; + c1 | c2 | c3 | c4 +----+----+-------+------------------------------ + 1 | 1 | 00001 | Fri Jan 02 00:00:00 1970 PST + 2 | 2 | 00002 | Sat Jan 03 00:00:00 1970 PST + 3 | 3 | 00003 | Sun Jan 04 00:00:00 1970 PST + 4 | 4 | 00004 | Mon Jan 05 00:00:00 1970 PST + 5 | 5 | 00005 | Tue Jan 06 00:00:00 1970 PST + 6 | 6 | 00006 | Wed Jan 07 00:00:00 1970 PST + 7 | 7 | 00007 | Thu Jan 08 00:00:00 1970 PST + 8 | 8 | 00008 | Fri Jan 09 00:00:00 1970 PST + 9 | 9 | 00009 | Sat Jan 10 00:00:00 1970 PST + 10 | 0 | 00010 | Sun Jan 11 00:00:00 1970 PST +(10 rows) + +-- fixed values +SELECT 'fixed', NULL FROM ft1 t1 WHERE c1 = 1; + ?column? | ?column? +----------+---------- + fixed | +(1 row) + +-- Test forcing the remote server to produce sorted data for a merge join. +SET enable_hashjoin TO false; +SET enable_nestloop TO false; +-- inner join; expressions in the clauses appear in the equivalence class list +EXPLAIN (VERBOSE, COSTS OFF) + SELECT t1.c1, t2."C 1" FROM ft2 t1 JOIN "S 1"."T 1" t2 ON (t1.c1 = t2."C 1") OFFSET 100 LIMIT 10; + QUERY PLAN +------------------------------------------------------------------------------------------------------ + Limit + Output: t1.c1, t2."C 1" + -> Merge Join + Output: t1.c1, t2."C 1" + Merge Cond: (t1.c1 = t2."C 1") + -> Foreign Scan on public.ft2 t1 + Output: t1.c1 + Node ID: 1 + Remote SQL: SELECT "C 1" FROM "S 1"."T 1" ORDER BY "C 1" ASC NULLS LAST + -> Materialize + Output: t2."C 1" + -> Partition Iterator + Output: t2."C 1" + Iterations: 2 + -> Partitioned Index Only Scan using t1_pkey on "S 1"."T 1" t2 + Output: t2."C 1" + Selected Partitions: 1..2 + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1" FROM "S 1"."T 1" ORDER BY "C 1" ASC NULLS LAST + Partition Iterator + Output: "C 1" + Iterations: 2 + -> Partitioned Index Only Scan using t1_pkey on "S 1"."T 1" + Output: "C 1" + Selected Partitions: 1..2 + +(27 rows) + +SELECT t1.c1, t2."C 1" FROM ft2 t1 JOIN "S 1"."T 1" t2 ON (t1.c1 = t2."C 1") OFFSET 100 LIMIT 10; + c1 | C 1 +-----+----- + 101 | 101 + 102 | 102 + 103 | 103 + 104 | 104 + 105 | 105 + 106 | 106 + 107 | 107 + 108 | 108 + 109 | 109 + 110 | 110 +(10 rows) + +-- outer join; expressions in the clauses do not appear in equivalence class +-- list but no output change as compared to the previous query +EXPLAIN (VERBOSE, COSTS OFF) + SELECT t1.c1, t2."C 1" FROM ft2 t1 LEFT JOIN "S 1"."T 1" t2 ON (t1.c1 = t2."C 1") OFFSET 100 LIMIT 10; + QUERY PLAN +------------------------------------------------------------------------------------------------------ + Limit + Output: t1.c1, t2."C 1" + -> Merge Left Join + Output: t1.c1, t2."C 1" + Merge Cond: (t1.c1 = t2."C 1") + -> Foreign Scan on public.ft2 t1 + Output: t1.c1 + Node ID: 1 + Remote SQL: SELECT "C 1" FROM "S 1"."T 1" ORDER BY "C 1" ASC NULLS LAST + -> Materialize + Output: t2."C 1" + -> Partition Iterator + Output: t2."C 1" + Iterations: 2 + -> Partitioned Index Only Scan using t1_pkey on "S 1"."T 1" t2 + Output: t2."C 1" + Selected Partitions: 1..2 + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1" FROM "S 1"."T 1" ORDER BY "C 1" ASC NULLS LAST + Partition Iterator + Output: "C 1" + Iterations: 2 + -> Partitioned Index Only Scan using t1_pkey on "S 1"."T 1" + Output: "C 1" + Selected Partitions: 1..2 + +(27 rows) + +SELECT t1.c1, t2."C 1" FROM ft2 t1 LEFT JOIN "S 1"."T 1" t2 ON (t1.c1 = t2."C 1") OFFSET 100 LIMIT 10; + c1 | C 1 +-----+----- + 101 | 101 + 102 | 102 + 103 | 103 + 104 | 104 + 105 | 105 + 106 | 106 + 107 | 107 + 108 | 108 + 109 | 109 + 110 | 110 +(10 rows) + +-- A join between local table and foreign join. ORDER BY clause is added to the +-- foreign join so that the local table can be joined using merge join strategy. +EXPLAIN (VERBOSE, COSTS OFF) + SELECT t1."C 1" FROM "S 1"."T 1" t1 left join ft1 t2 join ft2 t3 on (t2.c1 = t3.c1) on (t3.c1 = t1."C 1") OFFSET 100 LIMIT 10; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + Limit + Output: t1."C 1" + -> Merge Left Join + Output: t1."C 1" + Merge Cond: (t1."C 1" = t3.c1) + -> Partition Iterator + Output: t1."C 1" + Iterations: 2 + -> Partitioned Index Only Scan using t1_pkey on "S 1"."T 1" t1 + Output: t1."C 1" + Selected Partitions: 1..2 + -> Materialize + Output: t3.c1 + -> Foreign Scan + Output: t3.c1 + Node ID: 1 + Relations: (public.ft1 t2) INNER JOIN (public.ft2 t3) + Remote SQL: SELECT r3."C 1" FROM ("S 1"."T 1" r2 INNER JOIN "S 1"."T 1" r3 ON (((r2."C 1" = r3."C 1")))) ORDER BY r2."C 1" ASC NULLS LAST + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT r3."C 1" FROM ("S 1"."T 1" r2 INNER JOIN "S 1"."T 1" r3 ON (((r2."C 1" = r3."C 1")))) ORDER BY r2."C 1" ASC NULLS LAST + Sort + Output: r3."C 1", r2."C 1" + Sort Key: r3."C 1" + -> Hash Join + Output: r3."C 1", r2."C 1" + Hash Cond: (r2."C 1" = r3."C 1") + -> Partition Iterator + Output: r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" r2 + Output: r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8 + Selected Partitions: 1..2 + -> Hash + Output: r3."C 1" + -> Partition Iterator + Output: r3."C 1" + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" r3 + Output: r3."C 1" + Selected Partitions: 1..2 + +(42 rows) + +SELECT t1."C 1" FROM "S 1"."T 1" t1 left join ft1 t2 join ft2 t3 on (t2.c1 = t3.c1) on (t3.c1 = t1."C 1") OFFSET 100 LIMIT 10; + C 1 +----- + 101 + 102 + 103 + 104 + 105 + 106 + 107 + 108 + 109 + 110 +(10 rows) + +-- Test similar to above, except that the full join prevents any equivalence +-- classes from being merged. This produces single relation equivalence classes +-- included in join restrictions. +EXPLAIN (VERBOSE, COSTS OFF) + SELECT t1."C 1", t2.c1, t3.c1 FROM "S 1"."T 1" t1 left join ft1 t2 full join ft2 t3 on (t2.c1 = t3.c1) on (t3.c1 = t1."C 1") OFFSET 100 LIMIT 10; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Limit + Output: t1."C 1", t2.c1, t3.c1 + -> Merge Left Join + Output: t1."C 1", t2.c1, t3.c1 + Merge Cond: (t1."C 1" = t3.c1) + -> Partition Iterator + Output: t1."C 1" + Iterations: 2 + -> Partitioned Index Only Scan using t1_pkey on "S 1"."T 1" t1 + Output: t1."C 1" + Selected Partitions: 1..2 + -> Materialize + Output: t3.c1, t2.c1 + -> Foreign Scan + Output: t3.c1, t2.c1 + Node ID: 1 + Relations: (public.ft2 t3) LEFT JOIN (public.ft1 t2) + Remote SQL: SELECT r3."C 1", r2."C 1" FROM ("S 1"."T 1" r3 LEFT JOIN "S 1"."T 1" r2 ON (((r2."C 1" = r3."C 1")))) ORDER BY r3."C 1" ASC NULLS LAST + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT r3."C 1", r2."C 1" FROM ("S 1"."T 1" r3 LEFT JOIN "S 1"."T 1" r2 ON (((r2."C 1" = r3."C 1")))) ORDER BY r3."C 1" ASC NULLS LAST + Sort + Output: r3."C 1", r2."C 1" + Sort Key: r3."C 1" + -> Hash Left Join + Output: r3."C 1", r2."C 1" + Hash Cond: (r3."C 1" = r2."C 1") + -> Partition Iterator + Output: r3."C 1", r3.c2, r3.c3, r3.c4, r3.c5, r3.c6, r3.c7, r3.c8 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" r3 + Output: r3."C 1", r3.c2, r3.c3, r3.c4, r3.c5, r3.c6, r3.c7, r3.c8 + Selected Partitions: 1..2 + -> Hash + Output: r2."C 1" + -> Partition Iterator + Output: r2."C 1" + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" r2 + Output: r2."C 1" + Selected Partitions: 1..2 + +(42 rows) + +SELECT t1."C 1", t2.c1, t3.c1 FROM "S 1"."T 1" t1 left join ft1 t2 full join ft2 t3 on (t2.c1 = t3.c1) on (t3.c1 = t1."C 1") OFFSET 100 LIMIT 10; + C 1 | c1 | c1 +-----+-----+----- + 101 | 101 | 101 + 102 | 102 | 102 + 103 | 103 | 103 + 104 | 104 | 104 + 105 | 105 | 105 + 106 | 106 | 106 + 107 | 107 | 107 + 108 | 108 | 108 + 109 | 109 | 109 + 110 | 110 | 110 +(10 rows) + +-- Test similar to above with all full outer joins +EXPLAIN (VERBOSE, COSTS OFF) + SELECT t1."C 1", t2.c1, t3.c1 FROM "S 1"."T 1" t1 full join ft1 t2 full join ft2 t3 on (t2.c1 = t3.c1) on (t3.c1 = t1."C 1") OFFSET 100 LIMIT 10; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Limit + Output: t1."C 1", t2.c1, t3.c1 + -> Merge Full Join + Output: t1."C 1", t2.c1, t3.c1 + Merge Cond: (t1."C 1" = t3.c1) + -> Partition Iterator + Output: t1."C 1" + Iterations: 2 + -> Partitioned Index Only Scan using t1_pkey on "S 1"."T 1" t1 + Output: t1."C 1" + Selected Partitions: 1..2 + -> Materialize + Output: t2.c1, t3.c1 + -> Foreign Scan + Output: t2.c1, t3.c1 + Node ID: 1 + Relations: (public.ft1 t2) FULL JOIN (public.ft2 t3) + Remote SQL: SELECT r2."C 1", r3."C 1" FROM ("S 1"."T 1" r2 FULL JOIN "S 1"."T 1" r3 ON (((r2."C 1" = r3."C 1")))) ORDER BY r3."C 1" ASC NULLS LAST + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT r2."C 1", r3."C 1" FROM ("S 1"."T 1" r2 FULL JOIN "S 1"."T 1" r3 ON (((r2."C 1" = r3."C 1")))) ORDER BY r3."C 1" ASC NULLS LAST + Sort + Output: r2."C 1", r3."C 1" + Sort Key: r3."C 1" + -> Hash Full Join + Output: r2."C 1", r3."C 1" + Hash Cond: (r2."C 1" = r3."C 1") + -> Partition Iterator + Output: r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" r2 + Output: r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8 + Selected Partitions: 1..2 + -> Hash + Output: r3."C 1" + -> Partition Iterator + Output: r3."C 1" + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" r3 + Output: r3."C 1" + Selected Partitions: 1..2 + +(42 rows) + +SELECT t1."C 1", t2.c1, t3.c1 FROM "S 1"."T 1" t1 full join ft1 t2 full join ft2 t3 on (t2.c1 = t3.c1) on (t3.c1 = t1."C 1") OFFSET 100 LIMIT 10; + C 1 | c1 | c1 +-----+-----+----- + 101 | 101 | 101 + 102 | 102 | 102 + 103 | 103 | 103 + 104 | 104 | 104 + 105 | 105 | 105 + 106 | 106 | 106 + 107 | 107 | 107 + 108 | 108 | 108 + 109 | 109 | 109 + 110 | 110 | 110 +(10 rows) + +RESET enable_hashjoin; +RESET enable_nestloop; +-- Test executing assertion in estimate_path_cost_size() that makes sure that +-- retrieved_rows for foreign rel re-used to cost pre-sorted foreign paths is +-- a sensible value even when the rel has tuples=0 +CREATE TABLE loct_empty (c1 int NOT NULL, c2 text); +CREATE FOREIGN TABLE ft_empty (c1 int NOT NULL, c2 text) + SERVER loopback OPTIONS (table_name 'loct_empty'); +INSERT INTO loct_empty + SELECT id, 'AAA' || to_char(id, 'FM000') FROM generate_series(1, 100) id; +DELETE FROM loct_empty; +ANALYZE ft_empty; +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft_empty ORDER BY c1; + QUERY PLAN +---------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft_empty + Output: c1, c2 + Node ID: 1 + Remote SQL: SELECT c1, c2 FROM public.loct_empty ORDER BY c1 ASC NULLS LAST + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c1, c2 FROM public.loct_empty ORDER BY c1 ASC NULLS LAST + Sort + Output: c1, c2 + Sort Key: loct_empty.c1 + -> Seq Scan on public.loct_empty + Output: c1, c2 + +(13 rows) + +-- ====================================================================================================================================== +-- TEST-MODULE: WHERE with remotely-executable conditions +-- -------------------------------------- +-- ====================================================================================================================================== +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE t1.c1 = 1; -- Var, OpExpr(b), Const + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------ + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 1)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 1)) + Partitioned Index Scan using t1_pkey on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Index Cond: ("T 1"."C 1" = 1) + Selected Partitions: 1 + +(12 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE t1.c1 = 100 AND t1.c2 = 0; -- BoolExpr + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 100)) AND ((c2 = 0)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 100)) AND ((c2 = 0)) + Partitioned Index Scan using t1_pkey on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Index Cond: ("T 1"."C 1" = 100) + Filter: ("T 1".c2 = 0) + Selected Partitions: 1 + +(13 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 IS NULL; -- NullTest + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" IS NULL)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" IS NULL)) + Result + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + One-Time Filter: false + +(11 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 IS NOT NULL; -- NullTest + QUERY PLAN +---------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" + Partition Iterator + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Selected Partitions: 1..2 + +(14 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE round(abs(c1), 0) = 1; -- FuncExpr + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------ + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((round(abs("C 1"), 0) = 1::numeric)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((round(abs("C 1"), 0) = 1::numeric)) + Partition Iterator + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Filter: (round((abs("T 1"."C 1"))::numeric, 0) = 1::numeric) + Selected Partitions: 1..2 + +(15 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 = -c1; -- OpExpr(l) + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = (- "C 1"))) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = (- "C 1"))) + Partition Iterator + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Iterations: PART + -> Partitioned Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Filter: ("T 1"."C 1" = (- "T 1"."C 1")) + Selected Partitions: PART + +(15 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE (c1 IS NOT NULL) IS DISTINCT FROM (c1 IS NOT NULL); -- DistinctExpr + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((("C 1" IS NOT NULL) IS DISTINCT FROM ("C 1" IS NOT NULL))) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((("C 1" IS NOT NULL) IS DISTINCT FROM ("C 1" IS NOT NULL))) + Partition Iterator + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Filter: (("T 1"."C 1" IS NOT NULL) IS DISTINCT FROM ("T 1"."C 1" IS NOT NULL)) + Selected Partitions: 1..2 + +(15 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 = ANY(ARRAY[c2, 1, c1 + 0]); -- ScalarArrayOpExpr + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------ + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = ANY (ARRAY[c2, 1, ("C 1" + 0)]))) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = ANY (ARRAY[c2, 1, ("C 1" + 0)]))) + Partition Iterator + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Iterations: PART + -> Partitioned Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Filter: ("T 1"."C 1" = ANY (ARRAY["T 1".c2, 1, ("T 1"."C 1" + 0)])) + Selected Partitions: PART + +(15 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 = (ARRAY[c1,c2,3])[1]; -- SubscriptingRef + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = ((ARRAY["C 1", c2, 3])[1]))) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = ((ARRAY["C 1", c2, 3])[1]))) + Partition Iterator + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Filter: ("T 1"."C 1" = (ARRAY["T 1"."C 1", "T 1".c2, 3])[1]) + Selected Partitions: 1..2 + +(15 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c6 = E'foo''s\\bar'; -- check special chars + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((c6 = E'foo''s\\bar'::text)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((c6 = E'foo''s\\bar'::text)) + Partition Iterator + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Filter: (("T 1".c6)::text = 'foo''s\bar'::text) + Selected Partitions: 1..2 + +(15 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c8 = 'foo'; -- can't be sent to remote + QUERY PLAN +---------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Filter: (t1.c8 = 'foo'::user_enum) + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" + Partition Iterator + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Selected Partitions: 1..2 + +(15 rows) + +-- parameterized remote path for foreign table +EXPLAIN (VERBOSE, COSTS OFF) + SELECT * FROM "S 1"."T 1" a, ft2 b WHERE a."C 1" = 47 AND b.c1 = a.c2; + QUERY PLAN +------------------------------------------------------------------------------------------------------------- + Nested Loop + Output: a."C 1", a.c2, a.c3, a.c4, a.c5, a.c6, a.c7, a.c8, b.c1, b.c2, b.c3, b.c4, b.c5, b.c6, b.c7, b.c8 + Join Filter: (a.c2 = b.c1) + -> Partitioned Index Scan using t1_pkey on "S 1"."T 1" a + Output: a."C 1", a.c2, a.c3, a.c4, a.c5, a.c6, a.c7, a.c8 + Index Cond: (a."C 1" = 47) + Selected Partitions: 1 + -> Foreign Scan on public.ft2 b + Output: b.c1, b.c2, b.c3, b.c4, b.c5, b.c6, b.c7, b.c8 + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" + Partition Iterator + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Selected Partitions: 1..2 + +(21 rows) + +SELECT * FROM ft2 a, ft2 b WHERE a.c1 = 47 AND b.c1 = a.c2; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 | c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +----+----+-------+------------------------------+--------------------------+----+------------+-----+----+----+-------+------------------------------+--------------------------+----+------------+----- + 47 | 7 | 00047 | Tue Feb 17 00:00:00 1970 PST | Tue Feb 17 00:00:00 1970 | 7 | 7 | foo | 7 | 7 | 00007 | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7 | 7 | foo +(1 row) + +-- check both safe and unsafe join conditions +EXPLAIN (VERBOSE, COSTS OFF) + SELECT * FROM ft2 a, ft2 b + WHERE a.c2 = 6 AND b.c1 = a.c1 AND a.c8 = 'foo' AND b.c7 = upper(a.c7); + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------- + Hash Join + Output: a.c1, a.c2, a.c3, a.c4, a.c5, a.c6, a.c7, a.c8, b.c1, b.c2, b.c3, b.c4, b.c5, b.c6, b.c7, b.c8 + Hash Cond: ((b.c1 = a.c1) AND ((b.c7)::text = upper((a.c7)::text))) + -> Foreign Scan on public.ft2 b + Output: b.c1, b.c2, b.c3, b.c4, b.c5, b.c6, b.c7, b.c8 + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" + -> Hash + Output: a.c1, a.c2, a.c3, a.c4, a.c5, a.c6, a.c7, a.c8 + -> Foreign Scan on public.ft2 a + Output: a.c1, a.c2, a.c3, a.c4, a.c5, a.c6, a.c7, a.c8 + Filter: (a.c8 = 'foo'::user_enum) + Node ID: 2 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((c2 = 6)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" + Partition Iterator + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Selected Partitions: 1..2 + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((c2 = 6)) + Partition Iterator + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Filter: ("T 1".c2 = 6) + Selected Partitions: 1..2 + +(32 rows) + +SELECT * FROM ft2 a, ft2 b +WHERE a.c2 = 6 AND b.c1 = a.c1 AND a.c8 = 'foo' AND b.c7 = upper(a.c7); + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 | c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +-----+----+-------+------------------------------+--------------------------+----+------------+-----+-----+----+-------+------------------------------+--------------------------+----+------------+----- + 6 | 6 | 00006 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo | 6 | 6 | 00006 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo + 16 | 6 | 00016 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo | 16 | 6 | 00016 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo + 26 | 6 | 00026 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo | 26 | 6 | 00026 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo + 36 | 6 | 00036 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo | 36 | 6 | 00036 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo + 46 | 6 | 00046 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo | 46 | 6 | 00046 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo + 56 | 6 | 00056 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo | 56 | 6 | 00056 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo + 66 | 6 | 00066 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo | 66 | 6 | 00066 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo + 76 | 6 | 00076 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo | 76 | 6 | 00076 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo + 86 | 6 | 00086 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo | 86 | 6 | 00086 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo + 96 | 6 | 00096 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo | 96 | 6 | 00096 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo + 106 | 6 | 00106 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo | 106 | 6 | 00106 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo + 116 | 6 | 00116 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo | 116 | 6 | 00116 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo + 126 | 6 | 00126 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo | 126 | 6 | 00126 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo + 136 | 6 | 00136 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo | 136 | 6 | 00136 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo + 146 | 6 | 00146 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo | 146 | 6 | 00146 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo + 156 | 6 | 00156 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo | 156 | 6 | 00156 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo + 166 | 6 | 00166 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo | 166 | 6 | 00166 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo + 176 | 6 | 00176 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo | 176 | 6 | 00176 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo + 186 | 6 | 00186 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo | 186 | 6 | 00186 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo + 196 | 6 | 00196 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo | 196 | 6 | 00196 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo + 206 | 6 | 00206 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo | 206 | 6 | 00206 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo + 216 | 6 | 00216 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo | 216 | 6 | 00216 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo + 226 | 6 | 00226 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo | 226 | 6 | 00226 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo + 236 | 6 | 00236 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo | 236 | 6 | 00236 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo + 246 | 6 | 00246 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo | 246 | 6 | 00246 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo + 256 | 6 | 00256 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo | 256 | 6 | 00256 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo + 266 | 6 | 00266 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo | 266 | 6 | 00266 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo + 276 | 6 | 00276 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo | 276 | 6 | 00276 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo + 286 | 6 | 00286 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo | 286 | 6 | 00286 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo + 296 | 6 | 00296 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo | 296 | 6 | 00296 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo + 306 | 6 | 00306 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo | 306 | 6 | 00306 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo + 316 | 6 | 00316 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo | 316 | 6 | 00316 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo + 326 | 6 | 00326 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo | 326 | 6 | 00326 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo + 336 | 6 | 00336 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo | 336 | 6 | 00336 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo + 346 | 6 | 00346 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo | 346 | 6 | 00346 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo + 356 | 6 | 00356 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo | 356 | 6 | 00356 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo + 366 | 6 | 00366 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo | 366 | 6 | 00366 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo + 376 | 6 | 00376 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo | 376 | 6 | 00376 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo + 386 | 6 | 00386 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo | 386 | 6 | 00386 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo + 396 | 6 | 00396 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo | 396 | 6 | 00396 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo + 406 | 6 | 00406 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo | 406 | 6 | 00406 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo + 416 | 6 | 00416 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo | 416 | 6 | 00416 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo + 426 | 6 | 00426 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo | 426 | 6 | 00426 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo + 436 | 6 | 00436 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo | 436 | 6 | 00436 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo + 446 | 6 | 00446 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo | 446 | 6 | 00446 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo + 456 | 6 | 00456 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo | 456 | 6 | 00456 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo + 466 | 6 | 00466 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo | 466 | 6 | 00466 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo + 476 | 6 | 00476 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo | 476 | 6 | 00476 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo + 486 | 6 | 00486 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo | 486 | 6 | 00486 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo + 496 | 6 | 00496 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo | 496 | 6 | 00496 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo + 506 | 6 | 00506 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo | 506 | 6 | 00506 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo + 516 | 6 | 00516 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo | 516 | 6 | 00516 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo + 526 | 6 | 00526 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo | 526 | 6 | 00526 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo + 536 | 6 | 00536 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo | 536 | 6 | 00536 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo + 546 | 6 | 00546 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo | 546 | 6 | 00546 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo + 556 | 6 | 00556 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo | 556 | 6 | 00556 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo + 566 | 6 | 00566 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo | 566 | 6 | 00566 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo + 576 | 6 | 00576 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo | 576 | 6 | 00576 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo + 586 | 6 | 00586 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo | 586 | 6 | 00586 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo + 596 | 6 | 00596 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo | 596 | 6 | 00596 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo + 606 | 6 | 00606 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo | 606 | 6 | 00606 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo + 616 | 6 | 00616 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo | 616 | 6 | 00616 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo + 626 | 6 | 00626 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo | 626 | 6 | 00626 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo + 636 | 6 | 00636 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo | 636 | 6 | 00636 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo + 646 | 6 | 00646 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo | 646 | 6 | 00646 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo + 656 | 6 | 00656 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo | 656 | 6 | 00656 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo + 666 | 6 | 00666 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo | 666 | 6 | 00666 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo + 676 | 6 | 00676 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo | 676 | 6 | 00676 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo + 686 | 6 | 00686 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo | 686 | 6 | 00686 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo + 696 | 6 | 00696 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo | 696 | 6 | 00696 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo + 706 | 6 | 00706 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo | 706 | 6 | 00706 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo + 716 | 6 | 00716 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo | 716 | 6 | 00716 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo + 726 | 6 | 00726 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo | 726 | 6 | 00726 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo + 736 | 6 | 00736 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo | 736 | 6 | 00736 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo + 746 | 6 | 00746 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo | 746 | 6 | 00746 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo + 756 | 6 | 00756 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo | 756 | 6 | 00756 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo + 766 | 6 | 00766 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo | 766 | 6 | 00766 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo + 776 | 6 | 00776 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo | 776 | 6 | 00776 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo + 786 | 6 | 00786 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo | 786 | 6 | 00786 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo + 796 | 6 | 00796 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo | 796 | 6 | 00796 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo + 806 | 6 | 00806 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo | 806 | 6 | 00806 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo + 816 | 6 | 00816 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo | 816 | 6 | 00816 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo + 826 | 6 | 00826 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo | 826 | 6 | 00826 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo + 836 | 6 | 00836 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo | 836 | 6 | 00836 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo + 846 | 6 | 00846 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo | 846 | 6 | 00846 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo + 856 | 6 | 00856 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo | 856 | 6 | 00856 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo + 866 | 6 | 00866 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo | 866 | 6 | 00866 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo + 876 | 6 | 00876 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo | 876 | 6 | 00876 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo + 886 | 6 | 00886 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo | 886 | 6 | 00886 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo + 896 | 6 | 00896 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo | 896 | 6 | 00896 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo + 906 | 6 | 00906 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo | 906 | 6 | 00906 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo + 916 | 6 | 00916 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo | 916 | 6 | 00916 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo + 926 | 6 | 00926 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo | 926 | 6 | 00926 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo + 936 | 6 | 00936 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo | 936 | 6 | 00936 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo + 946 | 6 | 00946 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo | 946 | 6 | 00946 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo + 956 | 6 | 00956 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo | 956 | 6 | 00956 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo + 966 | 6 | 00966 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo | 966 | 6 | 00966 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo + 976 | 6 | 00976 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo | 976 | 6 | 00976 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo + 986 | 6 | 00986 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo | 986 | 6 | 00986 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo + 996 | 6 | 00996 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo | 996 | 6 | 00996 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo +(100 rows) + +-- bug before 9.3.5 due to sloppy handling of remote-estimate parameters +SELECT * FROM ft1 WHERE c1 = ANY (ARRAY(SELECT c1 FROM ft2 WHERE c1 < 5)); + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +----+----+-------+------------------------------+--------------------------+----+------------+----- + 1 | 1 | 00001 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1 | 1 | foo + 2 | 2 | 00002 | Sat Jan 03 00:00:00 1970 PST | Sat Jan 03 00:00:00 1970 | 2 | 2 | foo + 3 | 3 | 00003 | Sun Jan 04 00:00:00 1970 PST | Sun Jan 04 00:00:00 1970 | 3 | 3 | foo + 4 | 4 | 00004 | Mon Jan 05 00:00:00 1970 PST | Mon Jan 05 00:00:00 1970 | 4 | 4 | foo +(4 rows) + +SELECT * FROM ft2 WHERE c1 = ANY (ARRAY(SELECT c1 FROM ft1 WHERE c1 < 5)); + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +----+----+-------+------------------------------+--------------------------+----+------------+----- + 1 | 1 | 00001 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1 | 1 | foo + 2 | 2 | 00002 | Sat Jan 03 00:00:00 1970 PST | Sat Jan 03 00:00:00 1970 | 2 | 2 | foo + 3 | 3 | 00003 | Sun Jan 04 00:00:00 1970 PST | Sun Jan 04 00:00:00 1970 | 3 | 3 | foo + 4 | 4 | 00004 | Mon Jan 05 00:00:00 1970 PST | Mon Jan 05 00:00:00 1970 | 4 | 4 | foo +(4 rows) + +-- we should not push order by clause with volatile expressions or unsafe +-- collations +EXPLAIN (VERBOSE, COSTS OFF) + SELECT * FROM ft2 ORDER BY ft2.c1, random(); + QUERY PLAN +---------------------------------------------------------------------------------------------------- + Sort + Output: c1, c2, c3, c4, c5, c6, c7, c8, (random()) + Sort Key: ft2.c1, (random()) + -> Foreign Scan on public.ft2 + Output: c1, c2, c3, c4, c5, c6, c7, c8, random() + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" + Partition Iterator + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Selected Partitions: 1..2 + +(17 rows) + +EXPLAIN (VERBOSE, COSTS OFF) + SELECT * FROM ft2 ORDER BY ft2.c1, ft2.c3 collate "C"; + QUERY PLAN +---------------------------------------------------------------------------------------------------- + Sort + Output: c1, c2, c3, c4, c5, c6, c7, c8, ((c3)::text) + Sort Key: ft2.c1, ft2.c3 COLLATE "C" + -> Foreign Scan on public.ft2 + Output: c1, c2, c3, c4, c5, c6, c7, c8, (c3)::text + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" + Partition Iterator + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Selected Partitions: 1..2 + +(17 rows) + +-- user-defined operator/function +CREATE FUNCTION postgres_fdw_abs(int) RETURNS int AS $$ +BEGIN +RETURN abs($1); +END +$$ LANGUAGE plpgsql IMMUTABLE; +CREATE OPERATOR === ( + LEFTARG = int, + RIGHTARG = int, + PROCEDURE = int4eq, + COMMUTATOR = === +); +-- built-in operators and functions can be shipped for remote execution +EXPLAIN (VERBOSE, COSTS OFF) + SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = abs(t1.c2); + QUERY PLAN +------------------------------------------------------------------------------------------------------ + Foreign Scan + Output: (count(c3)) + Node ID: 1 + Relations: Aggregate on (public.ft1 t1) + Remote SQL: SELECT count(c3) FROM "S 1"."T 1" WHERE (("C 1" = abs(c2))) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT count(c3) FROM "S 1"."T 1" WHERE (("C 1" = abs(c2))) + Aggregate + Output: count(c3) + -> Partition Iterator + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Filter: ("T 1"."C 1" = abs("T 1".c2)) + Selected Partitions: 1..2 + +(18 rows) + +SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = abs(t1.c2); + count +------- + 9 +(1 row) + +EXPLAIN (VERBOSE, COSTS OFF) + SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = t1.c2; + QUERY PLAN +------------------------------------------------------------------------------------------------- + Foreign Scan + Output: (count(c3)) + Node ID: 1 + Relations: Aggregate on (public.ft1 t1) + Remote SQL: SELECT count(c3) FROM "S 1"."T 1" WHERE (("C 1" = c2)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT count(c3) FROM "S 1"."T 1" WHERE (("C 1" = c2)) + Aggregate + Output: count(c3) + -> Partition Iterator + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Filter: ("T 1"."C 1" = "T 1".c2) + Selected Partitions: 1..2 + +(18 rows) + +SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = t1.c2; + count +------- + 9 +(1 row) + +-- by default, user-defined ones cannot +EXPLAIN (VERBOSE, COSTS OFF) + SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = postgres_fdw_abs(t1.c2); + QUERY PLAN +-------------------------------------------------------------------------------- + Aggregate + Output: count(c3) + -> Foreign Scan on public.ft1 t1 + Output: c3, c1, c2 + Filter: (t1.c1 = postgres_fdw_abs(t1.c2)) + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3 FROM "S 1"."T 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3 FROM "S 1"."T 1" + Partition Iterator + Output: "C 1", c2, c3 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3 + Selected Partitions: 1..2 + +(17 rows) + +SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = postgres_fdw_abs(t1.c2); + count +------- + 9 +(1 row) + +EXPLAIN (VERBOSE, COSTS OFF) + SELECT count(c3) FROM ft1 t1 WHERE t1.c1 === t1.c2; + QUERY PLAN +-------------------------------------------------------------------------------- + Aggregate + Output: count(c3) + -> Foreign Scan on public.ft1 t1 + Output: c3 + Filter: (t1.c1 === t1.c2) + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3 FROM "S 1"."T 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3 FROM "S 1"."T 1" + Partition Iterator + Output: "C 1", c2, c3 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3 + Selected Partitions: 1..2 + +(17 rows) + +SELECT count(c3) FROM ft1 t1 WHERE t1.c1 === t1.c2; + count +------- + 9 +(1 row) + +-- ORDER BY can be shipped, though +EXPLAIN (VERBOSE, COSTS OFF) + SELECT * FROM ft1 t1 WHERE t1.c1 === t1.c2 order by t1.c2 limit 1; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------- + Limit + Output: c1, c2, c3, c4, c5, c6, c7, c8 + -> Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Filter: (t1.c1 === t1.c2) + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" ORDER BY c2 ASC NULLS LAST + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" ORDER BY c2 ASC NULLS LAST + Sort + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Sort Key: "T 1".c2 + -> Partition Iterator + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Selected Partitions: 1..2 + +(20 rows) + +SELECT * FROM ft1 t1 WHERE t1.c1 === t1.c2 order by t1.c2 limit 1; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +----+----+-------+------------------------------+--------------------------+----+------------+----- + 1 | 1 | 00001 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1 | 1 | foo +(1 row) + +-- but let's put them in an extension ... +ALTER EXTENSION postgres_fdw ADD FUNCTION postgres_fdw_abs(int); +ALTER EXTENSION postgres_fdw ADD OPERATOR === (int, int); +ALTER SERVER loopback OPTIONS (ADD extensions 'postgres_fdw'); --nspt +ERROR: invalid option "extensions" +HINT: Valid options in this context are: service, connect_timeout, dbname, host, remote_nodename, hostaddr, port, localhost, localport, application_name, fencedUdfRPCMode, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, rw_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, krbsrvname, prototype, connection_info, connectionExtraInfo, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, updatable +-- ... now they can be shipped +EXPLAIN (VERBOSE, COSTS OFF) + SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = postgres_fdw_abs(t1.c2); + QUERY PLAN +-------------------------------------------------------------------------------- + Aggregate + Output: count(c3) + -> Foreign Scan on public.ft1 t1 + Output: c3, c1, c2 + Filter: (t1.c1 = postgres_fdw_abs(t1.c2)) + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3 FROM "S 1"."T 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3 FROM "S 1"."T 1" + Partition Iterator + Output: "C 1", c2, c3 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3 + Selected Partitions: 1..2 + +(17 rows) + +SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = postgres_fdw_abs(t1.c2); + count +------- + 9 +(1 row) + +EXPLAIN (VERBOSE, COSTS OFF) + SELECT count(c3) FROM ft1 t1 WHERE t1.c1 === t1.c2; + QUERY PLAN +-------------------------------------------------------------------------------- + Aggregate + Output: count(c3) + -> Foreign Scan on public.ft1 t1 + Output: c3 + Filter: (t1.c1 === t1.c2) + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3 FROM "S 1"."T 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3 FROM "S 1"."T 1" + Partition Iterator + Output: "C 1", c2, c3 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3 + Selected Partitions: 1..2 + +(17 rows) + +SELECT count(c3) FROM ft1 t1 WHERE t1.c1 === t1.c2; + count +------- + 9 +(1 row) + +-- and both ORDER BY and LIMIT can be shipped +EXPLAIN (VERBOSE, COSTS OFF) + SELECT * FROM ft1 t1 WHERE t1.c1 === t1.c2 order by t1.c2 limit 1; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------- + Limit + Output: c1, c2, c3, c4, c5, c6, c7, c8 + -> Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Filter: (t1.c1 === t1.c2) + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" ORDER BY c2 ASC NULLS LAST + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" ORDER BY c2 ASC NULLS LAST + Sort + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Sort Key: "T 1".c2 + -> Partition Iterator + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Selected Partitions: 1..2 + +(20 rows) + +SELECT * FROM ft1 t1 WHERE t1.c1 === t1.c2 order by t1.c2 limit 1; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +----+----+-------+------------------------------+--------------------------+----+------------+----- + 1 | 1 | 00001 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1 | 1 | foo +(1 row) + +-- case when expr +explain (verbose, costs off) select * from ft1 where case c1 when 1 then 0 end = 0; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (((CASE "C 1" WHEN 1 THEN 0 ELSE NULL::integer END) = 0)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (((CASE "C 1" WHEN 1 THEN 0 ELSE NULL::integer END) = 0)) + Partition Iterator + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Filter: (CASE "T 1"."C 1" WHEN 1 THEN 0 ELSE NULL::integer END = 0) + Selected Partitions: 1..2 + +(15 rows) + +select * from ft1 where case c1 when 1 then 0 end = 0; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +----+----+-------+------------------------------+--------------------------+----+------------+----- + 1 | 1 | 00001 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1 | 1 | foo +(1 row) + +-- ====================================================================================================================================== +-- TEST-MODULE: JOIN queries +-- -------------------------------------- +-- ====================================================================================================================================== +-- Analyze ft4 and ft5 so that we have better statistics. These tables do not +-- have use_remote_estimate set. +ANALYZE ft4; +ANALYZE ft5; +-- join two tables +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: t1.c1, t2.c1, t1.c3 + Node ID: 1 + Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2) + Remote SQL: SELECT r1."C 1", r2."C 1", r1.c3 FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) ORDER BY r1.c3 ASC NULLS LAST, r1."C 1" ASC NULLS LAST LIMIT 10::bigint OFFSET 100::bigint + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT r1."C 1", r2."C 1", r1.c3 FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) ORDER BY r1.c3 ASC NULLS LAST, r1."C 1" ASC NULLS LAST LIMIT 10::bigint OFFSET 100::bigint + Limit + Output: r1."C 1", r2."C 1", r1.c3 + -> Sort + Output: r1."C 1", r2."C 1", r1.c3 + Sort Key: r1.c3, r1."C 1" + -> Hash Join + Output: r1."C 1", r2."C 1", r1.c3 + Hash Cond: (r1."C 1" = r2."C 1") + -> Partition Iterator + Output: r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" r1 + Output: r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8 + Selected Partitions: 1..2 + -> Hash + Output: r2."C 1" + -> Partition Iterator + Output: r2."C 1" + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" r2 + Output: r2."C 1" + Selected Partitions: 1..2 + +(31 rows) + +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; + c1 | c1 +-----+----- + 101 | 101 + 102 | 102 + 103 | 103 + 104 | 104 + 105 | 105 + 106 | 106 + 107 | 107 + 108 | 108 + 109 | 109 + 110 | 110 +(10 rows) + +-- join three tables +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) JOIN ft4 t3 ON (t3.c1 = t1.c1) ORDER BY t1.c3, t1.c1 OFFSET 10 LIMIT 10; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + Foreign Scan + Output: t1.c1, t2.c2, t3.c3, t1.c3 + Node ID: 1 + Relations: ((public.ft1 t1) INNER JOIN (public.ft2 t2)) INNER JOIN (public.ft4 t3) + Remote SQL: SELECT r1."C 1", r2.c2, r4.c3, r1.c3 FROM (("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) INNER JOIN "S 1"."T 3" r4 ON (((r1."C 1" = r4.c1)))) ORDER BY r1.c3 ASC NULLS LAST, r1."C 1" ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT r1."C 1", r2.c2, r4.c3, r1.c3 FROM (("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) INNER JOIN "S 1"."T 3" r4 ON (((r1."C 1" = r4.c1)))) ORDER BY r1.c3 ASC NULLS LAST, r1."C 1" ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint + Limit + Output: r1."C 1", r2.c2, r4.c3, r1.c3 + -> Sort + Output: r1."C 1", r2.c2, r4.c3, r1.c3 + Sort Key: r1.c3, r1."C 1" + -> Merge Join + Output: r1."C 1", r2.c2, r4.c3, r1.c3 + Merge Cond: (r1."C 1" = r4.c1) + -> Merge Join + Output: r1."C 1", r1.c3, r2.c2, r2."C 1" + Merge Cond: (r1."C 1" = r2."C 1") + -> Partition Iterator + Output: r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8 + Iterations: 2 + -> Partitioned Index Scan using t1_pkey on "S 1"."T 1" r1 + Output: r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8 + Selected Partitions: 1..2 + -> Materialize + Output: r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8 + -> Partition Iterator + Output: r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8 + Iterations: 2 + -> Partitioned Index Scan using t1_pkey on "S 1"."T 1" r2 + Output: r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8 + Selected Partitions: 1..2 + -> Sort + Output: r4.c3, r4.c1 + Sort Key: r4.c1 + -> Partition Iterator + Output: r4.c3, r4.c1 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 3" r4 + Output: r4.c3, r4.c1 + Selected Partitions: 1..2 + +(43 rows) + +SELECT t1.c1, t2.c2, t3.c3 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) JOIN ft4 t3 ON (t3.c1 = t1.c1) ORDER BY t1.c3, t1.c1 OFFSET 10 LIMIT 10; + c1 | c2 | c3 +----+----+-------- + 22 | 2 | AAA022 + 24 | 4 | AAA024 + 26 | 6 | AAA026 + 28 | 8 | AAA028 + 30 | 0 | AAA030 + 32 | 2 | AAA032 + 34 | 4 | AAA034 + 36 | 6 | AAA036 + 38 | 8 | AAA038 + 40 | 0 | AAA040 +(10 rows) + +-- left outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: t1.c1, t2.c1 + Node ID: 1 + Relations: (public.ft4 t1) LEFT JOIN (public.ft5 t2) + Remote SQL: SELECT r1.c1, r2.c1 FROM ("S 1"."T 3" r1 LEFT JOIN "S 1"."T 4" r2 ON (((r1.c1 = r2.c1)))) ORDER BY r1.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT r1.c1, r2.c1 FROM ("S 1"."T 3" r1 LEFT JOIN "S 1"."T 4" r2 ON (((r1.c1 = r2.c1)))) ORDER BY r1.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint + Limit + Output: r1.c1, r2.c1 + -> Sort + Output: r1.c1, r2.c1 + Sort Key: r1.c1, r2.c1 + -> Hash Left Join + Output: r1.c1, r2.c1 + Hash Cond: (r1.c1 = r2.c1) + -> Partition Iterator + Output: r1.c1, r1.c2, r1.c3 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 3" r1 + Output: r1.c1, r1.c2, r1.c3 + Selected Partitions: 1..2 + -> Hash + Output: r2.c1 + -> Partition Iterator + Output: r2.c1 + Iterations: 2, Sub Iterations: 4 + -> Partitioned Seq Scan on "S 1"."T 4" r2 + Output: r2.c1 + Selected Partitions: 1..2 + Selected Subpartitions: ALL + +(32 rows) + +SELECT t1.c1, t2.c1 FROM ft4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; + c1 | c1 +----+---- + 22 | + 24 | 24 + 26 | + 28 | + 30 | 30 + 32 | + 34 | + 36 | 36 + 38 | + 40 | +(10 rows) + +-- left outer join three tables +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) LEFT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: t1.c1, t2.c2, t3.c3 + Node ID: 1 + Relations: ((public.ft2 t1) LEFT JOIN (public.ft2 t2)) LEFT JOIN (public.ft4 t3) + Remote SQL: SELECT r1."C 1", r2.c2, r4.c3 FROM (("S 1"."T 1" r1 LEFT JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) LEFT JOIN "S 1"."T 3" r4 ON (((r2."C 1" = r4.c1)))) LIMIT 10::bigint OFFSET 10::bigint + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT r1."C 1", r2.c2, r4.c3 FROM (("S 1"."T 1" r1 LEFT JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) LEFT JOIN "S 1"."T 3" r4 ON (((r2."C 1" = r4.c1)))) LIMIT 10::bigint OFFSET 10::bigint + Limit + Output: r1."C 1", r2.c2, r4.c3 + -> Hash Left Join + Output: r1."C 1", r2.c2, r4.c3 + Hash Cond: (r2."C 1" = r4.c1) + -> Merge Left Join + Output: r1."C 1", r2.c2, r2."C 1" + Merge Cond: (r1."C 1" = r2."C 1") + -> Partition Iterator + Output: r1."C 1" + Iterations: 2 + -> Partitioned Index Only Scan using t1_pkey on "S 1"."T 1" r1 + Output: r1."C 1" + Selected Partitions: 1..2 + -> Materialize + Output: r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8 + -> Partition Iterator + Output: r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8 + Iterations: 2 + -> Partitioned Index Scan using t1_pkey on "S 1"."T 1" r2 + Output: r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8 + Selected Partitions: 1..2 + -> Hash + Output: r4.c3, r4.c1 + -> Partition Iterator + Output: r4.c3, r4.c1 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 3" r4 + Output: r4.c3, r4.c1 + Selected Partitions: 1..2 + +(39 rows) + +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) LEFT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + c1 | c2 | c3 +----+----+-------- + 11 | 1 | + 12 | 2 | AAA012 + 13 | 3 | + 14 | 4 | AAA014 + 15 | 5 | + 16 | 6 | AAA016 + 17 | 7 | + 18 | 8 | AAA018 + 19 | 9 | + 20 | 0 | AAA020 +(10 rows) + +-- left outer join + placement of clauses. +-- clauses within the nullable side are not pulled up, but top level clause on +-- non-nullable side is pushed into non-nullable side +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t1.c2, t2.c1, t2.c2 FROM ft4 t1 LEFT JOIN (SELECT * FROM ft5 WHERE c1 < 10) t2 ON (t1.c1 = t2.c1) WHERE t1.c1 < 10; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + Foreign Scan + Output: t1.c1, t1.c2, ft5.c1, ft5.c2 + Node ID: 1 + Relations: (public.ft4 t1) LEFT JOIN (public.ft5) + Remote SQL: SELECT r1.c1, r1.c2, r4.c1, r4.c2 FROM ("S 1"."T 3" r1 LEFT JOIN "S 1"."T 4" r4 ON (((r1.c1 = r4.c1)) AND ((r4.c1 < 10)))) WHERE ((r1.c1 < 10)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT r1.c1, r1.c2, r4.c1, r4.c2 FROM ("S 1"."T 3" r1 LEFT JOIN "S 1"."T 4" r4 ON (((r1.c1 = r4.c1)) AND ((r4.c1 < 10)))) WHERE ((r1.c1 < 10)) + Hash Left Join + Output: r1.c1, r1.c2, r4.c1, r4.c2 + Hash Cond: (r1.c1 = r4.c1) + -> Partition Iterator + Output: r1.c1, r1.c2, r1.c3 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 3" r1 + Output: r1.c1, r1.c2, r1.c3 + Filter: (r1.c1 < 10) + Selected Partitions: 1..2 + -> Hash + Output: r4.c1, r4.c2 + -> Partition Iterator + Output: r4.c1, r4.c2 + Iterations: 1, Sub Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 4" r4 + Output: r4.c1, r4.c2 + Filter: (r4.c1 < 10) + Selected Partitions: 1 + Selected Subpartitions: ALL + +(29 rows) + +SELECT t1.c1, t1.c2, t2.c1, t2.c2 FROM ft4 t1 LEFT JOIN (SELECT * FROM ft5 WHERE c1 < 10) t2 ON (t1.c1 = t2.c1) WHERE t1.c1 < 10; + c1 | c2 | c1 | c2 +----+----+----+---- + 2 | 3 | | + 6 | 7 | 6 | 7 + 8 | 9 | | + 4 | 5 | | +(4 rows) + +-- clauses within the nullable side are not pulled up, but the top level clause +-- on nullable side is not pushed down into nullable side +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t1.c2, t2.c1, t2.c2 FROM ft4 t1 LEFT JOIN (SELECT * FROM ft5 WHERE c1 < 10) t2 ON (t1.c1 = t2.c1) + WHERE (t2.c1 < 10 OR t2.c1 IS NULL) AND t1.c1 < 10; + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: t1.c1, t1.c2, ft5.c1, ft5.c2 + Node ID: 1 + Relations: (public.ft4 t1) LEFT JOIN (public.ft5) + Remote SQL: SELECT r1.c1, r1.c2, r4.c1, r4.c2 FROM ("S 1"."T 3" r1 LEFT JOIN "S 1"."T 4" r4 ON (((r1.c1 = r4.c1)) AND ((r4.c1 < 10)))) WHERE (((r4.c1 < 10) OR (r4.c1 IS NULL))) AND ((r1.c1 < 10)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT r1.c1, r1.c2, r4.c1, r4.c2 FROM ("S 1"."T 3" r1 LEFT JOIN "S 1"."T 4" r4 ON (((r1.c1 = r4.c1)) AND ((r4.c1 < 10)))) WHERE (((r4.c1 < 10) OR (r4.c1 IS NULL))) AND ((r1.c1 < 10)) + Hash Left Join + Output: r1.c1, r1.c2, r4.c1, r4.c2 + Hash Cond: (r1.c1 = r4.c1) + Filter: ((r4.c1 < 10) OR (r4.c1 IS NULL)) + -> Partition Iterator + Output: r1.c1, r1.c2, r1.c3 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 3" r1 + Output: r1.c1, r1.c2, r1.c3 + Filter: (r1.c1 < 10) + Selected Partitions: 1..2 + -> Hash + Output: r4.c1, r4.c2 + -> Partition Iterator + Output: r4.c1, r4.c2 + Iterations: 1, Sub Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 4" r4 + Output: r4.c1, r4.c2 + Filter: (r4.c1 < 10) + Selected Partitions: 1 + Selected Subpartitions: ALL + +(30 rows) + +SELECT t1.c1, t1.c2, t2.c1, t2.c2 FROM ft4 t1 LEFT JOIN (SELECT * FROM ft5 WHERE c1 < 10) t2 ON (t1.c1 = t2.c1) + WHERE (t2.c1 < 10 OR t2.c1 IS NULL) AND t1.c1 < 10; + c1 | c2 | c1 | c2 +----+----+----+---- + 2 | 3 | | + 6 | 7 | 6 | 7 + 8 | 9 | | + 4 | 5 | | +(4 rows) + +-- right outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft5 t1 RIGHT JOIN ft4 t2 ON (t1.c1 = t2.c1) ORDER BY t2.c1, t1.c1 OFFSET 10 LIMIT 10; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: t1.c1, t2.c1 + Node ID: 1 + Relations: (public.ft4 t2) LEFT JOIN (public.ft5 t1) + Remote SQL: SELECT r1.c1, r2.c1 FROM ("S 1"."T 3" r2 LEFT JOIN "S 1"."T 4" r1 ON (((r1.c1 = r2.c1)))) ORDER BY r2.c1 ASC NULLS LAST, r1.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT r1.c1, r2.c1 FROM ("S 1"."T 3" r2 LEFT JOIN "S 1"."T 4" r1 ON (((r1.c1 = r2.c1)))) ORDER BY r2.c1 ASC NULLS LAST, r1.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint + Limit + Output: r1.c1, r2.c1 + -> Sort + Output: r1.c1, r2.c1 + Sort Key: r2.c1, r1.c1 + -> Hash Left Join + Output: r1.c1, r2.c1 + Hash Cond: (r2.c1 = r1.c1) + -> Partition Iterator + Output: r2.c1, r2.c2, r2.c3 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 3" r2 + Output: r2.c1, r2.c2, r2.c3 + Selected Partitions: 1..2 + -> Hash + Output: r1.c1 + -> Partition Iterator + Output: r1.c1 + Iterations: 2, Sub Iterations: 4 + -> Partitioned Seq Scan on "S 1"."T 4" r1 + Output: r1.c1 + Selected Partitions: 1..2 + Selected Subpartitions: ALL + +(32 rows) + +SELECT t1.c1, t2.c1 FROM ft5 t1 RIGHT JOIN ft4 t2 ON (t1.c1 = t2.c1) ORDER BY t2.c1, t1.c1 OFFSET 10 LIMIT 10; + c1 | c1 +----+---- + | 22 + 24 | 24 + | 26 + | 28 + 30 | 30 + | 32 + | 34 + 36 | 36 + | 38 + | 40 +(10 rows) + +-- right outer join three tables +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 RIGHT JOIN ft2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: t1.c1, t2.c2, t3.c3 + Node ID: 1 + Relations: ((public.ft4 t3) LEFT JOIN (public.ft2 t2)) LEFT JOIN (public.ft2 t1) + Remote SQL: SELECT r1."C 1", r2.c2, r4.c3 FROM (("S 1"."T 3" r4 LEFT JOIN "S 1"."T 1" r2 ON (((r2."C 1" = r4.c1)))) LEFT JOIN "S 1"."T 1" r1 ON (((r1."C 1" = r2."C 1")))) LIMIT 10::bigint OFFSET 10::bigint + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT r1."C 1", r2.c2, r4.c3 FROM (("S 1"."T 3" r4 LEFT JOIN "S 1"."T 1" r2 ON (((r2."C 1" = r4.c1)))) LEFT JOIN "S 1"."T 1" r1 ON (((r1."C 1" = r2."C 1")))) LIMIT 10::bigint OFFSET 10::bigint + Limit + Output: r1."C 1", r2.c2, r4.c3 + -> Merge Right Join + Output: r1."C 1", r2.c2, r4.c3 + Merge Cond: (r2."C 1" = r4.c1) + -> Merge Left Join + Output: r2.c2, r2."C 1", r1."C 1" + Merge Cond: (r2."C 1" = r1."C 1") + -> Partition Iterator + Output: r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8 + Iterations: 2 + -> Partitioned Index Scan using t1_pkey on "S 1"."T 1" r2 + Output: r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8 + Selected Partitions: 1..2 + -> Materialize + Output: r1."C 1" + -> Partition Iterator + Output: r1."C 1" + Iterations: 2 + -> Partitioned Index Only Scan using t1_pkey on "S 1"."T 1" r1 + Output: r1."C 1" + Selected Partitions: 1..2 + -> Sort + Output: r4.c3, r4.c1 + Sort Key: r4.c1 + -> Partition Iterator + Output: r4.c3, r4.c1 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 3" r4 + Output: r4.c3, r4.c1 + Selected Partitions: 1..2 + +(40 rows) + +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 RIGHT JOIN ft2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + c1 | c2 | c3 +----+----+-------- + 22 | 2 | AAA022 + 24 | 4 | AAA024 + 26 | 6 | AAA026 + 28 | 8 | AAA028 + 30 | 0 | AAA030 + 32 | 2 | AAA032 + 34 | 4 | AAA034 + 36 | 6 | AAA036 + 38 | 8 | AAA038 + 40 | 0 | AAA040 +(10 rows) + +-- full outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft4 t1 FULL JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 45 LIMIT 10; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: t1.c1, t2.c1 + Node ID: 1 + Relations: (public.ft4 t1) FULL JOIN (public.ft5 t2) + Remote SQL: SELECT r1.c1, r2.c1 FROM ("S 1"."T 3" r1 FULL JOIN "S 1"."T 4" r2 ON (((r1.c1 = r2.c1)))) ORDER BY r1.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 45::bigint + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT r1.c1, r2.c1 FROM ("S 1"."T 3" r1 FULL JOIN "S 1"."T 4" r2 ON (((r1.c1 = r2.c1)))) ORDER BY r1.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 45::bigint + Limit + Output: r1.c1, r2.c1 + -> Sort + Output: r1.c1, r2.c1 + Sort Key: r1.c1, r2.c1 + -> Hash Full Join + Output: r1.c1, r2.c1 + Hash Cond: (r1.c1 = r2.c1) + -> Partition Iterator + Output: r1.c1, r1.c2, r1.c3 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 3" r1 + Output: r1.c1, r1.c2, r1.c3 + Selected Partitions: 1..2 + -> Hash + Output: r2.c1 + -> Partition Iterator + Output: r2.c1 + Iterations: 2, Sub Iterations: 4 + -> Partitioned Seq Scan on "S 1"."T 4" r2 + Output: r2.c1 + Selected Partitions: 1..2 + Selected Subpartitions: ALL + +(32 rows) + +SELECT t1.c1, t2.c1 FROM ft4 t1 FULL JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 45 LIMIT 10; + c1 | c1 +-----+---- + 92 | + 94 | + 96 | 96 + 98 | + 100 | + | 3 + | 9 + | 15 + | 21 + | 27 +(10 rows) + +-- full outer join with restrictions on the joining relations +-- a. the joining relations are both base relations +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + Sort + Output: ft4.c1, ft5.c1 + Sort Key: ft4.c1, ft5.c1 + -> Foreign Scan + Output: ft4.c1, ft5.c1 + Node ID: 1 + Relations: (public.ft4) FULL JOIN (public.ft5) + Remote SQL: SELECT s4.c1, s5.c1 FROM ((SELECT c1 FROM "S 1"."T 3" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s4(c1) FULL JOIN (SELECT c1 FROM "S 1"."T 4" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s5(c1) ON (((s4.c1 = s5.c1)))) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT s4.c1, s5.c1 FROM ((SELECT c1 FROM "S 1"."T 3" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s4(c1) FULL JOIN (SELECT c1 FROM "S 1"."T 4" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s5(c1) ON (((s4.c1 = s5.c1)))) + Hash Full Join + Output: "T 3".c1, "T 4".c1 + Hash Cond: ("T 3".c1 = "T 4".c1) + -> Partition Iterator + Output: "T 3".c1, "T 3".c2, "T 3".c3 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 3" + Output: "T 3".c1, "T 3".c2, "T 3".c3 + Filter: (("T 3".c1 >= 50) AND ("T 3".c1 <= 60)) + Selected Partitions: 1..2 + -> Hash + Output: "T 4".c1 + -> Partition Iterator + Output: "T 4".c1 + Iterations: 1, Sub Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 4" + Output: "T 4".c1 + Filter: (("T 4".c1 >= 50) AND ("T 4".c1 <= 60)) + Selected Partitions: 2 + Selected Subpartitions: ALL + +(32 rows) + +SELECT t1.c1, t2.c1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1; + c1 | c1 +----+---- + 50 | + 52 | + 54 | 54 + 56 | + 58 | + 60 | 60 + | 51 + | 57 +(8 rows) + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT 1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t2 ON (TRUE) OFFSET 10 LIMIT 10; + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: 1 + Node ID: 1 + Relations: (public.ft4) FULL JOIN (public.ft5) + Remote SQL: SELECT NULL FROM ((SELECT NULL FROM "S 1"."T 3" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s4 FULL JOIN (SELECT NULL FROM "S 1"."T 4" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s5 ON (TRUE)) LIMIT 10::bigint OFFSET 10::bigint + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT NULL FROM ((SELECT NULL FROM "S 1"."T 3" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s4 FULL JOIN (SELECT NULL FROM "S 1"."T 4" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s5 ON (TRUE)) LIMIT 10::bigint OFFSET 10::bigint + Limit + Output: (NULL::text) + -> Subquery Scan on subquery + Output: NULL::text + -> Result + Output: (NULL::text), (NULL::text) + -> Append + -> Nested Loop Left Join + Output: (NULL::text), (NULL::text) + -> Partition Iterator + Output: NULL::text + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 3" + Output: NULL::text + Filter: (("S 1"."T 3".c1 >= 50) AND ("S 1"."T 3".c1 <= 60)) + Selected Partitions: 1..2 + -> Materialize + Output: (NULL::text) + -> Partition Iterator + Output: NULL::text + Iterations: 1, Sub Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 4" + Output: NULL::text + Filter: (("S 1"."T 4".c1 >= 50) AND ("S 1"."T 4".c1 <= 60)) + Selected Partitions: 2 + Selected Subpartitions: ALL + -> Nested Loop Left Anti Full Join + Output: (NULL::text), (NULL::text) + -> Partition Iterator + Output: NULL::text + Iterations: 1, Sub Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 4" + Output: NULL::text + Filter: (("S 1"."T 4".c1 >= 50) AND ("S 1"."T 4".c1 <= 60)) + Selected Partitions: 2 + Selected Subpartitions: ALL + -> Materialize + Output: (NULL::text) + -> Partition Iterator + Output: NULL::text + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 3" + Output: NULL::text + Filter: (("S 1"."T 3".c1 >= 50) AND ("S 1"."T 3".c1 <= 60)) + Selected Partitions: 1..2 + +(54 rows) + +SELECT 1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t2 ON (TRUE) OFFSET 10 LIMIT 10; + ?column? +---------- + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 +(10 rows) + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT count(*) FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t2 ON (TRUE) OFFSET 10 LIMIT 2; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Limit + Output: (count(*)) + -> Aggregate + Output: count(*) + -> Foreign Scan + Node ID: 1 + Relations: (public.ft4) FULL JOIN (public.ft5) + Remote SQL: SELECT NULL FROM ((SELECT NULL FROM "S 1"."T 3" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s4 FULL JOIN (SELECT NULL FROM "S 1"."T 4" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s5 ON (TRUE)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT NULL FROM ((SELECT NULL FROM "S 1"."T 3" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s4 FULL JOIN (SELECT NULL FROM "S 1"."T 4" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s5 ON (TRUE)) + Subquery Scan on subquery + Output: NULL::text + -> Result + Output: (NULL::text), (NULL::text) + -> Append + -> Nested Loop Left Join + Output: (NULL::text), (NULL::text) + -> Partition Iterator + Output: NULL::text + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 3" + Output: NULL::text + Filter: (("S 1"."T 3".c1 >= 50) AND ("S 1"."T 3".c1 <= 60)) + Selected Partitions: 1..2 + -> Materialize + Output: (NULL::text) + -> Partition Iterator + Output: NULL::text + Iterations: 1, Sub Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 4" + Output: NULL::text + Filter: (("S 1"."T 4".c1 >= 50) AND ("S 1"."T 4".c1 <= 60)) + Selected Partitions: 2 + Selected Subpartitions: ALL + -> Nested Loop Left Anti Full Join + Output: (NULL::text), (NULL::text) + -> Partition Iterator + Output: NULL::text + Iterations: 1, Sub Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 4" + Output: NULL::text + Filter: (("S 1"."T 4".c1 >= 50) AND ("S 1"."T 4".c1 <= 60)) + Selected Partitions: 2 + Selected Subpartitions: ALL + -> Materialize + Output: (NULL::text) + -> Partition Iterator + Output: NULL::text + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 3" + Output: NULL::text + Filter: (("S 1"."T 3".c1 >= 50) AND ("S 1"."T 3".c1 <= 60)) + Selected Partitions: 1..2 + +(55 rows) + +SELECT count(*) FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t2 ON (TRUE) OFFSET 10 LIMIT 2; + count +------- +(0 rows) + +-- b. one of the joining relations is a base relation and the other is a join +-- relation +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, ss.a, ss.b FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT t2.c1, t3.c1 FROM ft4 t2 LEFT JOIN ft5 t3 ON (t2.c1 = t3.c1) WHERE (t2.c1 between 50 and 60)) ss(a, b) ON (t1.c1 = ss.a) ORDER BY t1.c1, ss.a, ss.b; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Sort + Output: ft4.c1, t2.c1, t3.c1 + Sort Key: ft4.c1, t2.c1, t3.c1 + -> Foreign Scan + Output: ft4.c1, t2.c1, t3.c1 + Node ID: 1 + Relations: (public.ft4) FULL JOIN ((public.ft4 t2) LEFT JOIN (public.ft5 t3)) + Remote SQL: SELECT s4.c1, s8.c1, s8.c2 FROM ((SELECT c1 FROM "S 1"."T 3" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s4(c1) FULL JOIN (SELECT r5.c1, r6.c1 FROM ("S 1"."T 3" r5 LEFT JOIN "S 1"."T 4" r6 ON (((r5.c1 = r6.c1)))) WHERE ((r5.c1 >= 50)) AND ((r5.c1 <= 60))) s8(c1, c2) ON (((s4.c1 = s8.c1)))) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT s4.c1, s8.c1, s8.c2 FROM ((SELECT c1 FROM "S 1"."T 3" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s4(c1) FULL JOIN (SELECT r5.c1, r6.c1 FROM ("S 1"."T 3" r5 LEFT JOIN "S 1"."T 4" r6 ON (((r5.c1 = r6.c1)))) WHERE ((r5.c1 >= 50)) AND ((r5.c1 <= 60))) s8(c1, c2) ON (((s4.c1 = s8.c1)))) + Hash Full Join + Output: "T 3".c1, r5.c1, r6.c1 + Hash Cond: (r5.c1 = "T 3".c1) + -> Hash Right Join + Output: r5.c1, r6.c1 + Hash Cond: (r6.c1 = r5.c1) + -> Partition Iterator + Output: r6.c1, r6.c2, r6.c3 + Iterations: 2, Sub Iterations: 4 + -> Partitioned Seq Scan on "S 1"."T 4" r6 + Output: r6.c1, r6.c2, r6.c3 + Selected Partitions: 1..2 + Selected Subpartitions: ALL + -> Hash + Output: r5.c1 + -> Partition Iterator + Output: r5.c1 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 3" r5 + Output: r5.c1 + Filter: ((r5.c1 >= 50) AND (r5.c1 <= 60)) + Selected Partitions: 1..2 + -> Hash + Output: "T 3".c1 + -> Partition Iterator + Output: "T 3".c1 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 3" + Output: "T 3".c1 + Filter: (("T 3".c1 >= 50) AND ("T 3".c1 <= 60)) + Selected Partitions: 1..2 + +(43 rows) + +SELECT t1.c1, ss.a, ss.b FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT t2.c1, t3.c1 FROM ft4 t2 LEFT JOIN ft5 t3 ON (t2.c1 = t3.c1) WHERE (t2.c1 between 50 and 60)) ss(a, b) ON (t1.c1 = ss.a) ORDER BY t1.c1, ss.a, ss.b; + c1 | a | b +----+----+---- + 50 | 50 | + 52 | 52 | + 54 | 54 | 54 + 56 | 56 | + 58 | 58 | + 60 | 60 | 60 +(6 rows) + +-- c. test deparsing the remote query as nested subqueries +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, ss.a, ss.b FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT t2.c1, t3.c1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t2 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t3 ON (t2.c1 = t3.c1) WHERE t2.c1 IS NULL OR t2.c1 IS NOT NULL) ss(a, b) ON (t1.c1 = ss.a) ORDER BY t1.c1, ss.a, ss.b; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Sort + Output: public.ft4.c1, public.ft4.c1, ft5.c1 + Sort Key: public.ft4.c1, public.ft4.c1, ft5.c1 + -> Foreign Scan + Output: public.ft4.c1, public.ft4.c1, ft5.c1 + Node ID: 1 + Relations: (public.ft4) FULL JOIN ((public.ft4) FULL JOIN (public.ft5)) + Remote SQL: SELECT s4.c1, s10.c1, s10.c2 FROM ((SELECT c1 FROM "S 1"."T 3" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s4(c1) FULL JOIN (SELECT s8.c1, s9.c1 FROM ((SELECT c1 FROM "S 1"."T 3" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s8(c1) FULL JOIN (SELECT c1 FROM "S 1"."T 4" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s9(c1) ON (((s8.c1 = s9.c1)))) WHERE (((s8.c1 IS NULL) OR (s8.c1 IS NOT NULL)))) s10(c1, c2) ON (((s4.c1 = s10.c1)))) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT s4.c1, s10.c1, s10.c2 FROM ((SELECT c1 FROM "S 1"."T 3" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s4(c1) FULL JOIN (SELECT s8.c1, s9.c1 FROM ((SELECT c1 FROM "S 1"."T 3" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s8(c1) FULL JOIN (SELECT c1 FROM "S 1"."T 4" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s9(c1) ON (((s8.c1 = s9.c1)))) WHERE (((s8.c1 IS NULL) OR (s8.c1 IS NOT NULL)))) s10(c1, c2) ON (((s4.c1 = s10.c1)))) + Hash Full Join + Output: "S 1"."T 3".c1, "S 1"."T 3".c1, "T 4".c1 + Hash Cond: ("S 1"."T 3".c1 = "S 1"."T 3".c1) + -> Partition Iterator + Output: "S 1"."T 3".c1, "S 1"."T 3".c2, "S 1"."T 3".c3 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 3" + Output: "S 1"."T 3".c1, "S 1"."T 3".c2, "S 1"."T 3".c3 + Filter: (("S 1"."T 3".c1 >= 50) AND ("S 1"."T 3".c1 <= 60)) + Selected Partitions: 1..2 + -> Hash + Output: "S 1"."T 3".c1, "T 4".c1 + -> Hash Full Join + Output: "S 1"."T 3".c1, "T 4".c1 + Hash Cond: ("S 1"."T 3".c1 = "T 4".c1) + Filter: (("S 1"."T 3".c1 IS NULL) OR ("S 1"."T 3".c1 IS NOT NULL)) + -> Partition Iterator + Output: "S 1"."T 3".c1, "S 1"."T 3".c2, "S 1"."T 3".c3 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 3" + Output: "S 1"."T 3".c1, "S 1"."T 3".c2, "S 1"."T 3".c3 + Filter: (("S 1"."T 3".c1 >= 50) AND ("S 1"."T 3".c1 <= 60)) + Selected Partitions: 1..2 + -> Hash + Output: "T 4".c1 + -> Partition Iterator + Output: "T 4".c1 + Iterations: 1, Sub Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 4" + Output: "T 4".c1 + Filter: (("T 4".c1 >= 50) AND ("T 4".c1 <= 60)) + Selected Partitions: 2 + Selected Subpartitions: ALL + +(45 rows) + +SELECT t1.c1, ss.a, ss.b FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT t2.c1, t3.c1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t2 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t3 ON (t2.c1 = t3.c1) WHERE t2.c1 IS NULL OR t2.c1 IS NOT NULL) ss(a, b) ON (t1.c1 = ss.a) ORDER BY t1.c1, ss.a, ss.b; + c1 | a | b +----+----+---- + 50 | 50 | + 52 | 52 | + 54 | 54 | 54 + 56 | 56 | + 58 | 58 | + 60 | 60 | 60 + | | 51 + | | 57 +(8 rows) + +-- d. test deparsing rowmarked relations as subqueries +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, ss.a, ss.b FROM (SELECT c1 FROM "S 1"."T 3" WHERE c1 = 50) t1 INNER JOIN (SELECT t2.c1, t3.c1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t2 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t3 ON (t2.c1 = t3.c1) WHERE t2.c1 IS NULL OR t2.c1 IS NOT NULL) ss(a, b) ON (TRUE) ORDER BY t1.c1, ss.a, ss.b FOR UPDATE OF t1; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------- + Sort + Output: "T 3".c1, ft4.c1, ft5.c1, "T 3".ctid, "T 3".tableoid, ft4.*, ft5.* + Sort Key: ft4.c1, ft5.c1 + -> LockRows + Output: "T 3".c1, ft4.c1, ft5.c1, "T 3".ctid, "T 3".tableoid, ft4.*, ft5.* + -> Nested Loop + Output: "T 3".c1, ft4.c1, ft5.c1, "T 3".ctid, "T 3".tableoid, ft4.*, ft5.* + -> Partitioned Index Scan using t3_pkey on "S 1"."T 3" + Output: "T 3".c1, "T 3".ctid, "T 3".tableoid + Index Cond: ("T 3".c1 = 50) + Selected Partitions: 1 + -> Hash Full Join + Output: ft4.c1, ft4.*, ft5.c1, ft5.* + Hash Cond: (ft4.c1 = ft5.c1) + Filter: ((ft4.c1 IS NULL) OR (ft4.c1 IS NOT NULL)) + -> Foreign Scan on public.ft4 + Output: ft4.c1, ft4.* + Node ID: 1 + Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 3" WHERE ((c1 >= 50)) AND ((c1 <= 60)) + -> Hash + Output: ft5.c1, ft5.* + -> Foreign Scan on public.ft5 + Output: ft5.c1, ft5.* + Node ID: 2 + Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 4" WHERE ((c1 >= 50)) AND ((c1 <= 60)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c1, c2, c3 FROM "S 1"."T 3" WHERE ((c1 >= 50)) AND ((c1 <= 60)) + Partition Iterator + Output: c1, c2, c3 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 3" + Output: c1, c2, c3 + Filter: (("T 3".c1 >= 50) AND ("T 3".c1 <= 60)) + Selected Partitions: 1..2 + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c1, c2, c3 FROM "S 1"."T 4" WHERE ((c1 >= 50)) AND ((c1 <= 60)) + Partition Iterator + Output: c1, c2, c3 + Iterations: 1, Sub Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 4" + Output: c1, c2, c3 + Filter: (("T 4".c1 >= 50) AND ("T 4".c1 <= 60)) + Selected Partitions: 2 + Selected Subpartitions: ALL + +(45 rows) + +SELECT t1.c1, ss.a, ss.b FROM (SELECT c1 FROM "S 1"."T 3" WHERE c1 = 50) t1 INNER JOIN (SELECT t2.c1, t3.c1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t2 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t3 ON (t2.c1 = t3.c1) WHERE t2.c1 IS NULL OR t2.c1 IS NOT NULL) ss(a, b) ON (TRUE) ORDER BY t1.c1, ss.a, ss.b FOR UPDATE OF t1; + c1 | a | b +----+----+---- + 50 | 50 | + 50 | 52 | + 50 | 54 | 54 + 50 | 56 | + 50 | 58 | + 50 | 60 | 60 + 50 | | 51 + 50 | | 57 +(8 rows) + +-- full outer join + inner join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1, t3.c1 FROM ft4 t1 INNER JOIN ft5 t2 ON (t1.c1 = t2.c1 + 1 and t1.c1 between 50 and 60) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) ORDER BY t1.c1, t2.c1, t3.c1 LIMIT 10; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: t1.c1, t2.c1, t3.c1 + Node ID: 1 + Relations: ((public.ft4 t1) INNER JOIN (public.ft5 t2)) FULL JOIN (public.ft4 t3) + Remote SQL: SELECT r1.c1, r2.c1, r4.c1 FROM (("S 1"."T 3" r1 INNER JOIN "S 1"."T 4" r2 ON (((r1.c1 = (r2.c1 + 1))) AND (((r2.c1 + 1) >= 50)) AND (((r2.c1 + 1) <= 60)) AND ((r1.c1 >= 50)) AND ((r1.c1 <= 60)))) FULL JOIN "S 1"."T 3" r4 ON (((r2.c1 = r4.c1)))) ORDER BY r1.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST, r4.c1 ASC NULLS LAST LIMIT 10::bigint + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT r1.c1, r2.c1, r4.c1 FROM (("S 1"."T 3" r1 INNER JOIN "S 1"."T 4" r2 ON (((r1.c1 = (r2.c1 + 1))) AND (((r2.c1 + 1) >= 50)) AND (((r2.c1 + 1) <= 60)) AND ((r1.c1 >= 50)) AND ((r1.c1 <= 60)))) FULL JOIN "S 1"."T 3" r4 ON (((r2.c1 = r4.c1)))) ORDER BY r1.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST, r4.c1 ASC NULLS LAST LIMIT 10::bigint + Limit + Output: r1.c1, r2.c1, r4.c1 + -> Sort + Output: r1.c1, r2.c1, r4.c1 + Sort Key: r1.c1, r2.c1, r4.c1 + -> Hash Full Join + Output: r1.c1, r2.c1, r4.c1 + Hash Cond: (r4.c1 = r2.c1) + -> Partition Iterator + Output: r4.c1, r4.c2, r4.c3 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 3" r4 + Output: r4.c1, r4.c2, r4.c3 + Selected Partitions: 1..2 + -> Hash + Output: r1.c1, r2.c1 + -> Nested Loop + Output: r1.c1, r2.c1 + Join Filter: (r1.c1 = (r2.c1 + 1)) + -> Partition Iterator + Output: r2.c1, r2.c2, r2.c3 + Iterations: 2, Sub Iterations: 4 + -> Partitioned Seq Scan on "S 1"."T 4" r2 + Output: r2.c1, r2.c2, r2.c3 + Filter: (((r2.c1 + 1) >= 50) AND ((r2.c1 + 1) <= 60)) + Selected Partitions: 1..2 + Selected Subpartitions: ALL + -> Partition Iterator + Output: r1.c1, r1.c2, r1.c3 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 3" r1 + Output: r1.c1, r1.c2, r1.c3 + Filter: ((r1.c1 >= 50) AND (r1.c1 <= 60)) + Selected Partitions: 1..2 + +(43 rows) + +SELECT t1.c1, t2.c1, t3.c1 FROM ft4 t1 INNER JOIN ft5 t2 ON (t1.c1 = t2.c1 + 1 and t1.c1 between 50 and 60) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) ORDER BY t1.c1, t2.c1, t3.c1 LIMIT 10; + c1 | c1 | c1 +----+----+---- + 52 | 51 | + 58 | 57 | + | | 2 + | | 4 + | | 6 + | | 8 + | | 10 + | | 12 + | | 14 + | | 16 +(10 rows) + +-- full outer join three tables +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: t1.c1, t2.c2, t3.c3 + Node ID: 1 + Relations: ((public.ft2 t1) FULL JOIN (public.ft2 t2)) FULL JOIN (public.ft4 t3) + Remote SQL: SELECT r1."C 1", r2.c2, r4.c3 FROM (("S 1"."T 1" r1 FULL JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) FULL JOIN "S 1"."T 3" r4 ON (((r2."C 1" = r4.c1)))) LIMIT 10::bigint OFFSET 10::bigint + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT r1."C 1", r2.c2, r4.c3 FROM (("S 1"."T 1" r1 FULL JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) FULL JOIN "S 1"."T 3" r4 ON (((r2."C 1" = r4.c1)))) LIMIT 10::bigint OFFSET 10::bigint + Limit + Output: r1."C 1", r2.c2, r4.c3 + -> Hash Full Join + Output: r1."C 1", r2.c2, r4.c3 + Hash Cond: (r2."C 1" = r4.c1) + -> Merge Full Join + Output: r1."C 1", r2.c2, r2."C 1" + Merge Cond: (r1."C 1" = r2."C 1") + -> Partition Iterator + Output: r1."C 1" + Iterations: 2 + -> Partitioned Index Only Scan using t1_pkey on "S 1"."T 1" r1 + Output: r1."C 1" + Selected Partitions: 1..2 + -> Materialize + Output: r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8 + -> Partition Iterator + Output: r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8 + Iterations: 2 + -> Partitioned Index Scan using t1_pkey on "S 1"."T 1" r2 + Output: r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8 + Selected Partitions: 1..2 + -> Hash + Output: r4.c3, r4.c1 + -> Partition Iterator + Output: r4.c3, r4.c1 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 3" r4 + Output: r4.c3, r4.c1 + Selected Partitions: 1..2 + +(39 rows) + +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + c1 | c2 | c3 +----+----+-------- + 11 | 1 | + 12 | 2 | AAA012 + 13 | 3 | + 14 | 4 | AAA014 + 15 | 5 | + 16 | 6 | AAA016 + 17 | 7 | + 18 | 8 | AAA018 + 19 | 9 | + 20 | 0 | AAA020 +(10 rows) + +-- full outer join + right outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: t1.c1, t2.c2, t3.c3 + Node ID: 1 + Relations: ((public.ft4 t3) LEFT JOIN (public.ft2 t2)) LEFT JOIN (public.ft2 t1) + Remote SQL: SELECT r1."C 1", r2.c2, r4.c3 FROM (("S 1"."T 3" r4 LEFT JOIN "S 1"."T 1" r2 ON (((r2."C 1" = r4.c1)))) LEFT JOIN "S 1"."T 1" r1 ON (((r1."C 1" = r2."C 1")))) LIMIT 10::bigint OFFSET 10::bigint + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT r1."C 1", r2.c2, r4.c3 FROM (("S 1"."T 3" r4 LEFT JOIN "S 1"."T 1" r2 ON (((r2."C 1" = r4.c1)))) LEFT JOIN "S 1"."T 1" r1 ON (((r1."C 1" = r2."C 1")))) LIMIT 10::bigint OFFSET 10::bigint + Limit + Output: r1."C 1", r2.c2, r4.c3 + -> Merge Right Join + Output: r1."C 1", r2.c2, r4.c3 + Merge Cond: (r2."C 1" = r4.c1) + -> Merge Left Join + Output: r2.c2, r2."C 1", r1."C 1" + Merge Cond: (r2."C 1" = r1."C 1") + -> Partition Iterator + Output: r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8 + Iterations: 2 + -> Partitioned Index Scan using t1_pkey on "S 1"."T 1" r2 + Output: r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8 + Selected Partitions: 1..2 + -> Materialize + Output: r1."C 1" + -> Partition Iterator + Output: r1."C 1" + Iterations: 2 + -> Partitioned Index Only Scan using t1_pkey on "S 1"."T 1" r1 + Output: r1."C 1" + Selected Partitions: 1..2 + -> Sort + Output: r4.c3, r4.c1 + Sort Key: r4.c1 + -> Partition Iterator + Output: r4.c3, r4.c1 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 3" r4 + Output: r4.c3, r4.c1 + Selected Partitions: 1..2 + +(40 rows) + +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + c1 | c2 | c3 +----+----+-------- + 22 | 2 | AAA022 + 24 | 4 | AAA024 + 26 | 6 | AAA026 + 28 | 8 | AAA028 + 30 | 0 | AAA030 + 32 | 2 | AAA032 + 34 | 4 | AAA034 + 36 | 6 | AAA036 + 38 | 8 | AAA038 + 40 | 0 | AAA040 +(10 rows) + +-- right outer join + full outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 RIGHT JOIN ft2 t2 ON (t1.c1 = t2.c1) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: t1.c1, t2.c2, t3.c3 + Node ID: 1 + Relations: ((public.ft2 t2) LEFT JOIN (public.ft2 t1)) FULL JOIN (public.ft4 t3) + Remote SQL: SELECT r1."C 1", r2.c2, r4.c3 FROM (("S 1"."T 1" r2 LEFT JOIN "S 1"."T 1" r1 ON (((r1."C 1" = r2."C 1")))) FULL JOIN "S 1"."T 3" r4 ON (((r2."C 1" = r4.c1)))) LIMIT 10::bigint OFFSET 10::bigint + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT r1."C 1", r2.c2, r4.c3 FROM (("S 1"."T 1" r2 LEFT JOIN "S 1"."T 1" r1 ON (((r1."C 1" = r2."C 1")))) FULL JOIN "S 1"."T 3" r4 ON (((r2."C 1" = r4.c1)))) LIMIT 10::bigint OFFSET 10::bigint + Limit + Output: r1."C 1", r2.c2, r4.c3 + -> Hash Full Join + Output: r1."C 1", r2.c2, r4.c3 + Hash Cond: (r2."C 1" = r4.c1) + -> Merge Left Join + Output: r2.c2, r2."C 1", r1."C 1" + Merge Cond: (r2."C 1" = r1."C 1") + -> Partition Iterator + Output: r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8 + Iterations: 2 + -> Partitioned Index Scan using t1_pkey on "S 1"."T 1" r2 + Output: r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8 + Selected Partitions: 1..2 + -> Materialize + Output: r1."C 1" + -> Partition Iterator + Output: r1."C 1" + Iterations: 2 + -> Partitioned Index Only Scan using t1_pkey on "S 1"."T 1" r1 + Output: r1."C 1" + Selected Partitions: 1..2 + -> Hash + Output: r4.c3, r4.c1 + -> Partition Iterator + Output: r4.c3, r4.c1 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 3" r4 + Output: r4.c3, r4.c1 + Selected Partitions: 1..2 + +(39 rows) + +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 RIGHT JOIN ft2 t2 ON (t1.c1 = t2.c1) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + c1 | c2 | c3 +----+----+-------- + 11 | 1 | + 12 | 2 | AAA012 + 13 | 3 | + 14 | 4 | AAA014 + 15 | 5 | + 16 | 6 | AAA016 + 17 | 7 | + 18 | 8 | AAA018 + 19 | 9 | + 20 | 0 | AAA020 +(10 rows) + +-- full outer join + left outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) LEFT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: t1.c1, t2.c2, t3.c3 + Node ID: 1 + Relations: ((public.ft2 t1) FULL JOIN (public.ft2 t2)) LEFT JOIN (public.ft4 t3) + Remote SQL: SELECT r1."C 1", r2.c2, r4.c3 FROM (("S 1"."T 1" r1 FULL JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) LEFT JOIN "S 1"."T 3" r4 ON (((r2."C 1" = r4.c1)))) LIMIT 10::bigint OFFSET 10::bigint + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT r1."C 1", r2.c2, r4.c3 FROM (("S 1"."T 1" r1 FULL JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) LEFT JOIN "S 1"."T 3" r4 ON (((r2."C 1" = r4.c1)))) LIMIT 10::bigint OFFSET 10::bigint + Limit + Output: r1."C 1", r2.c2, r4.c3 + -> Hash Left Join + Output: r1."C 1", r2.c2, r4.c3 + Hash Cond: (r2."C 1" = r4.c1) + -> Merge Full Join + Output: r1."C 1", r2.c2, r2."C 1" + Merge Cond: (r1."C 1" = r2."C 1") + -> Partition Iterator + Output: r1."C 1" + Iterations: 2 + -> Partitioned Index Only Scan using t1_pkey on "S 1"."T 1" r1 + Output: r1."C 1" + Selected Partitions: 1..2 + -> Materialize + Output: r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8 + -> Partition Iterator + Output: r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8 + Iterations: 2 + -> Partitioned Index Scan using t1_pkey on "S 1"."T 1" r2 + Output: r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8 + Selected Partitions: 1..2 + -> Hash + Output: r4.c3, r4.c1 + -> Partition Iterator + Output: r4.c3, r4.c1 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 3" r4 + Output: r4.c3, r4.c1 + Selected Partitions: 1..2 + +(39 rows) + +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) LEFT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + c1 | c2 | c3 +----+----+-------- + 11 | 1 | + 12 | 2 | AAA012 + 13 | 3 | + 14 | 4 | AAA014 + 15 | 5 | + 16 | 6 | AAA016 + 17 | 7 | + 18 | 8 | AAA018 + 19 | 9 | + 20 | 0 | AAA020 +(10 rows) + +-- left outer join + full outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: t1.c1, t2.c2, t3.c3 + Node ID: 1 + Relations: ((public.ft2 t1) LEFT JOIN (public.ft2 t2)) FULL JOIN (public.ft4 t3) + Remote SQL: SELECT r1."C 1", r2.c2, r4.c3 FROM (("S 1"."T 1" r1 LEFT JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) FULL JOIN "S 1"."T 3" r4 ON (((r2."C 1" = r4.c1)))) LIMIT 10::bigint OFFSET 10::bigint + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT r1."C 1", r2.c2, r4.c3 FROM (("S 1"."T 1" r1 LEFT JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) FULL JOIN "S 1"."T 3" r4 ON (((r2."C 1" = r4.c1)))) LIMIT 10::bigint OFFSET 10::bigint + Limit + Output: r1."C 1", r2.c2, r4.c3 + -> Hash Full Join + Output: r1."C 1", r2.c2, r4.c3 + Hash Cond: (r2."C 1" = r4.c1) + -> Merge Left Join + Output: r1."C 1", r2.c2, r2."C 1" + Merge Cond: (r1."C 1" = r2."C 1") + -> Partition Iterator + Output: r1."C 1" + Iterations: 2 + -> Partitioned Index Only Scan using t1_pkey on "S 1"."T 1" r1 + Output: r1."C 1" + Selected Partitions: 1..2 + -> Materialize + Output: r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8 + -> Partition Iterator + Output: r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8 + Iterations: 2 + -> Partitioned Index Scan using t1_pkey on "S 1"."T 1" r2 + Output: r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8 + Selected Partitions: 1..2 + -> Hash + Output: r4.c3, r4.c1 + -> Partition Iterator + Output: r4.c3, r4.c1 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 3" r4 + Output: r4.c3, r4.c1 + Selected Partitions: 1..2 + +(39 rows) + +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + c1 | c2 | c3 +----+----+-------- + 11 | 1 | + 12 | 2 | AAA012 + 13 | 3 | + 14 | 4 | AAA014 + 15 | 5 | + 16 | 6 | AAA016 + 17 | 7 | + 18 | 8 | AAA018 + 19 | 9 | + 20 | 0 | AAA020 +(10 rows) + +SET enable_memoize TO off; --nspt +ERROR: unrecognized configuration parameter "enable_memoize" +-- right outer join + left outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 RIGHT JOIN ft2 t2 ON (t1.c1 = t2.c1) LEFT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: t1.c1, t2.c2, t3.c3 + Node ID: 1 + Relations: ((public.ft2 t2) LEFT JOIN (public.ft2 t1)) LEFT JOIN (public.ft4 t3) + Remote SQL: SELECT r1."C 1", r2.c2, r4.c3 FROM (("S 1"."T 1" r2 LEFT JOIN "S 1"."T 1" r1 ON (((r1."C 1" = r2."C 1")))) LEFT JOIN "S 1"."T 3" r4 ON (((r2."C 1" = r4.c1)))) LIMIT 10::bigint OFFSET 10::bigint + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT r1."C 1", r2.c2, r4.c3 FROM (("S 1"."T 1" r2 LEFT JOIN "S 1"."T 1" r1 ON (((r1."C 1" = r2."C 1")))) LEFT JOIN "S 1"."T 3" r4 ON (((r2."C 1" = r4.c1)))) LIMIT 10::bigint OFFSET 10::bigint + Limit + Output: r1."C 1", r2.c2, r4.c3 + -> Hash Left Join + Output: r1."C 1", r2.c2, r4.c3 + Hash Cond: (r2."C 1" = r4.c1) + -> Merge Left Join + Output: r2.c2, r2."C 1", r1."C 1" + Merge Cond: (r2."C 1" = r1."C 1") + -> Partition Iterator + Output: r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8 + Iterations: 2 + -> Partitioned Index Scan using t1_pkey on "S 1"."T 1" r2 + Output: r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8 + Selected Partitions: 1..2 + -> Materialize + Output: r1."C 1" + -> Partition Iterator + Output: r1."C 1" + Iterations: 2 + -> Partitioned Index Only Scan using t1_pkey on "S 1"."T 1" r1 + Output: r1."C 1" + Selected Partitions: 1..2 + -> Hash + Output: r4.c3, r4.c1 + -> Partition Iterator + Output: r4.c3, r4.c1 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 3" r4 + Output: r4.c3, r4.c1 + Selected Partitions: 1..2 + +(39 rows) + +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 RIGHT JOIN ft2 t2 ON (t1.c1 = t2.c1) LEFT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + c1 | c2 | c3 +----+----+-------- + 11 | 1 | + 12 | 2 | AAA012 + 13 | 3 | + 14 | 4 | AAA014 + 15 | 5 | + 16 | 6 | AAA016 + 17 | 7 | + 18 | 8 | AAA018 + 19 | 9 | + 20 | 0 | AAA020 +(10 rows) + +RESET enable_memoize; --nspt +ERROR: unrecognized configuration parameter "enable_memoize" +-- left outer join + right outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: t1.c1, t2.c2, t3.c3 + Node ID: 1 + Relations: (public.ft4 t3) LEFT JOIN ((public.ft2 t1) INNER JOIN (public.ft2 t2)) + Remote SQL: SELECT r1."C 1", r2.c2, r4.c3 FROM ("S 1"."T 3" r4 LEFT JOIN ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) ON (((r2."C 1" = r4.c1)))) LIMIT 10::bigint OFFSET 10::bigint + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT r1."C 1", r2.c2, r4.c3 FROM ("S 1"."T 3" r4 LEFT JOIN ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) ON (((r2."C 1" = r4.c1)))) LIMIT 10::bigint OFFSET 10::bigint + Limit + Output: r1."C 1", r2.c2, r4.c3 + -> Merge Right Join + Output: r1."C 1", r2.c2, r4.c3 + Merge Cond: (r2."C 1" = r4.c1) + -> Merge Join + Output: r1."C 1", r2.c2, r2."C 1" + Merge Cond: (r1."C 1" = r2."C 1") + -> Partition Iterator + Output: r1."C 1" + Iterations: 2 + -> Partitioned Index Only Scan using t1_pkey on "S 1"."T 1" r1 + Output: r1."C 1" + Selected Partitions: 1..2 + -> Materialize + Output: r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8 + -> Partition Iterator + Output: r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8 + Iterations: 2 + -> Partitioned Index Scan using t1_pkey on "S 1"."T 1" r2 + Output: r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8 + Selected Partitions: 1..2 + -> Sort + Output: r4.c3, r4.c1 + Sort Key: r4.c1 + -> Partition Iterator + Output: r4.c3, r4.c1 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 3" r4 + Output: r4.c3, r4.c1 + Selected Partitions: 1..2 + +(40 rows) + +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + c1 | c2 | c3 +----+----+-------- + 22 | 2 | AAA022 + 24 | 4 | AAA024 + 26 | 6 | AAA026 + 28 | 8 | AAA028 + 30 | 0 | AAA030 + 32 | 2 | AAA032 + 34 | 4 | AAA034 + 36 | 6 | AAA036 + 38 | 8 | AAA038 + 40 | 0 | AAA040 +(10 rows) + +-- full outer join + WHERE clause, only matched rows +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft4 t1 FULL JOIN ft5 t2 ON (t1.c1 = t2.c1) WHERE (t1.c1 = t2.c1 OR t1.c1 IS NULL) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Limit + Output: t1.c1, t2.c1 + -> Sort + Output: t1.c1, t2.c1 + Sort Key: t1.c1, t2.c1 + -> Foreign Scan + Output: t1.c1, t2.c1 + Node ID: 1 + Relations: (public.ft4 t1) FULL JOIN (public.ft5 t2) + Remote SQL: SELECT r1.c1, r2.c1 FROM ("S 1"."T 3" r1 FULL JOIN "S 1"."T 4" r2 ON (((r1.c1 = r2.c1)))) WHERE (((r1.c1 = r2.c1) OR (r1.c1 IS NULL))) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT r1.c1, r2.c1 FROM ("S 1"."T 3" r1 FULL JOIN "S 1"."T 4" r2 ON (((r1.c1 = r2.c1)))) WHERE (((r1.c1 = r2.c1) OR (r1.c1 IS NULL))) + Hash Full Join + Output: r1.c1, r2.c1 + Hash Cond: (r1.c1 = r2.c1) + Filter: ((r1.c1 = r2.c1) OR (r1.c1 IS NULL)) + -> Partition Iterator + Output: r1.c1, r1.c2, r1.c3 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 3" r1 + Output: r1.c1, r1.c2, r1.c3 + Selected Partitions: 1..2 + -> Hash + Output: r2.c1 + -> Partition Iterator + Output: r2.c1 + Iterations: 2, Sub Iterations: 4 + -> Partitioned Seq Scan on "S 1"."T 4" r2 + Output: r2.c1 + Selected Partitions: 1..2 + Selected Subpartitions: ALL + +(33 rows) + +SELECT t1.c1, t2.c1 FROM ft4 t1 FULL JOIN ft5 t2 ON (t1.c1 = t2.c1) WHERE (t1.c1 = t2.c1 OR t1.c1 IS NULL) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; + c1 | c1 +----+---- + 66 | 66 + 72 | 72 + 78 | 78 + 84 | 84 + 90 | 90 + 96 | 96 + | 3 + | 9 + | 15 + | 21 +(10 rows) + +-- full outer join + WHERE clause with shippable extensions set +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t1.c3 FROM ft1 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE postgres_fdw_abs(t1.c1) > 0 OFFSET 10 LIMIT 10; + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------------- + Limit + Output: t1.c1, t2.c2, t1.c3 + -> Foreign Scan + Output: t1.c1, t2.c2, t1.c3 + Filter: (postgres_fdw_abs(t1.c1) > 0) + Node ID: 1 + Relations: (public.ft1 t1) FULL JOIN (public.ft2 t2) + Remote SQL: SELECT r1."C 1", r1.c3, r2.c2 FROM ("S 1"."T 1" r1 FULL JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT r1."C 1", r1.c3, r2.c2 FROM ("S 1"."T 1" r1 FULL JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) + Hash Full Join + Output: r1."C 1", r1.c3, r2.c2 + Hash Cond: (r1."C 1" = r2."C 1") + -> Partition Iterator + Output: r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" r1 + Output: r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8 + Selected Partitions: 1..2 + -> Hash + Output: r2.c2, r2."C 1" + -> Partition Iterator + Output: r2.c2, r2."C 1" + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" r2 + Output: r2.c2, r2."C 1" + Selected Partitions: 1..2 + +(29 rows) + +ALTER SERVER loopback OPTIONS (DROP extensions); --nspt +ERROR: option "extensions" not found +-- full outer join + WHERE clause with shippable extensions not set +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t1.c3 FROM ft1 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE postgres_fdw_abs(t1.c1) > 0 OFFSET 10 LIMIT 10; + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------------- + Limit + Output: t1.c1, t2.c2, t1.c3 + -> Foreign Scan + Output: t1.c1, t2.c2, t1.c3 + Filter: (postgres_fdw_abs(t1.c1) > 0) + Node ID: 1 + Relations: (public.ft1 t1) FULL JOIN (public.ft2 t2) + Remote SQL: SELECT r1."C 1", r1.c3, r2.c2 FROM ("S 1"."T 1" r1 FULL JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT r1."C 1", r1.c3, r2.c2 FROM ("S 1"."T 1" r1 FULL JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) + Hash Full Join + Output: r1."C 1", r1.c3, r2.c2 + Hash Cond: (r1."C 1" = r2."C 1") + -> Partition Iterator + Output: r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" r1 + Output: r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8 + Selected Partitions: 1..2 + -> Hash + Output: r2.c2, r2."C 1" + -> Partition Iterator + Output: r2.c2, r2."C 1" + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" r2 + Output: r2.c2, r2."C 1" + Selected Partitions: 1..2 + +(29 rows) + +ALTER SERVER loopback OPTIONS (ADD extensions 'postgres_fdw'); --nspt +ERROR: invalid option "extensions" +HINT: Valid options in this context are: service, connect_timeout, dbname, host, remote_nodename, hostaddr, port, localhost, localport, application_name, fencedUdfRPCMode, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, rw_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, krbsrvname, prototype, connection_info, connectionExtraInfo, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, updatable +-- join two tables with FOR UPDATE clause +-- tests whole-row reference for row marks +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR UPDATE OF t1; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------- + Limit + Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* + -> Sort + Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* + Sort Key: t1.c3, t1.c1 + -> LockRows + Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* + -> Hash Join + Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* + Hash Cond: (t1.c1 = t2.c1) + -> Foreign Scan on public.ft1 t1 + Output: t1.c1, t1.c3, t1.* + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" FOR UPDATE + -> Hash + Output: t2.c1, t2.* + -> Foreign Scan on public.ft2 t2 + Output: t2.c1, t2.* + Node ID: 2 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" FOR UPDATE + LockRows + Output: "C 1", c2, c3, c4, c5, c6, c7, c8, ctid, tableoid + -> Partition Iterator + Output: "C 1", c2, c3, c4, c5, c6, c7, c8, ctid, tableoid + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8, ctid, tableoid + Selected Partitions: 1..2 + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" + Partition Iterator + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Selected Partitions: 1..2 + +(39 rows) + +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR UPDATE OF t1; + c1 | c1 +-----+----- + 101 | 101 + 102 | 102 + 103 | 103 + 104 | 104 + 105 | 105 + 106 | 106 + 107 | 107 + 108 | 108 + 109 | 109 + 110 | 110 +(10 rows) + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR UPDATE; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------ + Limit + Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* + -> Sort + Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* + Sort Key: t1.c3, t1.c1 + -> LockRows + Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* + -> Hash Join + Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* + Hash Cond: (t2.c1 = t1.c1) + -> Foreign Scan on public.ft2 t2 + Output: t2.c1, t2.* + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" FOR UPDATE + -> Hash + Output: t1.c1, t1.c3, t1.* + -> Foreign Scan on public.ft1 t1 + Output: t1.c1, t1.c3, t1.* + Node ID: 2 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" FOR UPDATE + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" FOR UPDATE + LockRows + Output: "C 1", c2, c3, c4, c5, c6, c7, c8, ctid, tableoid + -> Partition Iterator + Output: "C 1", c2, c3, c4, c5, c6, c7, c8, ctid, tableoid + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8, ctid, tableoid + Selected Partitions: 1..2 + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" FOR UPDATE + LockRows + Output: "C 1", c2, c3, c4, c5, c6, c7, c8, ctid, tableoid + -> Partition Iterator + Output: "C 1", c2, c3, c4, c5, c6, c7, c8, ctid, tableoid + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8, ctid, tableoid + Selected Partitions: 1..2 + +(41 rows) + +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR UPDATE; + c1 | c1 +-----+----- + 101 | 101 + 102 | 102 + 103 | 103 + 104 | 104 + 105 | 105 + 106 | 106 + 107 | 107 + 108 | 108 + 109 | 109 + 110 | 110 +(10 rows) + +-- join two tables with FOR SHARE clause +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR SHARE OF t1; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------- + Limit + Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* + -> Sort + Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* + Sort Key: t1.c3, t1.c1 + -> LockRows + Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* + -> Hash Join + Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* + Hash Cond: (t1.c1 = t2.c1) + -> Foreign Scan on public.ft1 t1 + Output: t1.c1, t1.c3, t1.* + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" FOR SHARE + -> Hash + Output: t2.c1, t2.* + -> Foreign Scan on public.ft2 t2 + Output: t2.c1, t2.* + Node ID: 2 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" FOR SHARE + LockRows + Output: "C 1", c2, c3, c4, c5, c6, c7, c8, ctid, tableoid + -> Partition Iterator + Output: "C 1", c2, c3, c4, c5, c6, c7, c8, ctid, tableoid + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8, ctid, tableoid + Selected Partitions: 1..2 + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" + Partition Iterator + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Selected Partitions: 1..2 + +(39 rows) + +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR SHARE OF t1; + c1 | c1 +-----+----- + 101 | 101 + 102 | 102 + 103 | 103 + 104 | 104 + 105 | 105 + 106 | 106 + 107 | 107 + 108 | 108 + 109 | 109 + 110 | 110 +(10 rows) + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR SHARE; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------- + Limit + Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* + -> Sort + Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* + Sort Key: t1.c3, t1.c1 + -> LockRows + Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* + -> Hash Join + Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* + Hash Cond: (t2.c1 = t1.c1) + -> Foreign Scan on public.ft2 t2 + Output: t2.c1, t2.* + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" FOR SHARE + -> Hash + Output: t1.c1, t1.c3, t1.* + -> Foreign Scan on public.ft1 t1 + Output: t1.c1, t1.c3, t1.* + Node ID: 2 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" FOR SHARE + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" FOR SHARE + LockRows + Output: "C 1", c2, c3, c4, c5, c6, c7, c8, ctid, tableoid + -> Partition Iterator + Output: "C 1", c2, c3, c4, c5, c6, c7, c8, ctid, tableoid + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8, ctid, tableoid + Selected Partitions: 1..2 + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" FOR SHARE + LockRows + Output: "C 1", c2, c3, c4, c5, c6, c7, c8, ctid, tableoid + -> Partition Iterator + Output: "C 1", c2, c3, c4, c5, c6, c7, c8, ctid, tableoid + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8, ctid, tableoid + Selected Partitions: 1..2 + +(41 rows) + +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR SHARE; + c1 | c1 +-----+----- + 101 | 101 + 102 | 102 + 103 | 103 + 104 | 104 + 105 | 105 + 106 | 106 + 107 | 107 + 108 | 108 + 109 | 109 + 110 | 110 +(10 rows) + +-- join in CTE +EXPLAIN (VERBOSE, COSTS OFF) +WITH t (c1_1, c1_3, c2_1) AS MATERIALIZED (SELECT t1.c1, t1.c3, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1)) SELECT c1_1, c2_1 FROM t ORDER BY c1_3, c1_1 OFFSET 100 LIMIT 10; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------------- + Limit + Output: t.c1_1, t.c2_1, t.c1_3 + CTE t + -> Foreign Scan + Output: t1.c1, t1.c3, t2.c1 + Node ID: 1 + Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2) + Remote SQL: SELECT r1."C 1", r1.c3, r2."C 1" FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) + -> Sort + Output: t.c1_1, t.c2_1, t.c1_3 + Sort Key: t.c1_3, t.c1_1 + -> CTE Scan on t + Output: t.c1_1, t.c2_1, t.c1_3 + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT r1."C 1", r1.c3, r2."C 1" FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) + Hash Join + Output: r1."C 1", r1.c3, r2."C 1" + Hash Cond: (r1."C 1" = r2."C 1") + -> Partition Iterator + Output: r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" r1 + Output: r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8 + Selected Partitions: 1..2 + -> Hash + Output: r2."C 1" + -> Partition Iterator + Output: r2."C 1" + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" r2 + Output: r2."C 1" + Selected Partitions: 1..2 + +(34 rows) + +WITH t (c1_1, c1_3, c2_1) AS MATERIALIZED (SELECT t1.c1, t1.c3, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1)) SELECT c1_1, c2_1 FROM t ORDER BY c1_3, c1_1 OFFSET 100 LIMIT 10; + c1_1 | c2_1 +------+------ + 101 | 101 + 102 | 102 + 103 | 103 + 104 | 104 + 105 | 105 + 106 | 106 + 107 | 107 + 108 | 108 + 109 | 109 + 110 | 110 +(10 rows) + +-- ctid with whole-row reference, currently, does not support system column +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.ctid, t1, t2, t1.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; +ERROR: column t1.ctid does not exist +LINE 2: SELECT t1.ctid, t1, t2, t1.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1... + ^ +CONTEXT: referenced column: ctid +-- SEMI JOIN, not pushed down +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1 FROM ft1 t1 WHERE EXISTS (SELECT 1 FROM ft2 t2 WHERE t1.c1 = t2.c1) ORDER BY t1.c1 OFFSET 100 LIMIT 10; + QUERY PLAN +------------------------------------------------------------------------ + Limit + Output: t1.c1 + -> Sort + Output: t1.c1 + Sort Key: t1.c1 + -> Hash Semi Join + Output: t1.c1 + Hash Cond: (t1.c1 = t2.c1) + -> Foreign Scan on public.ft1 t1 + Output: t1.c1 + Node ID: 1 + Remote SQL: SELECT "C 1" FROM "S 1"."T 1" + -> Hash + Output: t2.c1 + -> Foreign Scan on public.ft2 t2 + Output: t2.c1 + Node ID: 2 + Remote SQL: SELECT "C 1" FROM "S 1"."T 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1" FROM "S 1"."T 1" + Partition Iterator + Output: "C 1" + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: "C 1" + Selected Partitions: 1..2 + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1" FROM "S 1"."T 1" + Partition Iterator + Output: "C 1" + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: "C 1" + Selected Partitions: 1..2 + +(35 rows) + +SELECT t1.c1 FROM ft1 t1 WHERE EXISTS (SELECT 1 FROM ft2 t2 WHERE t1.c1 = t2.c1) ORDER BY t1.c1 OFFSET 100 LIMIT 10; + c1 +----- + 101 + 102 + 103 + 104 + 105 + 106 + 107 + 108 + 109 + 110 +(10 rows) + +-- ANTI JOIN, not pushed down +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1 FROM ft1 t1 WHERE NOT EXISTS (SELECT 1 FROM ft2 t2 WHERE t1.c1 = t2.c2) ORDER BY t1.c1 OFFSET 100 LIMIT 10; + QUERY PLAN +------------------------------------------------------------------------------------------------------ + Limit + Output: t1.c1 + -> Merge Anti Join + Output: t1.c1 + Merge Cond: (t1.c1 = t2.c2) + -> Foreign Scan on public.ft1 t1 + Output: t1.c1 + Node ID: 1 + Remote SQL: SELECT "C 1" FROM "S 1"."T 1" ORDER BY "C 1" ASC NULLS LAST + -> Materialize + Output: t2.c2 + -> Foreign Scan on public.ft2 t2 + Output: t2.c2 + Node ID: 2 + Remote SQL: SELECT c2 FROM "S 1"."T 1" ORDER BY c2 ASC NULLS LAST + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1" FROM "S 1"."T 1" ORDER BY "C 1" ASC NULLS LAST + Partition Iterator + Output: "C 1" + Iterations: 2 + -> Partitioned Index Only Scan using t1_pkey on "S 1"."T 1" + Output: "C 1" + Selected Partitions: 1..2 + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c2 FROM "S 1"."T 1" ORDER BY c2 ASC NULLS LAST + Sort + Output: c2 + Sort Key: "T 1".c2 + -> Partition Iterator + Output: c2 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: c2 + Selected Partitions: 1..2 + +(35 rows) + +SELECT t1.c1 FROM ft1 t1 WHERE NOT EXISTS (SELECT 1 FROM ft2 t2 WHERE t1.c1 = t2.c2) ORDER BY t1.c1 OFFSET 100 LIMIT 10; + c1 +----- + 110 + 111 + 112 + 113 + 114 + 115 + 116 + 117 + 118 + 119 +(10 rows) + +-- CROSS JOIN can be pushed down +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 CROSS JOIN ft2 t2 ORDER BY t1.c1, t2.c1 OFFSET 100 LIMIT 10; + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: t1.c1, t2.c1 + Node ID: 1 + Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2) + Remote SQL: SELECT r1."C 1", r2."C 1" FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (TRUE)) ORDER BY r1."C 1" ASC NULLS LAST, r2."C 1" ASC NULLS LAST LIMIT 10::bigint OFFSET 100::bigint + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT r1."C 1", r2."C 1" FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (TRUE)) ORDER BY r1."C 1" ASC NULLS LAST, r2."C 1" ASC NULLS LAST LIMIT 10::bigint OFFSET 100::bigint + Limit + Output: r1."C 1", r2."C 1" + -> Sort + Output: r1."C 1", r2."C 1" + Sort Key: r1."C 1", r2."C 1" + -> Nested Loop + Output: r1."C 1", r2."C 1" + -> Partition Iterator + Output: r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" r1 + Output: r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8 + Selected Partitions: 1..2 + -> Materialize + Output: r2."C 1" + -> Partition Iterator + Output: r2."C 1" + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" r2 + Output: r2."C 1" + Selected Partitions: 1..2 + +(30 rows) + +SELECT t1.c1, t2.c1 FROM ft1 t1 CROSS JOIN ft2 t2 ORDER BY t1.c1, t2.c1 OFFSET 100 LIMIT 10; + c1 | c1 +----+----- + 1 | 101 + 1 | 102 + 1 | 103 + 1 | 104 + 1 | 105 + 1 | 106 + 1 | 107 + 1 | 108 + 1 | 109 + 1 | 110 +(10 rows) + +-- different server, not pushed down. No result expected. +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft5 t1 JOIN ft6 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 100 LIMIT 10; + QUERY PLAN +------------------------------------------------------------------------------------------------ + Limit + Output: t1.c1, t2.c1 + -> Merge Join + Output: t1.c1, t2.c1 + Merge Cond: (t1.c1 = t2.c1) + -> Foreign Scan on public.ft5 t1 + Output: t1.c1 + Node ID: 1 + Remote SQL: SELECT c1 FROM "S 1"."T 4" ORDER BY c1 ASC NULLS LAST + -> Materialize + Output: t2.c1 + -> Foreign Scan on public.ft6 t2 + Output: t2.c1 + Node ID: 2 + Remote SQL: SELECT c1 FROM "S 1"."T 4" ORDER BY c1 ASC NULLS LAST + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c1 FROM "S 1"."T 4" ORDER BY c1 ASC NULLS LAST + Sort + Output: c1 + Sort Key: "T 4".c1 + -> Partition Iterator + Output: c1 + Iterations: 2, Sub Iterations: 4 + -> Partitioned Seq Scan on "S 1"."T 4" + Output: c1 + Selected Partitions: 1..2 + Selected Subpartitions: ALL + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c1 FROM "S 1"."T 4" ORDER BY c1 ASC NULLS LAST + Sort + Output: c1 + Sort Key: "T 4".c1 + -> Partition Iterator + Output: c1 + Iterations: 2, Sub Iterations: 4 + -> Partitioned Seq Scan on "S 1"."T 4" + Output: c1 + Selected Partitions: 1..2 + Selected Subpartitions: ALL + +(40 rows) + +SELECT t1.c1, t2.c1 FROM ft5 t1 JOIN ft6 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 100 LIMIT 10; + c1 | c1 +----+---- +(0 rows) + +-- unsafe join conditions (c8 has a UDT), not pushed down. Practically a CROSS +-- JOIN since c8 in both tables has same value. +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 LEFT JOIN ft2 t2 ON (t1.c8 = t2.c8) ORDER BY t1.c1, t2.c1 OFFSET 100 LIMIT 10; + QUERY PLAN +---------------------------------------------------------------------------- + Limit + Output: t1.c1, t2.c1 + -> Sort + Output: t1.c1, t2.c1 + Sort Key: t1.c1, t2.c1 + -> Hash Left Join + Output: t1.c1, t2.c1 + Hash Cond: (t1.c8 = t2.c8) + -> Foreign Scan on public.ft1 t1 + Output: t1.c1, t1.c8 + Node ID: 1 + Remote SQL: SELECT "C 1", c8 FROM "S 1"."T 1" + -> Hash + Output: t2.c1, t2.c8 + -> Foreign Scan on public.ft2 t2 + Output: t2.c1, t2.c8 + Node ID: 2 + Remote SQL: SELECT "C 1", c8 FROM "S 1"."T 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c8 FROM "S 1"."T 1" + Partition Iterator + Output: "C 1", c8 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: "C 1", c8 + Selected Partitions: 1..2 + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c8 FROM "S 1"."T 1" + Partition Iterator + Output: "C 1", c8 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: "C 1", c8 + Selected Partitions: 1..2 + +(35 rows) + +SELECT t1.c1, t2.c1 FROM ft1 t1 LEFT JOIN ft2 t2 ON (t1.c8 = t2.c8) ORDER BY t1.c1, t2.c1 OFFSET 100 LIMIT 10; + c1 | c1 +----+----- + 1 | 101 + 1 | 102 + 1 | 103 + 1 | 104 + 1 | 105 + 1 | 106 + 1 | 107 + 1 | 108 + 1 | 109 + 1 | 110 +(10 rows) + +-- unsafe conditions on one side (c8 has a UDT), not pushed down. +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE t1.c8 = 'foo' ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; + QUERY PLAN +-------------------------------------------------------------------------------- + Limit + Output: t1.c1, t2.c1, t1.c3 + -> Sort + Output: t1.c1, t2.c1, t1.c3 + Sort Key: t1.c3, t1.c1 + -> Hash Left Join + Output: t1.c1, t2.c1, t1.c3 + Hash Cond: (t1.c1 = t2.c1) + -> Foreign Scan on public.ft1 t1 + Output: t1.c1, t1.c3 + Filter: (t1.c8 = 'foo'::user_enum) + Node ID: 1 + Remote SQL: SELECT "C 1", c3, c8 FROM "S 1"."T 1" + -> Hash + Output: t2.c1 + -> Foreign Scan on public.ft2 t2 + Output: t2.c1 + Node ID: 2 + Remote SQL: SELECT "C 1" FROM "S 1"."T 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c3, c8 FROM "S 1"."T 1" + Partition Iterator + Output: "C 1", c3, c8 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: "C 1", c3, c8 + Selected Partitions: 1..2 + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1" FROM "S 1"."T 1" + Partition Iterator + Output: "C 1" + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: "C 1" + Selected Partitions: 1..2 + +(36 rows) + +SELECT t1.c1, t2.c1 FROM ft1 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE t1.c8 = 'foo' ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; + c1 | c1 +-----+----- + 101 | 101 + 102 | 102 + 103 | 103 + 104 | 104 + 105 | 105 + 106 | 106 + 107 | 107 + 108 | 108 + 109 | 109 + 110 | 110 +(10 rows) + +-- join where unsafe to pushdown condition in WHERE clause has a column not +-- in the SELECT clause. In this test unsafe clause needs to have column +-- references from both joining sides so that the clause is not pushed down +-- into one of the joining sides. +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE t1.c8 = t2.c8 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Limit + Output: t1.c1, t2.c1, t1.c3 + -> Sort + Output: t1.c1, t2.c1, t1.c3 + Sort Key: t1.c3, t1.c1 + -> Foreign Scan + Output: t1.c1, t2.c1, t1.c3 + Filter: (t1.c8 = t2.c8) + Node ID: 1 + Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2) + Remote SQL: SELECT r1."C 1", r1.c3, r2."C 1", r1.c8, r2.c8 FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT r1."C 1", r1.c3, r2."C 1", r1.c8, r2.c8 FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) + Hash Join + Output: r1."C 1", r1.c3, r2."C 1", r1.c8, r2.c8 + Hash Cond: (r1."C 1" = r2."C 1") + -> Partition Iterator + Output: r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" r1 + Output: r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8 + Selected Partitions: 1..2 + -> Hash + Output: r2."C 1", r2.c8 + -> Partition Iterator + Output: r2."C 1", r2.c8 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" r2 + Output: r2."C 1", r2.c8 + Selected Partitions: 1..2 + +(32 rows) + +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE t1.c8 = t2.c8 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; + c1 | c1 +-----+----- + 101 | 101 + 102 | 102 + 103 | 103 + 104 | 104 + 105 | 105 + 106 | 106 + 107 | 107 + 108 | 108 + 109 | 109 + 110 | 110 +(10 rows) + +-- Aggregate after UNION, for testing setrefs +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1c1, avg(t1c1 + t2c1) FROM (SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) UNION SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1)) AS t (t1c1, t2c1) GROUP BY t1c1 ORDER BY t1c1 OFFSET 100 LIMIT 10; + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------------- + Limit + Output: t1.c1, (avg((t1.c1 + t2.c1))) + -> Sort + Output: t1.c1, (avg((t1.c1 + t2.c1))) + Sort Key: t1.c1 + -> HashAggregate + Output: t1.c1, avg((t1.c1 + t2.c1)) + Group By Key: t1.c1 + -> HashAggregate + Output: t1.c1, t2.c1 + Group By Key: t1.c1, t2.c1 + -> Append + -> Foreign Scan + Output: t1.c1, t2.c1 + Node ID: 1 + Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2) + Remote SQL: SELECT r1."C 1", r2."C 1" FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) + -> Foreign Scan + Output: t1.c1, t2.c1 + Node ID: 2 + Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2) + Remote SQL: SELECT r1."C 1", r2."C 1" FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT r1."C 1", r2."C 1" FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) + Hash Join + Output: r1."C 1", r2."C 1" + Hash Cond: (r1."C 1" = r2."C 1") + -> Partition Iterator + Output: r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" r1 + Output: r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8 + Selected Partitions: 1..2 + -> Hash + Output: r2."C 1" + -> Partition Iterator + Output: r2."C 1" + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" r2 + Output: r2."C 1" + Selected Partitions: 1..2 + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT r1."C 1", r2."C 1" FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) + Hash Join + Output: r1."C 1", r2."C 1" + Hash Cond: (r1."C 1" = r2."C 1") + -> Partition Iterator + Output: r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" r1 + Output: r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8 + Selected Partitions: 1..2 + -> Hash + Output: r2."C 1" + -> Partition Iterator + Output: r2."C 1" + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" r2 + Output: r2."C 1" + Selected Partitions: 1..2 + +(61 rows) + +SELECT t1c1, avg(t1c1 + t2c1) FROM (SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) UNION SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1)) AS t (t1c1, t2c1) GROUP BY t1c1 ORDER BY t1c1 OFFSET 100 LIMIT 10; + t1c1 | avg +------+---------------------- + 101 | 202.0000000000000000 + 102 | 204.0000000000000000 + 103 | 206.0000000000000000 + 104 | 208.0000000000000000 + 105 | 210.0000000000000000 + 106 | 212.0000000000000000 + 107 | 214.0000000000000000 + 108 | 216.0000000000000000 + 109 | 218.0000000000000000 + 110 | 220.0000000000000000 +(10 rows) + +-- join with lateral reference +EXPLAIN (VERBOSE, COSTS OFF) -- openGauss not support LATERAL +SELECT t1."C 1" FROM "S 1"."T 1" t1, LATERAL (SELECT DISTINCT t2.c1, t3.c1 FROM ft1 t2, ft2 t3 WHERE t2.c1 = t3.c1 AND t2.c2 = t1.c2) q ORDER BY t1."C 1" OFFSET 10 LIMIT 10; +ERROR: syntax error at or near "SELECT" +LINE 2: SELECT t1."C 1" FROM "S 1"."T 1" t1, LATERAL (SELECT DISTINC... + ^ +SELECT t1."C 1" FROM "S 1"."T 1" t1, LATERAL (SELECT DISTINCT t2.c1, t3.c1 FROM ft1 t2, ft2 t3 WHERE t2.c1 = t3.c1 AND t2.c2 = t1.c2) q ORDER BY t1."C 1" OFFSET 10 LIMIT 10; +ERROR: syntax error at or near "SELECT" +LINE 1: SELECT t1."C 1" FROM "S 1"."T 1" t1, LATERAL (SELECT DISTINC... + ^ +-- non-Var items in targetlist of the nullable rel of a join preventing +-- push-down in some cases +-- unable to push {ft1, ft2} +EXPLAIN (VERBOSE, COSTS OFF) +SELECT q.a, ft2.c1 FROM (SELECT 13 FROM ft1 WHERE c1 = 13) q(a) RIGHT JOIN ft2 ON (q.a = ft2.c1) WHERE ft2.c1 BETWEEN 10 AND 15; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------ + Nested Loop Left Join + Output: (13), ft2.c1 + Join Filter: (13 = ft2.c1) + -> Foreign Scan on public.ft2 + Output: ft2.c1 + Node ID: 1 + Remote SQL: SELECT "C 1" FROM "S 1"."T 1" WHERE (("C 1" >= 10)) AND (("C 1" <= 15)) ORDER BY "C 1" ASC NULLS LAST + -> Materialize + Output: (13) + -> Foreign Scan on public.ft1 + Output: 13 + Node ID: 2 + Remote SQL: SELECT NULL FROM "S 1"."T 1" WHERE (("C 1" = 13)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1" FROM "S 1"."T 1" WHERE (("C 1" >= 10)) AND (("C 1" <= 15)) ORDER BY "C 1" ASC NULLS LAST + Partitioned Index Only Scan using t1_pkey on "S 1"."T 1" + Output: "C 1" + Index Cond: (("T 1"."C 1" >= 10) AND ("T 1"."C 1" <= 15)) + Selected Partitions: 1 + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT NULL FROM "S 1"."T 1" WHERE (("C 1" = 13)) + Partitioned Index Only Scan using t1_pkey on "S 1"."T 1" + Output: NULL::text + Index Cond: ("T 1"."C 1" = 13) + Selected Partitions: 1 + +(26 rows) + +SELECT q.a, ft2.c1 FROM (SELECT 13 FROM ft1 WHERE c1 = 13) q(a) RIGHT JOIN ft2 ON (q.a = ft2.c1) WHERE ft2.c1 BETWEEN 10 AND 15; + a | c1 +----+---- + | 10 + | 11 + | 12 + 13 | 13 + | 14 + | 15 +(6 rows) + +-- ok to push {ft1, ft2} but not {ft1, ft2, ft4} +EXPLAIN (VERBOSE, COSTS OFF) +SELECT ft4.c1, q.* FROM ft4 LEFT JOIN (SELECT 13, ft1.c1, ft2.c1 FROM ft1 RIGHT JOIN ft2 ON (ft1.c1 = ft2.c1) WHERE ft1.c1 = 12) q(a, b, c) ON (ft4.c1 = q.b) WHERE ft4.c1 BETWEEN 10 AND 15; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Nested Loop Left Join + Output: ft4.c1, (13), ft1.c1, ft2.c1 + Join Filter: (ft4.c1 = ft1.c1) + -> Foreign Scan on public.ft4 + Output: ft4.c1 + Node ID: 1 + Remote SQL: SELECT c1 FROM "S 1"."T 3" WHERE ((c1 >= 10)) AND ((c1 <= 15)) + -> Materialize + Output: ft1.c1, ft2.c1, (13) + -> Foreign Scan + Output: ft1.c1, ft2.c1, 13 + Node ID: 2 + Relations: (public.ft1) INNER JOIN (public.ft2) + Remote SQL: SELECT r4."C 1", r5."C 1" FROM ("S 1"."T 1" r4 INNER JOIN "S 1"."T 1" r5 ON (((r5."C 1" = 12)) AND ((r4."C 1" = 12)))) ORDER BY r4."C 1" ASC NULLS LAST + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c1 FROM "S 1"."T 3" WHERE ((c1 >= 10)) AND ((c1 <= 15)) + Partition Iterator + Output: c1 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 3" + Output: c1 + Filter: (("T 3".c1 >= 10) AND ("T 3".c1 <= 15)) + Selected Partitions: 1..2 + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT r4."C 1", r5."C 1" FROM ("S 1"."T 1" r4 INNER JOIN "S 1"."T 1" r5 ON (((r5."C 1" = 12)) AND ((r4."C 1" = 12)))) ORDER BY r4."C 1" ASC NULLS LAST + Nested Loop + Output: r4."C 1", r5."C 1" + -> Partitioned Index Only Scan using t1_pkey on "S 1"."T 1" r4 + Output: r4."C 1" + Index Cond: (r4."C 1" = 12) + Selected Partitions: 1 + -> Partitioned Index Only Scan using t1_pkey on "S 1"."T 1" r5 + Output: r5."C 1" + Index Cond: (r5."C 1" = 12) + Selected Partitions: 1 + +(36 rows) + +SELECT ft4.c1, q.* FROM ft4 LEFT JOIN (SELECT 13, ft1.c1, ft2.c1 FROM ft1 RIGHT JOIN ft2 ON (ft1.c1 = ft2.c1) WHERE ft1.c1 = 12) q(a, b, c) ON (ft4.c1 = q.b) WHERE ft4.c1 BETWEEN 10 AND 15; + c1 | a | b | c +----+----+----+---- + 12 | 13 | 12 | 12 + 10 | | | + 14 | | | +(3 rows) + +-- join with nullable side with some columns with null values +UPDATE ft5 SET c3 = null where c1 % 9 = 0; +EXPLAIN (VERBOSE, COSTS OFF) +SELECT ft5, ft5.c1, ft5.c2, ft5.c3, ft4.c1, ft4.c2 FROM ft5 left join ft4 on ft5.c1 = ft4.c1 WHERE ft4.c1 BETWEEN 10 and 30 ORDER BY ft5.c1, ft4.c1; + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Sort + Output: ft5.*, ft5.c1, ft5.c2, ft5.c3, ft4.c1, ft4.c2 + Sort Key: ft5.c1 + -> Foreign Scan + Output: ft5.*, ft5.c1, ft5.c2, ft5.c3, ft4.c1, ft4.c2 + Node ID: 1 + Relations: (public.ft5) INNER JOIN (public.ft4) + Remote SQL: SELECT CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1.c1, r1.c2, r1.c3) END, r1.c1, r1.c2, r1.c3, r2.c1, r2.c2 FROM ("S 1"."T 4" r1 INNER JOIN "S 1"."T 3" r2 ON (((r1.c1 = r2.c1)) AND ((r2.c1 >= 10)) AND ((r2.c1 <= 30)) AND ((r1.c1 >= 10)) AND ((r1.c1 <= 30)))) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1.c1, r1.c2, r1.c3) END, r1.c1, r1.c2, r1.c3, r2.c1, r2.c2 FROM ("S 1"."T 4" r1 INNER JOIN "S 1"."T 3" r2 ON (((r1.c1 = r2.c1)) AND ((r2.c1 >= 10)) AND ((r2.c1 <= 30)) AND ((r1.c1 >= 10)) AND ((r1.c1 <= 30)))) + Hash Join + Output: CASE WHEN ((r1.*)::text IS NOT NULL) THEN ROW(r1.c1, r1.c2, r1.c3) ELSE NULL::record END, r1.c1, r1.c2, r1.c3, r2.c1, r2.c2 + Hash Cond: (r2.c1 = r1.c1) + -> Partition Iterator + Output: r2.c1, r2.c2, r2.c3 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 3" r2 + Output: r2.c1, r2.c2, r2.c3 + Filter: ((r2.c1 >= 10) AND (r2.c1 <= 30)) + Selected Partitions: 1..2 + -> Hash + Output: r1.*, r1.c1, r1.c2, r1.c3 + -> Partition Iterator + Output: r1.*, r1.c1, r1.c2, r1.c3 + Iterations: 1, Sub Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 4" r1 + Output: r1.*, r1.c1, r1.c2, r1.c3 + Filter: ((r1.c1 >= 10) AND (r1.c1 <= 30)) + Selected Partitions: 1 + Selected Subpartitions: ALL + +(32 rows) + +SELECT ft5, ft5.c1, ft5.c2, ft5.c3, ft4.c1, ft4.c2 FROM ft5 left join ft4 on ft5.c1 = ft4.c1 WHERE ft4.c1 BETWEEN 10 and 30 ORDER BY ft5.c1, ft4.c1; + ft5 | c1 | c2 | c3 | c1 | c2 +----------------+----+----+--------+----+---- + (12,13,AAA012) | 12 | 13 | AAA012 | 12 | 13 + (18,19,) | 18 | 19 | | 18 | 19 + (24,25,AAA024) | 24 | 25 | AAA024 | 24 | 25 + (30,31,AAA030) | 30 | 31 | AAA030 | 30 | 31 +(4 rows) + +-- multi-way join involving multiple merge joins +-- (this case used to have EPQ-related planning problems) +CREATE TABLE local_tbl (c1 int NOT NULL, c2 int NOT NULL, c3 text, CONSTRAINT local_tbl_pkey PRIMARY KEY (c1)); +NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "local_tbl_pkey" for table "local_tbl" +INSERT INTO local_tbl SELECT id, id % 10, to_char(id, 'FM0000') FROM generate_series(1, 1000) id; +ANALYZE local_tbl; +SET enable_nestloop TO false; +SET enable_hashjoin TO false; +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM ft1, ft2, ft4, ft5, local_tbl WHERE ft1.c1 = ft2.c1 AND ft1.c2 = ft4.c1 + AND ft1.c2 = ft5.c1 AND ft1.c2 = local_tbl.c1 AND ft1.c1 < 100 AND ft2.c1 < 100 FOR UPDATE; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + LockRows + Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft4.c1, ft4.c2, ft4.c3, ft5.c1, ft5.c2, ft5.c3, local_tbl.c1, local_tbl.c2, local_tbl.c3, local_tbl.ctid, ft1.*, ft2.*, ft4.*, ft5.* + -> Merge Join + Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft4.c1, ft4.c2, ft4.c3, ft5.c1, ft5.c2, ft5.c3, local_tbl.c1, local_tbl.c2, local_tbl.c3, local_tbl.ctid, ft1.*, ft2.*, ft4.*, ft5.* + Merge Cond: (ft2.c1 = ft1.c1) + -> Foreign Scan on public.ft2 + Output: ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.* + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" < 100)) ORDER BY "C 1" ASC NULLS LAST FOR UPDATE + -> Sort + Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft4.c1, ft4.c2, ft4.c3, ft4.*, ft5.c1, ft5.c2, ft5.c3, ft5.*, local_tbl.c1, local_tbl.c2, local_tbl.c3, local_tbl.ctid + Sort Key: ft1.c1 + -> Merge Join + Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft4.c1, ft4.c2, ft4.c3, ft4.*, ft5.c1, ft5.c2, ft5.c3, ft5.*, local_tbl.c1, local_tbl.c2, local_tbl.c3, local_tbl.ctid + Merge Cond: (ft4.c1 = ft1.c2) + -> Merge Join + Output: ft4.c1, ft4.c2, ft4.c3, ft4.*, ft5.c1, ft5.c2, ft5.c3, ft5.*, local_tbl.c1, local_tbl.c2, local_tbl.c3, local_tbl.ctid + Merge Cond: (ft4.c1 = local_tbl.c1) + -> Merge Join + Output: ft4.c1, ft4.c2, ft4.c3, ft4.*, ft5.c1, ft5.c2, ft5.c3, ft5.* + Merge Cond: (ft4.c1 = ft5.c1) + -> Sort + Output: ft4.c1, ft4.c2, ft4.c3, ft4.* + Sort Key: ft4.c1 + -> Foreign Scan on public.ft4 + Output: ft4.c1, ft4.c2, ft4.c3, ft4.* + Node ID: 2 + Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 3" FOR UPDATE + -> Sort + Output: ft5.c1, ft5.c2, ft5.c3, ft5.* + Sort Key: ft5.c1 + -> Foreign Scan on public.ft5 + Output: ft5.c1, ft5.c2, ft5.c3, ft5.* + Node ID: 3 + Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 4" FOR UPDATE + -> Index Scan using local_tbl_pkey on public.local_tbl + Output: local_tbl.c1, local_tbl.c2, local_tbl.c3, local_tbl.ctid + -> Sort + Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.* + Sort Key: ft1.c2 + -> Foreign Scan on public.ft1 + Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.* + Node ID: 4 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" < 100)) FOR UPDATE + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" < 100)) ORDER BY "C 1" ASC NULLS LAST FOR UPDATE + LockRows + Output: "C 1", c2, c3, c4, c5, c6, c7, c8, ctid, tableoid + -> Partitioned Index Scan using t1_pkey on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8, ctid, tableoid + Index Cond: ("T 1"."C 1" < 100) + Selected Partitions: 1 + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c1, c2, c3 FROM "S 1"."T 3" FOR UPDATE + LockRows + Output: c1, c2, c3, ctid, tableoid + -> Partition Iterator + Output: c1, c2, c3, ctid, tableoid + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 3" + Output: c1, c2, c3, ctid, tableoid + Selected Partitions: 1..2 + Node 3: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c1, c2, c3 FROM "S 1"."T 4" FOR UPDATE + LockRows + Output: c1, c2, c3, ctid, tableoid + -> Partition Iterator + Output: c1, c2, c3, ctid, tableoid + Iterations: 2, Sub Iterations: 4 + -> Partitioned Seq Scan on "S 1"."T 4" + Output: c1, c2, c3, ctid, tableoid + Selected Partitions: 1..2 + Selected Subpartitions: ALL + Node 4: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" < 100)) FOR UPDATE + LockRows + Output: "C 1", c2, c3, c4, c5, c6, c7, c8, ctid, tableoid + -> Partitioned Index Scan using t1_pkey on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8, ctid, tableoid + Index Cond: ("T 1"."C 1" < 100) + Selected Partitions: 1 + +(80 rows) + +SELECT * FROM ft1, ft2, ft4, ft5, local_tbl WHERE ft1.c1 = ft2.c1 AND ft1.c2 = ft4.c1 + AND ft1.c2 = ft5.c1 AND ft1.c2 = local_tbl.c1 AND ft1.c1 < 100 AND ft2.c1 < 100 FOR UPDATE; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 | c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 | c1 | c2 | c3 | c1 | c2 | c3 | c1 | c2 | c3 +----+----+-------+------------------------------+--------------------------+----+------------+-----+----+----+-------+------------------------------+--------------------------+----+------------+-----+----+----+--------+----+----+--------+----+----+------ + 6 | 6 | 00006 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo | 6 | 6 | 00006 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo | 6 | 7 | AAA006 | 6 | 7 | AAA006 | 6 | 6 | 0006 + 16 | 6 | 00016 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo | 16 | 6 | 00016 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo | 6 | 7 | AAA006 | 6 | 7 | AAA006 | 6 | 6 | 0006 + 26 | 6 | 00026 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo | 26 | 6 | 00026 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo | 6 | 7 | AAA006 | 6 | 7 | AAA006 | 6 | 6 | 0006 + 36 | 6 | 00036 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo | 36 | 6 | 00036 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo | 6 | 7 | AAA006 | 6 | 7 | AAA006 | 6 | 6 | 0006 + 46 | 6 | 00046 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo | 46 | 6 | 00046 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo | 6 | 7 | AAA006 | 6 | 7 | AAA006 | 6 | 6 | 0006 + 56 | 6 | 00056 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo | 56 | 6 | 00056 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo | 6 | 7 | AAA006 | 6 | 7 | AAA006 | 6 | 6 | 0006 + 66 | 6 | 00066 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo | 66 | 6 | 00066 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo | 6 | 7 | AAA006 | 6 | 7 | AAA006 | 6 | 6 | 0006 + 76 | 6 | 00076 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo | 76 | 6 | 00076 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo | 6 | 7 | AAA006 | 6 | 7 | AAA006 | 6 | 6 | 0006 + 86 | 6 | 00086 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo | 86 | 6 | 00086 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo | 6 | 7 | AAA006 | 6 | 7 | AAA006 | 6 | 6 | 0006 + 96 | 6 | 00096 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo | 96 | 6 | 00096 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo | 6 | 7 | AAA006 | 6 | 7 | AAA006 | 6 | 6 | 0006 +(10 rows) + +RESET enable_nestloop; +RESET enable_hashjoin; +DROP TABLE local_tbl; +-- check join pushdown in situations where multiple userids are involved +CREATE ROLE regress_view_owner sysadmin password 'QWERT@12345'; +CREATE USER MAPPING FOR regress_view_owner SERVER loopback; +GRANT SELECT ON ft4 TO regress_view_owner; +GRANT SELECT ON ft5 TO regress_view_owner; +CREATE VIEW v4 AS SELECT * FROM ft4; +CREATE VIEW v5 AS SELECT * FROM ft5; +ALTER VIEW v5 OWNER TO regress_view_owner; +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; -- can't be pushed down, different view owners + QUERY PLAN +------------------------------------------------------------------------- + Limit + Output: ft4.c1, ft5.c2, ft5.c1 + -> Sort + Output: ft4.c1, ft5.c2, ft5.c1 + Sort Key: ft4.c1, ft5.c1 + -> Hash Left Join + Output: ft4.c1, ft5.c2, ft5.c1 + Hash Cond: (ft4.c1 = ft5.c1) + -> Foreign Scan on public.ft4 + Output: ft4.c1 + Node ID: 1 + Remote SQL: SELECT c1 FROM "S 1"."T 3" + -> Hash + Output: ft5.c2, ft5.c1 + -> Foreign Scan on public.ft5 + Output: ft5.c2, ft5.c1 + Node ID: 2 + Remote SQL: SELECT c1, c2 FROM "S 1"."T 4" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c1 FROM "S 1"."T 3" + Partition Iterator + Output: c1 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 3" + Output: c1 + Selected Partitions: 1..2 + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c1, c2 FROM "S 1"."T 4" + Partition Iterator + Output: c1, c2 + Iterations: 2, Sub Iterations: 4 + -> Partitioned Seq Scan on "S 1"."T 4" + Output: c1, c2 + Selected Partitions: 1..2 + Selected Subpartitions: ALL + +(36 rows) + +SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; + c1 | c2 +----+---- + 22 | + 24 | 25 + 26 | + 28 | + 30 | 31 + 32 | + 34 | + 36 | 37 + 38 | + 40 | +(10 rows) + +ALTER VIEW v4 OWNER TO regress_view_owner; +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; -- can be pushed down + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: ft4.c1, ft5.c2, ft5.c1 + Node ID: 1 + Relations: (public.ft4) LEFT JOIN (public.ft5) + Remote SQL: SELECT r6.c1, r9.c2, r9.c1 FROM ("S 1"."T 3" r6 LEFT JOIN "S 1"."T 4" r9 ON (((r6.c1 = r9.c1)))) ORDER BY r6.c1 ASC NULLS LAST, r9.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT r6.c1, r9.c2, r9.c1 FROM ("S 1"."T 3" r6 LEFT JOIN "S 1"."T 4" r9 ON (((r6.c1 = r9.c1)))) ORDER BY r6.c1 ASC NULLS LAST, r9.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint + Limit + Output: r6.c1, r9.c2, r9.c1 + -> Sort + Output: r6.c1, r9.c2, r9.c1 + Sort Key: r6.c1, r9.c1 + -> Hash Left Join + Output: r6.c1, r9.c2, r9.c1 + Hash Cond: (r6.c1 = r9.c1) + -> Partition Iterator + Output: r6.c1, r6.c2, r6.c3 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 3" r6 + Output: r6.c1, r6.c2, r6.c3 + Selected Partitions: 1..2 + -> Hash + Output: r9.c2, r9.c1 + -> Partition Iterator + Output: r9.c2, r9.c1 + Iterations: 2, Sub Iterations: 4 + -> Partitioned Seq Scan on "S 1"."T 4" r9 + Output: r9.c2, r9.c1 + Selected Partitions: 1..2 + Selected Subpartitions: ALL + +(32 rows) + +SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; + c1 | c2 +----+---- + 22 | + 24 | 25 + 26 | + 28 | + 30 | 31 + 32 | + 34 | + 36 | 37 + 38 | + 40 | +(10 rows) + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; -- can't be pushed down, view owner not current user + QUERY PLAN +------------------------------------------------------------------------- + Limit + Output: ft4.c1, t2.c2, t2.c1 + -> Sort + Output: ft4.c1, t2.c2, t2.c1 + Sort Key: ft4.c1, t2.c1 + -> Hash Left Join + Output: ft4.c1, t2.c2, t2.c1 + Hash Cond: (ft4.c1 = t2.c1) + -> Foreign Scan on public.ft4 + Output: ft4.c1 + Node ID: 1 + Remote SQL: SELECT c1 FROM "S 1"."T 3" + -> Hash + Output: t2.c2, t2.c1 + -> Foreign Scan on public.ft5 t2 + Output: t2.c2, t2.c1 + Node ID: 2 + Remote SQL: SELECT c1, c2 FROM "S 1"."T 4" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c1 FROM "S 1"."T 3" + Partition Iterator + Output: c1 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 3" + Output: c1 + Selected Partitions: 1..2 + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c1, c2 FROM "S 1"."T 4" + Partition Iterator + Output: c1, c2 + Iterations: 2, Sub Iterations: 4 + -> Partitioned Seq Scan on "S 1"."T 4" + Output: c1, c2 + Selected Partitions: 1..2 + Selected Subpartitions: ALL + +(36 rows) + +SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; + c1 | c2 +----+---- + 22 | + 24 | 25 + 26 | + 28 | + 30 | 31 + 32 | + 34 | + 36 | 37 + 38 | + 40 | +(10 rows) + +ALTER VIEW v4 OWNER TO regress_view_owner; +-- PlaceHolder +explain(verbose, costs off) select * from ft1 t1 left join (select c1, case when c2 is not null then 1 else 0 end as cc from ft2) t2 on t1.c1 = t2.c1; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------ + Hash Left Join + Output: t1.c1, t1.c2, t1.c3, t1.c4, t1.c5, t1.c6, t1.c7, t1.c8, ft2.c1, (CASE WHEN (ft2.c2 IS NOT NULL) THEN 1 ELSE 0 END) + Hash Cond: (t1.c1 = ft2.c1) + -> Foreign Scan on public.ft1 t1 + Output: t1.c1, t1.c2, t1.c3, t1.c4, t1.c5, t1.c6, t1.c7, t1.c8 + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" + -> Hash + Output: ft2.c1, (CASE WHEN (ft2.c2 IS NOT NULL) THEN 1 ELSE 0 END) + -> Foreign Scan on public.ft2 + Output: ft2.c1, CASE WHEN (ft2.c2 IS NOT NULL) THEN 1 ELSE 0 END + Node ID: 2 + Remote SQL: SELECT "C 1", c2 FROM "S 1"."T 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" + Partition Iterator + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Selected Partitions: 1..2 + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2 FROM "S 1"."T 1" + Partition Iterator + Output: "C 1", c2 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: "C 1", c2 + Selected Partitions: 1..2 + +(30 rows) + +-- cleanup +DROP OWNED BY regress_view_owner; +DROP ROLE regress_view_owner; +-- ====================================================================================================================================== +-- TEST-MODULE: Aggregate and grouping queries +-- -------------------------------------- +-- openGauss not support filter +-- ====================================================================================================================================== +-- Simple aggregates +explain (verbose, costs off) +select count(c6), sum(c1), avg(c1), min(c2), max(c1), stddev(c2), sum(c1) * (random() <= 1)::int as sum2 from ft1 where c2 < 5 group by c2 order by 1, 2; + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: (count(c6)), (sum(c1)), (avg(c1)), (min(c2)), (max(c1)), (stddev(c2)), ((sum(c1)) * ((random() <= 1::double precision))::integer), c2 + Node ID: 1 + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT count(c6), sum("C 1"), avg("C 1"), min(c2), max("C 1"), stddev(c2), c2 FROM "S 1"."T 1" WHERE ((c2 < 5)) GROUP BY 7 ORDER BY count(c6) ASC NULLS LAST, sum("C 1") ASC NULLS LAST + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT count(c6), sum("C 1"), avg("C 1"), min(c2), max("C 1"), stddev(c2), c2 FROM "S 1"."T 1" WHERE ((c2 < 5)) GROUP BY 7 ORDER BY count(c6) ASC NULLS LAST, sum("C 1") ASC NULLS LAST + Sort + Output: (count(c6)), (sum("C 1")), (avg("C 1")), (min(c2)), (max("C 1")), (stddev(c2)), c2 + Sort Key: (count("T 1".c6)), (sum("T 1"."C 1")) + -> HashAggregate + Output: count(c6), sum("C 1"), avg("C 1"), min(c2), max("C 1"), stddev(c2), c2 + Group By Key: "T 1".c2 + -> Partition Iterator + Output: c2, c6, "C 1" + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: c2, c6, "C 1" + Filter: ("T 1".c2 < 5) + Selected Partitions: 1..2 + +(22 rows) + +select count(c6), sum(c1), avg(c1), min(c2), max(c1), stddev(c2), sum(c1) * (random() <= 1)::int as sum2 from ft1 where c2 < 5 group by c2 order by 1, 2; + count | sum | avg | min | max | stddev | sum2 +-------+-------+----------------------+-----+------+--------+------- + 100 | 49600 | 496.0000000000000000 | 1 | 991 | 0 | 49600 + 100 | 49700 | 497.0000000000000000 | 2 | 992 | 0 | 49700 + 100 | 49800 | 498.0000000000000000 | 3 | 993 | 0 | 49800 + 100 | 49900 | 499.0000000000000000 | 4 | 994 | 0 | 49900 + 100 | 50500 | 505.0000000000000000 | 0 | 1000 | 0 | 50500 +(5 rows) + +explain (verbose, costs off) +select count(c6), sum(c1), avg(c1), min(c2), max(c1), stddev(c2), sum(c1) * (random() <= 1)::int as sum2 from ft1 where c2 < 5 group by c2 order by 1, 2 limit 1; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: (count(c6)), (sum(c1)), (avg(c1)), (min(c2)), (max(c1)), (stddev(c2)), ((sum(c1)) * ((random() <= 1::double precision))::integer), c2 + Node ID: 1 + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT count(c6), sum("C 1"), avg("C 1"), min(c2), max("C 1"), stddev(c2), c2 FROM "S 1"."T 1" WHERE ((c2 < 5)) GROUP BY 7 ORDER BY count(c6) ASC NULLS LAST, sum("C 1") ASC NULLS LAST LIMIT 1::bigint + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT count(c6), sum("C 1"), avg("C 1"), min(c2), max("C 1"), stddev(c2), c2 FROM "S 1"."T 1" WHERE ((c2 < 5)) GROUP BY 7 ORDER BY count(c6) ASC NULLS LAST, sum("C 1") ASC NULLS LAST LIMIT 1::bigint + Limit + Output: (count(c6)), (sum("C 1")), (avg("C 1")), (min(c2)), (max("C 1")), (stddev(c2)), c2 + -> Sort + Output: (count(c6)), (sum("C 1")), (avg("C 1")), (min(c2)), (max("C 1")), (stddev(c2)), c2 + Sort Key: (count("T 1".c6)), (sum("T 1"."C 1")) + -> HashAggregate + Output: count(c6), sum("C 1"), avg("C 1"), min(c2), max("C 1"), stddev(c2), c2 + Group By Key: "T 1".c2 + -> Partition Iterator + Output: c2, c6, "C 1" + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: c2, c6, "C 1" + Filter: ("T 1".c2 < 5) + Selected Partitions: 1..2 + +(24 rows) + +select count(c6), sum(c1), avg(c1), min(c2), max(c1), stddev(c2), sum(c1) * (random() <= 1)::int as sum2 from ft1 where c2 < 5 group by c2 order by 1, 2 limit 1; + count | sum | avg | min | max | stddev | sum2 +-------+-------+----------------------+-----+-----+--------+------- + 100 | 49600 | 496.0000000000000000 | 1 | 991 | 0 | 49600 +(1 row) + +-- Aggregate is not pushed down as aggregation contains random() +explain (verbose, costs off) +select sum(c1 * (random() <= 1)::int) as sum, avg(c1) from ft1; + QUERY PLAN +----------------------------------------------------------------------------- + Aggregate + Output: sum((c1 * ((random() <= 1::double precision))::integer)), avg(c1) + -> Foreign Scan on public.ft1 + Output: c1 + Node ID: 1 + Remote SQL: SELECT "C 1" FROM "S 1"."T 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1" FROM "S 1"."T 1" + Partition Iterator + Output: "C 1" + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: "C 1" + Selected Partitions: 1..2 + +(16 rows) + +-- Aggregate over join query +explain (verbose, costs off) +select count(*), sum(t1.c1), avg(t2.c1) from ft1 t1 inner join ft1 t2 on (t1.c2 = t2.c2) where t1.c2 = 6; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: (count(*)), (sum(t1.c1)), (avg(t2.c1)) + Node ID: 1 + Relations: Aggregate on ((public.ft1 t1) INNER JOIN (public.ft1 t2)) + Remote SQL: SELECT count(*), sum(r1."C 1"), avg(r2."C 1") FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r2.c2 = 6)) AND ((r1.c2 = 6)))) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT count(*), sum(r1."C 1"), avg(r2."C 1") FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r2.c2 = 6)) AND ((r1.c2 = 6)))) + Aggregate + Output: count(*), sum(r1."C 1"), avg(r2."C 1") + -> Nested Loop + Output: r1."C 1", r2."C 1" + -> Partition Iterator + Output: r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" r1 + Output: r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8 + Filter: (r1.c2 = 6) + Selected Partitions: 1..2 + -> Materialize + Output: r2."C 1" + -> Partition Iterator + Output: r2."C 1" + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" r2 + Output: r2."C 1" + Filter: (r2.c2 = 6) + Selected Partitions: 1..2 + +(29 rows) + +select count(*), sum(t1.c1), avg(t2.c1) from ft1 t1 inner join ft1 t2 on (t1.c2 = t2.c2) where t1.c2 = 6; + count | sum | avg +-------+---------+---------------------- + 10000 | 5010000 | 501.0000000000000000 +(1 row) + +-- Not pushed down due to local conditions present in underneath input rel +explain (verbose, costs off) +select sum(t1.c1), count(t2.c1) from ft1 t1 inner join ft2 t2 on (t1.c1 = t2.c1) where ((t1.c1 * t2.c1)/(t1.c1 * t2.c1)) * random() <= 1; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------- + Aggregate + Output: sum(t1.c1), count(t2.c1) + -> Foreign Scan + Output: t1.c1, t2.c1 + Filter: ((((t1.c1 * t2.c1) / (t1.c1 * t2.c1)) * random()) <= 1::double precision) + Node ID: 1 + Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2) + Remote SQL: SELECT r1."C 1", r2."C 1" FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT r1."C 1", r2."C 1" FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) + Hash Join + Output: r1."C 1", r2."C 1" + Hash Cond: (r1."C 1" = r2."C 1") + -> Partition Iterator + Output: r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" r1 + Output: r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8 + Selected Partitions: 1..2 + -> Hash + Output: r2."C 1" + -> Partition Iterator + Output: r2."C 1" + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" r2 + Output: r2."C 1" + Selected Partitions: 1..2 + +(29 rows) + +-- GROUP BY clause having expressions +explain (verbose, costs off) +select c2/2, sum(c2) * (c2/2) from ft1 group by c2/2 order by c2/2; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: ((c2 / 2)), (((sum(c2))::double precision * (c2 / 2))) + Node ID: 1 + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT (c2 / 2), (sum(c2) * (c2 / 2)) FROM "S 1"."T 1" GROUP BY 1 ORDER BY (c2 / 2) ASC NULLS LAST + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT (c2 / 2), (sum(c2) * (c2 / 2)) FROM "S 1"."T 1" GROUP BY 1 ORDER BY (c2 / 2) ASC NULLS LAST + Sort + Output: ((c2 / 2)), (((sum(c2))::double precision * ((c2 / 2)))) + Sort Key: (("T 1".c2 / 2)) + -> HashAggregate + Output: ((c2 / 2)), ((sum(c2))::double precision * ((c2 / 2))) + Group By Key: ("T 1".c2 / 2) + -> Partition Iterator + Output: (c2 / 2), c2 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: (c2 / 2), c2 + Selected Partitions: 1..2 + +(21 rows) + +select c2/2, sum(c2) * (c2/2) from ft1 group by c2/2 order by c2/2; + ?column? | ?column? +----------+---------- + 0 | 0 + .5 | 50 + 1 | 200 + 1.5 | 450 + 2 | 800 + 2.5 | 1250 + 3 | 1800 + 3.5 | 2450 + 4 | 3200 + 4.5 | 4050 +(10 rows) + +-- Aggregates in subquery are pushed down. +explain (verbose, costs off) +select count(x.a), sum(x.a) from (select c2 a, sum(c1) b from ft1 group by c2, sqrt(c1) order by 1, 2) x; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------ + Aggregate + Output: count(ft1.c2), sum(ft1.c2) + -> Foreign Scan + Output: ft1.c2, (sum(ft1.c1)), (sqrt((ft1.c1)::double precision)) + Node ID: 1 + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT c2, sum("C 1"), sqrt("C 1") FROM "S 1"."T 1" GROUP BY 1, 3 ORDER BY c2 ASC NULLS LAST, sum("C 1") ASC NULLS LAST + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c2, sum("C 1"), sqrt("C 1") FROM "S 1"."T 1" GROUP BY 1, 3 ORDER BY c2 ASC NULLS LAST, sum("C 1") ASC NULLS LAST + Sort + Output: c2, (sum("C 1")), (sqrt(("C 1")::double precision)) + Sort Key: "T 1".c2, (sum("T 1"."C 1")) + -> HashAggregate + Output: c2, sum("C 1"), (sqrt(("C 1")::double precision)) + Group By Key: "T 1".c2, sqrt(("T 1"."C 1")::double precision) + -> Partition Iterator + Output: c2, sqrt(("C 1")::double precision), "C 1" + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: c2, sqrt(("C 1")::double precision), "C 1" + Selected Partitions: 1..2 + +(23 rows) + +select count(x.a), sum(x.a) from (select c2 a, sum(c1) b from ft1 group by c2, sqrt(c1) order by 1, 2) x; + count | sum +-------+------ + 1000 | 4500 +(1 row) + +-- Aggregate is still pushed down by taking unshippable expression out +explain (verbose, costs off) +select c2 * (random() <= 1)::int as sum1, sum(c1) * c2 as sum2 from ft1 group by c2 order by 1, 2; + QUERY PLAN +------------------------------------------------------------------------------------------------- + Sort + Output: ((c2 * ((random() <= 1::double precision))::integer)), ((sum(c1) * c2)), c2 + Sort Key: ((ft1.c2 * ((random() <= 1::double precision))::integer)), ((sum(ft1.c1) * ft1.c2)) + -> HashAggregate + Output: (c2 * ((random() <= 1::double precision))::integer), (sum(c1) * c2), c2 + Group By Key: ft1.c2 + -> Foreign Scan on public.ft1 + Output: c2, c1 + Node ID: 1 + Remote SQL: SELECT "C 1", c2 FROM "S 1"."T 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2 FROM "S 1"."T 1" + Partition Iterator + Output: "C 1", c2 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: "C 1", c2 + Selected Partitions: 1..2 + +(20 rows) + +select c2 * (random() <= 1)::int as sum1, sum(c1) * c2 as sum2 from ft1 group by c2 order by 1, 2; + sum1 | sum2 +------+-------- + 0 | 0 + 1 | 49600 + 2 | 99400 + 3 | 149400 + 4 | 199600 + 5 | 250000 + 6 | 300600 + 7 | 351400 + 8 | 402400 + 9 | 453600 +(10 rows) + +-- Aggregate with unshippable GROUP BY clause are not pushed +explain (verbose, costs off) +select c2 * (random() <= 1)::int as c2 from ft2 group by c2 * (random() <= 1)::int order by 1; + QUERY PLAN +----------------------------------------------------------------------------- + Group + Output: ((c2 * ((random() <= 1::double precision))::integer)) + Group By Key: ((ft2.c2 * ((random() <= 1::double precision))::integer)) + -> Sort + Output: ((c2 * ((random() <= 1::double precision))::integer)) + Sort Key: ((ft2.c2 * ((random() <= 1::double precision))::integer)) + -> Foreign Scan on public.ft2 + Output: (c2 * ((random() <= 1::double precision))::integer) + Node ID: 1 + Remote SQL: SELECT c2 FROM "S 1"."T 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c2 FROM "S 1"."T 1" + Partition Iterator + Output: c2 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: c2 + Selected Partitions: 1..2 + +(20 rows) + +-- GROUP BY clause in various forms, cardinal, alias and constant expression +explain (verbose, costs off) +select count(c2) w, c2 x, 5 y, 7.0 z from ft1 group by 2, y, 9.0::int order by 2; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: (count(c2)), c2, (5), (7.0), (9) + Node ID: 1 + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT count(c2), c2, 5, 7.0, 9 FROM "S 1"."T 1" GROUP BY 2, 3, 5 ORDER BY c2 ASC NULLS LAST + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT count(c2), c2, 5, 7.0, 9 FROM "S 1"."T 1" GROUP BY 2, 3, 5 ORDER BY c2 ASC NULLS LAST + Sort + Output: (count(c2)), c2, (5), (7.0), (9) + Sort Key: "T 1".c2 + -> HashAggregate + Output: count(c2), c2, (5), 7.0, (9) + Group By Key: "T 1".c2, 5, 9 + -> Partition Iterator + Output: c2, 5, 9 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: c2, 5, 9 + Selected Partitions: 1..2 + +(21 rows) + +select count(c2) w, c2 x, 5 y, 7.0 z from ft1 group by 2, y, 9.0::int order by 2; + w | x | y | z +-----+---+---+----- + 100 | 0 | 5 | 7.0 + 100 | 1 | 5 | 7.0 + 100 | 2 | 5 | 7.0 + 100 | 3 | 5 | 7.0 + 100 | 4 | 5 | 7.0 + 100 | 5 | 5 | 7.0 + 100 | 6 | 5 | 7.0 + 100 | 7 | 5 | 7.0 + 100 | 8 | 5 | 7.0 + 100 | 9 | 5 | 7.0 +(10 rows) + +-- GROUP BY clause referring to same column multiple times +-- Also, ORDER BY contains an aggregate function +explain (verbose, costs off) +select c2, c2 from ft1 where c2 > 6 group by 1, 2 order by sum(c1); + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: c2, c2, (sum(c1)) + Node ID: 1 + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT c2, c2, sum("C 1") FROM "S 1"."T 1" WHERE ((c2 > 6)) GROUP BY 1, 2 ORDER BY sum("C 1") ASC NULLS LAST + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c2, c2, sum("C 1") FROM "S 1"."T 1" WHERE ((c2 > 6)) GROUP BY 1, 2 ORDER BY sum("C 1") ASC NULLS LAST + Sort + Output: c2, c2, (sum("C 1")) + Sort Key: (sum("T 1"."C 1")) + -> HashAggregate + Output: c2, c2, sum("C 1") + Group By Key: "T 1".c2, "T 1".c2 + -> Partition Iterator + Output: c2, "C 1" + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: c2, "C 1" + Filter: ("T 1".c2 > 6) + Selected Partitions: 1..2 + +(22 rows) + +select c2, c2 from ft1 where c2 > 6 group by 1, 2 order by sum(c1); + c2 | c2 +----+---- + 7 | 7 + 8 | 8 + 9 | 9 +(3 rows) + +-- Testing HAVING clause shippability +explain (verbose, costs off) +select c2, sum(c1) from ft2 group by c2 having avg(c1) < 500 and sum(c1) < 49800 order by c2; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: c2, (sum(c1)) + Node ID: 1 + Relations: Aggregate on (public.ft2) + Remote SQL: SELECT c2, sum("C 1") FROM "S 1"."T 1" GROUP BY 1 HAVING ((avg("C 1") < 500::numeric)) AND ((sum("C 1") < 49800)) ORDER BY c2 ASC NULLS LAST + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c2, sum("C 1") FROM "S 1"."T 1" GROUP BY 1 HAVING ((avg("C 1") < 500::numeric)) AND ((sum("C 1") < 49800)) ORDER BY c2 ASC NULLS LAST + Sort + Output: c2, (sum("C 1")) + Sort Key: "T 1".c2 + -> HashAggregate + Output: c2, sum("C 1") + Group By Key: "T 1".c2 + Filter: ((avg("T 1"."C 1") < 500::numeric) AND (sum("T 1"."C 1") < 49800)) + -> Partition Iterator + Output: c2, "C 1" + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: c2, "C 1" + Selected Partitions: 1..2 + +(22 rows) + +select c2, sum(c1) from ft2 group by c2 having avg(c1) < 500 and sum(c1) < 49800 order by c2; + c2 | sum +----+------- + 1 | 49600 + 2 | 49700 +(2 rows) + +-- Unshippable HAVING clause will be evaluated locally, and other qual in HAVING clause is pushed down +explain (verbose, costs off) +select count(*) from (select c5, count(c1) from ft1 group by c5, sqrt(c2) having (avg(c1) / avg(c1)) * random() <= 1 and avg(c1) < 500) x; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------ + Aggregate + Output: count(*) + -> Foreign Scan + Output: ft1.c5, (count(ft1.c1)), (sqrt((ft1.c2)::double precision)) + Filter: (((((avg(ft1.c1)) / (avg(ft1.c1))))::double precision * random()) <= 1::double precision) + Node ID: 1 + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT c5, count("C 1"), sqrt(c2), avg("C 1") FROM "S 1"."T 1" GROUP BY 1, 3 HAVING ((avg("C 1") < 500::numeric)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c5, count("C 1"), sqrt(c2), avg("C 1") FROM "S 1"."T 1" GROUP BY 1, 3 HAVING ((avg("C 1") < 500::numeric)) + HashAggregate + Output: c5, count("C 1"), (sqrt((c2)::double precision)), avg("C 1") + Group By Key: "T 1".c5, sqrt(("T 1".c2)::double precision) + Filter: (avg("T 1"."C 1") < 500::numeric) + -> Partition Iterator + Output: c5, sqrt((c2)::double precision), "C 1" + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: c5, sqrt((c2)::double precision), "C 1" + Selected Partitions: 1..2 + +(22 rows) + +select count(*) from (select c5, count(c1) from ft1 group by c5, sqrt(c2) having (avg(c1) / avg(c1)) * random() <= 1 and avg(c1) < 500) x; + count +------- + 49 +(1 row) + +-- Aggregate in HAVING clause is not pushable, and thus aggregation is not pushed down +explain (verbose, costs off) +select sum(c1) from ft1 group by c2 having avg(c1 * (random() <= 1)::int) > 100 order by 1; + QUERY PLAN +----------------------------------------------------------------------------------------------- + Sort + Output: (sum(c1)), c2 + Sort Key: (sum(ft1.c1)) + -> HashAggregate + Output: sum(c1), c2 + Group By Key: ft1.c2 + Filter: (avg((ft1.c1 * ((random() <= 1::double precision))::integer)) > 100::numeric) + -> Foreign Scan on public.ft1 + Output: c2, c1 + Node ID: 1 + Remote SQL: SELECT "C 1", c2 FROM "S 1"."T 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2 FROM "S 1"."T 1" + Partition Iterator + Output: "C 1", c2 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: "C 1", c2 + Selected Partitions: 1..2 + +(21 rows) + +-- Remote aggregate in combination with a local Param (for the output +-- of an initplan) can be trouble, per bug #15781 +explain (verbose, costs off) +select exists(select 1 from pg_enum), sum(c1) from ft1; + QUERY PLAN +----------------------------------------------------------------------------- + Foreign Scan + Output: $0, (sum(ft1.c1)) + Node ID: 1 + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT sum("C 1") FROM "S 1"."T 1" + InitPlan 1 (returns $0) + -> Seq Scan on pg_catalog.pg_enum + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT sum("C 1") FROM "S 1"."T 1" + Aggregate + Output: sum("C 1") + -> Partition Iterator + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Selected Partitions: 1..2 + +(19 rows) + +select exists(select 1 from pg_enum), sum(c1) from ft1; + exists | sum +--------+-------- + t | 500500 +(1 row) + +explain (verbose, costs off) +select exists(select 1 from pg_enum), sum(c1) from ft1 group by 1; + QUERY PLAN +------------------------------------------------------------------------ + GroupAggregate + Output: ($0), sum(ft1.c1) + Group By Key: $0 + InitPlan 1 (returns $0) + -> Seq Scan on pg_catalog.pg_enum + -> Foreign Scan on public.ft1 + Output: $0, ft1.c1 + Node ID: 1 + Remote SQL: SELECT "C 1" FROM "S 1"."T 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1" FROM "S 1"."T 1" + Partition Iterator + Output: "C 1" + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: "C 1" + Selected Partitions: 1..2 + +(19 rows) + +select exists(select 1 from pg_enum), sum(c1) from ft1 group by 1; + exists | sum +--------+-------- + t | 500500 +(1 row) + +-- Testing ORDER BY, DISTINCT, FILTER, Ordered-sets and VARIADIC within aggregates +-- ORDER BY within aggregate, same column used to order +explain (verbose, costs off) +select array_agg(c1 order by c1) from ft1 where c1 < 100 group by c2 order by 1; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: (array_agg(c1 ORDER BY c1)), c2 + Node ID: 1 + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT array_agg("C 1" ORDER BY "C 1" ASC NULLS LAST), c2 FROM "S 1"."T 1" WHERE (("C 1" < 100)) GROUP BY 2 ORDER BY array_agg("C 1" ORDER BY "C 1" ASC NULLS LAST) ASC NULLS LAST + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT array_agg("C 1" ORDER BY "C 1" ASC NULLS LAST), c2 FROM "S 1"."T 1" WHERE (("C 1" < 100)) GROUP BY 2 ORDER BY array_agg("C 1" ORDER BY "C 1" ASC NULLS LAST) ASC NULLS LAST + Sort + Output: (array_agg("C 1" ORDER BY "C 1")), c2 + Sort Key: (array_agg("T 1"."C 1" ORDER BY "T 1"."C 1")) + -> GroupAggregate + Output: array_agg("C 1" ORDER BY "C 1"), c2 + Group By Key: "T 1".c2 + -> Sort + Output: c2, "C 1" + Sort Key: "T 1".c2 + -> Partitioned Index Scan using t1_pkey on "S 1"."T 1" + Output: c2, "C 1" + Index Cond: ("T 1"."C 1" < 100) + Selected Partitions: 1 + +(22 rows) + +select array_agg(c1 order by c1) from ft1 where c1 < 100 group by c2 order by 1; + array_agg +-------------------------------- + {1,11,21,31,41,51,61,71,81,91} + {2,12,22,32,42,52,62,72,82,92} + {3,13,23,33,43,53,63,73,83,93} + {4,14,24,34,44,54,64,74,84,94} + {5,15,25,35,45,55,65,75,85,95} + {6,16,26,36,46,56,66,76,86,96} + {7,17,27,37,47,57,67,77,87,97} + {8,18,28,38,48,58,68,78,88,98} + {9,19,29,39,49,59,69,79,89,99} + {10,20,30,40,50,60,70,80,90} +(10 rows) + +-- ORDER BY within aggregate, different column used to order also using DESC +explain (verbose, costs off) +select array_agg(c5 order by c1 desc) from ft2 where c2 = 6 and c1 < 50; + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: (array_agg(c5 ORDER BY c1 DESC)) + Node ID: 1 + Relations: Aggregate on (public.ft2) + Remote SQL: SELECT array_agg(c5 ORDER BY "C 1" DESC NULLS FIRST) FROM "S 1"."T 1" WHERE (("C 1" < 50)) AND ((c2 = 6)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT array_agg(c5 ORDER BY "C 1" DESC NULLS FIRST) FROM "S 1"."T 1" WHERE (("C 1" < 50)) AND ((c2 = 6)) + Aggregate + Output: array_agg(c5 ORDER BY "C 1" DESC) + -> Partitioned Index Scan using t1_pkey on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Index Cond: ("T 1"."C 1" < 50) + Filter: ("T 1".c2 = 6) + Selected Partitions: 1 + +(16 rows) + +select array_agg(c5 order by c1 desc) from ft2 where c2 = 6 and c1 < 50; + array_agg +------------------------------------------------------------------------------------------------------------------------------------------ + {"Mon Feb 16 00:00:00 1970","Fri Feb 06 00:00:00 1970","Tue Jan 27 00:00:00 1970","Sat Jan 17 00:00:00 1970","Wed Jan 07 00:00:00 1970"} +(1 row) + +-- DISTINCT within aggregate +explain (verbose, costs off) +select array_agg(distinct (t1.c1)%5) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: (array_agg(DISTINCT (t1.c1 % 5))), ((t2.c1 % 3)) + Node ID: 1 + Relations: Aggregate on ((public.ft4 t1) FULL JOIN (public.ft5 t2)) + Remote SQL: SELECT array_agg(DISTINCT (r1.c1 % 5)), (r2.c1 % 3) FROM ("S 1"."T 3" r1 FULL JOIN "S 1"."T 4" r2 ON (((r1.c1 = r2.c1)))) WHERE (((r1.c1 < 20) OR ((r1.c1 IS NULL) AND (r2.c1 < 5)))) GROUP BY 2 ORDER BY array_agg(DISTINCT (r1.c1 % 5)) ASC NULLS LAST + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT array_agg(DISTINCT (r1.c1 % 5)), (r2.c1 % 3) FROM ("S 1"."T 3" r1 FULL JOIN "S 1"."T 4" r2 ON (((r1.c1 = r2.c1)))) WHERE (((r1.c1 < 20) OR ((r1.c1 IS NULL) AND (r2.c1 < 5)))) GROUP BY 2 ORDER BY array_agg(DISTINCT (r1.c1 % 5)) ASC NULLS LAST + Sort + Output: (array_agg(DISTINCT (r1.c1 % 5))), ((r2.c1 % 3)) + Sort Key: (array_agg(DISTINCT (r1.c1 % 5))) + -> GroupAggregate + Output: array_agg(DISTINCT (r1.c1 % 5)), ((r2.c1 % 3)) + Group By Key: ((r2.c1 % 3)) + -> Sort + Output: ((r2.c1 % 3)), r1.c1 + Sort Key: ((r2.c1 % 3)) + -> Hash Full Join + Output: (r2.c1 % 3), r1.c1 + Hash Cond: (r1.c1 = r2.c1) + Filter: ((r1.c1 < 20) OR ((r1.c1 IS NULL) AND (r2.c1 < 5))) + -> Partition Iterator + Output: r1.c1, r1.c2, r1.c3 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 3" r1 + Output: r1.c1, r1.c2, r1.c3 + Selected Partitions: 1..2 + -> Hash + Output: r2.c1 + -> Partition Iterator + Output: r2.c1 + Iterations: 2, Sub Iterations: 4 + -> Partitioned Seq Scan on "S 1"."T 4" r2 + Output: r2.c1 + Selected Partitions: 1..2 + Selected Subpartitions: ALL + +(37 rows) + +select array_agg(distinct (t1.c1)%5) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1; + array_agg +-------------- + {0,1,2,3,4} + {1,2,3,NULL} +(2 rows) + +-- DISTINCT combined with ORDER BY within aggregate +explain (verbose, costs off) +select array_agg(distinct (t1.c1)%5 order by (t1.c1)%5) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: (array_agg(DISTINCT (t1.c1 % 5) ORDER BY (t1.c1 % 5))), ((t2.c1 % 3)) + Node ID: 1 + Relations: Aggregate on ((public.ft4 t1) FULL JOIN (public.ft5 t2)) + Remote SQL: SELECT array_agg(DISTINCT (r1.c1 % 5) ORDER BY ((r1.c1 % 5)) ASC NULLS LAST), (r2.c1 % 3) FROM ("S 1"."T 3" r1 FULL JOIN "S 1"."T 4" r2 ON (((r1.c1 = r2.c1)))) WHERE (((r1.c1 < 20) OR ((r1.c1 IS NULL) AND (r2.c1 < 5)))) GROUP BY 2 ORDER BY array_agg(DISTINCT (r1.c1 % 5) ORDER BY ((r1.c1 % 5)) ASC NULLS LAST) ASC NULLS LAST + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT array_agg(DISTINCT (r1.c1 % 5) ORDER BY ((r1.c1 % 5)) ASC NULLS LAST), (r2.c1 % 3) FROM ("S 1"."T 3" r1 FULL JOIN "S 1"."T 4" r2 ON (((r1.c1 = r2.c1)))) WHERE (((r1.c1 < 20) OR ((r1.c1 IS NULL) AND (r2.c1 < 5)))) GROUP BY 2 ORDER BY array_agg(DISTINCT (r1.c1 % 5) ORDER BY ((r1.c1 % 5)) ASC NULLS LAST) ASC NULLS LAST + Sort + Output: (array_agg(DISTINCT (r1.c1 % 5) ORDER BY (r1.c1 % 5))), ((r2.c1 % 3)) + Sort Key: (array_agg(DISTINCT (r1.c1 % 5) ORDER BY (r1.c1 % 5))) + -> GroupAggregate + Output: array_agg(DISTINCT (r1.c1 % 5) ORDER BY (r1.c1 % 5)), ((r2.c1 % 3)) + Group By Key: ((r2.c1 % 3)) + -> Sort + Output: ((r2.c1 % 3)), r1.c1 + Sort Key: ((r2.c1 % 3)) + -> Hash Full Join + Output: (r2.c1 % 3), r1.c1 + Hash Cond: (r1.c1 = r2.c1) + Filter: ((r1.c1 < 20) OR ((r1.c1 IS NULL) AND (r2.c1 < 5))) + -> Partition Iterator + Output: r1.c1, r1.c2, r1.c3 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 3" r1 + Output: r1.c1, r1.c2, r1.c3 + Selected Partitions: 1..2 + -> Hash + Output: r2.c1 + -> Partition Iterator + Output: r2.c1 + Iterations: 2, Sub Iterations: 4 + -> Partitioned Seq Scan on "S 1"."T 4" r2 + Output: r2.c1 + Selected Partitions: 1..2 + Selected Subpartitions: ALL + +(37 rows) + +select array_agg(distinct (t1.c1)%5 order by (t1.c1)%5) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1; + array_agg +-------------- + {0,1,2,3,4} + {1,2,3,NULL} +(2 rows) + +explain (verbose, costs off) +select array_agg(distinct (t1.c1)%5 order by (t1.c1)%5 desc nulls last) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: (array_agg(DISTINCT (t1.c1 % 5) ORDER BY (t1.c1 % 5) DESC NULLS LAST)), ((t2.c1 % 3)) + Node ID: 1 + Relations: Aggregate on ((public.ft4 t1) FULL JOIN (public.ft5 t2)) + Remote SQL: SELECT array_agg(DISTINCT (r1.c1 % 5) ORDER BY ((r1.c1 % 5)) DESC NULLS LAST), (r2.c1 % 3) FROM ("S 1"."T 3" r1 FULL JOIN "S 1"."T 4" r2 ON (((r1.c1 = r2.c1)))) WHERE (((r1.c1 < 20) OR ((r1.c1 IS NULL) AND (r2.c1 < 5)))) GROUP BY 2 ORDER BY array_agg(DISTINCT (r1.c1 % 5) ORDER BY ((r1.c1 % 5)) DESC NULLS LAST) ASC NULLS LAST + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT array_agg(DISTINCT (r1.c1 % 5) ORDER BY ((r1.c1 % 5)) DESC NULLS LAST), (r2.c1 % 3) FROM ("S 1"."T 3" r1 FULL JOIN "S 1"."T 4" r2 ON (((r1.c1 = r2.c1)))) WHERE (((r1.c1 < 20) OR ((r1.c1 IS NULL) AND (r2.c1 < 5)))) GROUP BY 2 ORDER BY array_agg(DISTINCT (r1.c1 % 5) ORDER BY ((r1.c1 % 5)) DESC NULLS LAST) ASC NULLS LAST + Sort + Output: (array_agg(DISTINCT (r1.c1 % 5) ORDER BY (r1.c1 % 5) DESC NULLS LAST)), ((r2.c1 % 3)) + Sort Key: (array_agg(DISTINCT (r1.c1 % 5) ORDER BY (r1.c1 % 5) DESC NULLS LAST)) + -> GroupAggregate + Output: array_agg(DISTINCT (r1.c1 % 5) ORDER BY (r1.c1 % 5) DESC NULLS LAST), ((r2.c1 % 3)) + Group By Key: ((r2.c1 % 3)) + -> Sort + Output: ((r2.c1 % 3)), r1.c1 + Sort Key: ((r2.c1 % 3)) + -> Hash Full Join + Output: (r2.c1 % 3), r1.c1 + Hash Cond: (r1.c1 = r2.c1) + Filter: ((r1.c1 < 20) OR ((r1.c1 IS NULL) AND (r2.c1 < 5))) + -> Partition Iterator + Output: r1.c1, r1.c2, r1.c3 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 3" r1 + Output: r1.c1, r1.c2, r1.c3 + Selected Partitions: 1..2 + -> Hash + Output: r2.c1 + -> Partition Iterator + Output: r2.c1 + Iterations: 2, Sub Iterations: 4 + -> Partitioned Seq Scan on "S 1"."T 4" r2 + Output: r2.c1 + Selected Partitions: 1..2 + Selected Subpartitions: ALL + +(37 rows) + +select array_agg(distinct (t1.c1)%5 order by (t1.c1)%5 desc nulls last) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1; + array_agg +-------------- + {3,2,1,NULL} + {4,3,2,1,0} +(2 rows) + +--nspt FILTER within aggregate +-- openGauss not support FILTER within aggregate, leve a err case, make others run as normal +explain (verbose, costs off) +select sum(c1) filter (where c1 < 100 and c2 > 5) from ft1 group by c2 order by 1 nulls last; +ERROR: syntax error at or near "filter" +LINE 2: select sum(c1) filter (where c1 < 100 and c2 > 5) from ft1 g... + ^ +select sum(c1) filter (where c1 < 100 and c2 > 5) from ft1 group by c2 order by 1 nulls last; +ERROR: syntax error at or near "filter" +LINE 1: select sum(c1) filter (where c1 < 100 and c2 > 5) from ft1 g... + ^ +explain (verbose, costs off) +select sum(c1)/* filter (where c1 < 100 and c2 > 5)*/ from ft1 group by c2 order by 1 nulls last; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: (sum(c1)), c2 + Node ID: 1 + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT sum("C 1"), c2 FROM "S 1"."T 1" GROUP BY 2 ORDER BY sum("C 1") ASC NULLS LAST + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT sum("C 1"), c2 FROM "S 1"."T 1" GROUP BY 2 ORDER BY sum("C 1") ASC NULLS LAST + Sort + Output: (sum("C 1")), c2 + Sort Key: (sum("T 1"."C 1")) + -> HashAggregate + Output: sum("C 1"), c2 + Group By Key: "T 1".c2 + -> Partition Iterator + Output: c2, "C 1" + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: c2, "C 1" + Selected Partitions: 1..2 + +(21 rows) + +select sum(c1)/* filter (where c1 < 100 and c2 > 5)*/ from ft1 group by c2 order by 1 nulls last; + sum +------- + 49600 + 49700 + 49800 + 49900 + 50000 + 50100 + 50200 + 50300 + 50400 + 50500 +(10 rows) + +-- DISTINCT, ORDER BY and FILTER within aggregate +explain (verbose, costs off) +select sum(c1%3), sum(distinct c1%3 order by c1%3)/* filter (where c1%3 < 2)*/, c2 from ft1 where c2 = 6 group by c2; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + Foreign Scan + Output: (sum((c1 % 3))), (sum(DISTINCT (c1 % 3) ORDER BY (c1 % 3))), c2 + Node ID: 1 + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT sum(("C 1" % 3)), sum(DISTINCT ("C 1" % 3) ORDER BY (("C 1" % 3)) ASC NULLS LAST), c2 FROM "S 1"."T 1" WHERE ((c2 = 6)) GROUP BY 3 + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT sum(("C 1" % 3)), sum(DISTINCT ("C 1" % 3) ORDER BY (("C 1" % 3)) ASC NULLS LAST), c2 FROM "S 1"."T 1" WHERE ((c2 = 6)) GROUP BY 3 + GroupAggregate + Output: sum(("C 1" % 3)), sum(DISTINCT ("C 1" % 3) ORDER BY ("C 1" % 3)), c2 + Group By Key: "T 1".c2 + -> Partition Iterator + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Filter: ("T 1".c2 = 6) + Selected Partitions: 1..2 + +(19 rows) + +select sum(c1%3), sum(distinct c1%3 order by c1%3)/* filter (where c1%3 < 2)*/, c2 from ft1 where c2 = 6 group by c2; + sum | sum | c2 +-----+-----+---- + 99 | 3 | 6 +(1 row) + +-- Outer query is aggregation query +explain (verbose, costs off) +select distinct (select count(*) /*filter (where t2.c2 = 6 and t2.c1 < 10)*/ from ft1 t1 where t1.c1 = 6) from ft2 t2 where t2.c2 % 6 = 0 order by 1; + QUERY PLAN +---------------------------------------------------------------------------------------------- + Unique + Output: ($0) + InitPlan 1 (returns $0) + -> Aggregate + Output: count(*) + -> Foreign Scan on public.ft1 t1 + Node ID: 1 + Remote SQL: SELECT NULL FROM "S 1"."T 1" WHERE (("C 1" = 6)) + -> Foreign Scan on public.ft2 t2 + Output: $0 + Node ID: 2 + Remote SQL: SELECT NULL FROM "S 1"."T 1" WHERE (((c2 % 6) = 0)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT NULL FROM "S 1"."T 1" WHERE (("C 1" = 6)) + Partitioned Index Only Scan using t1_pkey on "S 1"."T 1" + Output: NULL::text + Index Cond: ("T 1"."C 1" = 6) + Selected Partitions: 1 + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT NULL FROM "S 1"."T 1" WHERE (((c2 % 6) = 0)) + Partition Iterator + Output: NULL::text + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: NULL::text + Filter: (("T 1".c2 % 6) = 0) + Selected Partitions: 1..2 + +(28 rows) + +select distinct (select count(*) /*filter (where t2.c2 = 6 and t2.c1 < 10)*/ from ft1 t1 where t1.c1 = 6) from ft2 t2 where t2.c2 % 6 = 0 order by 1; + count +------- + 1 +(1 row) + +-- Inner query is aggregation query +explain (verbose, costs off) +select distinct (select count(t1.c1) /*filter (where t2.c2 = 6 and t2.c1 < 10)*/ from ft1 t1 where t1.c1 = 6) from ft2 t2 where t2.c2 % 6 = 0 order by 1; + QUERY PLAN +---------------------------------------------------------------------------------------------- + Unique + Output: ($0) + InitPlan 1 (returns $0) + -> Aggregate + Output: count(t1.c1) + -> Foreign Scan on public.ft1 t1 + Output: t1.c1 + Node ID: 1 + Remote SQL: SELECT "C 1" FROM "S 1"."T 1" WHERE (("C 1" = 6)) + -> Foreign Scan on public.ft2 t2 + Output: $0 + Node ID: 2 + Remote SQL: SELECT NULL FROM "S 1"."T 1" WHERE (((c2 % 6) = 0)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1" FROM "S 1"."T 1" WHERE (("C 1" = 6)) + Partitioned Index Only Scan using t1_pkey on "S 1"."T 1" + Output: "C 1" + Index Cond: ("T 1"."C 1" = 6) + Selected Partitions: 1 + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT NULL FROM "S 1"."T 1" WHERE (((c2 % 6) = 0)) + Partition Iterator + Output: NULL::text + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: NULL::text + Filter: (("T 1".c2 % 6) = 0) + Selected Partitions: 1..2 + +(29 rows) + +select distinct (select count(t1.c1) /*filter (where t2.c2 = 6 and t2.c1 < 10)*/ from ft1 t1 where t1.c1 = 6) from ft2 t2 where t2.c2 % 6 = 0 order by 1; + count +------- + 1 +(1 row) + +-- Aggregate not pushed down as FILTER condition is not pushable +explain (verbose, costs off) select sum(c1) /*filter (where (c1 / c1) * random() <= 1)*/ from ft1 group by c2 order by 1; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: (sum(c1)), c2 + Node ID: 1 + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT sum("C 1"), c2 FROM "S 1"."T 1" GROUP BY 2 ORDER BY sum("C 1") ASC NULLS LAST + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT sum("C 1"), c2 FROM "S 1"."T 1" GROUP BY 2 ORDER BY sum("C 1") ASC NULLS LAST + Sort + Output: (sum("C 1")), c2 + Sort Key: (sum("T 1"."C 1")) + -> HashAggregate + Output: sum("C 1"), c2 + Group By Key: "T 1".c2 + -> Partition Iterator + Output: c2, "C 1" + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: c2, "C 1" + Selected Partitions: 1..2 + +(21 rows) + +explain (verbose, costs off) select sum(c2) /*filter (where c2 in (select c2 from ft1 where c2 < 5))*/ from ft1; + QUERY PLAN +-------------------------------------------------------------------------- + Foreign Scan + Output: (sum(c2)) + Node ID: 1 + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT sum(c2) FROM "S 1"."T 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT sum(c2) FROM "S 1"."T 1" + Aggregate + Output: sum(c2) + -> Partition Iterator + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Selected Partitions: 1..2 + +(17 rows) + +--nspt Ordered-sets within aggregate +--nspt Using multiple arguments within aggregates +-- User defined function for user defined aggregate, VARIADIC +create function least_accum(anyelement, variadic anyarray) +returns anyelement language sql as + 'select least($1, min($2[i])) from generate_subscripts($2,1) g(i)'; +create aggregate least_agg(variadic items anyarray) ( + stype = anyelement, sfunc = least_accum +); +ERROR: syntax error at or near "variadic" +LINE 1: create aggregate least_agg(variadic items anyarray) ( + ^ +-- Disable hash aggregation for plan stability. +set enable_hashagg to false; +-- Not pushed down due to user defined aggregate +explain (verbose, costs off) +select c2, least_agg(c1) from ft1 group by c2 order by c2; +ERROR: function least_agg(integer) does not exist +LINE 2: select c2, least_agg(c1) from ft1 group by c2 order by c2; + ^ +HINT: No function matches the given name and argument types. You might need to add explicit type casts. +CONTEXT: referenced column: least_agg +-- Add function and aggregate into extension +alter extension postgres_fdw add function least_accum(anyelement, variadic anyarray); +ERROR: function least_accum(anyelement,anyarray) is already a member of extension "postgres_fdw" +alter extension postgres_fdw add aggregate least_agg(variadic items anyarray); +ERROR: syntax error at or near "variadic" +LINE 1: ...er extension postgres_fdw add aggregate least_agg(variadic i... + ^ +alter server loopback options (set extensions 'postgres_fdw'); +ERROR: option "extensions" not found +-- Now aggregate will be pushed. Aggregate will display VARIADIC argument. +explain (verbose, costs off) +select c2, least_agg(c1) from ft1 where c2 < 100 group by c2 order by c2; +ERROR: function least_agg(integer) does not exist +LINE 2: select c2, least_agg(c1) from ft1 where c2 < 100 group by c2... + ^ +HINT: No function matches the given name and argument types. You might need to add explicit type casts. +CONTEXT: referenced column: least_agg +select c2, least_agg(c1) from ft1 where c2 < 100 group by c2 order by c2; +ERROR: function least_agg(integer) does not exist +LINE 1: select c2, least_agg(c1) from ft1 where c2 < 100 group by c2... + ^ +HINT: No function matches the given name and argument types. You might need to add explicit type casts. +CONTEXT: referenced column: least_agg +-- Remove function and aggregate from extension +alter extension postgres_fdw drop function least_accum(anyelement, variadic anyarray); +alter extension postgres_fdw drop aggregate least_agg(variadic items anyarray); +ERROR: syntax error at or near "variadic" +LINE 1: ...r extension postgres_fdw drop aggregate least_agg(variadic i... + ^ +alter server loopback options (set extensions 'postgres_fdw'); +ERROR: option "extensions" not found +-- Not pushed down as we have dropped objects from extension. +explain (verbose, costs off) +select c2, least_agg(c1) from ft1 group by c2 order by c2; +ERROR: function least_agg(integer) does not exist +LINE 2: select c2, least_agg(c1) from ft1 group by c2 order by c2; + ^ +HINT: No function matches the given name and argument types. You might need to add explicit type casts. +CONTEXT: referenced column: least_agg +-- Cleanup +reset enable_hashagg; +drop aggregate least_agg(variadic items anyarray); +ERROR: syntax error at or near "variadic" +LINE 1: drop aggregate least_agg(variadic items anyarray); + ^ +drop function least_accum(anyelement, variadic anyarray); +--nspt Testing USING OPERATOR() in ORDER BY within aggregate. +--nspt For this, we need user defined operators along with operator family and +--nspt operator class. Create those and then add them in extension. Note that +--nspt user defined objects are considered unshippable unless they are part of +--nspt the extension. +create operator public.<^ ( + leftarg = int4, + rightarg = int4, + procedure = int4eq +); +create operator public.=^ ( + leftarg = int4, + rightarg = int4, + procedure = int4lt +); +create operator public.>^ ( + leftarg = int4, + rightarg = int4, + procedure = int4gt +); +create operator family my_op_family using btree; +ERROR: user defined operator is not yet supported. +create function my_op_cmp(a int, b int) returns int as + $$begin return btint4cmp(a, b); end $$ language plpgsql; +create operator class my_op_class for type int using btree family my_op_family as + operator 1 public.<^, + operator 3 public.=^, + operator 5 public.>^, + function 1 my_op_cmp(int, int); +ERROR: operator family "my_op_family" does not exist for access method "btree" +-- NOTICES: +-- openGauss not support create operator class. remove the case. If you want to restore the test case later, refer to README in the file header. +-- This will not be pushed as sort operator is now removed from the extension. +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; +ERROR: operator <^ is not a valid ordering operator +LINE 2: select array_agg(c1 order by c1 using operator(public.<^)) f... + ^ +HINT: Ordering operators must be "<" or ">" members of btree operator families. +CONTEXT: referenced column: array_agg +-- Cleanup +drop operator class my_op_class using btree; +ERROR: operator class "my_op_class" does not exist for access method "btree" +drop function my_op_cmp(a int, b int); +drop operator family my_op_family using btree; +ERROR: operator family "my_op_family" does not exist for access method "btree" +drop operator public.>^(int, int); +drop operator public.=^(int, int); +drop operator public.<^(int, int); +-- Input relation to aggregate push down hook is not safe to pushdown and thus +-- the aggregate cannot be pushed down to foreign server. +explain (verbose, costs off) +select count(t1.c3) from ft2 t1 left join ft2 t2 on (t1.c1 = random() * t2.c2); + QUERY PLAN +------------------------------------------------------------------------------------------- + Aggregate + Output: count(t1.c3) + -> Nested Loop Left Join + Output: t1.c3 + Join Filter: ((t1.c1)::double precision = (random() * (t2.c2)::double precision)) + -> Foreign Scan on public.ft2 t1 + Output: t1.c3, t1.c1 + Node ID: 1 + Remote SQL: SELECT "C 1", c3 FROM "S 1"."T 1" + -> Materialize + Output: t2.c2 + -> Foreign Scan on public.ft2 t2 + Output: t2.c2 + Node ID: 2 + Remote SQL: SELECT c2 FROM "S 1"."T 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c3 FROM "S 1"."T 1" + Partition Iterator + Output: "C 1", c3 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: "C 1", c3 + Selected Partitions: 1..2 + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c2 FROM "S 1"."T 1" + Partition Iterator + Output: c2 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: c2 + Selected Partitions: 1..2 + +(32 rows) + +-- Subquery in FROM clause having aggregate +explain (verbose, costs off) +select count(*), x.b from ft1, (select c2 a, sum(c1) b from ft1 group by c2) x where ft1.c2 = x.a group by x.b order by 1, 2; + QUERY PLAN +----------------------------------------------------------------------------------------------- + Sort + Output: (count(*)), x.b + Sort Key: (count(*)), x.b + -> HashAggregate + Output: count(*), x.b + Group By Key: x.b + -> Hash Join + Output: x.b + Hash Cond: (public.ft1.c2 = x.a) + -> Foreign Scan on public.ft1 + Output: public.ft1.c2 + Node ID: 1 + Remote SQL: SELECT c2 FROM "S 1"."T 1" + -> Hash + Output: x.b, x.a + -> Subquery Scan on x + Output: x.b, x.a + -> Foreign Scan + Output: public.ft1.c2, (sum(public.ft1.c1)) + Node ID: 2 + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT c2, sum("C 1") FROM "S 1"."T 1" GROUP BY 1 + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c2 FROM "S 1"."T 1" + Partition Iterator + Output: c2 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: c2 + Selected Partitions: 1..2 + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c2, sum("C 1") FROM "S 1"."T 1" GROUP BY 1 + HashAggregate + Output: c2, sum("C 1") + Group By Key: "T 1".c2 + -> Partition Iterator + Output: c2, "C 1" + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: c2, "C 1" + Selected Partitions: 1..2 + +(42 rows) + +select count(*), x.b from ft1, (select c2 a, sum(c1) b from ft1 group by c2) x where ft1.c2 = x.a group by x.b order by 1, 2; + count | b +-------+------- + 100 | 49600 + 100 | 49700 + 100 | 49800 + 100 | 49900 + 100 | 50000 + 100 | 50100 + 100 | 50200 + 100 | 50300 + 100 | 50400 + 100 | 50500 +(10 rows) + +-- FULL join with IS NULL check in HAVING +explain (verbose, costs off) +select avg(t1.c1), sum(t2.c1) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) group by t2.c1 having (avg(t1.c1) is null and sum(t2.c1) < 10) or sum(t2.c1) is null order by 1 nulls last, 2; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: (avg(t1.c1)), (sum(t2.c1)), t2.c1 + Node ID: 1 + Relations: Aggregate on ((public.ft4 t1) FULL JOIN (public.ft5 t2)) + Remote SQL: SELECT avg(r1.c1), sum(r2.c1), r2.c1 FROM ("S 1"."T 3" r1 FULL JOIN "S 1"."T 4" r2 ON (((r1.c1 = r2.c1)))) GROUP BY 3 HAVING ((((avg(r1.c1) IS NULL) AND (sum(r2.c1) < 10)) OR (sum(r2.c1) IS NULL))) ORDER BY avg(r1.c1) ASC NULLS LAST, sum(r2.c1) ASC NULLS LAST + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT avg(r1.c1), sum(r2.c1), r2.c1 FROM ("S 1"."T 3" r1 FULL JOIN "S 1"."T 4" r2 ON (((r1.c1 = r2.c1)))) GROUP BY 3 HAVING ((((avg(r1.c1) IS NULL) AND (sum(r2.c1) < 10)) OR (sum(r2.c1) IS NULL))) ORDER BY avg(r1.c1) ASC NULLS LAST, sum(r2.c1) ASC NULLS LAST + Sort + Output: (avg(r1.c1)), (sum(r2.c1)), r2.c1 + Sort Key: (avg(r1.c1)), (sum(r2.c1)) + -> HashAggregate + Output: avg(r1.c1), sum(r2.c1), r2.c1 + Group By Key: r2.c1 + Filter: (((avg(r1.c1) IS NULL) AND (sum(r2.c1) < 10)) OR (sum(r2.c1) IS NULL)) + -> Hash Full Join + Output: r1.c1, r2.c1 + Hash Cond: (r1.c1 = r2.c1) + -> Partition Iterator + Output: r1.c1, r1.c2, r1.c3 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 3" r1 + Output: r1.c1, r1.c2, r1.c3 + Selected Partitions: 1..2 + -> Hash + Output: r2.c1 + -> Partition Iterator + Output: r2.c1 + Iterations: 2, Sub Iterations: 4 + -> Partitioned Seq Scan on "S 1"."T 4" r2 + Output: r2.c1 + Selected Partitions: 1..2 + Selected Subpartitions: ALL + +(34 rows) + +select avg(t1.c1), sum(t2.c1) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) group by t2.c1 having (avg(t1.c1) is null and sum(t2.c1) < 10) or sum(t2.c1) is null order by 1 nulls last, 2; + avg | sum +---------------------+----- + 51.0000000000000000 | + | 3 + | 9 +(3 rows) + +-- Aggregate over FULL join needing to deparse the joining relations as +-- subqueries. +explain (verbose, costs off) +select count(*), sum(t1.c1), avg(t2.c1) from (select c1 from ft4 where c1 between 50 and 60) t1 full join (select c1 from ft5 where c1 between 50 and 60) t2 on (t1.c1 = t2.c1); + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: (count(*)), (sum(ft4.c1)), (avg(ft5.c1)) + Node ID: 1 + Relations: Aggregate on ((public.ft4) FULL JOIN (public.ft5)) + Remote SQL: SELECT count(*), sum(s4.c1), avg(s5.c1) FROM ((SELECT c1 FROM "S 1"."T 3" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s4(c1) FULL JOIN (SELECT c1 FROM "S 1"."T 4" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s5(c1) ON (((s4.c1 = s5.c1)))) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT count(*), sum(s4.c1), avg(s5.c1) FROM ((SELECT c1 FROM "S 1"."T 3" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s4(c1) FULL JOIN (SELECT c1 FROM "S 1"."T 4" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s5(c1) ON (((s4.c1 = s5.c1)))) + Aggregate + Output: count(*), sum("T 3".c1), avg("T 4".c1) + -> Hash Full Join + Output: "T 3".c1, "T 4".c1 + Hash Cond: ("T 3".c1 = "T 4".c1) + -> Partition Iterator + Output: "T 3".c1, "T 3".c2, "T 3".c3 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 3" + Output: "T 3".c1, "T 3".c2, "T 3".c3 + Filter: (("T 3".c1 >= 50) AND ("T 3".c1 <= 60)) + Selected Partitions: 1..2 + -> Hash + Output: "T 4".c1 + -> Partition Iterator + Output: "T 4".c1 + Iterations: 1, Sub Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 4" + Output: "T 4".c1 + Filter: (("T 4".c1 >= 50) AND ("T 4".c1 <= 60)) + Selected Partitions: 2 + Selected Subpartitions: ALL + +(31 rows) + +select count(*), sum(t1.c1), avg(t2.c1) from (select c1 from ft4 where c1 between 50 and 60) t1 full join (select c1 from ft5 where c1 between 50 and 60) t2 on (t1.c1 = t2.c1); + count | sum | avg +-------+-----+--------------------- + 8 | 330 | 55.5000000000000000 +(1 row) + +-- ORDER BY expression is part of the target list but not pushed down to +-- foreign server. +explain (verbose, costs off) +select sum(c2) * (random() <= 1)::int as sum from ft1 order by 1; + QUERY PLAN +---------------------------------------------------------------------------- + Sort + Output: ((sum(c2) * ((random() <= 1::double precision))::integer)) + Sort Key: ((sum(ft1.c2) * ((random() <= 1::double precision))::integer)) + -> Aggregate + Output: (sum(c2) * ((random() <= 1::double precision))::integer) + -> Foreign Scan on public.ft1 + Output: c2 + Node ID: 1 + Remote SQL: SELECT c2 FROM "S 1"."T 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c2 FROM "S 1"."T 1" + Partition Iterator + Output: c2 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: c2 + Selected Partitions: 1..2 + +(19 rows) + +select sum(c2) * (random() <= 1)::int as sum from ft1 order by 1; + sum +------ + 4500 +(1 row) + +--nspt LATERAL join, with parameterization +-- NOTICES: +-- openGauss not support create operator class. remove the case. If you want to restore the test case later, refer to README in the file header. +-- Check with placeHolderVars +explain (verbose, costs off) +select sum(q.a), count(q.b) from ft4 left join (select 13, avg(ft1.c1), sum(ft2.c1) from ft1 right join ft2 on (ft1.c1 = ft2.c1)) q(a, b, c) on (ft4.c1 <= q.b); + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------------------- + Aggregate + Output: sum(q.a), count(q.b) + -> Nested Loop Left Join + Output: q.a, q.b + Join Filter: ((ft4.c1)::numeric <= q.b) + -> Foreign Scan on public.ft4 + Output: ft4.c1 + Node ID: 1 + Remote SQL: SELECT c1 FROM "S 1"."T 3" + -> Materialize + Output: q.a, q.b + -> Subquery Scan on q + Output: q.a, q.b + -> Foreign Scan + Output: (13), (avg(ft1.c1)), (sum(ft2.c1)) + Node ID: 2 + Relations: Aggregate on ((public.ft2) LEFT JOIN (public.ft1)) + Remote SQL: SELECT 13, avg(r1."C 1"), sum(r2."C 1") FROM ("S 1"."T 1" r2 LEFT JOIN "S 1"."T 1" r1 ON (((r1."C 1" = r2."C 1")))) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c1 FROM "S 1"."T 3" + Partition Iterator + Output: c1 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 3" + Output: c1 + Selected Partitions: 1..2 + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT 13, avg(r1."C 1"), sum(r2."C 1") FROM ("S 1"."T 1" r2 LEFT JOIN "S 1"."T 1" r1 ON (((r1."C 1" = r2."C 1")))) + Aggregate + Output: 13, avg(r1."C 1"), sum(r2."C 1") + -> Hash Left Join + Output: r2."C 1", r1."C 1" + Hash Cond: (r2."C 1" = r1."C 1") + -> Partition Iterator + Output: r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" r2 + Output: r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8 + Selected Partitions: 1..2 + -> Hash + Output: r1."C 1" + -> Partition Iterator + Output: r1."C 1" + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" r1 + Output: r1."C 1" + Selected Partitions: 1..2 + +(48 rows) + +select sum(q.a), count(q.b) from ft4 left join (select 13, avg(ft1.c1), sum(ft2.c1) from ft1 right join ft2 on (ft1.c1 = ft2.c1)) q(a, b, c) on (ft4.c1 <= q.b); + sum | count +-----+------- + 650 | 50 +(1 row) + +-- Not supported cases +-- Grouping sets +explain (verbose, costs off) +select c2, sum(c1) from ft1 where c2 < 3 group by rollup(c2) order by 1 nulls last; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------ + Sort + Output: c2, (sum(c1)) + Sort Key: ft1.c2 + -> GroupAggregate + Output: c2, sum(c1) + Group By Key: ft1.c2 + Group By Key: () + -> Foreign Scan on public.ft1 + Output: c2, c1 + Node ID: 1 + Remote SQL: SELECT "C 1", c2 FROM "S 1"."T 1" WHERE ((c2 < 3)) ORDER BY c2 ASC NULLS LAST + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2 FROM "S 1"."T 1" WHERE ((c2 < 3)) ORDER BY c2 ASC NULLS LAST + Sort + Output: "C 1", c2 + Sort Key: "T 1".c2 + -> Partition Iterator + Output: "C 1", c2 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: "C 1", c2 + Filter: ("T 1".c2 < 3) + Selected Partitions: 1..2 + +(25 rows) + +select c2, sum(c1) from ft1 where c2 < 3 group by rollup(c2) order by 1 nulls last; + c2 | sum +----+-------- + 0 | 50500 + 1 | 49600 + 2 | 49700 + | 149800 +(4 rows) + +explain (verbose, costs off) +select c2, sum(c1) from ft1 where c2 < 3 group by cube(c2) order by 1 nulls last; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------ + Sort + Output: c2, (sum(c1)) + Sort Key: ft1.c2 + -> GroupAggregate + Output: c2, sum(c1) + Group By Key: ft1.c2 + Group By Key: () + -> Foreign Scan on public.ft1 + Output: c2, c1 + Node ID: 1 + Remote SQL: SELECT "C 1", c2 FROM "S 1"."T 1" WHERE ((c2 < 3)) ORDER BY c2 ASC NULLS LAST + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2 FROM "S 1"."T 1" WHERE ((c2 < 3)) ORDER BY c2 ASC NULLS LAST + Sort + Output: "C 1", c2 + Sort Key: "T 1".c2 + -> Partition Iterator + Output: "C 1", c2 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: "C 1", c2 + Filter: ("T 1".c2 < 3) + Selected Partitions: 1..2 + +(25 rows) + +select c2, sum(c1) from ft1 where c2 < 3 group by cube(c2) order by 1 nulls last; + c2 | sum +----+-------- + 0 | 50500 + 1 | 49600 + 2 | 49700 + | 149800 +(4 rows) + +explain (verbose, costs off) +select c2, c6, sum(c1) from ft1 where c2 < 3 group by grouping sets(c2, c6) order by 1 nulls last, 2 nulls last; + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------- + Sort + Output: c2, c6, (sum(c1)) + Sort Key: ft1.c2, ft1.c6 + -> GroupAggregate + Output: c2, c6, sum(c1) + Group By Key: ft1.c2 + Sort Key: ft1.c6 + Group By Key: ft1.c6 + -> Foreign Scan on public.ft1 + Output: c2, c6, c1 + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c6 FROM "S 1"."T 1" WHERE ((c2 < 3)) ORDER BY c2 ASC NULLS LAST + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c6 FROM "S 1"."T 1" WHERE ((c2 < 3)) ORDER BY c2 ASC NULLS LAST + Sort + Output: "C 1", c2, c6 + Sort Key: "T 1".c2 + -> Partition Iterator + Output: "C 1", c2, c6 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c6 + Filter: ("T 1".c2 < 3) + Selected Partitions: 1..2 + +(26 rows) + +select c2, c6, sum(c1) from ft1 where c2 < 3 group by grouping sets(c2, c6) order by 1 nulls last, 2 nulls last; + c2 | c6 | sum +----+----+------- + 0 | | 50500 + 1 | | 49600 + 2 | | 49700 + | 0 | 50500 + | 1 | 49600 + | 2 | 49700 +(6 rows) + +explain (verbose, costs off) +select c2, sum(c1), grouping(c2) from ft1 where c2 < 3 group by c2 order by 1 nulls last; + QUERY PLAN +--------------------------------------------------------------------------------------------- + Sort + Output: c2, (sum(c1)), (GROUPING(c2)) + Sort Key: ft1.c2 + -> HashAggregate + Output: c2, sum(c1), GROUPING(c2) + Group By Key: ft1.c2 + -> Foreign Scan on public.ft1 + Output: c2, c1 + Node ID: 1 + Remote SQL: SELECT "C 1", c2 FROM "S 1"."T 1" WHERE ((c2 < 3)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2 FROM "S 1"."T 1" WHERE ((c2 < 3)) + Partition Iterator + Output: "C 1", c2 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: "C 1", c2 + Filter: ("T 1".c2 < 3) + Selected Partitions: 1..2 + +(21 rows) + +select c2, sum(c1), grouping(c2) from ft1 where c2 < 3 group by c2 order by 1 nulls last; + c2 | sum | grouping +----+-------+---------- + 0 | 50500 | 0 + 1 | 49600 | 0 + 2 | 49700 | 0 +(3 rows) + +-- DISTINCT itself is not pushed down, whereas underneath aggregate is pushed +explain (verbose, costs off) +select distinct sum(c1)/1000 s from ft2 where c2 < 6 group by c2 order by 1; + QUERY PLAN +--------------------------------------------------------------------------------------------- + Unique + Output: ((sum(c1) / 1000)), c2 + -> Sort + Output: ((sum(c1) / 1000)), c2 + Sort Key: ((sum(ft2.c1) / 1000)) + -> HashAggregate + Output: (sum(c1) / 1000), c2 + Group By Key: ft2.c2 + -> Foreign Scan on public.ft2 + Output: c2, c1 + Node ID: 1 + Remote SQL: SELECT "C 1", c2 FROM "S 1"."T 1" WHERE ((c2 < 6)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2 FROM "S 1"."T 1" WHERE ((c2 < 6)) + Partition Iterator + Output: "C 1", c2 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: "C 1", c2 + Filter: ("T 1".c2 < 6) + Selected Partitions: 1..2 + +(23 rows) + +select distinct sum(c1)/1000 s from ft2 where c2 < 6 group by c2 order by 1; + s +------ + 49.6 + 49.7 + 49.8 + 49.9 + 50 + 50.5 +(6 rows) + +-- WindowAgg +explain (verbose, costs off) +select c2, sum(c2), count(c2) over (partition by c2%2) from ft2 where c2 < 10 group by c2 order by 1; + QUERY PLAN +------------------------------------------------------------------------------------------ + Sort + Output: c2, (sum(c2)), (count(c2) OVER (PARTITION BY ((c2 % 2)))), ((c2 % 2)) + Sort Key: ft2.c2 + -> WindowAgg + Output: c2, (sum(c2)), count(c2) OVER (PARTITION BY ((c2 % 2))), ((c2 % 2)) + -> Sort + Output: c2, ((c2 % 2)), (sum(c2)) + Sort Key: ((ft2.c2 % 2)) + -> GroupAggregate + Output: c2, (c2 % 2), sum(c2) + Group By Key: ft2.c2 + -> Sort + Output: c2 + Sort Key: ft2.c2 + -> Foreign Scan on public.ft2 + Output: c2 + Node ID: 1 + Remote SQL: SELECT c2 FROM "S 1"."T 1" WHERE ((c2 < 10)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c2 FROM "S 1"."T 1" WHERE ((c2 < 10)) + Partition Iterator + Output: c2 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: c2 + Filter: ("T 1".c2 < 10) + Selected Partitions: 1..2 + +(29 rows) + +select c2, sum(c2), count(c2) over (partition by c2%2) from ft2 where c2 < 10 group by c2 order by 1; + c2 | sum | count +----+-----+------- + 0 | 0 | 5 + 1 | 100 | 5 + 2 | 200 | 5 + 3 | 300 | 5 + 4 | 400 | 5 + 5 | 500 | 5 + 6 | 600 | 5 + 7 | 700 | 5 + 8 | 800 | 5 + 9 | 900 | 5 +(10 rows) + +explain (verbose, costs off) +select c2, array_agg(c2) over (partition by c2%2 order by c2 desc) from ft1 where c2 < 10 group by c2 order by 1; + QUERY PLAN +------------------------------------------------------------------------------------------------------------- + Sort + Output: c2, (array_agg(c2) OVER (PARTITION BY ((c2 % 2)) ORDER BY c2 USING = NULLS LAST)), ((c2 % 2)) + Sort Key: ft1.c2 + -> WindowAgg + Output: c2, array_agg(c2) OVER (PARTITION BY ((c2 % 2)) ORDER BY c2 USING = NULLS LAST), ((c2 % 2)) + -> Sort + Output: c2, ((c2 % 2)) + Sort Key: ((ft1.c2 % 2)), ft1.c2 DESC + -> HashAggregate + Output: c2, (c2 % 2) + Group By Key: ft1.c2 + -> Foreign Scan on public.ft1 + Output: c2 + Node ID: 1 + Remote SQL: SELECT c2 FROM "S 1"."T 1" WHERE ((c2 < 10)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c2 FROM "S 1"."T 1" WHERE ((c2 < 10)) + Partition Iterator + Output: c2 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: c2 + Filter: ("T 1".c2 < 10) + Selected Partitions: 1..2 + +(26 rows) + +select c2, array_agg(c2) over (partition by c2%2 order by c2 desc) from ft1 where c2 < 10 group by c2 order by 1; + c2 | array_agg +----+------------- + 0 | {8,6,4,2,0} + 1 | {9,7,5,3,1} + 2 | {8,6,4,2} + 3 | {9,7,5,3} + 4 | {8,6,4} + 5 | {9,7,5} + 6 | {8,6} + 7 | {9,7} + 8 | {8} + 9 | {9} +(10 rows) + +explain (verbose, costs off) +select c2, array_agg(c2) over (partition by c2%2 order by c2 range between current row and unbounded following) from ft1 where c2 < 10 group by c2 order by 1; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------------------------------------- + Sort + Output: c2, (array_agg(c2) OVER (PARTITION BY ((c2 % 2)) ORDER BY c2 USING = NULLS LAST RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)), ((c2 % 2)) + Sort Key: ft1.c2 + -> WindowAgg + Output: c2, array_agg(c2) OVER (PARTITION BY ((c2 % 2)) ORDER BY c2 USING = NULLS LAST RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING), ((c2 % 2)) + -> Sort + Output: c2, ((c2 % 2)) + Sort Key: ((ft1.c2 % 2)), ft1.c2 + -> HashAggregate + Output: c2, (c2 % 2) + Group By Key: ft1.c2 + -> Foreign Scan on public.ft1 + Output: c2 + Node ID: 1 + Remote SQL: SELECT c2 FROM "S 1"."T 1" WHERE ((c2 < 10)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c2 FROM "S 1"."T 1" WHERE ((c2 < 10)) + Partition Iterator + Output: c2 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: c2 + Filter: ("T 1".c2 < 10) + Selected Partitions: 1..2 + +(26 rows) + +select c2, array_agg(c2) over (partition by c2%2 order by c2 range between current row and unbounded following) from ft1 where c2 < 10 group by c2 order by 1; + c2 | array_agg +----+------------- + 0 | {0,2,4,6,8} + 1 | {1,3,5,7,9} + 2 | {2,4,6,8} + 3 | {3,5,7,9} + 4 | {4,6,8} + 5 | {5,7,9} + 6 | {6,8} + 7 | {7,9} + 8 | {8} + 9 | {9} +(10 rows) + +-- Same group by +explain verbose select c1,c1,c1,count(*) from ft1 group by 1,1,1; + QUERY PLAN +----------------------------------------------------------------------------------- + HashAggregate (cost=147.00..157.00 rows=1000 width=12) + Output: c1, c1, c1, count(*) + Group By Key: ft1.c1 + -> Foreign Scan on public.ft1 (cost=100.00..142.00 rows=1000 width=4) + Output: c1 + Node ID: 1 + Remote SQL: SELECT "C 1" FROM "S 1"."T 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS ON) SELECT "C 1" FROM "S 1"."T 1" + Partition Iterator (cost=0.00..22.00 rows=1000 width=4) + Output: "C 1" + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" (cost=0.00..22.00 rows=1000 width=4) + Output: "C 1" + Selected Partitions: 1..2 + +(17 rows) + +set enable_hashagg = off; +explain verbose select c1,c1,c1,count(*) from ft1 group by 1,1,1; + QUERY PLAN +-------------------------------------------------------------------------------------------- + Foreign Scan (cost=105.00..157.00 rows=1000 width=0) + Output: c1, c1, c1, (count(*)) + Node ID: 1 + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT "C 1", count(*) FROM "S 1"."T 1" GROUP BY 1 + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS ON) SELECT "C 1", count(*) FROM "S 1"."T 1" GROUP BY 1 + HashAggregate (cost=27.00..37.00 rows=1000 width=12) + Output: "C 1", count(*) + Group By Key: "T 1"."C 1" + -> Partition Iterator (cost=0.00..22.00 rows=1000 width=4) + Output: "C 1" + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" (cost=0.00..22.00 rows=1000 width=4) + Output: "C 1" + Selected Partitions: 1..2 + +(18 rows) + +set enable_hashagg = on; +-- ====================================================================================================================================== +-- TEST-MODULE: parameterized queries +-- -------------------------------------- +-- ====================================================================================================================================== +-- simple join +PREPARE st1(int, int) AS SELECT t1.c3, t2.c3 FROM ft1 t1, ft2 t2 WHERE t1.c1 = $1 AND t2.c1 = $2; +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st1(1, 2); + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: t1.c3, t2.c3 + Node ID: 1 + Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2) + Remote SQL: SELECT r1.c3, r2.c3 FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r2."C 1" = $1::integer)) AND ((r1."C 1" = $2::integer)))) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT r1.c3, r2.c3 FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r2."C 1" = $1::integer)) AND ((r1."C 1" = $2::integer)))) + failed to get remote plan, error massage is: + there is no parameter $1 + +(11 rows) + +EXECUTE st1(1, 1); + c3 | c3 +-------+------- + 00001 | 00001 +(1 row) + +EXECUTE st1(101, 101); + c3 | c3 +-------+------- + 00101 | 00101 +(1 row) + +-- subquery using stable function (can't be sent to remote) +PREPARE st2(int) AS SELECT * FROM ft1 t1 WHERE t1.c1 < $2 AND t1.c3 IN (SELECT c3 FROM ft2 t2 WHERE c1 > $1 AND "date"(c4) = '1970-01-17'::date) ORDER BY c1; +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st2(10, 20); + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------- + Sort + Output: t1.c1, t1.c2, t1.c3, t1.c4, t1.c5, t1.c6, t1.c7, t1.c8 + Sort Key: t1.c1 + -> Hash Semi Join + Output: t1.c1, t1.c2, t1.c3, t1.c4, t1.c5, t1.c6, t1.c7, t1.c8 + Hash Cond: (t1.c3 = t2.c3) + -> Foreign Scan on public.ft1 t1 + Output: t1.c1, t1.c2, t1.c3, t1.c4, t1.c5, t1.c6, t1.c7, t1.c8 + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" < $1::integer)) + -> Hash + Output: t2.c3 + -> Foreign Scan on public.ft2 t2 + Output: t2.c3 + Filter: ("date"(t2.c4) = 'Sat Jan 17 00:00:00 1970'::timestamp(0) without time zone) + Node ID: 2 + Remote SQL: SELECT c3, c4 FROM "S 1"."T 1" WHERE (("C 1" > $1::integer)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" < $1::integer)) + failed to get remote plan, error massage is: + there is no parameter $1 + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c3, c4 FROM "S 1"."T 1" WHERE (("C 1" > $1::integer)) + failed to get remote plan, error massage is: + current transaction is aborted, commands ignored until end of transaction block, firstChar[Q] + +(26 rows) + +EXECUTE st2(10, 20); + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +----+----+-------+------------------------------+--------------------------+----+------------+----- + 16 | 6 | 00016 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo +(1 row) + +EXECUTE st2(101, 121); + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +-----+----+-------+------------------------------+--------------------------+----+------------+----- + 116 | 6 | 00116 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo +(1 row) + +-- subquery using immutable function (can be sent to remote) +PREPARE st3(int) AS SELECT * FROM ft1 t1 WHERE t1.c1 < $2 AND t1.c3 IN (SELECT c3 FROM ft2 t2 WHERE c1 > $1 AND "date"(c5) = '1970-01-17'::date) ORDER BY c1; +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st3(10, 20); + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + Sort + Output: t1.c1, t1.c2, t1.c3, t1.c4, t1.c5, t1.c6, t1.c7, t1.c8 + Sort Key: t1.c1 + -> Hash Semi Join + Output: t1.c1, t1.c2, t1.c3, t1.c4, t1.c5, t1.c6, t1.c7, t1.c8 + Hash Cond: (t1.c3 = t2.c3) + -> Foreign Scan on public.ft1 t1 + Output: t1.c1, t1.c2, t1.c3, t1.c4, t1.c5, t1.c6, t1.c7, t1.c8 + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" < $1::integer)) + -> Hash + Output: t2.c3 + -> Foreign Scan on public.ft2 t2 + Output: t2.c3 + Node ID: 2 + Remote SQL: SELECT c3 FROM "S 1"."T 1" WHERE (("C 1" > $1::integer)) AND (("date"(c5) = '1970-01-17 00:00:00'::timestamp(0) without time zone)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" < $1::integer)) + failed to get remote plan, error massage is: + there is no parameter $1 + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c3 FROM "S 1"."T 1" WHERE (("C 1" > $1::integer)) AND (("date"(c5) = '1970-01-17 00:00:00'::timestamp(0) without time zone)) + failed to get remote plan, error massage is: + current transaction is aborted, commands ignored until end of transaction block, firstChar[Q] + +(25 rows) + +EXECUTE st3(10, 20); + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +----+----+-------+------------------------------+--------------------------+----+------------+----- + 16 | 6 | 00016 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo +(1 row) + +EXECUTE st3(20, 30); + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +----+----+----+----+----+----+----+---- +(0 rows) + +-- custom plan should be chosen initially +PREPARE st4(int) AS SELECT * FROM ft1 t1 WHERE t1.c1 = $1; +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st4(1); + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = $1::integer)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = $1::integer)) + failed to get remote plan, error massage is: + there is no parameter $1 + +(10 rows) + +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st4(1); + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = $1::integer)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = $1::integer)) + failed to get remote plan, error massage is: + there is no parameter $1 + +(10 rows) + +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st4(1); + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = $1::integer)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = $1::integer)) + failed to get remote plan, error massage is: + there is no parameter $1 + +(10 rows) + +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st4(1); + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = $1::integer)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = $1::integer)) + failed to get remote plan, error massage is: + there is no parameter $1 + +(10 rows) + +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st4(1); + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = $1::integer)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = $1::integer)) + failed to get remote plan, error massage is: + there is no parameter $1 + +(10 rows) + +-- once we try it enough times, should switch to generic plan +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st4(1); + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = $1::integer)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = $1::integer)) + failed to get remote plan, error massage is: + there is no parameter $1 + +(10 rows) + +-- value of $1 should not be sent to remote +PREPARE st5(user_enum,int) AS SELECT * FROM ft1 t1 WHERE c8 = $1 and c1 = $2; +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st5('foo', 1); + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Filter: (t1.c8 = $1) + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = $1::integer)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = $1::integer)) + failed to get remote plan, error massage is: + there is no parameter $1 + +(11 rows) + +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st5('foo', 1); + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Filter: (t1.c8 = $1) + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = $1::integer)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = $1::integer)) + failed to get remote plan, error massage is: + there is no parameter $1 + +(11 rows) + +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st5('foo', 1); + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Filter: (t1.c8 = $1) + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = $1::integer)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = $1::integer)) + failed to get remote plan, error massage is: + there is no parameter $1 + +(11 rows) + +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st5('foo', 1); + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Filter: (t1.c8 = $1) + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = $1::integer)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = $1::integer)) + failed to get remote plan, error massage is: + there is no parameter $1 + +(11 rows) + +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st5('foo', 1); + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Filter: (t1.c8 = $1) + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = $1::integer)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = $1::integer)) + failed to get remote plan, error massage is: + there is no parameter $1 + +(11 rows) + +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st5('foo', 1); + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Filter: (t1.c8 = $1) + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = $1::integer)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = $1::integer)) + failed to get remote plan, error massage is: + there is no parameter $1 + +(11 rows) + +EXECUTE st5('foo', 1); + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +----+----+-------+------------------------------+--------------------------+----+------------+----- + 1 | 1 | 00001 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1 | 1 | foo +(1 row) + +-- altering FDW options requires replanning +PREPARE st6 AS SELECT * FROM ft1 t1 WHERE t1.c1 = t1.c2; +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st6; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = c2)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = c2)) + Partition Iterator + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Filter: ("T 1"."C 1" = "T 1".c2) + Selected Partitions: 1..2 + +(15 rows) + +PREPARE st7 AS INSERT INTO ft1 (c1,c2,c3) VALUES (1001,101,'foo'); +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st7; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Insert on public.ft1 + -> Result + Output: NULL::integer, 1001, 101, 'foo'::text, NULL::timestamp with time zone, NULL::timestamp without time zone, NULL::character varying, 'ft1 '::character(10), NULL::user_enum +(3 rows) + +ALTER TABLE "S 1"."T 1" RENAME TO "T 0"; +ALTER FOREIGN TABLE ft1 OPTIONS (SET table_name 'T 0'); +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st6; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 0" WHERE (("C 1" = c2)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 0" WHERE (("C 1" = c2)) + Partition Iterator + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 0" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Filter: ("T 0"."C 1" = "T 0".c2) + Selected Partitions: 1..2 + +(15 rows) + +EXECUTE st6; --nspt + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +----+----+-------+------------------------------+--------------------------+----+------------+----- + 1 | 1 | 00001 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1 | 1 | foo + 2 | 2 | 00002 | Sat Jan 03 00:00:00 1970 PST | Sat Jan 03 00:00:00 1970 | 2 | 2 | foo + 3 | 3 | 00003 | Sun Jan 04 00:00:00 1970 PST | Sun Jan 04 00:00:00 1970 | 3 | 3 | foo + 4 | 4 | 00004 | Mon Jan 05 00:00:00 1970 PST | Mon Jan 05 00:00:00 1970 | 4 | 4 | foo + 5 | 5 | 00005 | Tue Jan 06 00:00:00 1970 PST | Tue Jan 06 00:00:00 1970 | 5 | 5 | foo + 6 | 6 | 00006 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo + 7 | 7 | 00007 | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7 | 7 | foo + 8 | 8 | 00008 | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8 | 8 | foo + 9 | 9 | 00009 | Sat Jan 10 00:00:00 1970 PST | Sat Jan 10 00:00:00 1970 | 9 | 9 | foo +(9 rows) + +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st7; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Insert on public.ft1 + -> Result + Output: NULL::integer, 1001, 101, 'foo'::text, NULL::timestamp with time zone, NULL::timestamp without time zone, NULL::character varying, 'ft1 '::character(10), NULL::user_enum +(3 rows) + +ALTER TABLE "S 1"."T 0" RENAME TO "T 1"; +ALTER FOREIGN TABLE ft1 OPTIONS (SET table_name 'T 1'); +PREPARE st8 AS SELECT count(c3) FROM ft1 t1 WHERE t1.c1 === t1.c2; +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st8; + QUERY PLAN +-------------------------------------------------------------------------------- + Aggregate + Output: count(c3) + -> Foreign Scan on public.ft1 t1 + Output: c3 + Filter: (t1.c1 === t1.c2) + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3 FROM "S 1"."T 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3 FROM "S 1"."T 1" + Partition Iterator + Output: "C 1", c2, c3 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3 + Selected Partitions: 1..2 + +(17 rows) + +ALTER SERVER loopback OPTIONS (DROP extensions); +ERROR: option "extensions" not found +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st8; + QUERY PLAN +-------------------------------------------------------------------------------- + Aggregate + Output: count(c3) + -> Foreign Scan on public.ft1 t1 + Output: c3 + Filter: (t1.c1 === t1.c2) + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3 FROM "S 1"."T 1" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3 FROM "S 1"."T 1" + Partition Iterator + Output: "C 1", c2, c3 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3 + Selected Partitions: 1..2 + +(17 rows) + +EXECUTE st8; + count +------- + 9 +(1 row) + +ALTER SERVER loopback OPTIONS (ADD extensions 'postgres_fdw'); +ERROR: invalid option "extensions" +HINT: Valid options in this context are: service, connect_timeout, dbname, host, remote_nodename, hostaddr, port, localhost, localport, application_name, fencedUdfRPCMode, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, rw_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, krbsrvname, prototype, connection_info, connectionExtraInfo, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, updatable +-- cleanup +DEALLOCATE st1; +DEALLOCATE st2; +DEALLOCATE st3; +DEALLOCATE st4; +DEALLOCATE st5; +DEALLOCATE st6; +DEALLOCATE st7; +DEALLOCATE st8; +-- System columns, except ctid and oid, should not be sent to remote +-- openGauss NOT SUPPROT select system columns in ftable. +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM ft1 t1 WHERE t1.tableoid = 'pg_class'::regclass LIMIT 1; +ERROR: column t1.tableoid does not exist +LINE 2: SELECT * FROM ft1 t1 WHERE t1.tableoid = 'pg_class'::regclas... + ^ +SELECT * FROM ft1 t1 WHERE t1.tableoid = 'ft1'::regclass LIMIT 1; +ERROR: column t1.tableoid does not exist +LINE 1: SELECT * FROM ft1 t1 WHERE t1.tableoid = 'ft1'::regclass LIM... + ^ +EXPLAIN (VERBOSE, COSTS OFF) +SELECT tableoid::regclass, * FROM ft1 t1 LIMIT 1; +ERROR: column "tableoid" does not exist +LINE 2: SELECT tableoid::regclass, * FROM ft1 t1 LIMIT 1; + ^ +CONTEXT: referenced column: tableoid +SELECT tableoid::regclass, * FROM ft1 t1 LIMIT 1; +ERROR: column "tableoid" does not exist +LINE 1: SELECT tableoid::regclass, * FROM ft1 t1 LIMIT 1; + ^ +CONTEXT: referenced column: tableoid +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM ft1 t1 WHERE t1.ctid = '(0,2)'; +ERROR: column t1.ctid does not exist +LINE 2: SELECT * FROM ft1 t1 WHERE t1.ctid = '(0,2)'; + ^ +SELECT * FROM ft1 t1 WHERE t1.ctid = '(0,2)'; +ERROR: column t1.ctid does not exist +LINE 1: SELECT * FROM ft1 t1 WHERE t1.ctid = '(0,2)'; + ^ +EXPLAIN (VERBOSE, COSTS OFF) +SELECT ctid, * FROM ft1 t1 LIMIT 1; +ERROR: column "ctid" does not exist +LINE 2: SELECT ctid, * FROM ft1 t1 LIMIT 1; + ^ +CONTEXT: referenced column: ctid +SELECT ctid, * FROM ft1 t1 LIMIT 1; +ERROR: column "ctid" does not exist +LINE 1: SELECT ctid, * FROM ft1 t1 LIMIT 1; + ^ +CONTEXT: referenced column: ctid +-- ====================================================================================================================================== +-- TEST-MODULE: used in PL/pgSQL function +-- -------------------------------------- +-- ====================================================================================================================================== +CREATE OR REPLACE FUNCTION f_test(p_c1 int) RETURNS int AS $$ +DECLARE + v_c1 int; +BEGIN + SELECT c1 INTO v_c1 FROM ft1 WHERE c1 = p_c1 LIMIT 1; + PERFORM c1 FROM ft1 WHERE c1 = p_c1 AND p_c1 = v_c1 LIMIT 1; + RETURN v_c1; +END; +$$ LANGUAGE plpgsql; +SELECT f_test(100); + f_test +-------- + 100 +(1 row) + +DROP FUNCTION f_test(int); +-- ====================================================================================================================================== +-- TEST-MODULE: test writable foreign table stuff +-- -------------------------------------- +-- ====================================================================================================================================== +EXPLAIN (verbose, costs off) +INSERT INTO ft2 (c1,c2,c3) SELECT c1+1000,c2+100, c3 || c3 FROM ft2 LIMIT 20; + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Insert on public.ft2 + -> Subquery Scan on "*SELECT*" + Output: "*SELECT*"."?column?", "*SELECT*"."?column?", NULL::integer, "*SELECT*"."?column?", NULL::timestamp with time zone, NULL::timestamp without time zone, NULL::character varying, 'ft2 '::character(10), NULL::user_enum + -> Foreign Scan on public.ft2 + Output: (public.ft2.c1 + 1000), (public.ft2.c2 + 100), (public.ft2.c3 || public.ft2.c3) + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3 FROM "S 1"."T 1" LIMIT 20::bigint + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3 FROM "S 1"."T 1" LIMIT 20::bigint + Limit + Output: "C 1", c2, c3 + -> Partition Iterator + Output: "C 1", c2, c3 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3 + Selected Partitions: 1..2 + +(19 rows) + +INSERT INTO ft2 (c1,c2,c3) SELECT c1+1000,c2+100, c3 || c3 FROM ft2 LIMIT 20; +INSERT INTO ft2 (c1,c2,c3) + VALUES (1101,201,'aaa'), (1102,202,'bbb'), (1103,203,'ccc') RETURNING *; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +------+-----+-----+----+----+----+------------+---- + 1101 | 201 | aaa | | | | ft2 | + 1102 | 202 | bbb | | | | ft2 | + 1103 | 203 | ccc | | | | ft2 | +(3 rows) + +INSERT INTO ft2 (c1,c2,c3) VALUES (1104,204,'ddd'), (1105,205,'eee'); +EXPLAIN (verbose, costs off) +UPDATE ft2 SET c2 = c2 + 300, c3 = c3 || '_update3' WHERE c1 % 10 = 3; -- can be pushed down + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------------------- + Update on public.ft2 + -> Foreign Scan on public.ft2 + Output: c1, (c2 + 300), NULL::integer, (c3 || '_update3'::text), c4, c5, c6, c7, c8, ctid, tableoid + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8, ctid, tableoid FROM "S 1"."T 1" WHERE ((("C 1" % 10) = 3)) FOR UPDATE + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8, ctid, tableoid FROM "S 1"."T 1" WHERE ((("C 1" % 10) = 3)) FOR UPDATE + LockRows + Output: "C 1", c2, c3, c4, c5, c6, c7, c8, ctid, tableoid, ctid, tableoid + -> Partition Iterator + Output: "C 1", c2, c3, c4, c5, c6, c7, c8, ctid, tableoid, ctid, tableoid + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8, ctid, tableoid, ctid, tableoid + Filter: (("T 1"."C 1" % 10) = 3) + Selected Partitions: 1..2 + +(18 rows) + +UPDATE ft2 SET c2 = c2 + 300, c3 = c3 || '_update3' WHERE c1 % 10 = 3; +EXPLAIN (verbose, costs off) +UPDATE ft2 SET c2 = c2 + 400, c3 = c3 || '_update7' WHERE c1 % 10 = 7 RETURNING *; -- can be pushed down + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------------------- + Update on public.ft2 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + -> Foreign Scan on public.ft2 + Output: c1, (c2 + 400), NULL::integer, (c3 || '_update7'::text), c4, c5, c6, c7, c8, ctid, tableoid + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8, ctid, tableoid FROM "S 1"."T 1" WHERE ((("C 1" % 10) = 7)) FOR UPDATE + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8, ctid, tableoid FROM "S 1"."T 1" WHERE ((("C 1" % 10) = 7)) FOR UPDATE + LockRows + Output: "C 1", c2, c3, c4, c5, c6, c7, c8, ctid, tableoid, ctid, tableoid + -> Partition Iterator + Output: "C 1", c2, c3, c4, c5, c6, c7, c8, ctid, tableoid, ctid, tableoid + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8, ctid, tableoid, ctid, tableoid + Filter: (("T 1"."C 1" % 10) = 7) + Selected Partitions: 1..2 + +(19 rows) + +UPDATE ft2 SET c2 = c2 + 400, c3 = c3 || '_update7' WHERE c1 % 10 = 7 RETURNING *; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +------+-----+--------------------+------------------------------+--------------------------+----+------------+----- + 7 | 407 | 00007_update7 | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7 | 7 | foo + 17 | 407 | 00017_update7 | Sun Jan 18 00:00:00 1970 PST | Sun Jan 18 00:00:00 1970 | 7 | 7 | foo + 27 | 407 | 00027_update7 | Wed Jan 28 00:00:00 1970 PST | Wed Jan 28 00:00:00 1970 | 7 | 7 | foo + 37 | 407 | 00037_update7 | Sat Feb 07 00:00:00 1970 PST | Sat Feb 07 00:00:00 1970 | 7 | 7 | foo + 47 | 407 | 00047_update7 | Tue Feb 17 00:00:00 1970 PST | Tue Feb 17 00:00:00 1970 | 7 | 7 | foo + 57 | 407 | 00057_update7 | Fri Feb 27 00:00:00 1970 PST | Fri Feb 27 00:00:00 1970 | 7 | 7 | foo + 67 | 407 | 00067_update7 | Mon Mar 09 00:00:00 1970 PST | Mon Mar 09 00:00:00 1970 | 7 | 7 | foo + 77 | 407 | 00077_update7 | Thu Mar 19 00:00:00 1970 PST | Thu Mar 19 00:00:00 1970 | 7 | 7 | foo + 87 | 407 | 00087_update7 | Sun Mar 29 00:00:00 1970 PST | Sun Mar 29 00:00:00 1970 | 7 | 7 | foo + 97 | 407 | 00097_update7 | Wed Apr 08 00:00:00 1970 PST | Wed Apr 08 00:00:00 1970 | 7 | 7 | foo + 107 | 407 | 00107_update7 | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7 | 7 | foo + 117 | 407 | 00117_update7 | Sun Jan 18 00:00:00 1970 PST | Sun Jan 18 00:00:00 1970 | 7 | 7 | foo + 127 | 407 | 00127_update7 | Wed Jan 28 00:00:00 1970 PST | Wed Jan 28 00:00:00 1970 | 7 | 7 | foo + 137 | 407 | 00137_update7 | Sat Feb 07 00:00:00 1970 PST | Sat Feb 07 00:00:00 1970 | 7 | 7 | foo + 147 | 407 | 00147_update7 | Tue Feb 17 00:00:00 1970 PST | Tue Feb 17 00:00:00 1970 | 7 | 7 | foo + 157 | 407 | 00157_update7 | Fri Feb 27 00:00:00 1970 PST | Fri Feb 27 00:00:00 1970 | 7 | 7 | foo + 167 | 407 | 00167_update7 | Mon Mar 09 00:00:00 1970 PST | Mon Mar 09 00:00:00 1970 | 7 | 7 | foo + 177 | 407 | 00177_update7 | Thu Mar 19 00:00:00 1970 PST | Thu Mar 19 00:00:00 1970 | 7 | 7 | foo + 187 | 407 | 00187_update7 | Sun Mar 29 00:00:00 1970 PST | Sun Mar 29 00:00:00 1970 | 7 | 7 | foo + 197 | 407 | 00197_update7 | Wed Apr 08 00:00:00 1970 PST | Wed Apr 08 00:00:00 1970 | 7 | 7 | foo + 207 | 407 | 00207_update7 | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7 | 7 | foo + 217 | 407 | 00217_update7 | Sun Jan 18 00:00:00 1970 PST | Sun Jan 18 00:00:00 1970 | 7 | 7 | foo + 227 | 407 | 00227_update7 | Wed Jan 28 00:00:00 1970 PST | Wed Jan 28 00:00:00 1970 | 7 | 7 | foo + 237 | 407 | 00237_update7 | Sat Feb 07 00:00:00 1970 PST | Sat Feb 07 00:00:00 1970 | 7 | 7 | foo + 247 | 407 | 00247_update7 | Tue Feb 17 00:00:00 1970 PST | Tue Feb 17 00:00:00 1970 | 7 | 7 | foo + 257 | 407 | 00257_update7 | Fri Feb 27 00:00:00 1970 PST | Fri Feb 27 00:00:00 1970 | 7 | 7 | foo + 267 | 407 | 00267_update7 | Mon Mar 09 00:00:00 1970 PST | Mon Mar 09 00:00:00 1970 | 7 | 7 | foo + 277 | 407 | 00277_update7 | Thu Mar 19 00:00:00 1970 PST | Thu Mar 19 00:00:00 1970 | 7 | 7 | foo + 287 | 407 | 00287_update7 | Sun Mar 29 00:00:00 1970 PST | Sun Mar 29 00:00:00 1970 | 7 | 7 | foo + 297 | 407 | 00297_update7 | Wed Apr 08 00:00:00 1970 PST | Wed Apr 08 00:00:00 1970 | 7 | 7 | foo + 307 | 407 | 00307_update7 | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7 | 7 | foo + 317 | 407 | 00317_update7 | Sun Jan 18 00:00:00 1970 PST | Sun Jan 18 00:00:00 1970 | 7 | 7 | foo + 327 | 407 | 00327_update7 | Wed Jan 28 00:00:00 1970 PST | Wed Jan 28 00:00:00 1970 | 7 | 7 | foo + 337 | 407 | 00337_update7 | Sat Feb 07 00:00:00 1970 PST | Sat Feb 07 00:00:00 1970 | 7 | 7 | foo + 347 | 407 | 00347_update7 | Tue Feb 17 00:00:00 1970 PST | Tue Feb 17 00:00:00 1970 | 7 | 7 | foo + 357 | 407 | 00357_update7 | Fri Feb 27 00:00:00 1970 PST | Fri Feb 27 00:00:00 1970 | 7 | 7 | foo + 367 | 407 | 00367_update7 | Mon Mar 09 00:00:00 1970 PST | Mon Mar 09 00:00:00 1970 | 7 | 7 | foo + 377 | 407 | 00377_update7 | Thu Mar 19 00:00:00 1970 PST | Thu Mar 19 00:00:00 1970 | 7 | 7 | foo + 387 | 407 | 00387_update7 | Sun Mar 29 00:00:00 1970 PST | Sun Mar 29 00:00:00 1970 | 7 | 7 | foo + 397 | 407 | 00397_update7 | Wed Apr 08 00:00:00 1970 PST | Wed Apr 08 00:00:00 1970 | 7 | 7 | foo + 407 | 407 | 00407_update7 | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7 | 7 | foo + 417 | 407 | 00417_update7 | Sun Jan 18 00:00:00 1970 PST | Sun Jan 18 00:00:00 1970 | 7 | 7 | foo + 427 | 407 | 00427_update7 | Wed Jan 28 00:00:00 1970 PST | Wed Jan 28 00:00:00 1970 | 7 | 7 | foo + 437 | 407 | 00437_update7 | Sat Feb 07 00:00:00 1970 PST | Sat Feb 07 00:00:00 1970 | 7 | 7 | foo + 447 | 407 | 00447_update7 | Tue Feb 17 00:00:00 1970 PST | Tue Feb 17 00:00:00 1970 | 7 | 7 | foo + 457 | 407 | 00457_update7 | Fri Feb 27 00:00:00 1970 PST | Fri Feb 27 00:00:00 1970 | 7 | 7 | foo + 467 | 407 | 00467_update7 | Mon Mar 09 00:00:00 1970 PST | Mon Mar 09 00:00:00 1970 | 7 | 7 | foo + 477 | 407 | 00477_update7 | Thu Mar 19 00:00:00 1970 PST | Thu Mar 19 00:00:00 1970 | 7 | 7 | foo + 487 | 407 | 00487_update7 | Sun Mar 29 00:00:00 1970 PST | Sun Mar 29 00:00:00 1970 | 7 | 7 | foo + 497 | 407 | 00497_update7 | Wed Apr 08 00:00:00 1970 PST | Wed Apr 08 00:00:00 1970 | 7 | 7 | foo + 507 | 407 | 00507_update7 | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7 | 7 | foo + 517 | 407 | 00517_update7 | Sun Jan 18 00:00:00 1970 PST | Sun Jan 18 00:00:00 1970 | 7 | 7 | foo + 527 | 407 | 00527_update7 | Wed Jan 28 00:00:00 1970 PST | Wed Jan 28 00:00:00 1970 | 7 | 7 | foo + 537 | 407 | 00537_update7 | Sat Feb 07 00:00:00 1970 PST | Sat Feb 07 00:00:00 1970 | 7 | 7 | foo + 547 | 407 | 00547_update7 | Tue Feb 17 00:00:00 1970 PST | Tue Feb 17 00:00:00 1970 | 7 | 7 | foo + 557 | 407 | 00557_update7 | Fri Feb 27 00:00:00 1970 PST | Fri Feb 27 00:00:00 1970 | 7 | 7 | foo + 567 | 407 | 00567_update7 | Mon Mar 09 00:00:00 1970 PST | Mon Mar 09 00:00:00 1970 | 7 | 7 | foo + 577 | 407 | 00577_update7 | Thu Mar 19 00:00:00 1970 PST | Thu Mar 19 00:00:00 1970 | 7 | 7 | foo + 587 | 407 | 00587_update7 | Sun Mar 29 00:00:00 1970 PST | Sun Mar 29 00:00:00 1970 | 7 | 7 | foo + 597 | 407 | 00597_update7 | Wed Apr 08 00:00:00 1970 PST | Wed Apr 08 00:00:00 1970 | 7 | 7 | foo + 607 | 407 | 00607_update7 | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7 | 7 | foo + 617 | 407 | 00617_update7 | Sun Jan 18 00:00:00 1970 PST | Sun Jan 18 00:00:00 1970 | 7 | 7 | foo + 627 | 407 | 00627_update7 | Wed Jan 28 00:00:00 1970 PST | Wed Jan 28 00:00:00 1970 | 7 | 7 | foo + 637 | 407 | 00637_update7 | Sat Feb 07 00:00:00 1970 PST | Sat Feb 07 00:00:00 1970 | 7 | 7 | foo + 647 | 407 | 00647_update7 | Tue Feb 17 00:00:00 1970 PST | Tue Feb 17 00:00:00 1970 | 7 | 7 | foo + 657 | 407 | 00657_update7 | Fri Feb 27 00:00:00 1970 PST | Fri Feb 27 00:00:00 1970 | 7 | 7 | foo + 667 | 407 | 00667_update7 | Mon Mar 09 00:00:00 1970 PST | Mon Mar 09 00:00:00 1970 | 7 | 7 | foo + 677 | 407 | 00677_update7 | Thu Mar 19 00:00:00 1970 PST | Thu Mar 19 00:00:00 1970 | 7 | 7 | foo + 687 | 407 | 00687_update7 | Sun Mar 29 00:00:00 1970 PST | Sun Mar 29 00:00:00 1970 | 7 | 7 | foo + 697 | 407 | 00697_update7 | Wed Apr 08 00:00:00 1970 PST | Wed Apr 08 00:00:00 1970 | 7 | 7 | foo + 707 | 407 | 00707_update7 | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7 | 7 | foo + 717 | 407 | 00717_update7 | Sun Jan 18 00:00:00 1970 PST | Sun Jan 18 00:00:00 1970 | 7 | 7 | foo + 727 | 407 | 00727_update7 | Wed Jan 28 00:00:00 1970 PST | Wed Jan 28 00:00:00 1970 | 7 | 7 | foo + 737 | 407 | 00737_update7 | Sat Feb 07 00:00:00 1970 PST | Sat Feb 07 00:00:00 1970 | 7 | 7 | foo + 747 | 407 | 00747_update7 | Tue Feb 17 00:00:00 1970 PST | Tue Feb 17 00:00:00 1970 | 7 | 7 | foo + 757 | 407 | 00757_update7 | Fri Feb 27 00:00:00 1970 PST | Fri Feb 27 00:00:00 1970 | 7 | 7 | foo + 767 | 407 | 00767_update7 | Mon Mar 09 00:00:00 1970 PST | Mon Mar 09 00:00:00 1970 | 7 | 7 | foo + 777 | 407 | 00777_update7 | Thu Mar 19 00:00:00 1970 PST | Thu Mar 19 00:00:00 1970 | 7 | 7 | foo + 787 | 407 | 00787_update7 | Sun Mar 29 00:00:00 1970 PST | Sun Mar 29 00:00:00 1970 | 7 | 7 | foo + 797 | 407 | 00797_update7 | Wed Apr 08 00:00:00 1970 PST | Wed Apr 08 00:00:00 1970 | 7 | 7 | foo + 807 | 407 | 00807_update7 | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7 | 7 | foo + 817 | 407 | 00817_update7 | Sun Jan 18 00:00:00 1970 PST | Sun Jan 18 00:00:00 1970 | 7 | 7 | foo + 827 | 407 | 00827_update7 | Wed Jan 28 00:00:00 1970 PST | Wed Jan 28 00:00:00 1970 | 7 | 7 | foo + 837 | 407 | 00837_update7 | Sat Feb 07 00:00:00 1970 PST | Sat Feb 07 00:00:00 1970 | 7 | 7 | foo + 847 | 407 | 00847_update7 | Tue Feb 17 00:00:00 1970 PST | Tue Feb 17 00:00:00 1970 | 7 | 7 | foo + 857 | 407 | 00857_update7 | Fri Feb 27 00:00:00 1970 PST | Fri Feb 27 00:00:00 1970 | 7 | 7 | foo + 867 | 407 | 00867_update7 | Mon Mar 09 00:00:00 1970 PST | Mon Mar 09 00:00:00 1970 | 7 | 7 | foo + 877 | 407 | 00877_update7 | Thu Mar 19 00:00:00 1970 PST | Thu Mar 19 00:00:00 1970 | 7 | 7 | foo + 887 | 407 | 00887_update7 | Sun Mar 29 00:00:00 1970 PST | Sun Mar 29 00:00:00 1970 | 7 | 7 | foo + 897 | 407 | 00897_update7 | Wed Apr 08 00:00:00 1970 PST | Wed Apr 08 00:00:00 1970 | 7 | 7 | foo + 907 | 407 | 00907_update7 | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7 | 7 | foo + 917 | 407 | 00917_update7 | Sun Jan 18 00:00:00 1970 PST | Sun Jan 18 00:00:00 1970 | 7 | 7 | foo + 927 | 407 | 00927_update7 | Wed Jan 28 00:00:00 1970 PST | Wed Jan 28 00:00:00 1970 | 7 | 7 | foo + 937 | 407 | 00937_update7 | Sat Feb 07 00:00:00 1970 PST | Sat Feb 07 00:00:00 1970 | 7 | 7 | foo + 947 | 407 | 00947_update7 | Tue Feb 17 00:00:00 1970 PST | Tue Feb 17 00:00:00 1970 | 7 | 7 | foo + 957 | 407 | 00957_update7 | Fri Feb 27 00:00:00 1970 PST | Fri Feb 27 00:00:00 1970 | 7 | 7 | foo + 967 | 407 | 00967_update7 | Mon Mar 09 00:00:00 1970 PST | Mon Mar 09 00:00:00 1970 | 7 | 7 | foo + 977 | 407 | 00977_update7 | Thu Mar 19 00:00:00 1970 PST | Thu Mar 19 00:00:00 1970 | 7 | 7 | foo + 987 | 407 | 00987_update7 | Sun Mar 29 00:00:00 1970 PST | Sun Mar 29 00:00:00 1970 | 7 | 7 | foo + 997 | 407 | 00997_update7 | Wed Apr 08 00:00:00 1970 PST | Wed Apr 08 00:00:00 1970 | 7 | 7 | foo + 1007 | 507 | 0000700007_update7 | | | | ft2 | + 1017 | 507 | 0001700017_update7 | | | | ft2 | +(102 rows) + +EXPLAIN (verbose, costs off) +UPDATE ft2 SET c2 = ft2.c2 + 500, c3 = ft2.c3 || '_update9', c7 = DEFAULT + FROM ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 9; -- can be pushed down + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Update on public.ft2 + -> Nested Loop + Output: ft2.c1, (ft2.c2 + 500), NULL::integer, (ft2.c3 || '_update9'::text), ft2.c4, ft2.c5, ft2.c6, 'ft2 '::character(10), ft2.c8, ft2.ctid, ft2.tableoid, ft1.* + Join Filter: (ft2.c2 = ft1.c1) + -> Foreign Scan on public.ft2 + Output: ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c8, ft2.ctid, ft2.tableoid + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c8, ctid, tableoid FROM "S 1"."T 1" WHERE (((c2 % 10) = 9)) FOR UPDATE + -> Materialize + Output: ft1.*, ft1.c1 + -> Foreign Scan on public.ft1 + Output: ft1.*, ft1.c1 + Node ID: 2 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((("C 1" % 10) = 9)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c8, ctid, tableoid FROM "S 1"."T 1" WHERE (((c2 % 10) = 9)) FOR UPDATE + LockRows + Output: "C 1", c2, c3, c4, c5, c6, c8, ctid, tableoid, ctid, tableoid + -> Partition Iterator + Output: "C 1", c2, c3, c4, c5, c6, c8, ctid, tableoid, ctid, tableoid + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c8, ctid, tableoid, ctid, tableoid + Filter: (("T 1".c2 % 10) = 9) + Selected Partitions: 1..2 + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((("C 1" % 10) = 9)) + Partition Iterator + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Filter: (("T 1"."C 1" % 10) = 9) + Selected Partitions: 1..2 + +(35 rows) + +UPDATE ft2 SET c2 = ft2.c2 + 500, c3 = ft2.c3 || '_update9', c7 = DEFAULT + FROM ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 9; +EXPLAIN (verbose, costs off) + DELETE FROM ft2 WHERE c1 % 10 = 5 RETURNING c1, c4; -- can be pushed down + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------- + Delete on public.ft2 + Output: c1, c4 + -> Foreign Scan on public.ft2 + Output: ctid, tableoid + Node ID: 1 + Remote SQL: SELECT ctid, tableoid FROM "S 1"."T 1" WHERE ((("C 1" % 10) = 5)) FOR UPDATE + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT ctid, tableoid FROM "S 1"."T 1" WHERE ((("C 1" % 10) = 5)) FOR UPDATE + LockRows + Output: ctid, tableoid, ctid, tableoid + -> Partition Iterator + Output: ctid, tableoid, ctid, tableoid + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: ctid, tableoid, ctid, tableoid + Filter: (("T 1"."C 1" % 10) = 5) + Selected Partitions: 1..2 + +(19 rows) + +DELETE FROM ft2 WHERE c1 % 10 = 5 RETURNING c1, c4; + c1 | c4 +------+------------------------------ + 5 | Tue Jan 06 00:00:00 1970 PST + 15 | Fri Jan 16 00:00:00 1970 PST + 25 | Mon Jan 26 00:00:00 1970 PST + 35 | Thu Feb 05 00:00:00 1970 PST + 45 | Sun Feb 15 00:00:00 1970 PST + 55 | Wed Feb 25 00:00:00 1970 PST + 65 | Sat Mar 07 00:00:00 1970 PST + 75 | Tue Mar 17 00:00:00 1970 PST + 85 | Fri Mar 27 00:00:00 1970 PST + 95 | Mon Apr 06 00:00:00 1970 PST + 105 | Tue Jan 06 00:00:00 1970 PST + 115 | Fri Jan 16 00:00:00 1970 PST + 125 | Mon Jan 26 00:00:00 1970 PST + 135 | Thu Feb 05 00:00:00 1970 PST + 145 | Sun Feb 15 00:00:00 1970 PST + 155 | Wed Feb 25 00:00:00 1970 PST + 165 | Sat Mar 07 00:00:00 1970 PST + 175 | Tue Mar 17 00:00:00 1970 PST + 185 | Fri Mar 27 00:00:00 1970 PST + 195 | Mon Apr 06 00:00:00 1970 PST + 205 | Tue Jan 06 00:00:00 1970 PST + 215 | Fri Jan 16 00:00:00 1970 PST + 225 | Mon Jan 26 00:00:00 1970 PST + 235 | Thu Feb 05 00:00:00 1970 PST + 245 | Sun Feb 15 00:00:00 1970 PST + 255 | Wed Feb 25 00:00:00 1970 PST + 265 | Sat Mar 07 00:00:00 1970 PST + 275 | Tue Mar 17 00:00:00 1970 PST + 285 | Fri Mar 27 00:00:00 1970 PST + 295 | Mon Apr 06 00:00:00 1970 PST + 305 | Tue Jan 06 00:00:00 1970 PST + 315 | Fri Jan 16 00:00:00 1970 PST + 325 | Mon Jan 26 00:00:00 1970 PST + 335 | Thu Feb 05 00:00:00 1970 PST + 345 | Sun Feb 15 00:00:00 1970 PST + 355 | Wed Feb 25 00:00:00 1970 PST + 365 | Sat Mar 07 00:00:00 1970 PST + 375 | Tue Mar 17 00:00:00 1970 PST + 385 | Fri Mar 27 00:00:00 1970 PST + 395 | Mon Apr 06 00:00:00 1970 PST + 405 | Tue Jan 06 00:00:00 1970 PST + 415 | Fri Jan 16 00:00:00 1970 PST + 425 | Mon Jan 26 00:00:00 1970 PST + 435 | Thu Feb 05 00:00:00 1970 PST + 445 | Sun Feb 15 00:00:00 1970 PST + 455 | Wed Feb 25 00:00:00 1970 PST + 465 | Sat Mar 07 00:00:00 1970 PST + 475 | Tue Mar 17 00:00:00 1970 PST + 485 | Fri Mar 27 00:00:00 1970 PST + 495 | Mon Apr 06 00:00:00 1970 PST + 505 | Tue Jan 06 00:00:00 1970 PST + 515 | Fri Jan 16 00:00:00 1970 PST + 525 | Mon Jan 26 00:00:00 1970 PST + 535 | Thu Feb 05 00:00:00 1970 PST + 545 | Sun Feb 15 00:00:00 1970 PST + 555 | Wed Feb 25 00:00:00 1970 PST + 565 | Sat Mar 07 00:00:00 1970 PST + 575 | Tue Mar 17 00:00:00 1970 PST + 585 | Fri Mar 27 00:00:00 1970 PST + 595 | Mon Apr 06 00:00:00 1970 PST + 605 | Tue Jan 06 00:00:00 1970 PST + 615 | Fri Jan 16 00:00:00 1970 PST + 625 | Mon Jan 26 00:00:00 1970 PST + 635 | Thu Feb 05 00:00:00 1970 PST + 645 | Sun Feb 15 00:00:00 1970 PST + 655 | Wed Feb 25 00:00:00 1970 PST + 665 | Sat Mar 07 00:00:00 1970 PST + 675 | Tue Mar 17 00:00:00 1970 PST + 685 | Fri Mar 27 00:00:00 1970 PST + 695 | Mon Apr 06 00:00:00 1970 PST + 705 | Tue Jan 06 00:00:00 1970 PST + 715 | Fri Jan 16 00:00:00 1970 PST + 725 | Mon Jan 26 00:00:00 1970 PST + 735 | Thu Feb 05 00:00:00 1970 PST + 745 | Sun Feb 15 00:00:00 1970 PST + 755 | Wed Feb 25 00:00:00 1970 PST + 765 | Sat Mar 07 00:00:00 1970 PST + 775 | Tue Mar 17 00:00:00 1970 PST + 785 | Fri Mar 27 00:00:00 1970 PST + 795 | Mon Apr 06 00:00:00 1970 PST + 805 | Tue Jan 06 00:00:00 1970 PST + 815 | Fri Jan 16 00:00:00 1970 PST + 825 | Mon Jan 26 00:00:00 1970 PST + 835 | Thu Feb 05 00:00:00 1970 PST + 845 | Sun Feb 15 00:00:00 1970 PST + 855 | Wed Feb 25 00:00:00 1970 PST + 865 | Sat Mar 07 00:00:00 1970 PST + 875 | Tue Mar 17 00:00:00 1970 PST + 885 | Fri Mar 27 00:00:00 1970 PST + 895 | Mon Apr 06 00:00:00 1970 PST + 905 | Tue Jan 06 00:00:00 1970 PST + 915 | Fri Jan 16 00:00:00 1970 PST + 925 | Mon Jan 26 00:00:00 1970 PST + 935 | Thu Feb 05 00:00:00 1970 PST + 945 | Sun Feb 15 00:00:00 1970 PST + 955 | Wed Feb 25 00:00:00 1970 PST + 965 | Sat Mar 07 00:00:00 1970 PST + 975 | Tue Mar 17 00:00:00 1970 PST + 985 | Fri Mar 27 00:00:00 1970 PST + 995 | Mon Apr 06 00:00:00 1970 PST + 1005 | + 1015 | + 1105 | +(103 rows) + +EXPLAIN (verbose, costs off) +DELETE FROM ft2 USING ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 2; -- can be pushed down + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------- + Delete on public.ft2 + -> Nested Loop + Output: ft2.ctid, ft2.tableoid, ft1.* + Join Filter: (ft2.c2 = ft1.c1) + -> Foreign Scan on public.ft2 + Output: ft2.ctid, ft2.tableoid, ft2.c2 + Node ID: 1 + Remote SQL: SELECT c2, ctid, tableoid FROM "S 1"."T 1" WHERE (((c2 % 10) = 2)) FOR UPDATE + -> Materialize + Output: ft1.*, ft1.c1 + -> Foreign Scan on public.ft1 + Output: ft1.*, ft1.c1 + Node ID: 2 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((("C 1" % 10) = 2)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c2, ctid, tableoid FROM "S 1"."T 1" WHERE (((c2 % 10) = 2)) FOR UPDATE + LockRows + Output: c2, ctid, tableoid, ctid, tableoid + -> Partition Iterator + Output: c2, ctid, tableoid, ctid, tableoid + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: c2, ctid, tableoid, ctid, tableoid + Filter: (("T 1".c2 % 10) = 2) + Selected Partitions: 1..2 + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((("C 1" % 10) = 2)) + Partition Iterator + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Filter: (("T 1"."C 1" % 10) = 2) + Selected Partitions: 1..2 + +(35 rows) + +DELETE FROM ft2 USING ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 2; +SELECT c1,c2,c3,c4 FROM ft2 ORDER BY c1; + c1 | c2 | c3 | c4 +------+-----+--------------------+------------------------------ + 1 | 1 | 00001 | Fri Jan 02 00:00:00 1970 PST + 3 | 303 | 00003_update3 | Sun Jan 04 00:00:00 1970 PST + 4 | 4 | 00004 | Mon Jan 05 00:00:00 1970 PST + 6 | 6 | 00006 | Wed Jan 07 00:00:00 1970 PST + 7 | 407 | 00007_update7 | Thu Jan 08 00:00:00 1970 PST + 8 | 8 | 00008 | Fri Jan 09 00:00:00 1970 PST + 9 | 509 | 00009_update9 | Sat Jan 10 00:00:00 1970 PST + 10 | 0 | 00010 | Sun Jan 11 00:00:00 1970 PST + 11 | 1 | 00011 | Mon Jan 12 00:00:00 1970 PST + 13 | 303 | 00013_update3 | Wed Jan 14 00:00:00 1970 PST + 14 | 4 | 00014 | Thu Jan 15 00:00:00 1970 PST + 16 | 6 | 00016 | Sat Jan 17 00:00:00 1970 PST + 17 | 407 | 00017_update7 | Sun Jan 18 00:00:00 1970 PST + 18 | 8 | 00018 | Mon Jan 19 00:00:00 1970 PST + 19 | 509 | 00019_update9 | Tue Jan 20 00:00:00 1970 PST + 20 | 0 | 00020 | Wed Jan 21 00:00:00 1970 PST + 21 | 1 | 00021 | Thu Jan 22 00:00:00 1970 PST + 23 | 303 | 00023_update3 | Sat Jan 24 00:00:00 1970 PST + 24 | 4 | 00024 | Sun Jan 25 00:00:00 1970 PST + 26 | 6 | 00026 | Tue Jan 27 00:00:00 1970 PST + 27 | 407 | 00027_update7 | Wed Jan 28 00:00:00 1970 PST + 28 | 8 | 00028 | Thu Jan 29 00:00:00 1970 PST + 29 | 509 | 00029_update9 | Fri Jan 30 00:00:00 1970 PST + 30 | 0 | 00030 | Sat Jan 31 00:00:00 1970 PST + 31 | 1 | 00031 | Sun Feb 01 00:00:00 1970 PST + 33 | 303 | 00033_update3 | Tue Feb 03 00:00:00 1970 PST + 34 | 4 | 00034 | Wed Feb 04 00:00:00 1970 PST + 36 | 6 | 00036 | Fri Feb 06 00:00:00 1970 PST + 37 | 407 | 00037_update7 | Sat Feb 07 00:00:00 1970 PST + 38 | 8 | 00038 | Sun Feb 08 00:00:00 1970 PST + 39 | 509 | 00039_update9 | Mon Feb 09 00:00:00 1970 PST + 40 | 0 | 00040 | Tue Feb 10 00:00:00 1970 PST + 41 | 1 | 00041 | Wed Feb 11 00:00:00 1970 PST + 43 | 303 | 00043_update3 | Fri Feb 13 00:00:00 1970 PST + 44 | 4 | 00044 | Sat Feb 14 00:00:00 1970 PST + 46 | 6 | 00046 | Mon Feb 16 00:00:00 1970 PST + 47 | 407 | 00047_update7 | Tue Feb 17 00:00:00 1970 PST + 48 | 8 | 00048 | Wed Feb 18 00:00:00 1970 PST + 49 | 509 | 00049_update9 | Thu Feb 19 00:00:00 1970 PST + 50 | 0 | 00050 | Fri Feb 20 00:00:00 1970 PST + 51 | 1 | 00051 | Sat Feb 21 00:00:00 1970 PST + 53 | 303 | 00053_update3 | Mon Feb 23 00:00:00 1970 PST + 54 | 4 | 00054 | Tue Feb 24 00:00:00 1970 PST + 56 | 6 | 00056 | Thu Feb 26 00:00:00 1970 PST + 57 | 407 | 00057_update7 | Fri Feb 27 00:00:00 1970 PST + 58 | 8 | 00058 | Sat Feb 28 00:00:00 1970 PST + 59 | 509 | 00059_update9 | Sun Mar 01 00:00:00 1970 PST + 60 | 0 | 00060 | Mon Mar 02 00:00:00 1970 PST + 61 | 1 | 00061 | Tue Mar 03 00:00:00 1970 PST + 63 | 303 | 00063_update3 | Thu Mar 05 00:00:00 1970 PST + 64 | 4 | 00064 | Fri Mar 06 00:00:00 1970 PST + 66 | 6 | 00066 | Sun Mar 08 00:00:00 1970 PST + 67 | 407 | 00067_update7 | Mon Mar 09 00:00:00 1970 PST + 68 | 8 | 00068 | Tue Mar 10 00:00:00 1970 PST + 69 | 509 | 00069_update9 | Wed Mar 11 00:00:00 1970 PST + 70 | 0 | 00070 | Thu Mar 12 00:00:00 1970 PST + 71 | 1 | 00071 | Fri Mar 13 00:00:00 1970 PST + 73 | 303 | 00073_update3 | Sun Mar 15 00:00:00 1970 PST + 74 | 4 | 00074 | Mon Mar 16 00:00:00 1970 PST + 76 | 6 | 00076 | Wed Mar 18 00:00:00 1970 PST + 77 | 407 | 00077_update7 | Thu Mar 19 00:00:00 1970 PST + 78 | 8 | 00078 | Fri Mar 20 00:00:00 1970 PST + 79 | 509 | 00079_update9 | Sat Mar 21 00:00:00 1970 PST + 80 | 0 | 00080 | Sun Mar 22 00:00:00 1970 PST + 81 | 1 | 00081 | Mon Mar 23 00:00:00 1970 PST + 83 | 303 | 00083_update3 | Wed Mar 25 00:00:00 1970 PST + 84 | 4 | 00084 | Thu Mar 26 00:00:00 1970 PST + 86 | 6 | 00086 | Sat Mar 28 00:00:00 1970 PST + 87 | 407 | 00087_update7 | Sun Mar 29 00:00:00 1970 PST + 88 | 8 | 00088 | Mon Mar 30 00:00:00 1970 PST + 89 | 509 | 00089_update9 | Tue Mar 31 00:00:00 1970 PST + 90 | 0 | 00090 | Wed Apr 01 00:00:00 1970 PST + 91 | 1 | 00091 | Thu Apr 02 00:00:00 1970 PST + 93 | 303 | 00093_update3 | Sat Apr 04 00:00:00 1970 PST + 94 | 4 | 00094 | Sun Apr 05 00:00:00 1970 PST + 96 | 6 | 00096 | Tue Apr 07 00:00:00 1970 PST + 97 | 407 | 00097_update7 | Wed Apr 08 00:00:00 1970 PST + 98 | 8 | 00098 | Thu Apr 09 00:00:00 1970 PST + 99 | 509 | 00099_update9 | Fri Apr 10 00:00:00 1970 PST + 100 | 0 | 00100 | Thu Jan 01 00:00:00 1970 PST + 101 | 1 | 00101 | Fri Jan 02 00:00:00 1970 PST + 103 | 303 | 00103_update3 | Sun Jan 04 00:00:00 1970 PST + 104 | 4 | 00104 | Mon Jan 05 00:00:00 1970 PST + 106 | 6 | 00106 | Wed Jan 07 00:00:00 1970 PST + 107 | 407 | 00107_update7 | Thu Jan 08 00:00:00 1970 PST + 108 | 8 | 00108 | Fri Jan 09 00:00:00 1970 PST + 109 | 509 | 00109_update9 | Sat Jan 10 00:00:00 1970 PST + 110 | 0 | 00110 | Sun Jan 11 00:00:00 1970 PST + 111 | 1 | 00111 | Mon Jan 12 00:00:00 1970 PST + 113 | 303 | 00113_update3 | Wed Jan 14 00:00:00 1970 PST + 114 | 4 | 00114 | Thu Jan 15 00:00:00 1970 PST + 116 | 6 | 00116 | Sat Jan 17 00:00:00 1970 PST + 117 | 407 | 00117_update7 | Sun Jan 18 00:00:00 1970 PST + 118 | 8 | 00118 | Mon Jan 19 00:00:00 1970 PST + 119 | 509 | 00119_update9 | Tue Jan 20 00:00:00 1970 PST + 120 | 0 | 00120 | Wed Jan 21 00:00:00 1970 PST + 121 | 1 | 00121 | Thu Jan 22 00:00:00 1970 PST + 123 | 303 | 00123_update3 | Sat Jan 24 00:00:00 1970 PST + 124 | 4 | 00124 | Sun Jan 25 00:00:00 1970 PST + 126 | 6 | 00126 | Tue Jan 27 00:00:00 1970 PST + 127 | 407 | 00127_update7 | Wed Jan 28 00:00:00 1970 PST + 128 | 8 | 00128 | Thu Jan 29 00:00:00 1970 PST + 129 | 509 | 00129_update9 | Fri Jan 30 00:00:00 1970 PST + 130 | 0 | 00130 | Sat Jan 31 00:00:00 1970 PST + 131 | 1 | 00131 | Sun Feb 01 00:00:00 1970 PST + 133 | 303 | 00133_update3 | Tue Feb 03 00:00:00 1970 PST + 134 | 4 | 00134 | Wed Feb 04 00:00:00 1970 PST + 136 | 6 | 00136 | Fri Feb 06 00:00:00 1970 PST + 137 | 407 | 00137_update7 | Sat Feb 07 00:00:00 1970 PST + 138 | 8 | 00138 | Sun Feb 08 00:00:00 1970 PST + 139 | 509 | 00139_update9 | Mon Feb 09 00:00:00 1970 PST + 140 | 0 | 00140 | Tue Feb 10 00:00:00 1970 PST + 141 | 1 | 00141 | Wed Feb 11 00:00:00 1970 PST + 143 | 303 | 00143_update3 | Fri Feb 13 00:00:00 1970 PST + 144 | 4 | 00144 | Sat Feb 14 00:00:00 1970 PST + 146 | 6 | 00146 | Mon Feb 16 00:00:00 1970 PST + 147 | 407 | 00147_update7 | Tue Feb 17 00:00:00 1970 PST + 148 | 8 | 00148 | Wed Feb 18 00:00:00 1970 PST + 149 | 509 | 00149_update9 | Thu Feb 19 00:00:00 1970 PST + 150 | 0 | 00150 | Fri Feb 20 00:00:00 1970 PST + 151 | 1 | 00151 | Sat Feb 21 00:00:00 1970 PST + 153 | 303 | 00153_update3 | Mon Feb 23 00:00:00 1970 PST + 154 | 4 | 00154 | Tue Feb 24 00:00:00 1970 PST + 156 | 6 | 00156 | Thu Feb 26 00:00:00 1970 PST + 157 | 407 | 00157_update7 | Fri Feb 27 00:00:00 1970 PST + 158 | 8 | 00158 | Sat Feb 28 00:00:00 1970 PST + 159 | 509 | 00159_update9 | Sun Mar 01 00:00:00 1970 PST + 160 | 0 | 00160 | Mon Mar 02 00:00:00 1970 PST + 161 | 1 | 00161 | Tue Mar 03 00:00:00 1970 PST + 163 | 303 | 00163_update3 | Thu Mar 05 00:00:00 1970 PST + 164 | 4 | 00164 | Fri Mar 06 00:00:00 1970 PST + 166 | 6 | 00166 | Sun Mar 08 00:00:00 1970 PST + 167 | 407 | 00167_update7 | Mon Mar 09 00:00:00 1970 PST + 168 | 8 | 00168 | Tue Mar 10 00:00:00 1970 PST + 169 | 509 | 00169_update9 | Wed Mar 11 00:00:00 1970 PST + 170 | 0 | 00170 | Thu Mar 12 00:00:00 1970 PST + 171 | 1 | 00171 | Fri Mar 13 00:00:00 1970 PST + 173 | 303 | 00173_update3 | Sun Mar 15 00:00:00 1970 PST + 174 | 4 | 00174 | Mon Mar 16 00:00:00 1970 PST + 176 | 6 | 00176 | Wed Mar 18 00:00:00 1970 PST + 177 | 407 | 00177_update7 | Thu Mar 19 00:00:00 1970 PST + 178 | 8 | 00178 | Fri Mar 20 00:00:00 1970 PST + 179 | 509 | 00179_update9 | Sat Mar 21 00:00:00 1970 PST + 180 | 0 | 00180 | Sun Mar 22 00:00:00 1970 PST + 181 | 1 | 00181 | Mon Mar 23 00:00:00 1970 PST + 183 | 303 | 00183_update3 | Wed Mar 25 00:00:00 1970 PST + 184 | 4 | 00184 | Thu Mar 26 00:00:00 1970 PST + 186 | 6 | 00186 | Sat Mar 28 00:00:00 1970 PST + 187 | 407 | 00187_update7 | Sun Mar 29 00:00:00 1970 PST + 188 | 8 | 00188 | Mon Mar 30 00:00:00 1970 PST + 189 | 509 | 00189_update9 | Tue Mar 31 00:00:00 1970 PST + 190 | 0 | 00190 | Wed Apr 01 00:00:00 1970 PST + 191 | 1 | 00191 | Thu Apr 02 00:00:00 1970 PST + 193 | 303 | 00193_update3 | Sat Apr 04 00:00:00 1970 PST + 194 | 4 | 00194 | Sun Apr 05 00:00:00 1970 PST + 196 | 6 | 00196 | Tue Apr 07 00:00:00 1970 PST + 197 | 407 | 00197_update7 | Wed Apr 08 00:00:00 1970 PST + 198 | 8 | 00198 | Thu Apr 09 00:00:00 1970 PST + 199 | 509 | 00199_update9 | Fri Apr 10 00:00:00 1970 PST + 200 | 0 | 00200 | Thu Jan 01 00:00:00 1970 PST + 201 | 1 | 00201 | Fri Jan 02 00:00:00 1970 PST + 203 | 303 | 00203_update3 | Sun Jan 04 00:00:00 1970 PST + 204 | 4 | 00204 | Mon Jan 05 00:00:00 1970 PST + 206 | 6 | 00206 | Wed Jan 07 00:00:00 1970 PST + 207 | 407 | 00207_update7 | Thu Jan 08 00:00:00 1970 PST + 208 | 8 | 00208 | Fri Jan 09 00:00:00 1970 PST + 209 | 509 | 00209_update9 | Sat Jan 10 00:00:00 1970 PST + 210 | 0 | 00210 | Sun Jan 11 00:00:00 1970 PST + 211 | 1 | 00211 | Mon Jan 12 00:00:00 1970 PST + 213 | 303 | 00213_update3 | Wed Jan 14 00:00:00 1970 PST + 214 | 4 | 00214 | Thu Jan 15 00:00:00 1970 PST + 216 | 6 | 00216 | Sat Jan 17 00:00:00 1970 PST + 217 | 407 | 00217_update7 | Sun Jan 18 00:00:00 1970 PST + 218 | 8 | 00218 | Mon Jan 19 00:00:00 1970 PST + 219 | 509 | 00219_update9 | Tue Jan 20 00:00:00 1970 PST + 220 | 0 | 00220 | Wed Jan 21 00:00:00 1970 PST + 221 | 1 | 00221 | Thu Jan 22 00:00:00 1970 PST + 223 | 303 | 00223_update3 | Sat Jan 24 00:00:00 1970 PST + 224 | 4 | 00224 | Sun Jan 25 00:00:00 1970 PST + 226 | 6 | 00226 | Tue Jan 27 00:00:00 1970 PST + 227 | 407 | 00227_update7 | Wed Jan 28 00:00:00 1970 PST + 228 | 8 | 00228 | Thu Jan 29 00:00:00 1970 PST + 229 | 509 | 00229_update9 | Fri Jan 30 00:00:00 1970 PST + 230 | 0 | 00230 | Sat Jan 31 00:00:00 1970 PST + 231 | 1 | 00231 | Sun Feb 01 00:00:00 1970 PST + 233 | 303 | 00233_update3 | Tue Feb 03 00:00:00 1970 PST + 234 | 4 | 00234 | Wed Feb 04 00:00:00 1970 PST + 236 | 6 | 00236 | Fri Feb 06 00:00:00 1970 PST + 237 | 407 | 00237_update7 | Sat Feb 07 00:00:00 1970 PST + 238 | 8 | 00238 | Sun Feb 08 00:00:00 1970 PST + 239 | 509 | 00239_update9 | Mon Feb 09 00:00:00 1970 PST + 240 | 0 | 00240 | Tue Feb 10 00:00:00 1970 PST + 241 | 1 | 00241 | Wed Feb 11 00:00:00 1970 PST + 243 | 303 | 00243_update3 | Fri Feb 13 00:00:00 1970 PST + 244 | 4 | 00244 | Sat Feb 14 00:00:00 1970 PST + 246 | 6 | 00246 | Mon Feb 16 00:00:00 1970 PST + 247 | 407 | 00247_update7 | Tue Feb 17 00:00:00 1970 PST + 248 | 8 | 00248 | Wed Feb 18 00:00:00 1970 PST + 249 | 509 | 00249_update9 | Thu Feb 19 00:00:00 1970 PST + 250 | 0 | 00250 | Fri Feb 20 00:00:00 1970 PST + 251 | 1 | 00251 | Sat Feb 21 00:00:00 1970 PST + 253 | 303 | 00253_update3 | Mon Feb 23 00:00:00 1970 PST + 254 | 4 | 00254 | Tue Feb 24 00:00:00 1970 PST + 256 | 6 | 00256 | Thu Feb 26 00:00:00 1970 PST + 257 | 407 | 00257_update7 | Fri Feb 27 00:00:00 1970 PST + 258 | 8 | 00258 | Sat Feb 28 00:00:00 1970 PST + 259 | 509 | 00259_update9 | Sun Mar 01 00:00:00 1970 PST + 260 | 0 | 00260 | Mon Mar 02 00:00:00 1970 PST + 261 | 1 | 00261 | Tue Mar 03 00:00:00 1970 PST + 263 | 303 | 00263_update3 | Thu Mar 05 00:00:00 1970 PST + 264 | 4 | 00264 | Fri Mar 06 00:00:00 1970 PST + 266 | 6 | 00266 | Sun Mar 08 00:00:00 1970 PST + 267 | 407 | 00267_update7 | Mon Mar 09 00:00:00 1970 PST + 268 | 8 | 00268 | Tue Mar 10 00:00:00 1970 PST + 269 | 509 | 00269_update9 | Wed Mar 11 00:00:00 1970 PST + 270 | 0 | 00270 | Thu Mar 12 00:00:00 1970 PST + 271 | 1 | 00271 | Fri Mar 13 00:00:00 1970 PST + 273 | 303 | 00273_update3 | Sun Mar 15 00:00:00 1970 PST + 274 | 4 | 00274 | Mon Mar 16 00:00:00 1970 PST + 276 | 6 | 00276 | Wed Mar 18 00:00:00 1970 PST + 277 | 407 | 00277_update7 | Thu Mar 19 00:00:00 1970 PST + 278 | 8 | 00278 | Fri Mar 20 00:00:00 1970 PST + 279 | 509 | 00279_update9 | Sat Mar 21 00:00:00 1970 PST + 280 | 0 | 00280 | Sun Mar 22 00:00:00 1970 PST + 281 | 1 | 00281 | Mon Mar 23 00:00:00 1970 PST + 283 | 303 | 00283_update3 | Wed Mar 25 00:00:00 1970 PST + 284 | 4 | 00284 | Thu Mar 26 00:00:00 1970 PST + 286 | 6 | 00286 | Sat Mar 28 00:00:00 1970 PST + 287 | 407 | 00287_update7 | Sun Mar 29 00:00:00 1970 PST + 288 | 8 | 00288 | Mon Mar 30 00:00:00 1970 PST + 289 | 509 | 00289_update9 | Tue Mar 31 00:00:00 1970 PST + 290 | 0 | 00290 | Wed Apr 01 00:00:00 1970 PST + 291 | 1 | 00291 | Thu Apr 02 00:00:00 1970 PST + 293 | 303 | 00293_update3 | Sat Apr 04 00:00:00 1970 PST + 294 | 4 | 00294 | Sun Apr 05 00:00:00 1970 PST + 296 | 6 | 00296 | Tue Apr 07 00:00:00 1970 PST + 297 | 407 | 00297_update7 | Wed Apr 08 00:00:00 1970 PST + 298 | 8 | 00298 | Thu Apr 09 00:00:00 1970 PST + 299 | 509 | 00299_update9 | Fri Apr 10 00:00:00 1970 PST + 300 | 0 | 00300 | Thu Jan 01 00:00:00 1970 PST + 301 | 1 | 00301 | Fri Jan 02 00:00:00 1970 PST + 303 | 303 | 00303_update3 | Sun Jan 04 00:00:00 1970 PST + 304 | 4 | 00304 | Mon Jan 05 00:00:00 1970 PST + 306 | 6 | 00306 | Wed Jan 07 00:00:00 1970 PST + 307 | 407 | 00307_update7 | Thu Jan 08 00:00:00 1970 PST + 308 | 8 | 00308 | Fri Jan 09 00:00:00 1970 PST + 309 | 509 | 00309_update9 | Sat Jan 10 00:00:00 1970 PST + 310 | 0 | 00310 | Sun Jan 11 00:00:00 1970 PST + 311 | 1 | 00311 | Mon Jan 12 00:00:00 1970 PST + 313 | 303 | 00313_update3 | Wed Jan 14 00:00:00 1970 PST + 314 | 4 | 00314 | Thu Jan 15 00:00:00 1970 PST + 316 | 6 | 00316 | Sat Jan 17 00:00:00 1970 PST + 317 | 407 | 00317_update7 | Sun Jan 18 00:00:00 1970 PST + 318 | 8 | 00318 | Mon Jan 19 00:00:00 1970 PST + 319 | 509 | 00319_update9 | Tue Jan 20 00:00:00 1970 PST + 320 | 0 | 00320 | Wed Jan 21 00:00:00 1970 PST + 321 | 1 | 00321 | Thu Jan 22 00:00:00 1970 PST + 323 | 303 | 00323_update3 | Sat Jan 24 00:00:00 1970 PST + 324 | 4 | 00324 | Sun Jan 25 00:00:00 1970 PST + 326 | 6 | 00326 | Tue Jan 27 00:00:00 1970 PST + 327 | 407 | 00327_update7 | Wed Jan 28 00:00:00 1970 PST + 328 | 8 | 00328 | Thu Jan 29 00:00:00 1970 PST + 329 | 509 | 00329_update9 | Fri Jan 30 00:00:00 1970 PST + 330 | 0 | 00330 | Sat Jan 31 00:00:00 1970 PST + 331 | 1 | 00331 | Sun Feb 01 00:00:00 1970 PST + 333 | 303 | 00333_update3 | Tue Feb 03 00:00:00 1970 PST + 334 | 4 | 00334 | Wed Feb 04 00:00:00 1970 PST + 336 | 6 | 00336 | Fri Feb 06 00:00:00 1970 PST + 337 | 407 | 00337_update7 | Sat Feb 07 00:00:00 1970 PST + 338 | 8 | 00338 | Sun Feb 08 00:00:00 1970 PST + 339 | 509 | 00339_update9 | Mon Feb 09 00:00:00 1970 PST + 340 | 0 | 00340 | Tue Feb 10 00:00:00 1970 PST + 341 | 1 | 00341 | Wed Feb 11 00:00:00 1970 PST + 343 | 303 | 00343_update3 | Fri Feb 13 00:00:00 1970 PST + 344 | 4 | 00344 | Sat Feb 14 00:00:00 1970 PST + 346 | 6 | 00346 | Mon Feb 16 00:00:00 1970 PST + 347 | 407 | 00347_update7 | Tue Feb 17 00:00:00 1970 PST + 348 | 8 | 00348 | Wed Feb 18 00:00:00 1970 PST + 349 | 509 | 00349_update9 | Thu Feb 19 00:00:00 1970 PST + 350 | 0 | 00350 | Fri Feb 20 00:00:00 1970 PST + 351 | 1 | 00351 | Sat Feb 21 00:00:00 1970 PST + 353 | 303 | 00353_update3 | Mon Feb 23 00:00:00 1970 PST + 354 | 4 | 00354 | Tue Feb 24 00:00:00 1970 PST + 356 | 6 | 00356 | Thu Feb 26 00:00:00 1970 PST + 357 | 407 | 00357_update7 | Fri Feb 27 00:00:00 1970 PST + 358 | 8 | 00358 | Sat Feb 28 00:00:00 1970 PST + 359 | 509 | 00359_update9 | Sun Mar 01 00:00:00 1970 PST + 360 | 0 | 00360 | Mon Mar 02 00:00:00 1970 PST + 361 | 1 | 00361 | Tue Mar 03 00:00:00 1970 PST + 363 | 303 | 00363_update3 | Thu Mar 05 00:00:00 1970 PST + 364 | 4 | 00364 | Fri Mar 06 00:00:00 1970 PST + 366 | 6 | 00366 | Sun Mar 08 00:00:00 1970 PST + 367 | 407 | 00367_update7 | Mon Mar 09 00:00:00 1970 PST + 368 | 8 | 00368 | Tue Mar 10 00:00:00 1970 PST + 369 | 509 | 00369_update9 | Wed Mar 11 00:00:00 1970 PST + 370 | 0 | 00370 | Thu Mar 12 00:00:00 1970 PST + 371 | 1 | 00371 | Fri Mar 13 00:00:00 1970 PST + 373 | 303 | 00373_update3 | Sun Mar 15 00:00:00 1970 PST + 374 | 4 | 00374 | Mon Mar 16 00:00:00 1970 PST + 376 | 6 | 00376 | Wed Mar 18 00:00:00 1970 PST + 377 | 407 | 00377_update7 | Thu Mar 19 00:00:00 1970 PST + 378 | 8 | 00378 | Fri Mar 20 00:00:00 1970 PST + 379 | 509 | 00379_update9 | Sat Mar 21 00:00:00 1970 PST + 380 | 0 | 00380 | Sun Mar 22 00:00:00 1970 PST + 381 | 1 | 00381 | Mon Mar 23 00:00:00 1970 PST + 383 | 303 | 00383_update3 | Wed Mar 25 00:00:00 1970 PST + 384 | 4 | 00384 | Thu Mar 26 00:00:00 1970 PST + 386 | 6 | 00386 | Sat Mar 28 00:00:00 1970 PST + 387 | 407 | 00387_update7 | Sun Mar 29 00:00:00 1970 PST + 388 | 8 | 00388 | Mon Mar 30 00:00:00 1970 PST + 389 | 509 | 00389_update9 | Tue Mar 31 00:00:00 1970 PST + 390 | 0 | 00390 | Wed Apr 01 00:00:00 1970 PST + 391 | 1 | 00391 | Thu Apr 02 00:00:00 1970 PST + 393 | 303 | 00393_update3 | Sat Apr 04 00:00:00 1970 PST + 394 | 4 | 00394 | Sun Apr 05 00:00:00 1970 PST + 396 | 6 | 00396 | Tue Apr 07 00:00:00 1970 PST + 397 | 407 | 00397_update7 | Wed Apr 08 00:00:00 1970 PST + 398 | 8 | 00398 | Thu Apr 09 00:00:00 1970 PST + 399 | 509 | 00399_update9 | Fri Apr 10 00:00:00 1970 PST + 400 | 0 | 00400 | Thu Jan 01 00:00:00 1970 PST + 401 | 1 | 00401 | Fri Jan 02 00:00:00 1970 PST + 403 | 303 | 00403_update3 | Sun Jan 04 00:00:00 1970 PST + 404 | 4 | 00404 | Mon Jan 05 00:00:00 1970 PST + 406 | 6 | 00406 | Wed Jan 07 00:00:00 1970 PST + 407 | 407 | 00407_update7 | Thu Jan 08 00:00:00 1970 PST + 408 | 8 | 00408 | Fri Jan 09 00:00:00 1970 PST + 409 | 509 | 00409_update9 | Sat Jan 10 00:00:00 1970 PST + 410 | 0 | 00410 | Sun Jan 11 00:00:00 1970 PST + 411 | 1 | 00411 | Mon Jan 12 00:00:00 1970 PST + 413 | 303 | 00413_update3 | Wed Jan 14 00:00:00 1970 PST + 414 | 4 | 00414 | Thu Jan 15 00:00:00 1970 PST + 416 | 6 | 00416 | Sat Jan 17 00:00:00 1970 PST + 417 | 407 | 00417_update7 | Sun Jan 18 00:00:00 1970 PST + 418 | 8 | 00418 | Mon Jan 19 00:00:00 1970 PST + 419 | 509 | 00419_update9 | Tue Jan 20 00:00:00 1970 PST + 420 | 0 | 00420 | Wed Jan 21 00:00:00 1970 PST + 421 | 1 | 00421 | Thu Jan 22 00:00:00 1970 PST + 423 | 303 | 00423_update3 | Sat Jan 24 00:00:00 1970 PST + 424 | 4 | 00424 | Sun Jan 25 00:00:00 1970 PST + 426 | 6 | 00426 | Tue Jan 27 00:00:00 1970 PST + 427 | 407 | 00427_update7 | Wed Jan 28 00:00:00 1970 PST + 428 | 8 | 00428 | Thu Jan 29 00:00:00 1970 PST + 429 | 509 | 00429_update9 | Fri Jan 30 00:00:00 1970 PST + 430 | 0 | 00430 | Sat Jan 31 00:00:00 1970 PST + 431 | 1 | 00431 | Sun Feb 01 00:00:00 1970 PST + 433 | 303 | 00433_update3 | Tue Feb 03 00:00:00 1970 PST + 434 | 4 | 00434 | Wed Feb 04 00:00:00 1970 PST + 436 | 6 | 00436 | Fri Feb 06 00:00:00 1970 PST + 437 | 407 | 00437_update7 | Sat Feb 07 00:00:00 1970 PST + 438 | 8 | 00438 | Sun Feb 08 00:00:00 1970 PST + 439 | 509 | 00439_update9 | Mon Feb 09 00:00:00 1970 PST + 440 | 0 | 00440 | Tue Feb 10 00:00:00 1970 PST + 441 | 1 | 00441 | Wed Feb 11 00:00:00 1970 PST + 443 | 303 | 00443_update3 | Fri Feb 13 00:00:00 1970 PST + 444 | 4 | 00444 | Sat Feb 14 00:00:00 1970 PST + 446 | 6 | 00446 | Mon Feb 16 00:00:00 1970 PST + 447 | 407 | 00447_update7 | Tue Feb 17 00:00:00 1970 PST + 448 | 8 | 00448 | Wed Feb 18 00:00:00 1970 PST + 449 | 509 | 00449_update9 | Thu Feb 19 00:00:00 1970 PST + 450 | 0 | 00450 | Fri Feb 20 00:00:00 1970 PST + 451 | 1 | 00451 | Sat Feb 21 00:00:00 1970 PST + 453 | 303 | 00453_update3 | Mon Feb 23 00:00:00 1970 PST + 454 | 4 | 00454 | Tue Feb 24 00:00:00 1970 PST + 456 | 6 | 00456 | Thu Feb 26 00:00:00 1970 PST + 457 | 407 | 00457_update7 | Fri Feb 27 00:00:00 1970 PST + 458 | 8 | 00458 | Sat Feb 28 00:00:00 1970 PST + 459 | 509 | 00459_update9 | Sun Mar 01 00:00:00 1970 PST + 460 | 0 | 00460 | Mon Mar 02 00:00:00 1970 PST + 461 | 1 | 00461 | Tue Mar 03 00:00:00 1970 PST + 463 | 303 | 00463_update3 | Thu Mar 05 00:00:00 1970 PST + 464 | 4 | 00464 | Fri Mar 06 00:00:00 1970 PST + 466 | 6 | 00466 | Sun Mar 08 00:00:00 1970 PST + 467 | 407 | 00467_update7 | Mon Mar 09 00:00:00 1970 PST + 468 | 8 | 00468 | Tue Mar 10 00:00:00 1970 PST + 469 | 509 | 00469_update9 | Wed Mar 11 00:00:00 1970 PST + 470 | 0 | 00470 | Thu Mar 12 00:00:00 1970 PST + 471 | 1 | 00471 | Fri Mar 13 00:00:00 1970 PST + 473 | 303 | 00473_update3 | Sun Mar 15 00:00:00 1970 PST + 474 | 4 | 00474 | Mon Mar 16 00:00:00 1970 PST + 476 | 6 | 00476 | Wed Mar 18 00:00:00 1970 PST + 477 | 407 | 00477_update7 | Thu Mar 19 00:00:00 1970 PST + 478 | 8 | 00478 | Fri Mar 20 00:00:00 1970 PST + 479 | 509 | 00479_update9 | Sat Mar 21 00:00:00 1970 PST + 480 | 0 | 00480 | Sun Mar 22 00:00:00 1970 PST + 481 | 1 | 00481 | Mon Mar 23 00:00:00 1970 PST + 483 | 303 | 00483_update3 | Wed Mar 25 00:00:00 1970 PST + 484 | 4 | 00484 | Thu Mar 26 00:00:00 1970 PST + 486 | 6 | 00486 | Sat Mar 28 00:00:00 1970 PST + 487 | 407 | 00487_update7 | Sun Mar 29 00:00:00 1970 PST + 488 | 8 | 00488 | Mon Mar 30 00:00:00 1970 PST + 489 | 509 | 00489_update9 | Tue Mar 31 00:00:00 1970 PST + 490 | 0 | 00490 | Wed Apr 01 00:00:00 1970 PST + 491 | 1 | 00491 | Thu Apr 02 00:00:00 1970 PST + 493 | 303 | 00493_update3 | Sat Apr 04 00:00:00 1970 PST + 494 | 4 | 00494 | Sun Apr 05 00:00:00 1970 PST + 496 | 6 | 00496 | Tue Apr 07 00:00:00 1970 PST + 497 | 407 | 00497_update7 | Wed Apr 08 00:00:00 1970 PST + 498 | 8 | 00498 | Thu Apr 09 00:00:00 1970 PST + 499 | 509 | 00499_update9 | Fri Apr 10 00:00:00 1970 PST + 500 | 0 | 00500 | Thu Jan 01 00:00:00 1970 PST + 501 | 1 | 00501 | Fri Jan 02 00:00:00 1970 PST + 503 | 303 | 00503_update3 | Sun Jan 04 00:00:00 1970 PST + 504 | 4 | 00504 | Mon Jan 05 00:00:00 1970 PST + 506 | 6 | 00506 | Wed Jan 07 00:00:00 1970 PST + 507 | 407 | 00507_update7 | Thu Jan 08 00:00:00 1970 PST + 508 | 8 | 00508 | Fri Jan 09 00:00:00 1970 PST + 509 | 509 | 00509_update9 | Sat Jan 10 00:00:00 1970 PST + 510 | 0 | 00510 | Sun Jan 11 00:00:00 1970 PST + 511 | 1 | 00511 | Mon Jan 12 00:00:00 1970 PST + 513 | 303 | 00513_update3 | Wed Jan 14 00:00:00 1970 PST + 514 | 4 | 00514 | Thu Jan 15 00:00:00 1970 PST + 516 | 6 | 00516 | Sat Jan 17 00:00:00 1970 PST + 517 | 407 | 00517_update7 | Sun Jan 18 00:00:00 1970 PST + 518 | 8 | 00518 | Mon Jan 19 00:00:00 1970 PST + 519 | 509 | 00519_update9 | Tue Jan 20 00:00:00 1970 PST + 520 | 0 | 00520 | Wed Jan 21 00:00:00 1970 PST + 521 | 1 | 00521 | Thu Jan 22 00:00:00 1970 PST + 523 | 303 | 00523_update3 | Sat Jan 24 00:00:00 1970 PST + 524 | 4 | 00524 | Sun Jan 25 00:00:00 1970 PST + 526 | 6 | 00526 | Tue Jan 27 00:00:00 1970 PST + 527 | 407 | 00527_update7 | Wed Jan 28 00:00:00 1970 PST + 528 | 8 | 00528 | Thu Jan 29 00:00:00 1970 PST + 529 | 509 | 00529_update9 | Fri Jan 30 00:00:00 1970 PST + 530 | 0 | 00530 | Sat Jan 31 00:00:00 1970 PST + 531 | 1 | 00531 | Sun Feb 01 00:00:00 1970 PST + 533 | 303 | 00533_update3 | Tue Feb 03 00:00:00 1970 PST + 534 | 4 | 00534 | Wed Feb 04 00:00:00 1970 PST + 536 | 6 | 00536 | Fri Feb 06 00:00:00 1970 PST + 537 | 407 | 00537_update7 | Sat Feb 07 00:00:00 1970 PST + 538 | 8 | 00538 | Sun Feb 08 00:00:00 1970 PST + 539 | 509 | 00539_update9 | Mon Feb 09 00:00:00 1970 PST + 540 | 0 | 00540 | Tue Feb 10 00:00:00 1970 PST + 541 | 1 | 00541 | Wed Feb 11 00:00:00 1970 PST + 543 | 303 | 00543_update3 | Fri Feb 13 00:00:00 1970 PST + 544 | 4 | 00544 | Sat Feb 14 00:00:00 1970 PST + 546 | 6 | 00546 | Mon Feb 16 00:00:00 1970 PST + 547 | 407 | 00547_update7 | Tue Feb 17 00:00:00 1970 PST + 548 | 8 | 00548 | Wed Feb 18 00:00:00 1970 PST + 549 | 509 | 00549_update9 | Thu Feb 19 00:00:00 1970 PST + 550 | 0 | 00550 | Fri Feb 20 00:00:00 1970 PST + 551 | 1 | 00551 | Sat Feb 21 00:00:00 1970 PST + 553 | 303 | 00553_update3 | Mon Feb 23 00:00:00 1970 PST + 554 | 4 | 00554 | Tue Feb 24 00:00:00 1970 PST + 556 | 6 | 00556 | Thu Feb 26 00:00:00 1970 PST + 557 | 407 | 00557_update7 | Fri Feb 27 00:00:00 1970 PST + 558 | 8 | 00558 | Sat Feb 28 00:00:00 1970 PST + 559 | 509 | 00559_update9 | Sun Mar 01 00:00:00 1970 PST + 560 | 0 | 00560 | Mon Mar 02 00:00:00 1970 PST + 561 | 1 | 00561 | Tue Mar 03 00:00:00 1970 PST + 563 | 303 | 00563_update3 | Thu Mar 05 00:00:00 1970 PST + 564 | 4 | 00564 | Fri Mar 06 00:00:00 1970 PST + 566 | 6 | 00566 | Sun Mar 08 00:00:00 1970 PST + 567 | 407 | 00567_update7 | Mon Mar 09 00:00:00 1970 PST + 568 | 8 | 00568 | Tue Mar 10 00:00:00 1970 PST + 569 | 509 | 00569_update9 | Wed Mar 11 00:00:00 1970 PST + 570 | 0 | 00570 | Thu Mar 12 00:00:00 1970 PST + 571 | 1 | 00571 | Fri Mar 13 00:00:00 1970 PST + 573 | 303 | 00573_update3 | Sun Mar 15 00:00:00 1970 PST + 574 | 4 | 00574 | Mon Mar 16 00:00:00 1970 PST + 576 | 6 | 00576 | Wed Mar 18 00:00:00 1970 PST + 577 | 407 | 00577_update7 | Thu Mar 19 00:00:00 1970 PST + 578 | 8 | 00578 | Fri Mar 20 00:00:00 1970 PST + 579 | 509 | 00579_update9 | Sat Mar 21 00:00:00 1970 PST + 580 | 0 | 00580 | Sun Mar 22 00:00:00 1970 PST + 581 | 1 | 00581 | Mon Mar 23 00:00:00 1970 PST + 583 | 303 | 00583_update3 | Wed Mar 25 00:00:00 1970 PST + 584 | 4 | 00584 | Thu Mar 26 00:00:00 1970 PST + 586 | 6 | 00586 | Sat Mar 28 00:00:00 1970 PST + 587 | 407 | 00587_update7 | Sun Mar 29 00:00:00 1970 PST + 588 | 8 | 00588 | Mon Mar 30 00:00:00 1970 PST + 589 | 509 | 00589_update9 | Tue Mar 31 00:00:00 1970 PST + 590 | 0 | 00590 | Wed Apr 01 00:00:00 1970 PST + 591 | 1 | 00591 | Thu Apr 02 00:00:00 1970 PST + 593 | 303 | 00593_update3 | Sat Apr 04 00:00:00 1970 PST + 594 | 4 | 00594 | Sun Apr 05 00:00:00 1970 PST + 596 | 6 | 00596 | Tue Apr 07 00:00:00 1970 PST + 597 | 407 | 00597_update7 | Wed Apr 08 00:00:00 1970 PST + 598 | 8 | 00598 | Thu Apr 09 00:00:00 1970 PST + 599 | 509 | 00599_update9 | Fri Apr 10 00:00:00 1970 PST + 600 | 0 | 00600 | Thu Jan 01 00:00:00 1970 PST + 601 | 1 | 00601 | Fri Jan 02 00:00:00 1970 PST + 603 | 303 | 00603_update3 | Sun Jan 04 00:00:00 1970 PST + 604 | 4 | 00604 | Mon Jan 05 00:00:00 1970 PST + 606 | 6 | 00606 | Wed Jan 07 00:00:00 1970 PST + 607 | 407 | 00607_update7 | Thu Jan 08 00:00:00 1970 PST + 608 | 8 | 00608 | Fri Jan 09 00:00:00 1970 PST + 609 | 509 | 00609_update9 | Sat Jan 10 00:00:00 1970 PST + 610 | 0 | 00610 | Sun Jan 11 00:00:00 1970 PST + 611 | 1 | 00611 | Mon Jan 12 00:00:00 1970 PST + 613 | 303 | 00613_update3 | Wed Jan 14 00:00:00 1970 PST + 614 | 4 | 00614 | Thu Jan 15 00:00:00 1970 PST + 616 | 6 | 00616 | Sat Jan 17 00:00:00 1970 PST + 617 | 407 | 00617_update7 | Sun Jan 18 00:00:00 1970 PST + 618 | 8 | 00618 | Mon Jan 19 00:00:00 1970 PST + 619 | 509 | 00619_update9 | Tue Jan 20 00:00:00 1970 PST + 620 | 0 | 00620 | Wed Jan 21 00:00:00 1970 PST + 621 | 1 | 00621 | Thu Jan 22 00:00:00 1970 PST + 623 | 303 | 00623_update3 | Sat Jan 24 00:00:00 1970 PST + 624 | 4 | 00624 | Sun Jan 25 00:00:00 1970 PST + 626 | 6 | 00626 | Tue Jan 27 00:00:00 1970 PST + 627 | 407 | 00627_update7 | Wed Jan 28 00:00:00 1970 PST + 628 | 8 | 00628 | Thu Jan 29 00:00:00 1970 PST + 629 | 509 | 00629_update9 | Fri Jan 30 00:00:00 1970 PST + 630 | 0 | 00630 | Sat Jan 31 00:00:00 1970 PST + 631 | 1 | 00631 | Sun Feb 01 00:00:00 1970 PST + 633 | 303 | 00633_update3 | Tue Feb 03 00:00:00 1970 PST + 634 | 4 | 00634 | Wed Feb 04 00:00:00 1970 PST + 636 | 6 | 00636 | Fri Feb 06 00:00:00 1970 PST + 637 | 407 | 00637_update7 | Sat Feb 07 00:00:00 1970 PST + 638 | 8 | 00638 | Sun Feb 08 00:00:00 1970 PST + 639 | 509 | 00639_update9 | Mon Feb 09 00:00:00 1970 PST + 640 | 0 | 00640 | Tue Feb 10 00:00:00 1970 PST + 641 | 1 | 00641 | Wed Feb 11 00:00:00 1970 PST + 643 | 303 | 00643_update3 | Fri Feb 13 00:00:00 1970 PST + 644 | 4 | 00644 | Sat Feb 14 00:00:00 1970 PST + 646 | 6 | 00646 | Mon Feb 16 00:00:00 1970 PST + 647 | 407 | 00647_update7 | Tue Feb 17 00:00:00 1970 PST + 648 | 8 | 00648 | Wed Feb 18 00:00:00 1970 PST + 649 | 509 | 00649_update9 | Thu Feb 19 00:00:00 1970 PST + 650 | 0 | 00650 | Fri Feb 20 00:00:00 1970 PST + 651 | 1 | 00651 | Sat Feb 21 00:00:00 1970 PST + 653 | 303 | 00653_update3 | Mon Feb 23 00:00:00 1970 PST + 654 | 4 | 00654 | Tue Feb 24 00:00:00 1970 PST + 656 | 6 | 00656 | Thu Feb 26 00:00:00 1970 PST + 657 | 407 | 00657_update7 | Fri Feb 27 00:00:00 1970 PST + 658 | 8 | 00658 | Sat Feb 28 00:00:00 1970 PST + 659 | 509 | 00659_update9 | Sun Mar 01 00:00:00 1970 PST + 660 | 0 | 00660 | Mon Mar 02 00:00:00 1970 PST + 661 | 1 | 00661 | Tue Mar 03 00:00:00 1970 PST + 663 | 303 | 00663_update3 | Thu Mar 05 00:00:00 1970 PST + 664 | 4 | 00664 | Fri Mar 06 00:00:00 1970 PST + 666 | 6 | 00666 | Sun Mar 08 00:00:00 1970 PST + 667 | 407 | 00667_update7 | Mon Mar 09 00:00:00 1970 PST + 668 | 8 | 00668 | Tue Mar 10 00:00:00 1970 PST + 669 | 509 | 00669_update9 | Wed Mar 11 00:00:00 1970 PST + 670 | 0 | 00670 | Thu Mar 12 00:00:00 1970 PST + 671 | 1 | 00671 | Fri Mar 13 00:00:00 1970 PST + 673 | 303 | 00673_update3 | Sun Mar 15 00:00:00 1970 PST + 674 | 4 | 00674 | Mon Mar 16 00:00:00 1970 PST + 676 | 6 | 00676 | Wed Mar 18 00:00:00 1970 PST + 677 | 407 | 00677_update7 | Thu Mar 19 00:00:00 1970 PST + 678 | 8 | 00678 | Fri Mar 20 00:00:00 1970 PST + 679 | 509 | 00679_update9 | Sat Mar 21 00:00:00 1970 PST + 680 | 0 | 00680 | Sun Mar 22 00:00:00 1970 PST + 681 | 1 | 00681 | Mon Mar 23 00:00:00 1970 PST + 683 | 303 | 00683_update3 | Wed Mar 25 00:00:00 1970 PST + 684 | 4 | 00684 | Thu Mar 26 00:00:00 1970 PST + 686 | 6 | 00686 | Sat Mar 28 00:00:00 1970 PST + 687 | 407 | 00687_update7 | Sun Mar 29 00:00:00 1970 PST + 688 | 8 | 00688 | Mon Mar 30 00:00:00 1970 PST + 689 | 509 | 00689_update9 | Tue Mar 31 00:00:00 1970 PST + 690 | 0 | 00690 | Wed Apr 01 00:00:00 1970 PST + 691 | 1 | 00691 | Thu Apr 02 00:00:00 1970 PST + 693 | 303 | 00693_update3 | Sat Apr 04 00:00:00 1970 PST + 694 | 4 | 00694 | Sun Apr 05 00:00:00 1970 PST + 696 | 6 | 00696 | Tue Apr 07 00:00:00 1970 PST + 697 | 407 | 00697_update7 | Wed Apr 08 00:00:00 1970 PST + 698 | 8 | 00698 | Thu Apr 09 00:00:00 1970 PST + 699 | 509 | 00699_update9 | Fri Apr 10 00:00:00 1970 PST + 700 | 0 | 00700 | Thu Jan 01 00:00:00 1970 PST + 701 | 1 | 00701 | Fri Jan 02 00:00:00 1970 PST + 703 | 303 | 00703_update3 | Sun Jan 04 00:00:00 1970 PST + 704 | 4 | 00704 | Mon Jan 05 00:00:00 1970 PST + 706 | 6 | 00706 | Wed Jan 07 00:00:00 1970 PST + 707 | 407 | 00707_update7 | Thu Jan 08 00:00:00 1970 PST + 708 | 8 | 00708 | Fri Jan 09 00:00:00 1970 PST + 709 | 509 | 00709_update9 | Sat Jan 10 00:00:00 1970 PST + 710 | 0 | 00710 | Sun Jan 11 00:00:00 1970 PST + 711 | 1 | 00711 | Mon Jan 12 00:00:00 1970 PST + 713 | 303 | 00713_update3 | Wed Jan 14 00:00:00 1970 PST + 714 | 4 | 00714 | Thu Jan 15 00:00:00 1970 PST + 716 | 6 | 00716 | Sat Jan 17 00:00:00 1970 PST + 717 | 407 | 00717_update7 | Sun Jan 18 00:00:00 1970 PST + 718 | 8 | 00718 | Mon Jan 19 00:00:00 1970 PST + 719 | 509 | 00719_update9 | Tue Jan 20 00:00:00 1970 PST + 720 | 0 | 00720 | Wed Jan 21 00:00:00 1970 PST + 721 | 1 | 00721 | Thu Jan 22 00:00:00 1970 PST + 723 | 303 | 00723_update3 | Sat Jan 24 00:00:00 1970 PST + 724 | 4 | 00724 | Sun Jan 25 00:00:00 1970 PST + 726 | 6 | 00726 | Tue Jan 27 00:00:00 1970 PST + 727 | 407 | 00727_update7 | Wed Jan 28 00:00:00 1970 PST + 728 | 8 | 00728 | Thu Jan 29 00:00:00 1970 PST + 729 | 509 | 00729_update9 | Fri Jan 30 00:00:00 1970 PST + 730 | 0 | 00730 | Sat Jan 31 00:00:00 1970 PST + 731 | 1 | 00731 | Sun Feb 01 00:00:00 1970 PST + 733 | 303 | 00733_update3 | Tue Feb 03 00:00:00 1970 PST + 734 | 4 | 00734 | Wed Feb 04 00:00:00 1970 PST + 736 | 6 | 00736 | Fri Feb 06 00:00:00 1970 PST + 737 | 407 | 00737_update7 | Sat Feb 07 00:00:00 1970 PST + 738 | 8 | 00738 | Sun Feb 08 00:00:00 1970 PST + 739 | 509 | 00739_update9 | Mon Feb 09 00:00:00 1970 PST + 740 | 0 | 00740 | Tue Feb 10 00:00:00 1970 PST + 741 | 1 | 00741 | Wed Feb 11 00:00:00 1970 PST + 743 | 303 | 00743_update3 | Fri Feb 13 00:00:00 1970 PST + 744 | 4 | 00744 | Sat Feb 14 00:00:00 1970 PST + 746 | 6 | 00746 | Mon Feb 16 00:00:00 1970 PST + 747 | 407 | 00747_update7 | Tue Feb 17 00:00:00 1970 PST + 748 | 8 | 00748 | Wed Feb 18 00:00:00 1970 PST + 749 | 509 | 00749_update9 | Thu Feb 19 00:00:00 1970 PST + 750 | 0 | 00750 | Fri Feb 20 00:00:00 1970 PST + 751 | 1 | 00751 | Sat Feb 21 00:00:00 1970 PST + 753 | 303 | 00753_update3 | Mon Feb 23 00:00:00 1970 PST + 754 | 4 | 00754 | Tue Feb 24 00:00:00 1970 PST + 756 | 6 | 00756 | Thu Feb 26 00:00:00 1970 PST + 757 | 407 | 00757_update7 | Fri Feb 27 00:00:00 1970 PST + 758 | 8 | 00758 | Sat Feb 28 00:00:00 1970 PST + 759 | 509 | 00759_update9 | Sun Mar 01 00:00:00 1970 PST + 760 | 0 | 00760 | Mon Mar 02 00:00:00 1970 PST + 761 | 1 | 00761 | Tue Mar 03 00:00:00 1970 PST + 763 | 303 | 00763_update3 | Thu Mar 05 00:00:00 1970 PST + 764 | 4 | 00764 | Fri Mar 06 00:00:00 1970 PST + 766 | 6 | 00766 | Sun Mar 08 00:00:00 1970 PST + 767 | 407 | 00767_update7 | Mon Mar 09 00:00:00 1970 PST + 768 | 8 | 00768 | Tue Mar 10 00:00:00 1970 PST + 769 | 509 | 00769_update9 | Wed Mar 11 00:00:00 1970 PST + 770 | 0 | 00770 | Thu Mar 12 00:00:00 1970 PST + 771 | 1 | 00771 | Fri Mar 13 00:00:00 1970 PST + 773 | 303 | 00773_update3 | Sun Mar 15 00:00:00 1970 PST + 774 | 4 | 00774 | Mon Mar 16 00:00:00 1970 PST + 776 | 6 | 00776 | Wed Mar 18 00:00:00 1970 PST + 777 | 407 | 00777_update7 | Thu Mar 19 00:00:00 1970 PST + 778 | 8 | 00778 | Fri Mar 20 00:00:00 1970 PST + 779 | 509 | 00779_update9 | Sat Mar 21 00:00:00 1970 PST + 780 | 0 | 00780 | Sun Mar 22 00:00:00 1970 PST + 781 | 1 | 00781 | Mon Mar 23 00:00:00 1970 PST + 783 | 303 | 00783_update3 | Wed Mar 25 00:00:00 1970 PST + 784 | 4 | 00784 | Thu Mar 26 00:00:00 1970 PST + 786 | 6 | 00786 | Sat Mar 28 00:00:00 1970 PST + 787 | 407 | 00787_update7 | Sun Mar 29 00:00:00 1970 PST + 788 | 8 | 00788 | Mon Mar 30 00:00:00 1970 PST + 789 | 509 | 00789_update9 | Tue Mar 31 00:00:00 1970 PST + 790 | 0 | 00790 | Wed Apr 01 00:00:00 1970 PST + 791 | 1 | 00791 | Thu Apr 02 00:00:00 1970 PST + 793 | 303 | 00793_update3 | Sat Apr 04 00:00:00 1970 PST + 794 | 4 | 00794 | Sun Apr 05 00:00:00 1970 PST + 796 | 6 | 00796 | Tue Apr 07 00:00:00 1970 PST + 797 | 407 | 00797_update7 | Wed Apr 08 00:00:00 1970 PST + 798 | 8 | 00798 | Thu Apr 09 00:00:00 1970 PST + 799 | 509 | 00799_update9 | Fri Apr 10 00:00:00 1970 PST + 800 | 0 | 00800 | Thu Jan 01 00:00:00 1970 PST + 801 | 1 | 00801 | Fri Jan 02 00:00:00 1970 PST + 803 | 303 | 00803_update3 | Sun Jan 04 00:00:00 1970 PST + 804 | 4 | 00804 | Mon Jan 05 00:00:00 1970 PST + 806 | 6 | 00806 | Wed Jan 07 00:00:00 1970 PST + 807 | 407 | 00807_update7 | Thu Jan 08 00:00:00 1970 PST + 808 | 8 | 00808 | Fri Jan 09 00:00:00 1970 PST + 809 | 509 | 00809_update9 | Sat Jan 10 00:00:00 1970 PST + 810 | 0 | 00810 | Sun Jan 11 00:00:00 1970 PST + 811 | 1 | 00811 | Mon Jan 12 00:00:00 1970 PST + 813 | 303 | 00813_update3 | Wed Jan 14 00:00:00 1970 PST + 814 | 4 | 00814 | Thu Jan 15 00:00:00 1970 PST + 816 | 6 | 00816 | Sat Jan 17 00:00:00 1970 PST + 817 | 407 | 00817_update7 | Sun Jan 18 00:00:00 1970 PST + 818 | 8 | 00818 | Mon Jan 19 00:00:00 1970 PST + 819 | 509 | 00819_update9 | Tue Jan 20 00:00:00 1970 PST + 820 | 0 | 00820 | Wed Jan 21 00:00:00 1970 PST + 821 | 1 | 00821 | Thu Jan 22 00:00:00 1970 PST + 823 | 303 | 00823_update3 | Sat Jan 24 00:00:00 1970 PST + 824 | 4 | 00824 | Sun Jan 25 00:00:00 1970 PST + 826 | 6 | 00826 | Tue Jan 27 00:00:00 1970 PST + 827 | 407 | 00827_update7 | Wed Jan 28 00:00:00 1970 PST + 828 | 8 | 00828 | Thu Jan 29 00:00:00 1970 PST + 829 | 509 | 00829_update9 | Fri Jan 30 00:00:00 1970 PST + 830 | 0 | 00830 | Sat Jan 31 00:00:00 1970 PST + 831 | 1 | 00831 | Sun Feb 01 00:00:00 1970 PST + 833 | 303 | 00833_update3 | Tue Feb 03 00:00:00 1970 PST + 834 | 4 | 00834 | Wed Feb 04 00:00:00 1970 PST + 836 | 6 | 00836 | Fri Feb 06 00:00:00 1970 PST + 837 | 407 | 00837_update7 | Sat Feb 07 00:00:00 1970 PST + 838 | 8 | 00838 | Sun Feb 08 00:00:00 1970 PST + 839 | 509 | 00839_update9 | Mon Feb 09 00:00:00 1970 PST + 840 | 0 | 00840 | Tue Feb 10 00:00:00 1970 PST + 841 | 1 | 00841 | Wed Feb 11 00:00:00 1970 PST + 843 | 303 | 00843_update3 | Fri Feb 13 00:00:00 1970 PST + 844 | 4 | 00844 | Sat Feb 14 00:00:00 1970 PST + 846 | 6 | 00846 | Mon Feb 16 00:00:00 1970 PST + 847 | 407 | 00847_update7 | Tue Feb 17 00:00:00 1970 PST + 848 | 8 | 00848 | Wed Feb 18 00:00:00 1970 PST + 849 | 509 | 00849_update9 | Thu Feb 19 00:00:00 1970 PST + 850 | 0 | 00850 | Fri Feb 20 00:00:00 1970 PST + 851 | 1 | 00851 | Sat Feb 21 00:00:00 1970 PST + 853 | 303 | 00853_update3 | Mon Feb 23 00:00:00 1970 PST + 854 | 4 | 00854 | Tue Feb 24 00:00:00 1970 PST + 856 | 6 | 00856 | Thu Feb 26 00:00:00 1970 PST + 857 | 407 | 00857_update7 | Fri Feb 27 00:00:00 1970 PST + 858 | 8 | 00858 | Sat Feb 28 00:00:00 1970 PST + 859 | 509 | 00859_update9 | Sun Mar 01 00:00:00 1970 PST + 860 | 0 | 00860 | Mon Mar 02 00:00:00 1970 PST + 861 | 1 | 00861 | Tue Mar 03 00:00:00 1970 PST + 863 | 303 | 00863_update3 | Thu Mar 05 00:00:00 1970 PST + 864 | 4 | 00864 | Fri Mar 06 00:00:00 1970 PST + 866 | 6 | 00866 | Sun Mar 08 00:00:00 1970 PST + 867 | 407 | 00867_update7 | Mon Mar 09 00:00:00 1970 PST + 868 | 8 | 00868 | Tue Mar 10 00:00:00 1970 PST + 869 | 509 | 00869_update9 | Wed Mar 11 00:00:00 1970 PST + 870 | 0 | 00870 | Thu Mar 12 00:00:00 1970 PST + 871 | 1 | 00871 | Fri Mar 13 00:00:00 1970 PST + 873 | 303 | 00873_update3 | Sun Mar 15 00:00:00 1970 PST + 874 | 4 | 00874 | Mon Mar 16 00:00:00 1970 PST + 876 | 6 | 00876 | Wed Mar 18 00:00:00 1970 PST + 877 | 407 | 00877_update7 | Thu Mar 19 00:00:00 1970 PST + 878 | 8 | 00878 | Fri Mar 20 00:00:00 1970 PST + 879 | 509 | 00879_update9 | Sat Mar 21 00:00:00 1970 PST + 880 | 0 | 00880 | Sun Mar 22 00:00:00 1970 PST + 881 | 1 | 00881 | Mon Mar 23 00:00:00 1970 PST + 883 | 303 | 00883_update3 | Wed Mar 25 00:00:00 1970 PST + 884 | 4 | 00884 | Thu Mar 26 00:00:00 1970 PST + 886 | 6 | 00886 | Sat Mar 28 00:00:00 1970 PST + 887 | 407 | 00887_update7 | Sun Mar 29 00:00:00 1970 PST + 888 | 8 | 00888 | Mon Mar 30 00:00:00 1970 PST + 889 | 509 | 00889_update9 | Tue Mar 31 00:00:00 1970 PST + 890 | 0 | 00890 | Wed Apr 01 00:00:00 1970 PST + 891 | 1 | 00891 | Thu Apr 02 00:00:00 1970 PST + 893 | 303 | 00893_update3 | Sat Apr 04 00:00:00 1970 PST + 894 | 4 | 00894 | Sun Apr 05 00:00:00 1970 PST + 896 | 6 | 00896 | Tue Apr 07 00:00:00 1970 PST + 897 | 407 | 00897_update7 | Wed Apr 08 00:00:00 1970 PST + 898 | 8 | 00898 | Thu Apr 09 00:00:00 1970 PST + 899 | 509 | 00899_update9 | Fri Apr 10 00:00:00 1970 PST + 900 | 0 | 00900 | Thu Jan 01 00:00:00 1970 PST + 901 | 1 | 00901 | Fri Jan 02 00:00:00 1970 PST + 903 | 303 | 00903_update3 | Sun Jan 04 00:00:00 1970 PST + 904 | 4 | 00904 | Mon Jan 05 00:00:00 1970 PST + 906 | 6 | 00906 | Wed Jan 07 00:00:00 1970 PST + 907 | 407 | 00907_update7 | Thu Jan 08 00:00:00 1970 PST + 908 | 8 | 00908 | Fri Jan 09 00:00:00 1970 PST + 909 | 509 | 00909_update9 | Sat Jan 10 00:00:00 1970 PST + 910 | 0 | 00910 | Sun Jan 11 00:00:00 1970 PST + 911 | 1 | 00911 | Mon Jan 12 00:00:00 1970 PST + 913 | 303 | 00913_update3 | Wed Jan 14 00:00:00 1970 PST + 914 | 4 | 00914 | Thu Jan 15 00:00:00 1970 PST + 916 | 6 | 00916 | Sat Jan 17 00:00:00 1970 PST + 917 | 407 | 00917_update7 | Sun Jan 18 00:00:00 1970 PST + 918 | 8 | 00918 | Mon Jan 19 00:00:00 1970 PST + 919 | 509 | 00919_update9 | Tue Jan 20 00:00:00 1970 PST + 920 | 0 | 00920 | Wed Jan 21 00:00:00 1970 PST + 921 | 1 | 00921 | Thu Jan 22 00:00:00 1970 PST + 923 | 303 | 00923_update3 | Sat Jan 24 00:00:00 1970 PST + 924 | 4 | 00924 | Sun Jan 25 00:00:00 1970 PST + 926 | 6 | 00926 | Tue Jan 27 00:00:00 1970 PST + 927 | 407 | 00927_update7 | Wed Jan 28 00:00:00 1970 PST + 928 | 8 | 00928 | Thu Jan 29 00:00:00 1970 PST + 929 | 509 | 00929_update9 | Fri Jan 30 00:00:00 1970 PST + 930 | 0 | 00930 | Sat Jan 31 00:00:00 1970 PST + 931 | 1 | 00931 | Sun Feb 01 00:00:00 1970 PST + 933 | 303 | 00933_update3 | Tue Feb 03 00:00:00 1970 PST + 934 | 4 | 00934 | Wed Feb 04 00:00:00 1970 PST + 936 | 6 | 00936 | Fri Feb 06 00:00:00 1970 PST + 937 | 407 | 00937_update7 | Sat Feb 07 00:00:00 1970 PST + 938 | 8 | 00938 | Sun Feb 08 00:00:00 1970 PST + 939 | 509 | 00939_update9 | Mon Feb 09 00:00:00 1970 PST + 940 | 0 | 00940 | Tue Feb 10 00:00:00 1970 PST + 941 | 1 | 00941 | Wed Feb 11 00:00:00 1970 PST + 943 | 303 | 00943_update3 | Fri Feb 13 00:00:00 1970 PST + 944 | 4 | 00944 | Sat Feb 14 00:00:00 1970 PST + 946 | 6 | 00946 | Mon Feb 16 00:00:00 1970 PST + 947 | 407 | 00947_update7 | Tue Feb 17 00:00:00 1970 PST + 948 | 8 | 00948 | Wed Feb 18 00:00:00 1970 PST + 949 | 509 | 00949_update9 | Thu Feb 19 00:00:00 1970 PST + 950 | 0 | 00950 | Fri Feb 20 00:00:00 1970 PST + 951 | 1 | 00951 | Sat Feb 21 00:00:00 1970 PST + 953 | 303 | 00953_update3 | Mon Feb 23 00:00:00 1970 PST + 954 | 4 | 00954 | Tue Feb 24 00:00:00 1970 PST + 956 | 6 | 00956 | Thu Feb 26 00:00:00 1970 PST + 957 | 407 | 00957_update7 | Fri Feb 27 00:00:00 1970 PST + 958 | 8 | 00958 | Sat Feb 28 00:00:00 1970 PST + 959 | 509 | 00959_update9 | Sun Mar 01 00:00:00 1970 PST + 960 | 0 | 00960 | Mon Mar 02 00:00:00 1970 PST + 961 | 1 | 00961 | Tue Mar 03 00:00:00 1970 PST + 963 | 303 | 00963_update3 | Thu Mar 05 00:00:00 1970 PST + 964 | 4 | 00964 | Fri Mar 06 00:00:00 1970 PST + 966 | 6 | 00966 | Sun Mar 08 00:00:00 1970 PST + 967 | 407 | 00967_update7 | Mon Mar 09 00:00:00 1970 PST + 968 | 8 | 00968 | Tue Mar 10 00:00:00 1970 PST + 969 | 509 | 00969_update9 | Wed Mar 11 00:00:00 1970 PST + 970 | 0 | 00970 | Thu Mar 12 00:00:00 1970 PST + 971 | 1 | 00971 | Fri Mar 13 00:00:00 1970 PST + 973 | 303 | 00973_update3 | Sun Mar 15 00:00:00 1970 PST + 974 | 4 | 00974 | Mon Mar 16 00:00:00 1970 PST + 976 | 6 | 00976 | Wed Mar 18 00:00:00 1970 PST + 977 | 407 | 00977_update7 | Thu Mar 19 00:00:00 1970 PST + 978 | 8 | 00978 | Fri Mar 20 00:00:00 1970 PST + 979 | 509 | 00979_update9 | Sat Mar 21 00:00:00 1970 PST + 980 | 0 | 00980 | Sun Mar 22 00:00:00 1970 PST + 981 | 1 | 00981 | Mon Mar 23 00:00:00 1970 PST + 983 | 303 | 00983_update3 | Wed Mar 25 00:00:00 1970 PST + 984 | 4 | 00984 | Thu Mar 26 00:00:00 1970 PST + 986 | 6 | 00986 | Sat Mar 28 00:00:00 1970 PST + 987 | 407 | 00987_update7 | Sun Mar 29 00:00:00 1970 PST + 988 | 8 | 00988 | Mon Mar 30 00:00:00 1970 PST + 989 | 509 | 00989_update9 | Tue Mar 31 00:00:00 1970 PST + 990 | 0 | 00990 | Wed Apr 01 00:00:00 1970 PST + 991 | 1 | 00991 | Thu Apr 02 00:00:00 1970 PST + 993 | 303 | 00993_update3 | Sat Apr 04 00:00:00 1970 PST + 994 | 4 | 00994 | Sun Apr 05 00:00:00 1970 PST + 996 | 6 | 00996 | Tue Apr 07 00:00:00 1970 PST + 997 | 407 | 00997_update7 | Wed Apr 08 00:00:00 1970 PST + 998 | 8 | 00998 | Thu Apr 09 00:00:00 1970 PST + 999 | 509 | 00999_update9 | Fri Apr 10 00:00:00 1970 PST + 1000 | 0 | 01000 | Thu Jan 01 00:00:00 1970 PST + 1001 | 101 | 0000100001 | + 1003 | 403 | 0000300003_update3 | + 1004 | 104 | 0000400004 | + 1006 | 106 | 0000600006 | + 1007 | 507 | 0000700007_update7 | + 1008 | 108 | 0000800008 | + 1009 | 609 | 0000900009_update9 | + 1010 | 100 | 0001000010 | + 1011 | 101 | 0001100011 | + 1013 | 403 | 0001300013_update3 | + 1014 | 104 | 0001400014 | + 1016 | 106 | 0001600016 | + 1017 | 507 | 0001700017_update7 | + 1018 | 108 | 0001800018 | + 1019 | 609 | 0001900019_update9 | + 1020 | 100 | 0002000020 | + 1101 | 201 | aaa | + 1103 | 503 | ccc_update3 | + 1104 | 204 | ddd | +(819 rows) + +--nspt openGauss not have tableoid, also not support select system columns. +EXPLAIN (verbose, costs off) +INSERT INTO ft2 (c1,c2,c3) VALUES (1200,999,'foo') RETURNING tableoid::regclass; +ERROR: column "tableoid" does not exist +LINE 2: ... ft2 (c1,c2,c3) VALUES (1200,999,'foo') RETURNING tableoid::... + ^ +CONTEXT: referenced column: tableoid +INSERT INTO ft2 (c1,c2,c3) VALUES (1200,999,'foo') RETURNING tableoid::regclass; +ERROR: column "tableoid" does not exist +LINE 1: ... ft2 (c1,c2,c3) VALUES (1200,999,'foo') RETURNING tableoid::... + ^ +CONTEXT: referenced column: tableoid +EXPLAIN (verbose, costs off) +UPDATE ft2 SET c3 = 'bar' WHERE c1 = 1200 RETURNING tableoid::regclass; -- can be pushed down +ERROR: column "tableoid" does not exist +LINE 2: ...DATE ft2 SET c3 = 'bar' WHERE c1 = 1200 RETURNING tableoid::... + ^ +CONTEXT: referenced column: tableoid +UPDATE ft2 SET c3 = 'bar' WHERE c1 = 1200 RETURNING tableoid::regclass; +ERROR: column "tableoid" does not exist +LINE 1: ...DATE ft2 SET c3 = 'bar' WHERE c1 = 1200 RETURNING tableoid::... + ^ +CONTEXT: referenced column: tableoid +EXPLAIN (verbose, costs off) +DELETE FROM ft2 WHERE c1 = 1200 RETURNING tableoid::regclass; -- can be pushed down +ERROR: column "tableoid" does not exist +LINE 2: DELETE FROM ft2 WHERE c1 = 1200 RETURNING tableoid::regclass... + ^ +CONTEXT: referenced column: tableoid +DELETE FROM ft2 WHERE c1 = 1200 RETURNING tableoid::regclass; +ERROR: column "tableoid" does not exist +LINE 1: DELETE FROM ft2 WHERE c1 = 1200 RETURNING tableoid::regclass... + ^ +CONTEXT: referenced column: tableoid +-- Test UPDATE/DELETE with RETURNING on a three-table join +INSERT INTO ft2 (c1,c2,c3) + SELECT id, id - 1200, to_char(id, 'FM00000') FROM generate_series(1201, 1300) id; +EXPLAIN (verbose, costs off) +UPDATE ft2 SET c3 = 'foo' + FROM ft4 INNER JOIN ft5 ON (ft4.c1 = ft5.c1) + WHERE ft2.c1 > 1200 AND ft2.c2 = ft4.c1 + RETURNING ft2, ft2.*, ft4, ft4.*; -- can be pushed down + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------ + Update on public.ft2 + Output: ft2.*, ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft4.*, ft4.c1, ft4.c2, ft4.c3 + -> Nested Loop + Output: ft2.c1, ft2.c2, NULL::integer, 'foo'::text, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.ctid, ft2.tableoid, ft4.*, ft5.*, ft4.c1, ft4.c2, ft4.c3 + Join Filter: (ft4.c1 = ft5.c1) + -> Nested Loop + Output: ft2.c1, ft2.c2, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.ctid, ft2.tableoid, ft4.*, ft4.c1, ft4.c2, ft4.c3 + Join Filter: (ft2.c2 = ft4.c1) + -> Foreign Scan on public.ft2 + Output: ft2.c1, ft2.c2, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.ctid, ft2.tableoid + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c4, c5, c6, c7, c8, ctid, tableoid FROM "S 1"."T 1" WHERE (("C 1" > 1200)) FOR UPDATE + -> Foreign Scan on public.ft4 + Output: ft4.*, ft4.c1, ft4.c2, ft4.c3 + Node ID: 2 + Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 3" + -> Foreign Scan on public.ft5 + Output: ft5.*, ft5.c1 + Node ID: 3 + Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 4" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c4, c5, c6, c7, c8, ctid, tableoid FROM "S 1"."T 1" WHERE (("C 1" > 1200)) FOR UPDATE + LockRows + Output: "C 1", c2, c4, c5, c6, c7, c8, ctid, tableoid, ctid, tableoid + -> Partitioned Index Scan using t1_pkey on "S 1"."T 1" + Output: "C 1", c2, c4, c5, c6, c7, c8, ctid, tableoid, ctid, tableoid + Index Cond: ("T 1"."C 1" > 1200) + Selected Partitions: 2 + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c1, c2, c3 FROM "S 1"."T 3" + Partition Iterator + Output: c1, c2, c3 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 3" + Output: c1, c2, c3 + Selected Partitions: 1..2 + Node 3: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c1, c2, c3 FROM "S 1"."T 4" + Partition Iterator + Output: c1, c2, c3 + Iterations: 2, Sub Iterations: 4 + -> Partitioned Seq Scan on "S 1"."T 4" + Output: c1, c2, c3 + Selected Partitions: 1..2 + Selected Subpartitions: ALL + +(45 rows) + +UPDATE ft2 SET c3 = 'foo' + FROM ft4 INNER JOIN ft5 ON (ft4.c1 = ft5.c1) + WHERE ft2.c1 > 1200 AND ft2.c2 = ft4.c1 + RETURNING ft2, ft2.*, ft4, ft4.*; + ft2 | c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 | ft4 | c1 | c2 | c3 +--------------------------------+------+----+-----+----+----+----+------------+----+----------------+----+----+-------- + (1206,6,foo,,,,"ft2 ",) | 1206 | 6 | foo | | | | ft2 | | (6,7,AAA006) | 6 | 7 | AAA006 + (1212,12,foo,,,,"ft2 ",) | 1212 | 12 | foo | | | | ft2 | | (12,13,AAA012) | 12 | 13 | AAA012 + (1218,18,foo,,,,"ft2 ",) | 1218 | 18 | foo | | | | ft2 | | (18,19,AAA018) | 18 | 19 | AAA018 + (1224,24,foo,,,,"ft2 ",) | 1224 | 24 | foo | | | | ft2 | | (24,25,AAA024) | 24 | 25 | AAA024 + (1230,30,foo,,,,"ft2 ",) | 1230 | 30 | foo | | | | ft2 | | (30,31,AAA030) | 30 | 31 | AAA030 + (1236,36,foo,,,,"ft2 ",) | 1236 | 36 | foo | | | | ft2 | | (36,37,AAA036) | 36 | 37 | AAA036 + (1242,42,foo,,,,"ft2 ",) | 1242 | 42 | foo | | | | ft2 | | (42,43,AAA042) | 42 | 43 | AAA042 + (1248,48,foo,,,,"ft2 ",) | 1248 | 48 | foo | | | | ft2 | | (48,49,AAA048) | 48 | 49 | AAA048 + (1254,54,foo,,,,"ft2 ",) | 1254 | 54 | foo | | | | ft2 | | (54,55,AAA054) | 54 | 55 | AAA054 + (1260,60,foo,,,,"ft2 ",) | 1260 | 60 | foo | | | | ft2 | | (60,61,AAA060) | 60 | 61 | AAA060 + (1266,66,foo,,,,"ft2 ",) | 1266 | 66 | foo | | | | ft2 | | (66,67,AAA066) | 66 | 67 | AAA066 + (1272,72,foo,,,,"ft2 ",) | 1272 | 72 | foo | | | | ft2 | | (72,73,AAA072) | 72 | 73 | AAA072 + (1278,78,foo,,,,"ft2 ",) | 1278 | 78 | foo | | | | ft2 | | (78,79,AAA078) | 78 | 79 | AAA078 + (1284,84,foo,,,,"ft2 ",) | 1284 | 84 | foo | | | | ft2 | | (84,85,AAA084) | 84 | 85 | AAA084 + (1290,90,foo,,,,"ft2 ",) | 1290 | 90 | foo | | | | ft2 | | (90,91,AAA090) | 90 | 91 | AAA090 + (1296,96,foo,,,,"ft2 ",) | 1296 | 96 | foo | | | | ft2 | | (96,97,AAA096) | 96 | 97 | AAA096 +(16 rows) + +EXPLAIN (verbose, costs off) +DELETE FROM ft2 + USING ft4 LEFT JOIN ft5 ON (ft4.c1 = ft5.c1) + WHERE ft2.c1 > 1200 AND ft2.c1 % 10 = 0 AND ft2.c2 = ft4.c1 + RETURNING 100; -- can be pushed down + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------ + Delete on public.ft2 + Output: 100 + -> Nested Loop Left Join + Output: ft2.ctid, ft2.tableoid, ft4.*, ft5.* + Join Filter: (ft4.c1 = ft5.c1) + -> Nested Loop + Output: ft2.ctid, ft2.tableoid, ft4.*, ft4.c1 + Join Filter: (ft2.c2 = ft4.c1) + -> Foreign Scan on public.ft2 + Output: ft2.ctid, ft2.tableoid, ft2.c2 + Node ID: 1 + Remote SQL: SELECT c2, ctid, tableoid FROM "S 1"."T 1" WHERE (("C 1" > 1200)) AND ((("C 1" % 10) = 0)) FOR UPDATE + -> Foreign Scan on public.ft4 + Output: ft4.*, ft4.c1 + Node ID: 2 + Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 3" + -> Foreign Scan on public.ft5 + Output: ft5.*, ft5.c1 + Node ID: 3 + Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 4" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c2, ctid, tableoid FROM "S 1"."T 1" WHERE (("C 1" > 1200)) AND ((("C 1" % 10) = 0)) FOR UPDATE + LockRows + Output: c2, ctid, tableoid, ctid, tableoid + -> Partitioned Index Scan using t1_pkey on "S 1"."T 1" + Output: c2, ctid, tableoid, ctid, tableoid + Index Cond: ("T 1"."C 1" > 1200) + Filter: (("T 1"."C 1" % 10) = 0) + Selected Partitions: 2 + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c1, c2, c3 FROM "S 1"."T 3" + Partition Iterator + Output: c1, c2, c3 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 3" + Output: c1, c2, c3 + Selected Partitions: 1..2 + Node 3: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c1, c2, c3 FROM "S 1"."T 4" + Partition Iterator + Output: c1, c2, c3 + Iterations: 2, Sub Iterations: 4 + -> Partitioned Seq Scan on "S 1"."T 4" + Output: c1, c2, c3 + Selected Partitions: 1..2 + Selected Subpartitions: ALL + +(46 rows) + +DELETE FROM ft2 + USING ft4 LEFT JOIN ft5 ON (ft4.c1 = ft5.c1) + WHERE ft2.c1 > 1200 AND ft2.c1 % 10 = 0 AND ft2.c2 = ft4.c1 + RETURNING 100; + ?column? +---------- + 100 + 100 + 100 + 100 + 100 + 100 + 100 + 100 + 100 + 100 +(10 rows) + +DELETE FROM ft2 WHERE ft2.c1 > 1200; +-- Test UPDATE with a MULTIEXPR sub-select +-- (maybe someday this'll be remotely executable, but not today) +EXPLAIN (verbose, costs off) +UPDATE ft2 AS target SET (c2, c7) = ( + SELECT c2 * 10, c7 + FROM ft2 AS src + WHERE target.c1 = src.c1 +) WHERE c1 > 1100; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------------------------------- + Update on public.ft2 target + -> Foreign Scan on public.ft2 target + Output: target.c1, (SubPlan 1), NULL::integer, target.c3, target.c4, target.c5, target.c6, (SubPlan 2), target.c8, target.ctid, target.tableoid + Node ID: 1 + Remote SQL: SELECT "C 1", c3, c4, c5, c6, c8, ctid, tableoid FROM "S 1"."T 1" WHERE (("C 1" > 1100)) FOR UPDATE + SubPlan 1 + -> Foreign Scan on public.ft2 src + Output: (src.c2 * 10) + Node ID: 2 + Remote SQL: SELECT c2 FROM "S 1"."T 1" WHERE (($1::integer = "C 1")) + SubPlan 2 + -> Foreign Scan on public.ft2 src + Output: src.c7 + Node ID: 3 + Remote SQL: SELECT c7 FROM "S 1"."T 1" WHERE (($1::integer = "C 1")) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c3, c4, c5, c6, c8, ctid, tableoid FROM "S 1"."T 1" WHERE (("C 1" > 1100)) FOR UPDATE + LockRows + Output: "C 1", c3, c4, c5, c6, c8, ctid, tableoid, ctid, tableoid + -> Partitioned Index Scan using t1_pkey on "S 1"."T 1" + Output: "C 1", c3, c4, c5, c6, c8, ctid, tableoid, ctid, tableoid + Index Cond: ("T 1"."C 1" > 1100) + Selected Partitions: 2 + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c2 FROM "S 1"."T 1" WHERE (($1::integer = "C 1")) + failed to get remote plan, error massage is: + there is no parameter $1 + Node 3: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c7 FROM "S 1"."T 1" WHERE (($1::integer = "C 1")) + failed to get remote plan, error massage is: + current transaction is aborted, commands ignored until end of transaction block, firstChar[Q] + +(31 rows) + +UPDATE ft2 AS target SET (c2, c7) = ( + SELECT c2 * 10, c7 + FROM ft2 AS src + WHERE target.c1 = src.c1 +) WHERE c1 > 1100; +UPDATE ft2 AS target SET (c2) = ( + SELECT c2 / 10 + FROM ft2 AS src + WHERE target.c1 = src.c1 +) WHERE c1 > 1100; +-- Test UPDATE involving a join that can be pushed down, +-- but a SET clause that can't be +EXPLAIN (VERBOSE, COSTS OFF) +UPDATE ft2 d SET c2 = CASE WHEN random() >= 0 THEN d.c2 ELSE 0 END + FROM ft2 AS t WHERE d.c1 = t.c1 AND d.c1 > 1000; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + Update on public.ft2 d + -> Nested Loop + Output: d.c1, CASE WHEN (random() >= 0::double precision) THEN d.c2 ELSE 0 END, NULL::integer, d.c3, d.c4, d.c5, d.c6, d.c7, d.c8, d.ctid, d.tableoid, t.* + Join Filter: (d.c1 = t.c1) + -> Foreign Scan on public.ft2 d + Output: d.c1, d.c2, d.c3, d.c4, d.c5, d.c6, d.c7, d.c8, d.ctid, d.tableoid + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8, ctid, tableoid FROM "S 1"."T 1" WHERE (("C 1" > 1000)) ORDER BY "C 1" ASC NULLS LAST FOR UPDATE + -> Foreign Scan on public.ft2 t + Output: t.*, t.c1 + Node ID: 2 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" > 1000)) ORDER BY "C 1" ASC NULLS LAST + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8, ctid, tableoid FROM "S 1"."T 1" WHERE (("C 1" > 1000)) ORDER BY "C 1" ASC NULLS LAST FOR UPDATE + LockRows + Output: "C 1", c2, c3, c4, c5, c6, c7, c8, ctid, tableoid, ctid, tableoid + -> Partitioned Index Scan using t1_pkey on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8, ctid, tableoid, ctid, tableoid + Index Cond: ("T 1"."C 1" > 1000) + Selected Partitions: 2 + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" > 1000)) ORDER BY "C 1" ASC NULLS LAST + Partitioned Index Scan using t1_pkey on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Index Cond: ("T 1"."C 1" > 1000) + Selected Partitions: 2 + +(27 rows) + +UPDATE ft2 d SET c2 = CASE WHEN random() >= 0 THEN d.c2 ELSE 0 END + FROM ft2 AS t WHERE d.c1 = t.c1 AND d.c1 > 1000; +-- Test UPDATE/DELETE with WHERE or JOIN/ON conditions containing +-- user-defined operators/functions +ALTER SERVER loopback OPTIONS (DROP extensions); +ERROR: option "extensions" not found +INSERT INTO ft2 (c1,c2,c3) + SELECT id, id % 10, to_char(id, 'FM00000') FROM generate_series(2001, 2010) id; +EXPLAIN (verbose, costs off) +UPDATE ft2 SET c3 = 'bar' WHERE postgres_fdw_abs(c1) > 2000 RETURNING *; -- can't be pushed down + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------- + Update on public.ft2 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + -> Foreign Scan on public.ft2 + Output: c1, c2, NULL::integer, 'bar'::text, c4, c5, c6, c7, c8, ctid, tableoid + Filter: (postgres_fdw_abs(ft2.c1) > 2000) + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c4, c5, c6, c7, c8, ctid, tableoid FROM "S 1"."T 1" FOR UPDATE + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c4, c5, c6, c7, c8, ctid, tableoid FROM "S 1"."T 1" FOR UPDATE + LockRows + Output: "C 1", c2, c4, c5, c6, c7, c8, ctid, tableoid, ctid, tableoid + -> Partition Iterator + Output: "C 1", c2, c4, c5, c6, c7, c8, ctid, tableoid, ctid, tableoid + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c4, c5, c6, c7, c8, ctid, tableoid, ctid, tableoid + Selected Partitions: 1..2 + +(19 rows) + +UPDATE ft2 SET c3 = 'bar' WHERE postgres_fdw_abs(c1) > 2000 RETURNING *; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +------+----+-----+----+----+----+------------+---- + 2001 | 1 | bar | | | | ft2 | + 2002 | 2 | bar | | | | ft2 | + 2003 | 3 | bar | | | | ft2 | + 2004 | 4 | bar | | | | ft2 | + 2005 | 5 | bar | | | | ft2 | + 2006 | 6 | bar | | | | ft2 | + 2007 | 7 | bar | | | | ft2 | + 2008 | 8 | bar | | | | ft2 | + 2009 | 9 | bar | | | | ft2 | + 2010 | 0 | bar | | | | ft2 | +(10 rows) + +EXPLAIN (verbose, costs off) +UPDATE ft2 SET c3 = 'baz' + FROM ft4 INNER JOIN ft5 ON (ft4.c1 = ft5.c1) + WHERE ft2.c1 > 2000 AND ft2.c2 === ft4.c1 + RETURNING ft2.*, ft4.*, ft5.*; -- can't be pushed down + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + Update on public.ft2 + Output: ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft4.c1, ft4.c2, ft4.c3, ft5.c1, ft5.c2, ft5.c3 + -> Hash Join + Output: ft2.c1, ft2.c2, NULL::integer, 'baz'::text, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.ctid, ft2.tableoid, ft4.*, ft5.*, ft4.c1, ft4.c2, ft4.c3, ft5.c1, ft5.c2, ft5.c3 + Hash Cond: (ft4.c1 = ft5.c1) + -> Nested Loop + Output: ft2.c1, ft2.c2, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.ctid, ft2.tableoid, ft4.*, ft4.c1, ft4.c2, ft4.c3 + Join Filter: (ft2.c2 === ft4.c1) + -> Foreign Scan on public.ft2 + Output: ft2.c1, ft2.c2, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.ctid, ft2.tableoid + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c4, c5, c6, c7, c8, ctid, tableoid FROM "S 1"."T 1" WHERE (("C 1" > 2000)) FOR UPDATE + -> Foreign Scan on public.ft4 + Output: ft4.*, ft4.c1, ft4.c2, ft4.c3 + Node ID: 2 + Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 3" + -> Hash + Output: ft5.*, ft5.c1, ft5.c2, ft5.c3 + -> Foreign Scan on public.ft5 + Output: ft5.*, ft5.c1, ft5.c2, ft5.c3 + Node ID: 3 + Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 4" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c4, c5, c6, c7, c8, ctid, tableoid FROM "S 1"."T 1" WHERE (("C 1" > 2000)) FOR UPDATE + LockRows + Output: "C 1", c2, c4, c5, c6, c7, c8, ctid, tableoid, ctid, tableoid + -> Partitioned Index Scan using t1_pkey on "S 1"."T 1" + Output: "C 1", c2, c4, c5, c6, c7, c8, ctid, tableoid, ctid, tableoid + Index Cond: ("T 1"."C 1" > 2000) + Selected Partitions: 2 + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c1, c2, c3 FROM "S 1"."T 3" + Partition Iterator + Output: c1, c2, c3 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 3" + Output: c1, c2, c3 + Selected Partitions: 1..2 + Node 3: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c1, c2, c3 FROM "S 1"."T 4" + Partition Iterator + Output: c1, c2, c3 + Iterations: 2, Sub Iterations: 4 + -> Partitioned Seq Scan on "S 1"."T 4" + Output: c1, c2, c3 + Selected Partitions: 1..2 + Selected Subpartitions: ALL + +(47 rows) + +UPDATE ft2 SET c3 = 'baz' + FROM ft4 INNER JOIN ft5 ON (ft4.c1 = ft5.c1) + WHERE ft2.c1 > 2000 AND ft2.c2 === ft4.c1 + RETURNING ft2.*, ft4.*, ft5.*; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 | c1 | c2 | c3 | c1 | c2 | c3 +------+----+-----+----+----+----+------------+----+----+----+--------+----+----+-------- + 2006 | 6 | baz | | | | ft2 | | 6 | 7 | AAA006 | 6 | 7 | AAA006 +(1 row) + +EXPLAIN (verbose, costs off) +DELETE FROM ft2 + USING ft4 INNER JOIN ft5 ON (ft4.c1 === ft5.c1) + WHERE ft2.c1 > 2000 AND ft2.c2 = ft4.c1 + RETURNING ft2.c1, ft2.c2, ft2.c3; -- can't be pushed down + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------- + Delete on public.ft2 + Output: ft2.c1, ft2.c2, ft2.c3 + -> Nested Loop + Output: ft2.ctid, ft2.tableoid, ft4.*, ft5.* + Join Filter: (ft4.c1 === ft5.c1) + -> Nested Loop + Output: ft2.ctid, ft2.tableoid, ft4.*, ft4.c1 + Join Filter: (ft2.c2 = ft4.c1) + -> Foreign Scan on public.ft2 + Output: ft2.ctid, ft2.tableoid, ft2.c2 + Node ID: 1 + Remote SQL: SELECT c2, ctid, tableoid FROM "S 1"."T 1" WHERE (("C 1" > 2000)) FOR UPDATE + -> Foreign Scan on public.ft4 + Output: ft4.*, ft4.c1 + Node ID: 2 + Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 3" + -> Foreign Scan on public.ft5 + Output: ft5.*, ft5.c1 + Node ID: 3 + Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 4" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c2, ctid, tableoid FROM "S 1"."T 1" WHERE (("C 1" > 2000)) FOR UPDATE + LockRows + Output: c2, ctid, tableoid, ctid, tableoid + -> Partitioned Index Scan using t1_pkey on "S 1"."T 1" + Output: c2, ctid, tableoid, ctid, tableoid + Index Cond: ("T 1"."C 1" > 2000) + Selected Partitions: 2 + Node 2: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c1, c2, c3 FROM "S 1"."T 3" + Partition Iterator + Output: c1, c2, c3 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 3" + Output: c1, c2, c3 + Selected Partitions: 1..2 + Node 3: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c1, c2, c3 FROM "S 1"."T 4" + Partition Iterator + Output: c1, c2, c3 + Iterations: 2, Sub Iterations: 4 + -> Partitioned Seq Scan on "S 1"."T 4" + Output: c1, c2, c3 + Selected Partitions: 1..2 + Selected Subpartitions: ALL + +(45 rows) + +DELETE FROM ft2 + USING ft4 INNER JOIN ft5 ON (ft4.c1 === ft5.c1) + WHERE ft2.c1 > 2000 AND ft2.c2 = ft4.c1 + RETURNING ft2.c1, ft2.c2, ft2.c3; + c1 | c2 | c3 +------+----+----- + 2006 | 6 | baz +(1 row) + +DELETE FROM ft2 WHERE ft2.c1 > 2000; +ALTER SERVER loopback OPTIONS (ADD extensions 'postgres_fdw'); +ERROR: invalid option "extensions" +HINT: Valid options in this context are: service, connect_timeout, dbname, host, remote_nodename, hostaddr, port, localhost, localport, application_name, fencedUdfRPCMode, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, rw_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, krbsrvname, prototype, connection_info, connectionExtraInfo, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, updatable +-- Test that trigger on remote table works as expected +CREATE OR REPLACE FUNCTION "S 1".F_BRTRIG() RETURNS trigger AS $$ +BEGIN + NEW.c3 = NEW.c3 || '_trig_update'; + RETURN NEW; +END; +$$ LANGUAGE plpgsql; +CREATE TRIGGER t1_br_insert BEFORE INSERT OR UPDATE + ON "S 1"."T 1" FOR EACH ROW EXECUTE PROCEDURE "S 1".F_BRTRIG(); +INSERT INTO ft2 (c1,c2,c3) VALUES (1208, 818, 'fff') RETURNING *; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +------+-----+-----------------+----+----+----+------------+---- + 1208 | 818 | fff_trig_update | | | | ft2 | +(1 row) + +INSERT INTO ft2 (c1,c2,c3,c6) VALUES (1218, 818, 'ggg', '(--;') RETURNING *; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +------+-----+-----------------+----+----+------+------------+---- + 1218 | 818 | ggg_trig_update | | | (--; | ft2 | +(1 row) + +UPDATE ft2 SET c2 = c2 + 600 WHERE c1 % 10 = 8 AND c1 < 1200 RETURNING *; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +------+-----+------------------------+------------------------------+--------------------------+----+------------+----- + 8 | 608 | 00008_trig_update | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8 | 8 | foo + 18 | 608 | 00018_trig_update | Mon Jan 19 00:00:00 1970 PST | Mon Jan 19 00:00:00 1970 | 8 | 8 | foo + 28 | 608 | 00028_trig_update | Thu Jan 29 00:00:00 1970 PST | Thu Jan 29 00:00:00 1970 | 8 | 8 | foo + 38 | 608 | 00038_trig_update | Sun Feb 08 00:00:00 1970 PST | Sun Feb 08 00:00:00 1970 | 8 | 8 | foo + 48 | 608 | 00048_trig_update | Wed Feb 18 00:00:00 1970 PST | Wed Feb 18 00:00:00 1970 | 8 | 8 | foo + 58 | 608 | 00058_trig_update | Sat Feb 28 00:00:00 1970 PST | Sat Feb 28 00:00:00 1970 | 8 | 8 | foo + 68 | 608 | 00068_trig_update | Tue Mar 10 00:00:00 1970 PST | Tue Mar 10 00:00:00 1970 | 8 | 8 | foo + 78 | 608 | 00078_trig_update | Fri Mar 20 00:00:00 1970 PST | Fri Mar 20 00:00:00 1970 | 8 | 8 | foo + 88 | 608 | 00088_trig_update | Mon Mar 30 00:00:00 1970 PST | Mon Mar 30 00:00:00 1970 | 8 | 8 | foo + 98 | 608 | 00098_trig_update | Thu Apr 09 00:00:00 1970 PST | Thu Apr 09 00:00:00 1970 | 8 | 8 | foo + 108 | 608 | 00108_trig_update | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8 | 8 | foo + 118 | 608 | 00118_trig_update | Mon Jan 19 00:00:00 1970 PST | Mon Jan 19 00:00:00 1970 | 8 | 8 | foo + 128 | 608 | 00128_trig_update | Thu Jan 29 00:00:00 1970 PST | Thu Jan 29 00:00:00 1970 | 8 | 8 | foo + 138 | 608 | 00138_trig_update | Sun Feb 08 00:00:00 1970 PST | Sun Feb 08 00:00:00 1970 | 8 | 8 | foo + 148 | 608 | 00148_trig_update | Wed Feb 18 00:00:00 1970 PST | Wed Feb 18 00:00:00 1970 | 8 | 8 | foo + 158 | 608 | 00158_trig_update | Sat Feb 28 00:00:00 1970 PST | Sat Feb 28 00:00:00 1970 | 8 | 8 | foo + 168 | 608 | 00168_trig_update | Tue Mar 10 00:00:00 1970 PST | Tue Mar 10 00:00:00 1970 | 8 | 8 | foo + 178 | 608 | 00178_trig_update | Fri Mar 20 00:00:00 1970 PST | Fri Mar 20 00:00:00 1970 | 8 | 8 | foo + 188 | 608 | 00188_trig_update | Mon Mar 30 00:00:00 1970 PST | Mon Mar 30 00:00:00 1970 | 8 | 8 | foo + 198 | 608 | 00198_trig_update | Thu Apr 09 00:00:00 1970 PST | Thu Apr 09 00:00:00 1970 | 8 | 8 | foo + 208 | 608 | 00208_trig_update | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8 | 8 | foo + 218 | 608 | 00218_trig_update | Mon Jan 19 00:00:00 1970 PST | Mon Jan 19 00:00:00 1970 | 8 | 8 | foo + 228 | 608 | 00228_trig_update | Thu Jan 29 00:00:00 1970 PST | Thu Jan 29 00:00:00 1970 | 8 | 8 | foo + 238 | 608 | 00238_trig_update | Sun Feb 08 00:00:00 1970 PST | Sun Feb 08 00:00:00 1970 | 8 | 8 | foo + 248 | 608 | 00248_trig_update | Wed Feb 18 00:00:00 1970 PST | Wed Feb 18 00:00:00 1970 | 8 | 8 | foo + 258 | 608 | 00258_trig_update | Sat Feb 28 00:00:00 1970 PST | Sat Feb 28 00:00:00 1970 | 8 | 8 | foo + 268 | 608 | 00268_trig_update | Tue Mar 10 00:00:00 1970 PST | Tue Mar 10 00:00:00 1970 | 8 | 8 | foo + 278 | 608 | 00278_trig_update | Fri Mar 20 00:00:00 1970 PST | Fri Mar 20 00:00:00 1970 | 8 | 8 | foo + 288 | 608 | 00288_trig_update | Mon Mar 30 00:00:00 1970 PST | Mon Mar 30 00:00:00 1970 | 8 | 8 | foo + 298 | 608 | 00298_trig_update | Thu Apr 09 00:00:00 1970 PST | Thu Apr 09 00:00:00 1970 | 8 | 8 | foo + 308 | 608 | 00308_trig_update | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8 | 8 | foo + 318 | 608 | 00318_trig_update | Mon Jan 19 00:00:00 1970 PST | Mon Jan 19 00:00:00 1970 | 8 | 8 | foo + 328 | 608 | 00328_trig_update | Thu Jan 29 00:00:00 1970 PST | Thu Jan 29 00:00:00 1970 | 8 | 8 | foo + 338 | 608 | 00338_trig_update | Sun Feb 08 00:00:00 1970 PST | Sun Feb 08 00:00:00 1970 | 8 | 8 | foo + 348 | 608 | 00348_trig_update | Wed Feb 18 00:00:00 1970 PST | Wed Feb 18 00:00:00 1970 | 8 | 8 | foo + 358 | 608 | 00358_trig_update | Sat Feb 28 00:00:00 1970 PST | Sat Feb 28 00:00:00 1970 | 8 | 8 | foo + 368 | 608 | 00368_trig_update | Tue Mar 10 00:00:00 1970 PST | Tue Mar 10 00:00:00 1970 | 8 | 8 | foo + 378 | 608 | 00378_trig_update | Fri Mar 20 00:00:00 1970 PST | Fri Mar 20 00:00:00 1970 | 8 | 8 | foo + 388 | 608 | 00388_trig_update | Mon Mar 30 00:00:00 1970 PST | Mon Mar 30 00:00:00 1970 | 8 | 8 | foo + 398 | 608 | 00398_trig_update | Thu Apr 09 00:00:00 1970 PST | Thu Apr 09 00:00:00 1970 | 8 | 8 | foo + 408 | 608 | 00408_trig_update | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8 | 8 | foo + 418 | 608 | 00418_trig_update | Mon Jan 19 00:00:00 1970 PST | Mon Jan 19 00:00:00 1970 | 8 | 8 | foo + 428 | 608 | 00428_trig_update | Thu Jan 29 00:00:00 1970 PST | Thu Jan 29 00:00:00 1970 | 8 | 8 | foo + 438 | 608 | 00438_trig_update | Sun Feb 08 00:00:00 1970 PST | Sun Feb 08 00:00:00 1970 | 8 | 8 | foo + 448 | 608 | 00448_trig_update | Wed Feb 18 00:00:00 1970 PST | Wed Feb 18 00:00:00 1970 | 8 | 8 | foo + 458 | 608 | 00458_trig_update | Sat Feb 28 00:00:00 1970 PST | Sat Feb 28 00:00:00 1970 | 8 | 8 | foo + 468 | 608 | 00468_trig_update | Tue Mar 10 00:00:00 1970 PST | Tue Mar 10 00:00:00 1970 | 8 | 8 | foo + 478 | 608 | 00478_trig_update | Fri Mar 20 00:00:00 1970 PST | Fri Mar 20 00:00:00 1970 | 8 | 8 | foo + 488 | 608 | 00488_trig_update | Mon Mar 30 00:00:00 1970 PST | Mon Mar 30 00:00:00 1970 | 8 | 8 | foo + 498 | 608 | 00498_trig_update | Thu Apr 09 00:00:00 1970 PST | Thu Apr 09 00:00:00 1970 | 8 | 8 | foo + 508 | 608 | 00508_trig_update | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8 | 8 | foo + 518 | 608 | 00518_trig_update | Mon Jan 19 00:00:00 1970 PST | Mon Jan 19 00:00:00 1970 | 8 | 8 | foo + 528 | 608 | 00528_trig_update | Thu Jan 29 00:00:00 1970 PST | Thu Jan 29 00:00:00 1970 | 8 | 8 | foo + 538 | 608 | 00538_trig_update | Sun Feb 08 00:00:00 1970 PST | Sun Feb 08 00:00:00 1970 | 8 | 8 | foo + 548 | 608 | 00548_trig_update | Wed Feb 18 00:00:00 1970 PST | Wed Feb 18 00:00:00 1970 | 8 | 8 | foo + 558 | 608 | 00558_trig_update | Sat Feb 28 00:00:00 1970 PST | Sat Feb 28 00:00:00 1970 | 8 | 8 | foo + 568 | 608 | 00568_trig_update | Tue Mar 10 00:00:00 1970 PST | Tue Mar 10 00:00:00 1970 | 8 | 8 | foo + 578 | 608 | 00578_trig_update | Fri Mar 20 00:00:00 1970 PST | Fri Mar 20 00:00:00 1970 | 8 | 8 | foo + 588 | 608 | 00588_trig_update | Mon Mar 30 00:00:00 1970 PST | Mon Mar 30 00:00:00 1970 | 8 | 8 | foo + 598 | 608 | 00598_trig_update | Thu Apr 09 00:00:00 1970 PST | Thu Apr 09 00:00:00 1970 | 8 | 8 | foo + 608 | 608 | 00608_trig_update | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8 | 8 | foo + 618 | 608 | 00618_trig_update | Mon Jan 19 00:00:00 1970 PST | Mon Jan 19 00:00:00 1970 | 8 | 8 | foo + 628 | 608 | 00628_trig_update | Thu Jan 29 00:00:00 1970 PST | Thu Jan 29 00:00:00 1970 | 8 | 8 | foo + 638 | 608 | 00638_trig_update | Sun Feb 08 00:00:00 1970 PST | Sun Feb 08 00:00:00 1970 | 8 | 8 | foo + 648 | 608 | 00648_trig_update | Wed Feb 18 00:00:00 1970 PST | Wed Feb 18 00:00:00 1970 | 8 | 8 | foo + 658 | 608 | 00658_trig_update | Sat Feb 28 00:00:00 1970 PST | Sat Feb 28 00:00:00 1970 | 8 | 8 | foo + 668 | 608 | 00668_trig_update | Tue Mar 10 00:00:00 1970 PST | Tue Mar 10 00:00:00 1970 | 8 | 8 | foo + 678 | 608 | 00678_trig_update | Fri Mar 20 00:00:00 1970 PST | Fri Mar 20 00:00:00 1970 | 8 | 8 | foo + 688 | 608 | 00688_trig_update | Mon Mar 30 00:00:00 1970 PST | Mon Mar 30 00:00:00 1970 | 8 | 8 | foo + 698 | 608 | 00698_trig_update | Thu Apr 09 00:00:00 1970 PST | Thu Apr 09 00:00:00 1970 | 8 | 8 | foo + 708 | 608 | 00708_trig_update | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8 | 8 | foo + 718 | 608 | 00718_trig_update | Mon Jan 19 00:00:00 1970 PST | Mon Jan 19 00:00:00 1970 | 8 | 8 | foo + 728 | 608 | 00728_trig_update | Thu Jan 29 00:00:00 1970 PST | Thu Jan 29 00:00:00 1970 | 8 | 8 | foo + 738 | 608 | 00738_trig_update | Sun Feb 08 00:00:00 1970 PST | Sun Feb 08 00:00:00 1970 | 8 | 8 | foo + 748 | 608 | 00748_trig_update | Wed Feb 18 00:00:00 1970 PST | Wed Feb 18 00:00:00 1970 | 8 | 8 | foo + 758 | 608 | 00758_trig_update | Sat Feb 28 00:00:00 1970 PST | Sat Feb 28 00:00:00 1970 | 8 | 8 | foo + 768 | 608 | 00768_trig_update | Tue Mar 10 00:00:00 1970 PST | Tue Mar 10 00:00:00 1970 | 8 | 8 | foo + 778 | 608 | 00778_trig_update | Fri Mar 20 00:00:00 1970 PST | Fri Mar 20 00:00:00 1970 | 8 | 8 | foo + 788 | 608 | 00788_trig_update | Mon Mar 30 00:00:00 1970 PST | Mon Mar 30 00:00:00 1970 | 8 | 8 | foo + 798 | 608 | 00798_trig_update | Thu Apr 09 00:00:00 1970 PST | Thu Apr 09 00:00:00 1970 | 8 | 8 | foo + 808 | 608 | 00808_trig_update | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8 | 8 | foo + 818 | 608 | 00818_trig_update | Mon Jan 19 00:00:00 1970 PST | Mon Jan 19 00:00:00 1970 | 8 | 8 | foo + 828 | 608 | 00828_trig_update | Thu Jan 29 00:00:00 1970 PST | Thu Jan 29 00:00:00 1970 | 8 | 8 | foo + 838 | 608 | 00838_trig_update | Sun Feb 08 00:00:00 1970 PST | Sun Feb 08 00:00:00 1970 | 8 | 8 | foo + 848 | 608 | 00848_trig_update | Wed Feb 18 00:00:00 1970 PST | Wed Feb 18 00:00:00 1970 | 8 | 8 | foo + 858 | 608 | 00858_trig_update | Sat Feb 28 00:00:00 1970 PST | Sat Feb 28 00:00:00 1970 | 8 | 8 | foo + 868 | 608 | 00868_trig_update | Tue Mar 10 00:00:00 1970 PST | Tue Mar 10 00:00:00 1970 | 8 | 8 | foo + 878 | 608 | 00878_trig_update | Fri Mar 20 00:00:00 1970 PST | Fri Mar 20 00:00:00 1970 | 8 | 8 | foo + 888 | 608 | 00888_trig_update | Mon Mar 30 00:00:00 1970 PST | Mon Mar 30 00:00:00 1970 | 8 | 8 | foo + 898 | 608 | 00898_trig_update | Thu Apr 09 00:00:00 1970 PST | Thu Apr 09 00:00:00 1970 | 8 | 8 | foo + 908 | 608 | 00908_trig_update | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8 | 8 | foo + 918 | 608 | 00918_trig_update | Mon Jan 19 00:00:00 1970 PST | Mon Jan 19 00:00:00 1970 | 8 | 8 | foo + 928 | 608 | 00928_trig_update | Thu Jan 29 00:00:00 1970 PST | Thu Jan 29 00:00:00 1970 | 8 | 8 | foo + 938 | 608 | 00938_trig_update | Sun Feb 08 00:00:00 1970 PST | Sun Feb 08 00:00:00 1970 | 8 | 8 | foo + 948 | 608 | 00948_trig_update | Wed Feb 18 00:00:00 1970 PST | Wed Feb 18 00:00:00 1970 | 8 | 8 | foo + 958 | 608 | 00958_trig_update | Sat Feb 28 00:00:00 1970 PST | Sat Feb 28 00:00:00 1970 | 8 | 8 | foo + 968 | 608 | 00968_trig_update | Tue Mar 10 00:00:00 1970 PST | Tue Mar 10 00:00:00 1970 | 8 | 8 | foo + 978 | 608 | 00978_trig_update | Fri Mar 20 00:00:00 1970 PST | Fri Mar 20 00:00:00 1970 | 8 | 8 | foo + 988 | 608 | 00988_trig_update | Mon Mar 30 00:00:00 1970 PST | Mon Mar 30 00:00:00 1970 | 8 | 8 | foo + 998 | 608 | 00998_trig_update | Thu Apr 09 00:00:00 1970 PST | Thu Apr 09 00:00:00 1970 | 8 | 8 | foo + 1008 | 708 | 0000800008_trig_update | | | | ft2 | + 1018 | 708 | 0001800018_trig_update | | | | ft2 | +(102 rows) + +-- Test errors thrown on remote side during update +ALTER TABLE "S 1"."T 1" ADD CONSTRAINT c2positive CHECK (c2 >= 0); +INSERT INTO ft1(c1, c2) VALUES(11, 12); -- duplicate key +ERROR: duplicate key value violates unique constraint "t1_pkey" +DETAIL: Key ("C 1")=(11) already exists. +CONTEXT: Remote SQL command: INSERT INTO "S 1"."T 1"("C 1", c2, c3, c4, c5, c6, c7, c8) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) +INSERT INTO ft1(c1, c2) VALUES(11, 12) ON DUPLICATE KEY UPDATE NOTHING; -- works +ERROR: INSERT ON DUPLICATE KEY UPDATE is not supported on foreign table. +INSERT INTO ft1(c1, c2) VALUES(11, 12) ON DUPLICATE KEY UPDATE c3 = 'ffg'; -- unsupported +ERROR: INSERT ON DUPLICATE KEY UPDATE is not supported on foreign table. +INSERT INTO ft1(c1, c2) VALUES(1111, -2); -- c2positive +ERROR: new row for relation "T 1" violates check constraint "c2positive" +DETAIL: N/A +CONTEXT: Remote SQL command: INSERT INTO "S 1"."T 1"("C 1", c2, c3, c4, c5, c6, c7, c8) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) +UPDATE ft1 SET c2 = -c2 WHERE c1 = 1; -- c2positive +ERROR: new row for relation "T 1" violates check constraint "c2positive" +DETAIL: N/A +CONTEXT: Remote SQL command: UPDATE "S 1"."T 1" SET c2 = $3 WHERE ctid = $1 and tableoid = $2 +-- Test savepoint/rollback behavior +select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; + c2 | count +-----+------- + 0 | 100 + 1 | 100 + 4 | 100 + 6 | 100 + 100 | 2 + 101 | 2 + 104 | 2 + 106 | 2 + 201 | 1 + 204 | 1 + 303 | 100 + 403 | 2 + 407 | 100 +(13 rows) + +select c2, count(*) from "S 1"."T 1" where c2 < 500 group by 1 order by 1; + c2 | count +-----+------- + 0 | 100 + 1 | 100 + 4 | 100 + 6 | 100 + 100 | 2 + 101 | 2 + 104 | 2 + 106 | 2 + 201 | 1 + 204 | 1 + 303 | 100 + 403 | 2 + 407 | 100 +(13 rows) + +begin; +update ft2 set c2 = 42 where c2 = 0; +select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; + c2 | count +-----+------- + 1 | 100 + 4 | 100 + 6 | 100 + 42 | 100 + 100 | 2 + 101 | 2 + 104 | 2 + 106 | 2 + 201 | 1 + 204 | 1 + 303 | 100 + 403 | 2 + 407 | 100 +(13 rows) + +savepoint s1; +update ft2 set c2 = 44 where c2 = 4; +select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; + c2 | count +-----+------- + 1 | 100 + 6 | 100 + 42 | 100 + 44 | 100 + 100 | 2 + 101 | 2 + 104 | 2 + 106 | 2 + 201 | 1 + 204 | 1 + 303 | 100 + 403 | 2 + 407 | 100 +(13 rows) + +release savepoint s1; +select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; + c2 | count +-----+------- + 1 | 100 + 6 | 100 + 42 | 100 + 44 | 100 + 100 | 2 + 101 | 2 + 104 | 2 + 106 | 2 + 201 | 1 + 204 | 1 + 303 | 100 + 403 | 2 + 407 | 100 +(13 rows) + +savepoint s2; +update ft2 set c2 = 46 where c2 = 6; +select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; + c2 | count +-----+------- + 1 | 100 + 42 | 100 + 44 | 100 + 46 | 100 + 100 | 2 + 101 | 2 + 104 | 2 + 106 | 2 + 201 | 1 + 204 | 1 + 303 | 100 + 403 | 2 + 407 | 100 +(13 rows) + +rollback to savepoint s2; +select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; + c2 | count +-----+------- + 1 | 100 + 6 | 100 + 42 | 100 + 44 | 100 + 100 | 2 + 101 | 2 + 104 | 2 + 106 | 2 + 201 | 1 + 204 | 1 + 303 | 100 + 403 | 2 + 407 | 100 +(13 rows) + +release savepoint s2; +select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; + c2 | count +-----+------- + 1 | 100 + 6 | 100 + 42 | 100 + 44 | 100 + 100 | 2 + 101 | 2 + 104 | 2 + 106 | 2 + 201 | 1 + 204 | 1 + 303 | 100 + 403 | 2 + 407 | 100 +(13 rows) + +savepoint s3; +update ft2 set c2 = -2 where c2 = 42 and c1 = 10; -- fail on remote side +ERROR: new row for relation "T 1" violates check constraint "c2positive" +DETAIL: N/A +CONTEXT: Remote SQL command: UPDATE "S 1"."T 1" SET c2 = $3 WHERE ctid = $1 and tableoid = $2 +rollback to savepoint s3; +select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; + c2 | count +-----+------- + 1 | 100 + 6 | 100 + 42 | 100 + 44 | 100 + 100 | 2 + 101 | 2 + 104 | 2 + 106 | 2 + 201 | 1 + 204 | 1 + 303 | 100 + 403 | 2 + 407 | 100 +(13 rows) + +release savepoint s3; +select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; + c2 | count +-----+------- + 1 | 100 + 6 | 100 + 42 | 100 + 44 | 100 + 100 | 2 + 101 | 2 + 104 | 2 + 106 | 2 + 201 | 1 + 204 | 1 + 303 | 100 + 403 | 2 + 407 | 100 +(13 rows) + +-- none of the above is committed yet remotely +select c2, count(*) from "S 1"."T 1" where c2 < 500 group by 1 order by 1; + c2 | count +-----+------- + 0 | 100 + 1 | 100 + 4 | 100 + 6 | 100 + 100 | 2 + 101 | 2 + 104 | 2 + 106 | 2 + 201 | 1 + 204 | 1 + 303 | 100 + 403 | 2 + 407 | 100 +(13 rows) + +commit; +select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; + c2 | count +-----+------- + 1 | 100 + 6 | 100 + 42 | 100 + 44 | 100 + 100 | 2 + 101 | 2 + 104 | 2 + 106 | 2 + 201 | 1 + 204 | 1 + 303 | 100 + 403 | 2 + 407 | 100 +(13 rows) + +select c2, count(*) from "S 1"."T 1" where c2 < 500 group by 1 order by 1; + c2 | count +-----+------- + 1 | 100 + 6 | 100 + 42 | 100 + 44 | 100 + 100 | 2 + 101 | 2 + 104 | 2 + 106 | 2 + 201 | 1 + 204 | 1 + 303 | 100 + 403 | 2 + 407 | 100 +(13 rows) + +VACUUM ANALYZE "S 1"."T 1"; +-- Above DMLs add data with c6 as NULL in ft1, so test ORDER BY NULLS LAST and NULLs +-- FIRST behavior here. +-- ORDER BY DESC NULLS LAST options +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 ORDER BY c6 DESC NULLS LAST, c1 OFFSET 795 LIMIT 10; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + Foreign Scan on public.ft1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" ORDER BY c6::text DESC NULLS LAST, "C 1" ASC NULLS LAST LIMIT 10::bigint OFFSET 795::bigint + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" ORDER BY c6::text DESC NULLS LAST, "C 1" ASC NULLS LAST LIMIT 10::bigint OFFSET 795::bigint + Limit + Output: "C 1", c2, c3, c4, c5, c6, c7, c8, ((c6)::text) + -> Sort + Output: "C 1", c2, c3, c4, c5, c6, c7, c8, ((c6)::text) + Sort Key: "T 1".c6 DESC NULLS LAST, "T 1"."C 1" + -> Partition Iterator + Output: "C 1", c2, c3, c4, c5, c6, c7, c8, (c6)::text + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8, (c6)::text + Selected Partitions: 1..2 + +(19 rows) + +SELECT * FROM ft1 ORDER BY c6 DESC NULLS LAST, c1 OFFSET 795 LIMIT 10; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +------+-----+--------------------+------------------------------+--------------------------+------+------------+----- + 960 | 42 | 00960_trig_update | Mon Mar 02 00:00:00 1970 PST | Mon Mar 02 00:00:00 1970 | 0 | 0 | foo + 970 | 42 | 00970_trig_update | Thu Mar 12 00:00:00 1970 PST | Thu Mar 12 00:00:00 1970 | 0 | 0 | foo + 980 | 42 | 00980_trig_update | Sun Mar 22 00:00:00 1970 PST | Sun Mar 22 00:00:00 1970 | 0 | 0 | foo + 990 | 42 | 00990_trig_update | Wed Apr 01 00:00:00 1970 PST | Wed Apr 01 00:00:00 1970 | 0 | 0 | foo + 1000 | 42 | 01000_trig_update | Thu Jan 01 00:00:00 1970 PST | Thu Jan 01 00:00:00 1970 | 0 | 0 | foo + 1218 | 818 | ggg_trig_update | | | (--; | ft2 | + 1001 | 101 | 0000100001 | | | | ft2 | + 1003 | 403 | 0000300003_update3 | | | | ft2 | + 1004 | 104 | 0000400004 | | | | ft2 | + 1006 | 106 | 0000600006 | | | | ft2 | +(10 rows) + +-- ORDER BY DESC NULLS FIRST options +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 ORDER BY c6 DESC NULLS FIRST, c1 OFFSET 15 LIMIT 10; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + Foreign Scan on public.ft1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" ORDER BY c6::text DESC NULLS FIRST, "C 1" ASC NULLS LAST LIMIT 10::bigint OFFSET 15::bigint + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" ORDER BY c6::text DESC NULLS FIRST, "C 1" ASC NULLS LAST LIMIT 10::bigint OFFSET 15::bigint + Limit + Output: "C 1", c2, c3, c4, c5, c6, c7, c8, ((c6)::text) + -> Sort + Output: "C 1", c2, c3, c4, c5, c6, c7, c8, ((c6)::text) + Sort Key: "T 1".c6 DESC, "T 1"."C 1" + -> Partition Iterator + Output: "C 1", c2, c3, c4, c5, c6, c7, c8, (c6)::text + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8, (c6)::text + Selected Partitions: 1..2 + +(19 rows) + +SELECT * FROM ft1 ORDER BY c6 DESC NULLS FIRST, c1 OFFSET 15 LIMIT 10; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +------+-----+-----------------+------------------------------+--------------------------+----+------------+----- + 1020 | 100 | 0002000020 | | | | ft2 | + 1101 | 201 | aaa | | | | ft2 | + 1103 | 503 | ccc_update3 | | | | ft2 | + 1104 | 204 | ddd | | | | ft2 | + 1208 | 818 | fff_trig_update | | | | ft2 | + 9 | 509 | 00009_update9 | Sat Jan 10 00:00:00 1970 PST | Sat Jan 10 00:00:00 1970 | 9 | ft2 | foo + 19 | 509 | 00019_update9 | Tue Jan 20 00:00:00 1970 PST | Tue Jan 20 00:00:00 1970 | 9 | ft2 | foo + 29 | 509 | 00029_update9 | Fri Jan 30 00:00:00 1970 PST | Fri Jan 30 00:00:00 1970 | 9 | ft2 | foo + 39 | 509 | 00039_update9 | Mon Feb 09 00:00:00 1970 PST | Mon Feb 09 00:00:00 1970 | 9 | ft2 | foo + 49 | 509 | 00049_update9 | Thu Feb 19 00:00:00 1970 PST | Thu Feb 19 00:00:00 1970 | 9 | ft2 | foo +(10 rows) + +-- ORDER BY ASC NULLS FIRST options +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 ORDER BY c6 ASC NULLS FIRST, c1 OFFSET 15 LIMIT 10; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Node ID: 1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" ORDER BY c6::text ASC NULLS FIRST, "C 1" ASC NULLS LAST LIMIT 10::bigint OFFSET 15::bigint + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" ORDER BY c6::text ASC NULLS FIRST, "C 1" ASC NULLS LAST LIMIT 10::bigint OFFSET 15::bigint + Limit + Output: "C 1", c2, c3, c4, c5, c6, c7, c8, ((c6)::text) + -> Sort + Output: "C 1", c2, c3, c4, c5, c6, c7, c8, ((c6)::text) + Sort Key: "T 1".c6 NULLS FIRST, "T 1"."C 1" + -> Partition Iterator + Output: "C 1", c2, c3, c4, c5, c6, c7, c8, (c6)::text + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8, (c6)::text + Selected Partitions: 1..2 + +(19 rows) + +SELECT * FROM ft1 ORDER BY c6 ASC NULLS FIRST, c1 OFFSET 15 LIMIT 10; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +------+-----+-------------------+------------------------------+--------------------------+------+------------+----- + 1020 | 100 | 0002000020 | | | | ft2 | + 1101 | 201 | aaa | | | | ft2 | + 1103 | 503 | ccc_update3 | | | | ft2 | + 1104 | 204 | ddd | | | | ft2 | + 1208 | 818 | fff_trig_update | | | | ft2 | + 1218 | 818 | ggg_trig_update | | | (--; | ft2 | + 10 | 42 | 00010_trig_update | Sun Jan 11 00:00:00 1970 PST | Sun Jan 11 00:00:00 1970 | 0 | 0 | foo + 20 | 42 | 00020_trig_update | Wed Jan 21 00:00:00 1970 PST | Wed Jan 21 00:00:00 1970 | 0 | 0 | foo + 30 | 42 | 00030_trig_update | Sat Jan 31 00:00:00 1970 PST | Sat Jan 31 00:00:00 1970 | 0 | 0 | foo + 40 | 42 | 00040_trig_update | Tue Feb 10 00:00:00 1970 PST | Tue Feb 10 00:00:00 1970 | 0 | 0 | foo +(10 rows) + +-- ====================================================================================================================================== +-- TEST-MODULE: check constraints +-- -------------------------------------- +-- openGauss not support to "ALTER FOREIGN TABLE ft1 ADD CONSTRAINT", so this test module +-- is unuseful. +-- ====================================================================================================================================== +-- Consistent check constraints provide consistent results +ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c2positive CHECK (c2 >= 0); +ERROR: "ft1" is not a table +EXPLAIN (VERBOSE, COSTS OFF) SELECT count(*) FROM ft1 WHERE c2 < 0; + QUERY PLAN +---------------------------------------------------------------------------------------- + Aggregate + Output: count(*) + -> Foreign Scan on public.ft1 + Node ID: 1 + Remote SQL: SELECT NULL FROM "S 1"."T 1" WHERE ((c2 < 0)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT NULL FROM "S 1"."T 1" WHERE ((c2 < 0)) + Partition Iterator + Output: NULL::text + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: NULL::text + Filter: ("T 1".c2 < 0) + Selected Partitions: 1..2 + +(16 rows) + +SELECT count(*) FROM ft1 WHERE c2 < 0; + count +------- + 0 +(1 row) + +SET constraint_exclusion = 'on'; +EXPLAIN (VERBOSE, COSTS OFF) SELECT count(*) FROM ft1 WHERE c2 < 0; + QUERY PLAN +---------------------------------------------------------------------------------------- + Aggregate + Output: count(*) + -> Foreign Scan on public.ft1 + Node ID: 1 + Remote SQL: SELECT NULL FROM "S 1"."T 1" WHERE ((c2 < 0)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT NULL FROM "S 1"."T 1" WHERE ((c2 < 0)) + Partition Iterator + Output: NULL::text + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: NULL::text + Filter: ("T 1".c2 < 0) + Selected Partitions: 1..2 + +(16 rows) + +SELECT count(*) FROM ft1 WHERE c2 < 0; + count +------- + 0 +(1 row) + +RESET constraint_exclusion; +-- check constraint is enforced on the remote side, not locally +INSERT INTO ft1(c1, c2) VALUES(1111, -2); -- c2positive +ERROR: new row for relation "T 1" violates check constraint "c2positive" +DETAIL: N/A +CONTEXT: Remote SQL command: INSERT INTO "S 1"."T 1"("C 1", c2, c3, c4, c5, c6, c7, c8) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) +UPDATE ft1 SET c2 = -c2 WHERE c1 = 1; -- c2positive +ERROR: new row for relation "T 1" violates check constraint "c2positive" +DETAIL: N/A +CONTEXT: Remote SQL command: UPDATE "S 1"."T 1" SET c2 = $3 WHERE ctid = $1 and tableoid = $2 +ALTER FOREIGN TABLE ft1 DROP CONSTRAINT ft1_c2positive; +ERROR: constraint "ft1_c2positive" of relation "ft1" does not exist +-- But inconsistent check constraints provide inconsistent results +ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c2negative CHECK (c2 < 0); +ERROR: "ft1" is not a table +EXPLAIN (VERBOSE, COSTS OFF) SELECT count(*) FROM ft1 WHERE c2 >= 0; + QUERY PLAN +--------------------------------------------------------------------------------------------- + Foreign Scan + Output: (count(*)) + Node ID: 1 + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT count(*) FROM "S 1"."T 1" WHERE ((c2 >= 0)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT count(*) FROM "S 1"."T 1" WHERE ((c2 >= 0)) + Aggregate + Output: count(*) + -> Partition Iterator + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Filter: ("T 1".c2 >= 0) + Selected Partitions: 1..2 + +(18 rows) + +SELECT count(*) FROM ft1 WHERE c2 >= 0; + count +------- + 821 +(1 row) + +SET constraint_exclusion = 'on'; +EXPLAIN (VERBOSE, COSTS OFF) SELECT count(*) FROM ft1 WHERE c2 >= 0; + QUERY PLAN +--------------------------------------------------------------------------------------------- + Foreign Scan + Output: (count(*)) + Node ID: 1 + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT count(*) FROM "S 1"."T 1" WHERE ((c2 >= 0)) + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT count(*) FROM "S 1"."T 1" WHERE ((c2 >= 0)) + Aggregate + Output: count(*) + -> Partition Iterator + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Iterations: 2 + -> Partitioned Seq Scan on "S 1"."T 1" + Output: "C 1", c2, c3, c4, c5, c6, c7, c8 + Filter: ("T 1".c2 >= 0) + Selected Partitions: 1..2 + +(18 rows) + +SELECT count(*) FROM ft1 WHERE c2 >= 0; + count +------- + 821 +(1 row) + +RESET constraint_exclusion; +-- local check constraint is not actually enforced +INSERT INTO ft1(c1, c2) VALUES(1111, 2); +UPDATE ft1 SET c2 = c2 + 1 WHERE c1 = 1; +ALTER FOREIGN TABLE ft1 DROP CONSTRAINT ft1_c2negative; +ERROR: constraint "ft1_c2negative" of relation "ft1" does not exist +-- ====================================================================================================================================== +-- TEST-MODULE: Incorrect Usage +-- -------------------------------------- +-- ====================================================================================================================================== +CREATE FOREIGN TABLE ftx ( + c1 int NOT NULL, + c2 int NOT NULL, + c3 text +) SERVER loopback OPTIONS (schema_name 'S 1', table_name 'T 4'); +EXPLAIN (VERBOSE, COSTS OFF)select * from ftx; + QUERY PLAN +----------------------------------------------------------------------------- + Foreign Scan on public.ftx + Output: c1, c2, c3 + Node ID: 1 + Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 4" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c1, c2, c3 FROM "S 1"."T 4" + Partition Iterator + Output: c1, c2, c3 + Iterations: 2, Sub Iterations: 4 + -> Partitioned Seq Scan on "S 1"."T 4" + Output: c1, c2, c3 + Selected Partitions: 1..2 + Selected Subpartitions: ALL + +(15 rows) + +select * from ftx; + c1 | c2 | c3 +----+-----+-------- + 12 | 13 | AAA012 + 39 | 40 | AAA039 + 48 | 49 | AAA048 + 18 | 19 | + 27 | 28 | + 45 | 46 | + 3 | 4 | AAA003 + 6 | 7 | AAA006 + 15 | 16 | AAA015 + 21 | 22 | AAA021 + 24 | 25 | AAA024 + 30 | 31 | AAA030 + 33 | 34 | AAA033 + 42 | 43 | AAA042 + 9 | 10 | + 36 | 37 | + 51 | 52 | AAA051 + 66 | 67 | AAA066 + 69 | 70 | AAA069 + 75 | 76 | AAA075 + 84 | 85 | AAA084 + 63 | 64 | + 57 | 58 | AAA057 + 60 | 61 | AAA060 + 78 | 79 | AAA078 + 87 | 88 | AAA087 + 93 | 94 | AAA093 + 96 | 97 | AAA096 + 54 | 55 | + 72 | 73 | + 81 | 82 | + 90 | 91 | + 99 | 100 | +(33 rows) + +EXPLAIN (VERBOSE, COSTS OFF)select * from ftx partition(ptb1); --ERR +ERROR: relation "ftx" is not partitioned table +DETAIL: N/A. +select * from ftx partition(ptb1); +ERROR: relation "ftx" is not partitioned table +DETAIL: N/A. +EXPLAIN (VERBOSE, COSTS OFF)select * from ftx partition(ptb1) subpartition(ptb11); --ERR +ERROR: relation "ftx" is not partitioned table +DETAIL: N/A. +select * from ftx partition(ptb1) subpartition(ptb11); +ERROR: relation "ftx" is not partitioned table +DETAIL: N/A. +DROP FOREIGN TABLE ftx; +CREATE FOREIGN TABLE ftx ( + c1 int NOT NULL, + c2 int NOT NULL, + c3 text +) SERVER loopback OPTIONS (schema_name 'S 1', table_name 'T 3 partition(ptb1)'); +--ERR +EXPLAIN (VERBOSE, COSTS OFF)select * from ftx; + QUERY PLAN +--------------------------------------------------------------------------------------------- + Foreign Scan on public.ftx + Output: c1, c2, c3 + Node ID: 1 + Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 3 partition(ptb1)" + + FDW remote plans: + Node 1: EXPLAIN (VERBOSE ON, COSTS OFF) SELECT c1, c2, c3 FROM "S 1"."T 3 partition(ptb1)" + failed to get remote plan, error massage is: + relation "S 1.T 3 partition(ptb1)" does not exist on datanode1 + +(10 rows) + +select * from ftx; +ERROR: relation "S 1.T 3 partition(ptb1)" does not exist on datanode1 +CONTEXT: Remote SQL command: SELECT c1, c2, c3 FROM "S 1"."T 3 partition(ptb1)" +DROP FOREIGN TABLE ftx; +-- ====================================================================================================================================== +-- TEST-MODULE: clean up all the test data +-- -------------------------------------- +-- heihei! +-- ====================================================================================================================================== +\c regression +drop database postgresfdw_test_db_partition; diff --git a/contrib/postgres_fdw/internal_interface.cpp b/contrib/postgres_fdw/internal_interface.cpp new file mode 100644 index 000000000..d60d55166 --- /dev/null +++ b/contrib/postgres_fdw/internal_interface.cpp @@ -0,0 +1,663 @@ +/* ------------------------------------------------------------------------- + * + * postgres_fdw.c + * Foreign-data wrapper for remote openGauss servers + * + * Portions Copyright (c) 2020 Huawei Technologies Co.,Ltd. + * Portions Copyright (c) 2012-2014, PostgreSQL Global Development Group + * + * IDENTIFICATION + * contrib/postgres_fdw/postgres_fdw.c + * + * ------------------------------------------------------------------------- + */ + +#ifndef FRONTEND + +#include "postgres.h" +#include "utils/memutils.h" +#else +#include "postgres_fe.h" +/* It's possible we could use a different value for this in frontend code */ +#define MaxAllocSize ((Size)0x3fffffff) /* 1 gigabyte - 1 */ +#endif + +#include "internal_interface.h" +#include "optimizer/pathnode.h" +#include "optimizer/cost.h" +#include "optimizer/planmain.h" + +/* + * Get a copy of an existing local path for a given join relation. + * + * This function is usually helpful to obtain an alternate local path for EPQ + * checks. + * + * Right now, this function only supports unparameterized foreign joins, so we + * only search for unparameterized path in the given list of paths. Since we + * are searching for a path which can be used to construct an alternative local + * plan for a foreign join, we look for only MergeJoin, HashJoin or NestLoop + * paths. + * + * If the inner or outer subpath of the chosen path is a ForeignScan, we + * replace it with its outer subpath. For this reason, and also because the + * planner might free the original path later, the path returned by this + * function is a shallow copy of the original. There's no need to copy + * the substructure, so we don't. + * + * Since the plan created using this path will presumably only be used to + * execute EPQ checks, efficiency of the path is not a concern. But since the + * path list in RelOptInfo is anyway sorted by total cost we are likely to + * choose the most efficient path, which is all for the best. + */ +Path *GetExistingLocalJoinPath(RelOptInfo *joinrel) +{ + ListCell *lc = NULL; + errno_t rc; + + Assert(IS_JOIN_REL(joinrel)); + + foreach (lc, joinrel->pathlist) { + Path *path = (Path *)lfirst(lc); + JoinPath *joinpath = NULL; + + /* Skip parameterized paths. */ + if (path->param_info != NULL) { + continue; + } + + switch (path->pathtype) { + case T_HashJoin: { + HashPath *hash_path = makeNode(HashPath); + rc = memcpy_s(hash_path, sizeof(HashPath), path, sizeof(HashPath)); + securec_check(rc, "\0", "\0"); + joinpath = (JoinPath *)hash_path; + } break; + + case T_NestLoop: { + NestPath *nest_path = makeNode(NestPath); + rc = memcpy_s(nest_path, sizeof(NestPath), path, sizeof(NestPath)); + securec_check(rc, "\0", "\0"); + joinpath = (JoinPath *)nest_path; + } break; + + case T_MergeJoin: { + MergePath *merge_path = makeNode(MergePath); + rc = memcpy_s(merge_path, sizeof(MergePath), path, sizeof(MergePath)); + securec_check(rc, "\0", "\0"); + joinpath = (JoinPath *)merge_path; + } break; + + default: + + /* + * Just skip anything else. We don't know if corresponding + * plan would build the output row from whole-row references + * of base relations and execute the EPQ checks. + */ + break; + } + + /* This path isn't good for us, check next. */ + if (!joinpath) { + continue; + } + + /* + * If either inner or outer path is a ForeignPath corresponding to a + * pushed down join, replace it with the fdw_outerpath, so that we + * maintain path for EPQ checks built entirely of local join + * strategies. + */ + if (IsA(joinpath->outerjoinpath, ForeignPath)) { + ForeignPath *foreign_path; + + foreign_path = (ForeignPath *)joinpath->outerjoinpath; + if (IS_JOIN_REL(foreign_path->path.parent)) { + joinpath->outerjoinpath = foreign_path->fdw_outerpath; + } + } + + if (IsA(joinpath->innerjoinpath, ForeignPath)) { + ForeignPath *foreign_path; + + foreign_path = (ForeignPath *)joinpath->innerjoinpath; + if (IS_JOIN_REL(foreign_path->path.parent)) { + joinpath->innerjoinpath = foreign_path->fdw_outerpath; + } + } + + return (Path *)joinpath; + } + return NULL; +} + +/* + * psprintf + * + * Format text data under the control of fmt (an sprintf-style format string) + * and return it in an allocated-on-demand buffer. The buffer is allocated + * with palloc in the backend, or malloc in frontend builds. Caller is + * responsible to free the buffer when no longer needed, if appropriate. + * + * Errors are not returned to the caller, but are reported via elog(ERROR) + * in the backend, or printf-to-stderr-and-exit() in frontend builds. + * One should therefore think twice about using this in libpq. + */ +char *psprintf(const char *fmt, ...) +{ + int save_errno = errno; + size_t len = 128; /* initial assumption about buffer size */ + + for (;;) { + char *result = NULL; + va_list args; + size_t newlen; + + /* + * Allocate result buffer. Note that in frontend this maps to malloc + * with exit-on-error. + */ + result = (char *)palloc(len); + + /* Try to format the data. */ + errno = save_errno; + va_start(args, fmt); + newlen = pvsnprintf(result, len, fmt, args); + va_end(args); + + if (newlen < len) { + return result; /* success */ + } + + /* Release buffer and loop around to try again with larger len. */ + pfree(result); + len = newlen; + } +} + +/* + * pvsnprintf + * + * Attempt to format text data under the control of fmt (an sprintf-style + * format string) and insert it into buf (which has length len). + * + * If successful, return the number of bytes emitted, not counting the + * trailing zero byte. This will always be strictly less than len. + * + * If there's not enough space in buf, return an estimate of the buffer size + * needed to succeed (this *must* be more than the given len, else callers + * might loop infinitely). + * + * Other error cases do not return, but exit via elog(ERROR) or exit(). + * Hence, this shouldn't be used inside libpq. + * + * Caution: callers must be sure to preserve their entry-time errno + * when looping, in case the fmt contains "%m". + * + * Note that the semantics of the return value are not exactly C99's. + * First, we don't promise that the estimated buffer size is exactly right; + * callers must be prepared to loop multiple times to get the right size. + * (Given a C99-compliant vsnprintf, that won't happen, but it is rumored + * that some implementations don't always return the same value ...) + * Second, we return the recommended buffer size, not one less than that; + * this lets overflow concerns be handled here rather than in the callers. + */ +size_t pvsnprintf(char *buf, size_t len, const char *fmt, va_list args) +{ + int nprinted = 0; + + nprinted = vsnprintf_s(buf, len, len, fmt, args); + securec_check_ss(nprinted, "\0", "\0"); + + /* We assume failure means the fmt is bogus, hence hard failure is OK */ + if (unlikely(nprinted < 0)) { +#ifndef FRONTEND + elog(ERROR, "vsnprintf failed: %m with format string \"%s\"", fmt); +#else + fprintf(stderr, "vsnprintf failed: %s with format string \"%s\"\n", strerror(errno), fmt); + exit(EXIT_FAILURE); +#endif + } + + if ((size_t)nprinted < len) { + /* Success. Note nprinted does not include trailing null. */ + return (size_t)nprinted; + } + + /* + * We assume a C99-compliant vsnprintf, so believe its estimate of the + * required space, and add one for the trailing null. (If it's wrong, the + * logic will still work, but we may loop multiple times.) + * + * Choke if the required space would exceed MaxAllocSize. Note we use + * this palloc-oriented overflow limit even when in frontend. + */ + if (unlikely((size_t)nprinted > MaxAllocSize - 1)) { +#ifndef FRONTEND + ereport(ERROR, (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), errmsg("out of memory"))); +#else + fprintf(stderr, _("out of memory\n")); + exit(EXIT_FAILURE); +#endif + } + + return nprinted + 1; +} + +/* + * create_foreign_join_path + * Creates a path corresponding to a scan of a foreign join, + * returning the pathnode. + * + * This function is never called from core Postgres; rather, it's expected + * to be called by the GetForeignJoinPaths function of a foreign data wrapper. + * We make the FDW supply all fields of the path, since we do not have any way + * to calculate them in core. However, there is a usually-sane default for + * the pathtarget (rel->reltarget), so we let a NULL for "target" select that. + */ +ForeignPath *create_foreign_join_path(PlannerInfo *root, RelOptInfo *rel, List *target, double rows, + Cost startup_cost, Cost total_cost, List *pathkeys, Relids required_outer, Path *fdw_outerpath, List *fdw_private) +{ + ForeignPath *pathnode = makeNode(ForeignPath); + + /* + * We should use get_joinrel_parampathinfo to handle parameterized paths, + * but the API of this function doesn't support it, and existing + * extensions aren't yet trying to build such paths anyway. For the + * moment just throw an error if someone tries it; eventually we should + * revisit this. + */ + if (!bms_is_empty(required_outer) || !bms_is_empty(rel->lateral_relids)) { + elog(ERROR, "parameterized foreign joins are not supported yet"); + } + + pathnode->path.pathtype = T_ForeignScan; + pathnode->path.parent = rel; + pathnode->path.param_info = NULL; /* XXX see above */ + pathnode->path.rows = rows; + pathnode->path.startup_cost = startup_cost; + pathnode->path.total_cost = total_cost; + pathnode->path.pathkeys = pathkeys; + pathnode->path.dop = 1; + + pathnode->fdw_outerpath = fdw_outerpath; + pathnode->fdw_private = fdw_private; + + return pathnode; +} + +/* + * Call ExecInitExpr() on a list of expressions, return a list of ExprStates. + */ +List *ExecInitExprList(List *nodes, PlanState *parent) +{ + List *result = NIL; + ListCell *lc = NULL; + + foreach (lc, nodes) { + Expr *e = (Expr *)lfirst(lc); + result = lappend(result, ExecInitExpr(e, parent)); + } + + return result; +} + +/* + * get_namespace_name_or_temp + * As above, but if it is this backend's temporary namespace, return + * "pg_temp" instead. + */ +char *get_namespace_name_or_temp(Oid nspid) +{ + if (isTempNamespace(nspid)) { + return pstrdup("pg_temp"); + } else { + return get_namespace_name(nspid); + } +} + +/* + * fetch_upper_rel + * Build a RelOptInfo describing some post-scan/join query processing, + * or return a pre-existing one if somebody already built it. + * + * An "upper" relation is identified by an UpperRelationKind and a Relids set. + * The meaning of the Relids set is not specified here, and very likely will + * vary for different relation kinds. + * + * Most of the fields in an upper-level RelOptInfo are not used and are not + * set here (though makeNode should ensure they're zeroes). We basically only + * care about fields that are of interest to add_path() and set_cheapest(). + */ +RelOptInfo *fetch_upper_rel(FDWUpperRelCxt *ufdwCxt, UpperRelationKind kind) +{ + /* + * For the moment, our indexing data structure is just a List for each + * relation kind. If we ever get so many of one kind that this stops + * working well, we can improve it. No code outside this function should + * assume anything about how to find a particular upperrel. + */ + if (ufdwCxt->upperRels[kind] != NULL) { + return ufdwCxt->upperRels[kind]; + } + + RelOptInfo *upperrel = makeNode(RelOptInfo); + upperrel->reloptkind = RELOPT_UPPER_REL; + upperrel->relids = NULL; + + upperrel->reltargetlist = NIL; + upperrel->pathlist = NIL; + upperrel->cheapest_startup_path = NULL; + upperrel->cheapest_total_path = NULL; + upperrel->cheapest_unique_path = NULL; + upperrel->cheapest_parameterized_paths = NIL; + + ufdwCxt->upperRels[kind] = upperrel; + + return upperrel; +} + +List* extract_target_from_tel(FDWUpperRelCxt *ufdw_cxt, PgFdwRelationInfo *fpinfo) +{ + List* tel = NIL; + switch (fpinfo->stage) { + case UPPERREL_INIT: + tel = ufdw_cxt->spjExtra->targetList; + break; + case UPPERREL_GROUP_AGG: + tel = ufdw_cxt->groupExtra->targetList; + break; + case UPPERREL_ORDERED: + tel = ufdw_cxt->orderExtra->targetList; + break; + case UPPERREL_FINAL: + tel = ufdw_cxt->finalExtra->targetList; + break; + default: + Assert(false); + break; + } + + fpinfo->complete_tlist = tel; + + List* tl = NULL; + ListCell* lc = NULL; + foreach(lc, tel) { + Assert(IsA(lfirst(lc), TargetEntry)); + TargetEntry* te = (TargetEntry*)lfirst(lc); + + tl = lappend(tl, copyObject(te->expr)); + } + + return tl; +} + +/* + * make_upper_rel + * + * Create a new grouping rel and set basic properties. + * + * input_rel represents the underlying scan/join relation. + * target is the output expected from the grouping relation. + */ +RelOptInfo *make_upper_rel(FDWUpperRelCxt *ufdwCxt, PgFdwRelationInfo *fpinfo) +{ + RelOptInfo *upper_rel = fetch_upper_rel(ufdwCxt, fpinfo->stage); + + /* Set target. */ + upper_rel->reltargetlist = extract_target_from_tel(ufdwCxt, fpinfo); + + /* + * If the input rel belongs to a single FDW, so does the grouped rel. + */ + upper_rel->serverid = ufdwCxt->currentRel->serverid; + upper_rel->userid = ufdwCxt->currentRel->userid; + upper_rel->useridiscurrent = ufdwCxt->currentRel->useridiscurrent; + upper_rel->fdwroutine = ufdwCxt->currentRel->fdwroutine; + upper_rel->fdw_private = fpinfo; + + return upper_rel; +} + +/* + * get_sortgroupref_clause_noerr + * As above, but return NULL rather than throwing an error if not found. + */ +SortGroupClause *get_sortgroupref_clause_noerr(Index sortref, List *clauses) +{ + ListCell *l = NULL; + + foreach (l, clauses) { + SortGroupClause *cl = (SortGroupClause *)lfirst(l); + + if (cl->tleSortGroupRef == sortref) { + return cl; + } + } + + return NULL; +} + +/* + * create_foreign_upper_path + * Creates a path corresponding to an upper relation that's computed + * directly by an FDW, returning the pathnode. + * + * This function is never called from core Postgres; rather, it's expected to + * be called by the GetForeignUpperPaths function of a foreign data wrapper. + * We make the FDW supply all fields of the path, since we do not have any way + * to calculate them in core. However, there is a usually-sane default for + * the pathtarget (rel->reltarget), so we let a NULL for "target" select that. + */ +ForeignPath *create_foreign_upper_path(PlannerInfo *root, RelOptInfo *rel, List *target, double rows, + Cost startup_cost, Cost total_cost, List *pathkeys, Path *fdw_outerpath, List *fdw_private) +{ + ForeignPath *pathnode = makeNode(ForeignPath); + + /* + * Upper relations should never have any lateral references, since joining + * is complete. + */ + Assert(bms_is_empty(rel->lateral_relids)); + + pathnode->path.pathtype = T_ForeignScan; + pathnode->path.parent = rel; + pathnode->path.param_info = NULL; + pathnode->path.rows = rows; + pathnode->path.startup_cost = startup_cost; + pathnode->path.total_cost = total_cost; + pathnode->path.pathkeys = pathkeys; + pathnode->path.dop = 1; + + pathnode->fdw_outerpath = fdw_outerpath; + pathnode->fdw_private = fdw_private; + + return pathnode; +} + +/* + * adjust_limit_rows_costs + * Adjust the size and cost estimates for a LimitPath node according to the + * offset/limit. + * + * This is only a cosmetic issue if we are at top level, but if we are + * building a subquery then it's important to report correct info to the outer + * planner. + * + * When the offset or count couldn't be estimated, use 10% of the estimated + * number of rows emitted from the subpath. + * + * XXX we don't bother to add eval costs of the offset/limit expressions + * themselves to the path costs. In theory we should, but in most cases those + * expressions are trivial and it's just not worth the trouble. + */ +void adjust_limit_rows_costs(double *rows, /* in/out parameter */ + Cost *startup_cost, /* in/out parameter */ + Cost *total_cost, /* in/out parameter */ + int64 offset_est, int64 count_est) +{ + double input_rows = *rows; + Cost input_startup_cost = *startup_cost; + Cost input_total_cost = *total_cost; + + if (offset_est != 0) { + double offset_rows; + + if (offset_est > 0) { + offset_rows = (double)offset_est; + } else { + offset_rows = clamp_row_est(input_rows * 0.10); + } + if (offset_rows > *rows) { + offset_rows = *rows; + } + if (input_rows > 0) { + *startup_cost += (input_total_cost - input_startup_cost) * offset_rows / input_rows; + } + *rows -= offset_rows; + if (*rows < 1) { + *rows = 1; + } + } + + if (count_est != 0) { + double count_rows; + + if (count_est > 0) { + count_rows = (double)count_est; + } else { + count_rows = clamp_row_est(input_rows * 0.10); + } + if (count_rows > *rows) { + count_rows = *rows; + } + if (input_rows > 0) { + *total_cost = *startup_cost + (input_total_cost - input_startup_cost) * count_rows / input_rows; + } + *rows = count_rows; + if (*rows < 1) { + *rows = 1; + } + } +} + +/* + * tlist_same_exprs + * Check whether two target lists contain the same expressions + * + * Note: this function is used to decide whether it's safe to jam a new tlist + * into a non-projection-capable plan node. Obviously we can't do that unless + * the node's tlist shows it already returns the column values we want. + * However, we can ignore the TargetEntry attributes resname, ressortgroupref, + * resorigtbl, resorigcol, and resjunk, because those are only labelings that + * don't affect the row values computed by the node. (Moreover, if we didn't + * ignore them, we'd frequently fail to make the desired optimization, since + * the planner tends to not bother to make resname etc. valid in intermediate + * plan nodes.) Note that on success, the caller must still jam the desired + * tlist into the plan node, else it won't have the desired labeling fields. + */ +static bool tlist_same_exprs(List *tlist1, List *tlist2) +{ + ListCell *lc1 = NULL; + ListCell *lc2 = NULL; + + if (list_length(tlist1) != list_length(tlist2)) { + return false; /* not same length, so can't match */ + } + + forboth(lc1, tlist1, lc2, tlist2) { + TargetEntry *tle1 = (TargetEntry *)lfirst(lc1); + TargetEntry *tle2 = (TargetEntry *)lfirst(lc2); + + if (!equal(tle1->expr, tle2->expr)) { + return false; + } + } + + return true; +} + +/* + * inject_projection_plan + * Insert a Result node to do a projection step. + * + * This is used in a few places where we decide on-the-fly that we need a + * projection step as part of the tree generated for some Path node. + * We should try to get rid of this in favor of doing it more honestly. + * + * One reason it's ugly is we have to be told the right parallel_safe marking + * to apply (since the tlist might be unsafe even if the child plan is safe). + */ +static Plan *inject_projection_plan(PlannerInfo *root, Plan *subplan, List *tlist) +{ + Plan *plan = NULL; + + plan = (Plan *)make_result(root, tlist, NULL, subplan); + + /* + * In principle, we should charge tlist eval cost plus cpu_per_tuple per + * row for the Result node. But the former has probably been factored in + * already and the latter was not accounted for during Path construction, + * so being formally correct might just make the EXPLAIN output look less + * consistent not more so. Hence, just copy the subplan's cost. + */ + copy_plan_costsize(plan, subplan); + + return plan; +} + +/* + * change_plan_targetlist + * Externally available wrapper for inject_projection_plan. + * + * This is meant for use by FDW plan-generation functions, which might + * want to adjust the tlist computed by some subplan tree. In general, + * a Result node is needed to compute the new tlist, but we can optimize + * some cases. + * + * In most cases, tlist_parallel_safe can just be passed as the parallel_safe + * flag of the FDW's own Path node. + */ +Plan *change_plan_targetlist(PlannerInfo *root, Plan *subplan, List *tlist) +{ + /* + * If the top plan node can't do projections and its existing target list + * isn't already what we need, we need to add a Result node to help it + * along. + */ + if (!is_projection_capable_plan(subplan) && !tlist_same_exprs(tlist, subplan->targetlist)) { + subplan = inject_projection_plan(root, subplan, tlist); + } else { + /* Else we can just replace the plan node's tlist */ + subplan->targetlist = tlist; + } + return subplan; +} + +/* + * apply_tlist_labeling + * Apply the TargetEntry labeling attributes of src_tlist to dest_tlist + * + * This is useful for reattaching column names etc to a plan's final output + * targetlist. + */ +void apply_tlist_labeling(List *dest_tlist, List *src_tlist) +{ + ListCell *ld = NULL; + ListCell *ls = NULL; + + Assert(list_length(dest_tlist) == list_length(src_tlist)); + forboth(ld, dest_tlist, ls, src_tlist) + { + TargetEntry *dest_tle = (TargetEntry *)lfirst(ld); + TargetEntry *src_tle = (TargetEntry *)lfirst(ls); + + Assert(dest_tle->resno == src_tle->resno); + dest_tle->resname = src_tle->resname; + dest_tle->ressortgroupref = src_tle->ressortgroupref; + dest_tle->resorigtbl = src_tle->resorigtbl; + dest_tle->resorigcol = src_tle->resorigcol; + dest_tle->resjunk = src_tle->resjunk; + } +} diff --git a/contrib/postgres_fdw/internal_interface.h b/contrib/postgres_fdw/internal_interface.h new file mode 100644 index 000000000..e50f5bf6b --- /dev/null +++ b/contrib/postgres_fdw/internal_interface.h @@ -0,0 +1,68 @@ +/* ------------------------------------------------------------------------- + * + * postgres_fdw.c + * Foreign-data wrapper for remote openGauss servers + * + * Portions Copyright (c) 2020 Huawei Technologies Co.,Ltd. + * Portions Copyright (c) 2012-2014, PostgreSQL Global Development Group + * + * IDENTIFICATION + * contrib/postgres_fdw/postgres_fdw.c + * + * ------------------------------------------------------------------------- + */ + +#ifndef _PGFDW_INTERNAL_INTERFACE_H_ +#define _PGFDW_INTERNAL_INTERFACE_H_ + +#include "c.h" +#include "datatypes.h" +#include "nodes/bitmapset.h" +#include "nodes/relation.h" +#include "nodes/nodes.h" +#include "nodes/execnodes.h" +#include "executor/executor.h" +#include "utils/lsyscache.h" +#include "postgres_fdw.h" + +/* + * This macro embodies the correct way to test whether a RestrictInfo is + * "pushed down" to a given outer join, that is, should be treated as a filter + * clause rather than a join clause at that outer join. This is certainly so + * if is_pushed_down is true; but examining that is not sufficient anymore, + * because outer-join clauses will get pushed down to lower outer joins when + * we generate a path for the lower outer join that is parameterized by the + * LHS of the upper one. We can detect such a clause by noting that its + * required_relids exceed the scope of the join. + */ +#define RINFO_IS_PUSHED_DOWN(rinfo, joinrelids) \ + ((rinfo)->is_pushed_down || !bms_is_subset((rinfo)->required_relids, joinrelids)) + +extern char *psprintf(const char *fmt, ...); +extern size_t pvsnprintf(char *buf, size_t len, const char *fmt, va_list args); + +extern Path *GetExistingLocalJoinPath(RelOptInfo *joinrel); +extern ForeignPath *create_foreign_join_path(PlannerInfo *root, RelOptInfo *rel, List *target, double rows, + Cost startup_cost, Cost total_cost, List *pathkeys, Relids required_outer, Path *fdw_outerpath, List *fdw_private); +ForeignPath *create_foreign_upper_path(PlannerInfo *root, RelOptInfo *rel, List *target, double rows, + Cost startup_cost, Cost total_cost, List *pathkeys, Path *fdw_outerpath, List *fdw_private); +extern List *ExecInitExprList(List *nodes, PlanState *parent); +extern char *get_namespace_name_or_temp(Oid nspid); +extern RelOptInfo *fetch_upper_rel(FDWUpperRelCxt *ufdwCxt, UpperRelationKind kind); +extern RelOptInfo *make_upper_rel(FDWUpperRelCxt *ufdwCxt, PgFdwRelationInfo *fpinfo); +extern void adjust_limit_rows_costs(double *rows, Cost *startup_cost, Cost *total_cost, int64 offset_est, int64 count_est); +extern Plan *change_plan_targetlist(PlannerInfo *root, Plan *subplan, List *tlist); +extern void apply_tlist_labeling(List *dest_tlist, List *src_tlist); +extern List* extract_target_from_tel(FDWUpperRelCxt *ufdw_cxt, PgFdwRelationInfo *fpinfo); + +#define boolVal(v) ((bool)(((Value*)(v))->val.ival)) + +extern SortGroupClause *get_sortgroupref_clause_noerr(Index sortref, List *clauses); + +/* Control flags for format_type_extended */ +#define FORMAT_TYPE_TYPEMOD_GIVEN 0x01 /* typemod defined by caller */ +#define FORMAT_TYPE_ALLOW_INVALID 0x02 /* allow invalid types */ +#define FORMAT_TYPE_FORCE_QUALIFY 0x04 /* force qualification of type */ +#define FORMAT_TYPE_INVALID_AS_NULL 0x08 /* NULL if undefined */ + +#endif \ No newline at end of file diff --git a/contrib/postgres_fdw/postgres_fdw.cpp b/contrib/postgres_fdw/postgres_fdw.cpp index e68eeb89c..d92fb166e 100644 --- a/contrib/postgres_fdw/postgres_fdw.cpp +++ b/contrib/postgres_fdw/postgres_fdw.cpp @@ -32,6 +32,7 @@ #include "optimizer/prep.h" #include "optimizer/restrictinfo.h" #include "optimizer/var.h" +#include "optimizer/tlist.h" #include "parser/parsetree.h" #include "utils/builtins.h" #include "utils/guc.h" @@ -39,6 +40,7 @@ #include "utils/memutils.h" #include "knl/knl_guc.h" #include "access/tableam.h" +#include "internal_interface.h" PG_MODULE_MAGIC; @@ -48,38 +50,8 @@ PG_MODULE_MAGIC; /* Default CPU cost to process 1 row (above and beyond cpu_tuple_cost). */ #define DEFAULT_FDW_TUPLE_COST 0.01 -/* - * FDW-specific planner information kept in RelOptInfo.fdw_private for a - * foreign table. This information is collected by postgresGetForeignRelSize. - */ -typedef struct PgFdwRelationInfo { - /* baserestrictinfo clauses, broken down into safe and unsafe subsets. */ - List *remote_conds; - List *local_conds; - - /* Bitmap of attr numbers we need to fetch from the remote server. */ - Bitmapset *attrs_used; - - /* Cost and selectivity of local_conds. */ - QualCost local_conds_cost; - Selectivity local_conds_sel; - - /* Estimated size and cost for a scan with baserestrictinfo quals. */ - double rows; - int width; - Cost startup_cost; - Cost total_cost; - - /* Options extracted from catalogs. */ - bool use_remote_estimate; - Cost fdw_startup_cost; - Cost fdw_tuple_cost; - - /* Cached catalog information. */ - ForeignTable *table; - ForeignServer *server; - UserMapping *user; /* only set in use_remote_estimate mode */ -} PgFdwRelationInfo; +/* If no remote estimates, assume a sort costs 20% extra */ +#define DEFAULT_FDW_SORT_MULTIPLIER 1.2 /* * Indexes of FDW-private information stored in fdw_private lists. @@ -98,7 +70,15 @@ enum FdwScanPrivateIndex { /* SQL statement to execute remotely (as a String node) */ FdwScanPrivateSelectSql, /* Integer list of attribute numbers retrieved by the SELECT */ - FdwScanPrivateRetrievedAttrs + FdwScanPrivateRetrievedAttrs, + /* Integer representing the desired fetch_size */ + FdwScanPrivateFetchSize, + + /* + * String describing join i.e. names of relations being joined and types + * of join, added when the scan is join + */ + FdwScanPrivateRelations }; /* @@ -127,6 +107,7 @@ enum FdwModifyPrivateIndex { */ typedef struct PgFdwScanState { Relation rel; /* relcache entry for the foreign table */ + TupleDesc tupdesc; /* tuple descriptor of scan */ AttInMetadata *attinmeta; /* attribute datatype conversion metadata */ /* extracted fdw_private data */ @@ -154,6 +135,8 @@ typedef struct PgFdwScanState { /* working memory contexts */ MemoryContext batch_cxt; /* context holding current batch of tuples */ MemoryContext temp_cxt; /* context for per-tuple temporary data */ + + int fetch_size; /* number of tuples per fetch */ } PgFdwScanState; /* @@ -174,7 +157,8 @@ typedef struct PgFdwModifyState { List *retrieved_attrs; /* attr numbers retrieved by RETURNING */ /* info about parameters for prepared statement */ - AttrNumber ctidAttno; /* attnum of input resjunk ctid column */ + AttrNumber ctidAttno; /* attnum of input resjunk ctid column */ + AttrNumber tboidAttno; /* attnum of input resjunk tableoid column */ int p_nums; /* number of parameters to transmit */ FmgrInfo *p_flinfo; /* output conversion functions for them */ @@ -205,12 +189,37 @@ typedef struct PgFdwAnalyzeState { MemoryContext temp_cxt; /* context for per-tuple temporary data */ } PgFdwAnalyzeState; +/* + * This enum describes what's kept in the fdw_private list for a ForeignPath. + * We store: + * + * 1) Boolean flag showing if the remote query has the final sort + * 2) Boolean flag showing if the remote query has the LIMIT clause + */ +enum FdwPathPrivateIndex { + /* has-final-sort flag (as a Boolean node) */ + FdwPathPrivateHasFinalSort, + /* has-limit flag (as a Boolean node) */ + FdwPathPrivateHasLimit +}; + +/* Struct for extra information passed to estimate_path_cost_size() */ +typedef struct { + List *target; + bool has_final_sort; + bool has_limit; + double limit_tuples; + int64 count_est; + int64 offset_est; +} PgFdwPathExtraData; + /* * Identify the attribute where data conversion fails. */ typedef struct ConversionLocation { Relation rel; /* foreign table's relcache entry */ AttrNumber cur_attno; /* attribute number being processed, or 0 */ + ForeignScanState *fsstate; /* plan node being processed, or NULL */ } ConversionLocation; /* @@ -224,8 +233,8 @@ extern "C" Datum postgres_fdw_handler(PG_FUNCTION_ARGS); */ static void postgresGetForeignRelSize(PlannerInfo *root, RelOptInfo *baserel, Oid foreigntableid); static void postgresGetForeignPaths(PlannerInfo *root, RelOptInfo *baserel, Oid foreigntableid); -static ForeignScan *postgresGetForeignPlan(PlannerInfo *root, RelOptInfo *baserel, Oid foreigntableid, - ForeignPath *best_path, List *tlist, List *scan_clauses); +static ForeignScan *postgresGetForeignPlan(PlannerInfo *root, RelOptInfo *foreignrel, Oid foreigntableid, + ForeignPath *best_path, List *tlist, List *scan_clauses, Plan *outer_plan); static void postgresBeginForeignScan(ForeignScanState *node, int eflags); static TupleTableSlot *postgresIterateForeignScan(ForeignScanState *node); static void postgresReScanForeignScan(ForeignScanState *node); @@ -245,14 +254,18 @@ static int postgresIsForeignRelUpdatable(Relation rel); static void postgresExplainForeignScan(ForeignScanState *node, ExplainState *es); static void postgresExplainForeignModify(ModifyTableState *mtstate, ResultRelInfo *rinfo, List *fdw_private, int subplan_index, ExplainState *es); +static void postgresExplainForeignScanRemote(ForeignScanState *node, ExplainState *es); static bool postgresAnalyzeForeignTable(Relation relation, AcquireSampleRowsFunc *func, BlockNumber *totalpages, void *additionalData, bool estimate_table_rownum); - +static void postgresGetForeignJoinPaths(PlannerInfo *root, RelOptInfo *joinrel, RelOptInfo *outerrel, + RelOptInfo *innerrel, JoinType jointype, SpecialJoinInfo *sjinfo, List *restrictlist); +static void _postgresGetForeignUpperPaths(FDWUpperRelCxt *ufdw_cxt, UpperRelationKind stage); +static void postgresGetForeignUpperPaths(FDWUpperRelCxt* ufdwCxt, UpperRelationKind stage, Plan* mainPlan); /* * Helper functions */ -static void estimate_path_cost_size(PlannerInfo *root, RelOptInfo *baserel, List *join_conds, double *p_rows, - int *p_width, Cost *p_startup_cost, Cost *p_total_cost); +static void estimate_path_cost_size(PlannerInfo *root, RelOptInfo *foreignrel, List *param_join_conds, List *pathkeys, + PgFdwPathExtraData *fpextra, double *p_rows, int *p_width, Cost *p_startup_cost, Cost *p_total_cost); static void get_remote_estimate(const char *sql, PGconn *conn, double *rows, int *width, Cost *startup_cost, Cost *total_cost); static void create_cursor(ForeignScanState *node); @@ -262,15 +275,36 @@ static PgFdwModifyState *createForeignModify(EState *estate, RangeTblEntry *rte, CmdType operation, Plan *subplan, char *query, List *target_attrs, bool has_returning, List *retrieved_attrs); static void prepare_foreign_modify(PgFdwModifyState *fmstate); -static const char **convert_prep_stmt_params(PgFdwModifyState *fmstate, ItemPointer tupleid, TupleTableSlot *slot); +static const char **convert_prep_stmt_params(PgFdwModifyState *fmstate, ItemPointer tupleid, ItemPointer tableid, + TupleTableSlot *slot); static void store_returning_result(PgFdwModifyState *fmstate, TupleTableSlot *slot, PGresult *res); static int postgresAcquireSampleRowsFunc(Relation relation, int elevel, HeapTuple *rows, int targrows, double *totalrows, double *totaldeadrows, void *additionalData, bool estimate_table_rownum); static void analyze_row_processor(PGresult *res, int row, PgFdwAnalyzeState *astate); static HeapTuple make_tuple_from_result_row(PGresult *res, int row, Relation rel, AttInMetadata *attinmeta, - List *retrieved_attrs, MemoryContext temp_context); + List *retrieved_attrs, ForeignScanState *fsstate, MemoryContext temp_context); static void conversion_error_callback(void *arg); +static void add_paths_with_pathkeys_for_rel(PlannerInfo *root, RelOptInfo *rel, Path *epq_path); +static void apply_server_options(PgFdwRelationInfo *fpinfo); +static void apply_table_options(PgFdwRelationInfo *fpinfo); +static void merge_fdw_options(PgFdwRelationInfo *fpinfo, const PgFdwRelationInfo *fpinfo_o, + const PgFdwRelationInfo *fpinfo_i); +static void prepare_query_params(PlanState *node, List *fdw_exprs, int numParams, FmgrInfo **param_flinfo, + List **param_exprs, const char ***param_values); + +static void add_foreign_grouping_paths(PlannerInfo *root, RelOptInfo *input_rel, RelOptInfo *grouped_rel, + GroupPathExtraData *extra); +static void add_foreign_ordered_paths(PlannerInfo *root, RelOptInfo *input_rel, RelOptInfo *ordered_rel, + OrderPathExtraData *extra); +static void add_foreign_final_paths(PlannerInfo *root, RelOptInfo *input_rel, RelOptInfo *final_rel, + FinalPathExtraData *extra); + +static void adjust_foreign_grouping_path_cost(PlannerInfo *root, List *pathkeys, double retrieved_rows, double width, + double limit_tuples, Cost *p_startup_cost, Cost *p_run_cost); + +static void process_query_params(ExprContext *econtext, FmgrInfo *param_flinfo, List *param_exprs, + const char **param_values); /* * Foreign-data wrapper handler function: return a struct with pointers @@ -302,11 +336,18 @@ Datum postgres_fdw_handler(PG_FUNCTION_ARGS) /* Support functions for EXPLAIN */ routine->ExplainForeignScan = postgresExplainForeignScan; routine->ExplainForeignModify = postgresExplainForeignModify; + routine->ExplainForeignScanRemote = postgresExplainForeignScanRemote; /* Support functions for ANALYZE */ routine->AnalyzeForeignTable = postgresAnalyzeForeignTable; routine->AcquireSampleRows = postgresAcquireSampleRowsFunc; + /* Support functions for join push-down */ + routine->GetForeignJoinPaths = postgresGetForeignJoinPaths; + + /* Support functions for upper relation push-down */ + routine->GetForeignUpperPaths = postgresGetForeignUpperPaths; + PG_RETURN_POINTER(routine); } @@ -328,6 +369,9 @@ static void postgresGetForeignRelSize(PlannerInfo *root, RelOptInfo *baserel, Oi PgFdwRelationInfo* fpinfo = (PgFdwRelationInfo *)palloc0(sizeof(PgFdwRelationInfo)); baserel->fdw_private = (void *)fpinfo; + /* Base foreign tables need to be pushed down always. */ + fpinfo->pushdown_safe = true; + /* Look up foreign-table catalog info. */ fpinfo->table = GetForeignTable(foreigntableid); fpinfo->server = GetForeignServer(fpinfo->table->serverid); @@ -339,26 +383,12 @@ static void postgresGetForeignRelSize(PlannerInfo *root, RelOptInfo *baserel, Oi fpinfo->use_remote_estimate = false; fpinfo->fdw_startup_cost = DEFAULT_FDW_STARTUP_COST; fpinfo->fdw_tuple_cost = DEFAULT_FDW_TUPLE_COST; + fpinfo->shippable_extensions = NIL; + fpinfo->fetch_size = 100; + fpinfo->async_capable = false; - foreach (lc, fpinfo->server->options) { - DefElem *def = (DefElem *)lfirst(lc); - - if (strcmp(def->defname, "use_remote_estimate") == 0) { - fpinfo->use_remote_estimate = defGetBoolean(def); - } else if (strcmp(def->defname, "fdw_startup_cost") == 0) { - fpinfo->fdw_startup_cost = strtod(defGetString(def), NULL); - } else if (strcmp(def->defname, "fdw_tuple_cost") == 0) { - fpinfo->fdw_tuple_cost = strtod(defGetString(def), NULL); - } - } - foreach (lc, fpinfo->table->options) { - DefElem *def = (DefElem *)lfirst(lc); - - if (strcmp(def->defname, "use_remote_estimate") == 0) { - fpinfo->use_remote_estimate = defGetBoolean(def); - break; /* only need the one value */ - } - } + apply_server_options(fpinfo); + apply_table_options(fpinfo); /* * If the table or the server is configured to use remote estimates, @@ -406,6 +436,15 @@ static void postgresGetForeignRelSize(PlannerInfo *root, RelOptInfo *baserel, Oi cost_qual_eval(&fpinfo->local_conds_cost, fpinfo->local_conds, root); + /* + * Set # of retrieved rows and cached relation costs to some negative + * value, so that we can detect when they are set to some sensible values, + * during one (usually the first) of the calls to estimate_path_cost_size. + */ + fpinfo->retrieved_rows = -1; + fpinfo->rel_startup_cost = -1; + fpinfo->rel_total_cost = -1; + /* * If the table or the server is configured to use remote estimates, * connect to the foreign server and execute EXPLAIN to estimate the @@ -419,7 +458,7 @@ static void postgresGetForeignRelSize(PlannerInfo *root, RelOptInfo *baserel, Oi * values in fpinfo so we don't need to do it again to generate the * basic foreign path. */ - estimate_path_cost_size(root, baserel, NIL, &fpinfo->rows, &fpinfo->width, &fpinfo->startup_cost, + estimate_path_cost_size(root, baserel, NIL, NIL, NULL, &fpinfo->rows, &fpinfo->width, &fpinfo->startup_cost, &fpinfo->total_cost); /* Report estimated baserel size to planner. */ @@ -444,9 +483,225 @@ static void postgresGetForeignRelSize(PlannerInfo *root, RelOptInfo *baserel, Oi set_baserel_size_estimates(root, baserel); /* Fill in basically-bogus cost estimates for use later. */ - estimate_path_cost_size(root, baserel, NIL, &fpinfo->rows, &fpinfo->width, &fpinfo->startup_cost, + estimate_path_cost_size(root, baserel, NIL, NIL, NULL, &fpinfo->rows, &fpinfo->width, &fpinfo->startup_cost, &fpinfo->total_cost); } + + /* + * fpinfo->relation_name gets the numeric rangetable index of the foreign + * table RTE. (If this query gets EXPLAIN'd, we'll convert that to a + * human-readable string at that time.) + */ + fpinfo->relation_name = psprintf("%u", baserel->relid); + + /* No outer and inner relations. */ + fpinfo->make_outerrel_subquery = false; + fpinfo->make_innerrel_subquery = false; + fpinfo->lower_subquery_rels = NULL; + /* Set the relation index. */ + fpinfo->relation_index = baserel->relid; +} + +/* + * get_useful_ecs_for_relation + * Determine which EquivalenceClasses might be involved in useful + * orderings of this relation. + * + * This function is in some respects a mirror image of the core function + * pathkeys_useful_for_merging: for a regular table, we know what indexes + * we have and want to test whether any of them are useful. For a foreign + * table, we don't know what indexes are present on the remote side but + * want to speculate about which ones we'd like to use if they existed. + * + * This function returns a list of potentially-useful equivalence classes, + * but it does not guarantee that an EquivalenceMember exists which contains + * Vars only from the given relation. For example, given ft1 JOIN t1 ON + * ft1.x + t1.x = 0, this function will say that the equivalence class + * containing ft1.x + t1.x is potentially useful. Supposing ft1 is remote and + * t1 is local (or on a different server), it will turn out that no useful + * ORDER BY clause can be generated. It's not our job to figure that out + * here; we're only interested in identifying relevant ECs. + */ +static List *get_useful_ecs_for_relation(PlannerInfo *root, RelOptInfo *rel) +{ + List *useful_eclass_list = NIL; + ListCell *lc = NULL; + Relids relids; + + /* + * First, consider whether any active EC is potentially useful for a merge + * join against this relation. + */ + if (rel->has_eclass_joins) { + foreach (lc, root->eq_classes) { + EquivalenceClass *cur_ec = (EquivalenceClass *)lfirst(lc); + + if (eclass_useful_for_merging(cur_ec, rel)) + useful_eclass_list = lappend(useful_eclass_list, cur_ec); + } + } + + /* + * Next, consider whether there are any non-EC derivable join clauses that + * are merge-joinable. If the joininfo list is empty, we can exit + * quickly. + */ + if (rel->joininfo == NIL) { + return useful_eclass_list; + } + + relids = rel->relids; + + /* Check each join clause in turn. */ + foreach (lc, rel->joininfo) { + RestrictInfo *restrictinfo = (RestrictInfo *)lfirst(lc); + + /* Consider only mergejoinable clauses */ + if (restrictinfo->mergeopfamilies == NIL) { + continue; + } + + /* Make sure we've got canonical ECs. */ + update_mergeclause_eclasses(root, restrictinfo); + + /* + * restrictinfo->mergeopfamilies != NIL is sufficient to guarantee + * that left_ec and right_ec will be initialized, per comments in + * distribute_qual_to_rels. + * + * We want to identify which side of this merge-joinable clause + * contains columns from the relation produced by this RelOptInfo. We + * test for overlap, not containment, because there could be extra + * relations on either side. For example, suppose we've got something + * like ((A JOIN B ON A.x = B.x) JOIN C ON A.y = C.y) LEFT JOIN D ON + * A.y = D.y. The input rel might be the joinrel between A and B, and + * we'll consider the join clause A.y = D.y. relids contains a + * relation not involved in the join class (B) and the equivalence + * class for the left-hand side of the clause contains a relation not + * involved in the input rel (C). Despite the fact that we have only + * overlap and not containment in either direction, A.y is potentially + * useful as a sort column. + * + * Note that it's even possible that relids overlaps neither side of + * the join clause. For example, consider A LEFT JOIN B ON A.x = B.x + * AND A.x = 1. The clause A.x = 1 will appear in B's joininfo list, + * but overlaps neither side of B. In that case, we just skip this + * join clause, since it doesn't suggest a useful sort order for this + * relation. + */ + if (bms_overlap(relids, restrictinfo->right_ec->ec_relids)) { + useful_eclass_list = list_append_unique_ptr(useful_eclass_list, restrictinfo->right_ec); + } else if (bms_overlap(relids, restrictinfo->left_ec->ec_relids)) { + useful_eclass_list = list_append_unique_ptr(useful_eclass_list, restrictinfo->left_ec); + } + } + + return useful_eclass_list; +} + +/* + * get_useful_pathkeys_for_relation + * Determine which orderings of a relation might be useful. + * + * Getting data in sorted order can be useful either because the requested + * order matches the final output ordering for the overall query we're + * planning, or because it enables an efficient merge join. Here, we try + * to figure out which pathkeys to consider. + */ +static List *get_useful_pathkeys_for_relation(PlannerInfo *root, RelOptInfo *rel) +{ + List *useful_pathkeys_list = NIL; + List *useful_eclass_list = NIL; + PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *)rel->fdw_private; + EquivalenceClass *query_ec = NULL; + ListCell *lc = NULL; + + /* + * Pushing the query_pathkeys to the remote server is always worth + * considering, because it might let us avoid a local sort. + */ + fpinfo->qp_is_pushdown_safe = false; + if (root->query_pathkeys) { + bool query_pathkeys_ok = true; + + foreach (lc, root->query_pathkeys) { + PathKey *pathkey = (PathKey *)lfirst(lc); + + /* + * The planner and executor don't have any clever strategy for + * taking data sorted by a prefix of the query's pathkeys and + * 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. + */ + if (!is_foreign_pathkey(root, rel, pathkey)) { + query_pathkeys_ok = false; + break; + } + } + + if (query_pathkeys_ok) { + useful_pathkeys_list = list_make1(list_copy(root->query_pathkeys)); + fpinfo->qp_is_pushdown_safe = true; + } + } + + /* + * Even if we're not using remote estimates, having the remote side do the + * sort generally won't be any worse than doing it locally, and it might + * be much better if the remote side can generate data in the right order + * without needing a sort at all. However, what we're going to do next is + * try to generate pathkeys that seem promising for possible merge joins, + * and that's more speculative. A wrong choice might hurt quite a bit, so + * bail out if we can't use remote estimates. + */ + if (!fpinfo->use_remote_estimate) { + return useful_pathkeys_list; + } + + /* Get the list of interesting EquivalenceClasses. */ + useful_eclass_list = get_useful_ecs_for_relation(root, rel); + + /* Extract unique EC for query, if any, so we don't consider it again. */ + if (list_length(root->query_pathkeys) == 1) { + PathKey *query_pathkey = (PathKey *)linitial(root->query_pathkeys); + + query_ec = query_pathkey->pk_eclass; + } + + /* + * As a heuristic, the only pathkeys we consider here are those of length + * one. It's surely possible to consider more, but since each one we + * choose to consider will generate a round-trip to the remote side, we + * need to be a bit cautious here. It would sure be nice to have a local + * cache of information about remote index definitions... + */ + foreach (lc, useful_eclass_list) { + EquivalenceClass *cur_ec = (EquivalenceClass *)lfirst(lc); + PathKey *pathkey = NULL; + + /* If redundant with what we did above, skip it. */ + if (cur_ec == query_ec) { + continue; + } + + /* Can't push down the sort if the EC's opfamily is not shippable. */ + if (!is_builtin(linitial_oid(cur_ec->ec_opfamilies))) { + continue; + } + + /* If no pushable expression for this rel, skip it. */ + if (find_em_for_rel(root, cur_ec, rel) == NULL) { + continue; + } + + /* Looks like we can generate a pathkey, so let's do it. */ + pathkey = + make_canonical_pathkey(root, cur_ec, linitial_oid(cur_ec->ec_opfamilies), BTLessStrategyNumber, false); + useful_pathkeys_list = lappend(useful_pathkeys_list, list_make1(pathkey)); + } + + return useful_pathkeys_list; } /* @@ -470,9 +725,12 @@ static void postgresGetForeignPaths(PlannerInfo *root, RelOptInfo *baserel, Oid */ ForeignPath* path = create_foreignscan_path(root, baserel, fpinfo->startup_cost, fpinfo->total_cost, NIL, /* no pathkeys */ - NULL, NIL); /* no fdw_private list */ + NULL, NULL, NIL); /* no fdw_private list */ add_path(root, baserel, (Path *)path); + /* Add paths with pathkeys */ + add_paths_with_pathkeys_for_rel(root, baserel, NULL); + /* * If we're not using remote estimates, stop here. We have no way to * estimate whether any join clauses would be worth sending across, so @@ -545,7 +803,7 @@ static void postgresGetForeignPaths(PlannerInfo *root, RelOptInfo *baserel, Oid Cost total_cost; /* Get a cost estimate from the remote */ - estimate_path_cost_size(root, baserel, param_info->ppi_clauses, &rows, &width, &startup_cost, &total_cost); + estimate_path_cost_size(root, baserel, param_info->ppi_clauses, NIL, NULL, &rows, &width, &startup_cost, &total_cost); /* * ppi_rows currently won't get looked at by anything, but still we @@ -556,7 +814,7 @@ static void postgresGetForeignPaths(PlannerInfo *root, RelOptInfo *baserel, Oid /* Make the path */ path = create_foreignscan_path(root, baserel, startup_cost, total_cost, NIL, /* no pathkeys */ - param_info->ppi_req_outer, NIL); /* no fdw_private list */ + param_info->ppi_req_outer, NULL, NIL); /* no fdw_private list */ add_path(root, baserel, (Path *)path); } } @@ -565,55 +823,161 @@ static void postgresGetForeignPaths(PlannerInfo *root, RelOptInfo *baserel, Oid * postgresGetForeignPlan * Create ForeignScan plan node which implements selected best path */ -static ForeignScan *postgresGetForeignPlan(PlannerInfo *root, RelOptInfo *baserel, Oid foreigntableid, - ForeignPath *best_path, List *tlist, List *scan_clauses) +static ForeignScan *postgresGetForeignPlan(PlannerInfo *root, RelOptInfo *foreignrel, Oid foreigntableid, + ForeignPath *best_path, List *tlist, List *scan_clauses, Plan *outer_plan) { - PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *)baserel->fdw_private; - Index scan_relid = baserel->relid; - List *remote_conds = NIL; + PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *)foreignrel->fdw_private; + Index scan_relid; + List *fdw_private = NIL; + List *remote_exprs = NIL; List *local_exprs = NIL; List *params_list = NIL; + List *fdw_scan_tlist = NIL; + List *fdw_recheck_quals = NIL; List *retrieved_attrs = NIL; StringInfoData sql; + bool has_final_sort = false; + bool has_limit = false; ListCell *lc = NULL; /* - * Separate the scan_clauses into those that can be executed remotely and - * those that can't. baserestrictinfo clauses that were previously - * determined to be safe or unsafe by classifyConditions are shown in - * fpinfo->remote_conds and fpinfo->local_conds. Anything else in the - * scan_clauses list will be a join clause, which we have to check for - * remote-safety. - * - * Note: the join clauses we see here should be the exact same ones - * previously examined by postgresGetForeignPaths. Possibly it'd be worth - * passing forward the classification work done then, rather than - * repeating it here. - * - * This code must match "extract_actual_clauses(scan_clauses, false)" - * except for the additional decision about remote versus local execution. - * Note however that we only strip the RestrictInfo nodes from the - * local_exprs list, since appendWhereClause expects a list of - * RestrictInfos. + * Get FDW private data created by postgresGetForeignUpperPaths(), if any. */ - foreach (lc, scan_clauses) { - RestrictInfo *rinfo = (RestrictInfo *)lfirst(lc); + if (best_path->fdw_private) { + has_final_sort = boolVal(list_nth(best_path->fdw_private, FdwPathPrivateHasFinalSort)); + has_limit = boolVal(list_nth(best_path->fdw_private, FdwPathPrivateHasLimit)); + } - Assert(IsA(rinfo, RestrictInfo)); + if (IS_SIMPLE_REL(foreignrel)) { + /* + * For base relations, set scan_relid as the relid of the relation. + */ + scan_relid = foreignrel->relid; - /* Ignore any pseudoconstants, they're dealt with elsewhere */ - if (rinfo->pseudoconstant) { - continue; + /* + * In a base-relation scan, we must apply the given scan_clauses. + * + * Separate the scan_clauses into those that can be executed remotely + * and those that can't. baserestrictinfo clauses that were + * previously determined to be safe or unsafe by classifyConditions + * are found in fpinfo->remote_conds and fpinfo->local_conds. Anything + * else in the scan_clauses list will be a join clause, which we have + * to check for remote-safety. + * + * Note: the join clauses we see here should be the exact same ones + * previously examined by postgresGetForeignPaths. Possibly it'd be + * worth passing forward the classification work done then, rather + * than repeating it here. + * + * This code must match "extract_actual_clauses(scan_clauses, false)" + * except for the additional decision about remote versus local + * execution. + */ + foreach (lc, scan_clauses) { + RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc); + + /* Ignore any pseudoconstants, they're dealt with elsewhere */ + if (rinfo->pseudoconstant) { + continue; + } + + if (list_member_ptr(fpinfo->remote_conds, rinfo)) { + remote_exprs = lappend(remote_exprs, rinfo->clause); + } else if (list_member_ptr(fpinfo->local_conds, rinfo)) { + local_exprs = lappend(local_exprs, rinfo->clause); + } else if (is_foreign_expr(root, foreignrel, rinfo->clause)) { + remote_exprs = lappend(remote_exprs, rinfo->clause); + } else { + local_exprs = lappend(local_exprs, rinfo->clause); + } } - if (list_member_ptr(fpinfo->remote_conds, rinfo)) { - remote_conds = lappend(remote_conds, rinfo); - } else if (list_member_ptr(fpinfo->local_conds, rinfo)) { - local_exprs = lappend(local_exprs, rinfo->clause); - } else if (is_foreign_expr(root, baserel, rinfo->clause)) { - remote_conds = lappend(remote_conds, rinfo); - } else { - local_exprs = lappend(local_exprs, rinfo->clause); + /* + * For a base-relation scan, we have to support EPQ recheck, which + * should recheck all the remote quals. + */ + fdw_recheck_quals = remote_exprs; + } else { + /* + * Join relation or upper relation - set scan_relid to 0. + */ + scan_relid = 0; + + /* + * For a join rel, baserestrictinfo is NIL and we are not considering + * parameterization right now, so there should be no scan_clauses for + * a joinrel or an upper rel either. + */ + Assert(!scan_clauses); + + /* + * Instead we get the conditions to apply from the fdw_private + * structure. + */ + remote_exprs = extract_actual_clauses(fpinfo->remote_conds, false); + local_exprs = extract_actual_clauses(fpinfo->local_conds, false); + + /* + * We leave fdw_recheck_quals empty in this case, since we never need + * to apply EPQ recheck clauses. In the case of a joinrel, EPQ + * recheck is handled elsewhere --- see postgresGetForeignJoinPaths(). + * If we're planning an upperrel (ie, remote grouping or aggregation) + * then there's no EPQ to do because SELECT FOR UPDATE wouldn't be + * allowed, and indeed we *can't* put the remote clauses into + * fdw_recheck_quals because the unaggregated Vars won't be available + * locally. + */ + + /* Build the list of columns to be fetched from the foreign server. */ + fdw_scan_tlist = build_tlist_to_deparse(foreignrel); + + /* + * Ensure that the outer plan produces a tuple whose descriptor + * matches our scan tuple slot. Also, remove the local conditions + * from outer plan's quals, lest they be evaluated twice, once by the + * local plan and once by the scan. + */ + if (outer_plan) { + ListCell *lc = NULL; + + /* + * Right now, we only consider grouping and aggregation beyond + * joins. Queries involving aggregates or grouping do not require + * EPQ mechanism, hence should not have an outer plan here. + */ + Assert(!IS_UPPER_REL(foreignrel)); + + /* + * First, update the plan's qual list if possible. In some cases + * the quals might be enforced below the topmost plan level, in + * which case we'll fail to remove them; it's not worth working + * harder than this. + */ + foreach (lc, local_exprs) { + Node *qual = (Node *)lfirst(lc); + + outer_plan->qual = list_delete(outer_plan->qual, qual); + + /* + * For an inner join the local conditions of foreign scan plan + * can be part of the joinquals as well. (They might also be + * in the mergequals or hashquals, but we can't touch those + * without breaking the plan.) + */ + if (IsA(outer_plan, NestLoop) || IsA(outer_plan, MergeJoin) || IsA(outer_plan, HashJoin)) { + Join *join_plan = (Join *)outer_plan; + + if (join_plan->jointype == JOIN_INNER) { + join_plan->joinqual = list_delete(join_plan->joinqual, qual); + } + } + } + + /* + * Now fix the subplan's tlist --- this might result in inserting + * a Result node atop the plan tree. + */ + outer_plan = change_plan_targetlist(root, outer_plan, fdw_scan_tlist); } } @@ -622,83 +986,94 @@ static ForeignScan *postgresGetForeignPlan(PlannerInfo *root, RelOptInfo *basere * expressions to be sent as parameters. */ initStringInfo(&sql); - deparseSelectSql(&sql, root, baserel, fpinfo->attrs_used, &retrieved_attrs); - if (remote_conds) { - appendWhereClause(&sql, root, baserel, remote_conds, true, ¶ms_list); - } + deparseSelectStmtForRel(&sql, root, foreignrel, fdw_scan_tlist, remote_exprs, best_path->path.pathkeys, + has_final_sort, has_limit, false, &retrieved_attrs, ¶ms_list); - /* - * Add FOR UPDATE/SHARE if appropriate. We apply locking during the - * initial row fetch, rather than later on as is done for local tables. - * The extra roundtrips involved in trying to duplicate the local - * semantics exactly don't seem worthwhile (see also comments for - * RowMarkType). - * - * Note: because we actually run the query as a cursor, this assumes that - * DECLARE CURSOR ... FOR UPDATE is supported, which it isn't before 8.3. - */ - if (baserel->relid == (unsigned int)linitial2_int(root->parse->resultRelations) && - (root->parse->commandType == CMD_UPDATE || root->parse->commandType == CMD_DELETE)) { - /* Relation is UPDATE/DELETE target, so use FOR UPDATE */ - appendStringInfoString(&sql, " FOR UPDATE"); - } else { - RowMarkClause *rc = get_parse_rowmark(root->parse, baserel->relid); - - if (rc) { - /* - * Relation is specified as a FOR UPDATE/SHARE target, so handle - * that. - * - * For now, just ignore any [NO] KEY specification, since (a) it's - * not clear what that means for a remote table that we don't have - * complete information about, and (b) it wouldn't work anyway on - * older remote servers. Likewise, we don't worry about NOWAIT. - */ - switch (rc->strength) { - case LCS_FORKEYSHARE: - case LCS_FORSHARE: - appendStringInfoString(&sql, " FOR SHARE"); - break; - case LCS_FORNOKEYUPDATE: - case LCS_FORUPDATE: - appendStringInfoString(&sql, " FOR UPDATE"); - break; - default: - ereport(ERROR, (errmsg("unknown lock type: %d", rc->strength))); - break; - } - } - } + /* Remember remote_exprs for possible use by postgresPlanDirectModify */ + fpinfo->final_remote_exprs = remote_exprs; /* * Build the fdw_private list that will be available to the executor. - * Items in the list must match enum FdwScanPrivateIndex, above. + * Items in the list must match order in enum FdwScanPrivateIndex. */ - List* fdw_private = list_make2(makeString(sql.data), retrieved_attrs); + fdw_private = list_make3(makeString(sql.data), retrieved_attrs, makeInteger(fpinfo->fetch_size)); + if (IS_JOIN_REL(foreignrel) || IS_UPPER_REL(foreignrel)) { + fdw_private = lappend(fdw_private, makeString(fpinfo->relation_name)); + } /* - * Create the ForeignScan node from target list, local filtering - * expressions, remote parameter expressions, and FDW private information. + * Create the ForeignScan node for the given relation. * * Note that the remote parameter expressions are stored in the fdw_exprs * field of the finished plan node; we can't keep them in private state * because then they wouldn't be subject to later planner processing. */ - return make_foreignscan(tlist, local_exprs, scan_relid, params_list, fdw_private); + return make_foreignscan(tlist, local_exprs, scan_relid, params_list, fdw_private, fdw_scan_tlist, fdw_recheck_quals, + outer_plan); +} + +/* + * Construct a tuple descriptor for the scan tuples handled by a foreign join. + */ +static TupleDesc get_tupdesc_for_join_scan_tuples(ForeignScanState *node) +{ + ForeignScan *fsplan = (ForeignScan *)node->ss.ps.plan; + EState *estate = node->ss.ps.state; + TupleDesc tupdesc; + + /* + * The core code has already set up a scan tuple slot based on + * fsplan->fdw_scan_tlist, and this slot's tupdesc is mostly good enough, + * but there's one case where it isn't. If we have any whole-row row + * identifier Vars, they may have vartype RECORD, and we need to replace + * that with the associated table's actual composite type. This ensures + * that when we read those ROW() expression values from the remote server, + * we can convert them to a composite type the local server knows. + */ + tupdesc = CreateTupleDescCopy(node->ss.ss_ScanTupleSlot->tts_tupleDescriptor); + for (int i = 0; i < tupdesc->natts; i++) { + Form_pg_attribute att = TupleDescAttr(tupdesc, i); + Var *var = NULL; + RangeTblEntry *rte = NULL; + Oid reltype; + + /* Nothing to do if it's not a generic RECORD attribute */ + if (att->atttypid != RECORDOID || att->atttypmod >= 0) { + continue; + } + + /* + * If we can't identify the referenced table, do nothing. This'll + * likely lead to failure later, but perhaps we can muddle through. + */ + var = (Var *)list_nth_node(TargetEntry, fsplan->fdw_scan_tlist, i)->expr; + if (!IsA(var, Var) || var->varattno != 0) { + continue; + } + rte = (RangeTblEntry*)list_nth(estate->es_range_table, var->varno - 1); + if (rte->rtekind != RTE_RELATION) { + continue; + } + reltype = get_rel_type_id(rte->relid); + if (!OidIsValid(reltype)) { + continue; + } + att->atttypid = reltype; + /* shouldn't need to change anything else */ + } + return tupdesc; } /* * postgresBeginForeignScan - * Initiate an executor scan of a foreign openGauss table. + * Initiate an executor scan of a foreign PostgreSQL table. */ static void postgresBeginForeignScan(ForeignScanState *node, int eflags) { ForeignScan *fsplan = (ForeignScan *)node->ss.ps.plan; EState *estate = node->ss.ps.state; - Oid userid; + PgFdwScanState *fsstate = NULL; int numParams; - int i; - ListCell *lc = NULL; /* * Do nothing in EXPLAIN (no ANALYZE) case. node->fdw_state stays NULL. @@ -710,27 +1085,14 @@ static void postgresBeginForeignScan(ForeignScanState *node, int eflags) /* * We'll save private state in node->fdw_state. */ - PgFdwScanState* fsstate = (PgFdwScanState *)palloc0(sizeof(PgFdwScanState)); + fsstate = (PgFdwScanState *)palloc0(sizeof(PgFdwScanState)); node->fdw_state = (void *)fsstate; - /* - * Identify which user to do the remote access as. This should match what - * ExecCheckRTEPerms() does. - */ - RangeTblEntry* rte = rt_fetch(fsplan->scan.scanrelid, estate->es_range_table); - userid = rte->checkAsUser ? rte->checkAsUser : GetUserId(); - - /* Get info about foreign table. */ - fsstate->rel = node->ss.ss_currentRelation; - ForeignTable* table = GetForeignTable(RelationGetRelid(fsstate->rel)); - ForeignServer* server = GetForeignServer(table->serverid); - UserMapping* user = GetUserMapping(userid, server->serverid); - /* * Get connection to the foreign server. Connection manager will * establish new connection if necessary. */ - fsstate->conn = GetConnection(server, user, false); + fsstate->conn = GetConnectionByFScanState(node, false); /* Assign a unique ID for my cursor */ fsstate->cursor_number = GetCursorNumber(fsstate->conn); @@ -739,49 +1101,35 @@ static void postgresBeginForeignScan(ForeignScanState *node, int eflags) /* Get private info created by planner functions. */ fsstate->query = strVal(list_nth(fsplan->fdw_private, FdwScanPrivateSelectSql)); fsstate->retrieved_attrs = (List *)list_nth(fsplan->fdw_private, FdwScanPrivateRetrievedAttrs); + fsstate->fetch_size = intVal(list_nth(fsplan->fdw_private, FdwScanPrivateFetchSize)); /* Create contexts for batches of tuples and per-tuple temp workspace. */ - fsstate->batch_cxt = AllocSetContextCreate(estate->es_query_cxt, "postgres_fdw tuple data", - ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE); + fsstate->batch_cxt = AllocSetContextCreate(estate->es_query_cxt, "postgres_fdw tuple data", ALLOCSET_DEFAULT_SIZES); fsstate->temp_cxt = AllocSetContextCreate(estate->es_query_cxt, "postgres_fdw temporary data", ALLOCSET_SMALL_MINSIZE, ALLOCSET_SMALL_INITSIZE, ALLOCSET_SMALL_MAXSIZE); - /* Get info we'll need for input data conversion. */ - fsstate->attinmeta = TupleDescGetAttInMetadata(RelationGetDescr(fsstate->rel)); - - /* Prepare for output conversion of parameters used in remote query. */ - numParams = list_length(fsplan->fdw_exprs); - fsstate->numParams = numParams; - fsstate->param_flinfo = (FmgrInfo *)palloc0(sizeof(FmgrInfo) * numParams); - - i = 0; - foreach (lc, fsplan->fdw_exprs) { - Node *param_expr = (Node *)lfirst(lc); - Oid typefnoid; - bool isvarlena; - - getTypeOutputInfo(exprType(param_expr), &typefnoid, &isvarlena); - fmgr_info(typefnoid, &fsstate->param_flinfo[i]); - i++; + /* + * Get info we'll need for converting data fetched from the foreign server + * into local representation and error reporting during that process. + */ + if (fsplan->scan.scanrelid > 0) { + fsstate->rel = node->ss.ss_currentRelation; + fsstate->tupdesc = RelationGetDescr(fsstate->rel); + } else { + fsstate->rel = NULL; + fsstate->tupdesc = get_tupdesc_for_join_scan_tuples(node); } - /* - * Prepare remote-parameter expressions for evaluation. (Note: in - * practice, we expect that all these expressions will be just Params, so - * we could possibly do something more efficient than using the full - * expression-eval machinery for this. But probably there would be little - * benefit, and it'd require postgres_fdw to know more than is desirable - * about Param evaluation.) - */ - fsstate->param_exprs = (List *)ExecInitExpr((Expr *)fsplan->fdw_exprs, (PlanState *)node); + fsstate->attinmeta = TupleDescGetAttInMetadata(fsstate->tupdesc); /* - * Allocate buffer for text form of query parameters, if any. + * Prepare for processing of parameters used in remote query, if any. */ + numParams = list_length(fsplan->fdw_exprs); + fsstate->numParams = numParams; if (numParams > 0) { - fsstate->param_values = (const char **)palloc0(numParams * sizeof(char *)); - } else { - fsstate->param_values = NULL; + prepare_query_params((PlanState *)node, fsplan->fdw_exprs, numParams, &fsstate->param_flinfo, + &fsstate->param_exprs, &fsstate->param_values); } } @@ -832,7 +1180,7 @@ static TupleTableSlot *postgresIterateForeignScan(ForeignScanState *node) static void postgresReScanForeignScan(ForeignScanState *node) { PgFdwScanState *fsstate = (PgFdwScanState *)node->fdw_state; - char sql[64]; + char sql[64] = {'\0'}; int rc; /* If we haven't created the cursor yet, nothing to do. */ @@ -922,6 +1270,17 @@ static void postgresAddForeignUpdateTargets(Query *parsetree, RangeTblEntry *tar /* ... and add it to the query's targetlist */ parsetree->targetList = lappend(parsetree->targetList, tle); + + /* + * Because of partition table, ctid alone cannot determine a tuple, and tableoid is required. + * Otherwise, the tuples that should not be operated will be operated. + * Foreign table can not distinguish the type of remote table at this time, so we always add it. + */ + Var* var2 = makeVar((Index)linitial_int(parsetree->resultRelations), TableOidAttributeNumber, + OIDOID, -1, InvalidOid, 0); + const char *attrname2 = "tableoid"; + TargetEntry* tle2 = makeTargetEntry((Expr *)var2, list_length(parsetree->targetList) + 1, pstrdup(attrname2), true); + parsetree->targetList = lappend(parsetree->targetList, tle2); } /* @@ -1058,13 +1417,13 @@ static void postgresBeginForeignModify(ModifyTableState *mtstate, ResultRelInfo static PgFdwModifyState *createForeignModify(EState *estate, RangeTblEntry *rte, ResultRelInfo *resultRelInfo, CmdType operation, Plan *subplan, char *query, List *target_attrs, bool has_returning, List *retrieved_attrs) { - PgFdwModifyState *fmstate; + PgFdwModifyState *fmstate = NULL; Relation rel = resultRelInfo->ri_RelationDesc; TupleDesc tupdesc = RelationGetDescr(rel); Oid userid; AttrNumber n_params; Oid typefnoid; - bool isvarlena; + bool isvarlena = false; ListCell *lc = NULL; /* Begin constructing PgFdwModifyState. */ @@ -1102,23 +1461,31 @@ static PgFdwModifyState *createForeignModify(EState *estate, RangeTblEntry *rte, } /* Prepare for output conversion of parameters used in prepared stmt. */ - n_params = list_length(fmstate->target_attrs) + 1; + n_params = list_length(fmstate->target_attrs) + 2; // + ctid and tableoid fmstate->p_flinfo = (FmgrInfo *)palloc0(sizeof(FmgrInfo) * n_params); fmstate->p_nums = 0; if (operation == CMD_UPDATE || operation == CMD_DELETE) { Assert(subplan != NULL); - /* Find the ctid resjunk column in the subplan's result */ + /* Find the ctid and tableoid resjunk column in the subplan's result */ fmstate->ctidAttno = ExecFindJunkAttributeInTlist(subplan->targetlist, "ctid"); if (!AttributeNumberIsValid(fmstate->ctidAttno)) { elog(ERROR, "could not find junk ctid column"); } + fmstate->tboidAttno = ExecFindJunkAttributeInTlist(subplan->targetlist, "tableoid"); + if (!AttributeNumberIsValid(fmstate->ctidAttno)) { + elog(ERROR, "could not find junk tableoid column"); + } - /* First transmittable parameter will be ctid */ + /* First and second transmittable parameter will be ctid and tableoid */ getTypeOutputInfo(TIDOID, &typefnoid, &isvarlena); fmgr_info(typefnoid, &fmstate->p_flinfo[fmstate->p_nums]); fmstate->p_nums++; + + getTypeOutputInfo(OIDOID, &typefnoid, &isvarlena); + fmgr_info(typefnoid, &fmstate->p_flinfo[fmstate->p_nums]); + fmstate->p_nums++; } if (operation == CMD_INSERT || operation == CMD_UPDATE) { @@ -1181,7 +1548,7 @@ static TupleTableSlot *postgresExecForeignInsert(EState *estate, ResultRelInfo * } /* Convert parameters needed by prepared statement to text form */ - const char **p_values = convert_prep_stmt_params(fmstate, NULL, slot); + const char **p_values = convert_prep_stmt_params(fmstate, NULL, NULL, slot); /* * Execute the prepared statement. @@ -1228,7 +1595,7 @@ static TupleTableSlot *postgresExecForeignUpdate(EState *estate, ResultRelInfo * TupleTableSlot *planSlot) { PgFdwModifyState *fmstate = (PgFdwModifyState *)resultRelInfo->ri_FdwState; - Datum datum; + Datum citd_datum, tboidd_atum; bool isNull = false; int n_rows; @@ -1237,15 +1604,21 @@ static TupleTableSlot *postgresExecForeignUpdate(EState *estate, ResultRelInfo * prepare_foreign_modify(fmstate); } - /* Get the ctid that was passed up as a resjunk column */ - datum = ExecGetJunkAttribute(planSlot, fmstate->ctidAttno, &isNull); + /* Get the ctid and tableoid that was passed up as a resjunk column */ + citd_datum = ExecGetJunkAttribute(planSlot, fmstate->ctidAttno, &isNull); /* shouldn't ever get a null result... */ if (isNull) { elog(ERROR, "ctid is NULL"); } + tboidd_atum = ExecGetJunkAttribute(planSlot, fmstate->tboidAttno, &isNull); + /* shouldn't ever get a null result... */ + if (isNull) { + elog(ERROR, "tableoid is NULL"); + } /* Convert parameters needed by prepared statement to text form */ - const char **p_values = convert_prep_stmt_params(fmstate, (ItemPointer)DatumGetPointer(datum), slot); + const char **p_values = convert_prep_stmt_params(fmstate, (ItemPointer)DatumGetPointer(citd_datum), + (ItemPointer)DatumGetPointer(tboidd_atum), slot); /* * Execute the prepared statement. @@ -1292,7 +1665,7 @@ static TupleTableSlot *postgresExecForeignDelete(EState *estate, ResultRelInfo * TupleTableSlot *planSlot) { PgFdwModifyState *fmstate = (PgFdwModifyState *)resultRelInfo->ri_FdwState; - Datum datum; + Datum citd_datum, tboidd_atum; bool isNull = false; int n_rows; @@ -1302,14 +1675,20 @@ static TupleTableSlot *postgresExecForeignDelete(EState *estate, ResultRelInfo * } /* Get the ctid that was passed up as a resjunk column */ - datum = ExecGetJunkAttribute(planSlot, fmstate->ctidAttno, &isNull); + citd_datum = ExecGetJunkAttribute(planSlot, fmstate->ctidAttno, &isNull); /* shouldn't ever get a null result... */ if (isNull) { elog(ERROR, "ctid is NULL"); } + tboidd_atum = ExecGetJunkAttribute(planSlot, fmstate->tboidAttno, &isNull); + /* shouldn't ever get a null result... */ + if (isNull) { + elog(ERROR, "tableoid is NULL"); + } /* Convert parameters needed by prepared statement to text form */ - const char **p_values = convert_prep_stmt_params(fmstate, (ItemPointer)DatumGetPointer(datum), NULL); + const char **p_values = convert_prep_stmt_params(fmstate, (ItemPointer)DatumGetPointer(citd_datum), + (ItemPointer)DatumGetPointer(tboidd_atum), NULL); /* * Execute the prepared statement. @@ -1363,7 +1742,7 @@ static void postgresEndForeignModify(EState *estate, ResultRelInfo *resultRelInf /* If we created a prepared statement, destroy it */ if (fmstate->p_name) { - char sql[64]; + char sql[64] = {'\0'}; int rc = snprintf_s(sql, sizeof(sql), sizeof(sql) - 1, "DEALLOCATE %s", fmstate->p_name); securec_check_ss(rc, "", ""); @@ -1431,13 +1810,93 @@ static int postgresIsForeignRelUpdatable(Relation rel) */ static void postgresExplainForeignScan(ForeignScanState *node, ExplainState *es) { + ForeignScan *plan = castNode(ForeignScan, node->ss.ps.plan); + List *fdw_private = plan->fdw_private; + + /* + * Identify foreign scans that are really joins or upper relations. The + * input looks something like "(1) LEFT JOIN (2)", and we must replace the + * digit string(s), which are RT indexes, with the correct relation names. + * We do that here, not when the plan is created, because we can't know + * what aliases ruleutils.c will assign at plan creation time. + */ + if (list_length(fdw_private) > FdwScanPrivateRelations) { + StringInfo relations; + char *rawrelations = NULL; + char *ptr = NULL; + int minrti, rtoffset; + + rawrelations = strVal(list_nth(fdw_private, FdwScanPrivateRelations)); + + /* + * A difficulty with using a string representation of RT indexes is + * that setrefs.c won't update the string when flattening the + * rangetable. To find out what rtoffset was applied, identify the + * minimum RT index appearing in the string and compare it to the + * minimum member of plan->fs_relids. (We expect all the relids in + * the join will have been offset by the same amount; the Asserts + * below should catch it if that ever changes.) + */ + minrti = INT_MAX; + ptr = rawrelations; + while (*ptr) { + if (isdigit((unsigned char)*ptr)) { + int rti = strtol(ptr, &ptr, 10); + + if (rti < minrti) { + minrti = rti; + } + } else { + ptr++; + } + } + rtoffset = bms_next_member(plan->fs_relids, -1) - minrti; + + /* Now we can translate the string */ + relations = makeStringInfo(); + ptr = rawrelations; + while (*ptr) { + if (isdigit((unsigned char)*ptr)) { + int rti = strtol(ptr, &ptr, 10); + RangeTblEntry *rte = NULL; + char *relname = NULL; + char *refname = NULL; + + rti += rtoffset; + Assert(bms_is_member(rti, plan->fs_relids)); + rte = rt_fetch(rti, es->rtable); + Assert(rte->rtekind == RTE_RELATION); + /* This logic should agree with explain.c's ExplainTargetRel */ + relname = get_rel_name(rte->relid); + if (es->verbose) { + char *name_space = get_namespace_name_or_temp(get_rel_namespace(rte->relid)); + appendStringInfo(relations, "%s.%s", quote_identifier(name_space), quote_identifier(relname)); + } else { + appendStringInfoString(relations, quote_identifier(relname)); + } + refname = rte->eref->aliasname; + if (strcmp(refname, relname) != 0) { + appendStringInfo(relations, " %s", quote_identifier(refname)); + } + } else { + appendStringInfoChar(relations, *ptr++); + } + } + ExplainPropertyText("Relations", relations->data, es); + } + + /* + * Add remote query, when VERBOSE option is specified. + */ if (es->verbose) { - List* fdw_private = ((ForeignScan *)node->ss.ps.plan)->fdw_private; - char* sql = strVal(list_nth(fdw_private, FdwScanPrivateSelectSql)); + char *sql = NULL; + + sql = strVal(list_nth(fdw_private, FdwScanPrivateSelectSql)); ExplainPropertyText("Remote SQL", sql, es); } } + /* * postgresExplainForeignModify * Produce extra output for EXPLAIN of a ModifyTable on a foreign table @@ -1452,25 +1911,66 @@ static void postgresExplainForeignModify(ModifyTableState *mtstate, ResultRelInf } } +static void postgresExplainForeignScanRemote(ForeignScanState *node, ExplainState *es) +{ + ForeignScan *plan = castNode(ForeignScan, node->ss.ps.plan); + List *fdw_private = plan->fdw_private; + char *sql = strVal(list_nth(fdw_private, FdwScanPrivateSelectSql)); + + /* 1. prepare the explain sql, only support inherit 'verbose' and 'costs' currently. */ + int len = strlen(sql) + 64; // length of explain (..) sql; + char *esql = (char*)palloc(len); + errno_t rc = sprintf_s(esql, len, "EXPLAIN (VERBOSE %s, COSTS %s) %s", + es->verbose ? "ON" : "OFF", + es->costs ? "ON" : "OFF", + sql); + securec_check_ss(rc, "\0", "\0"); + + /* 2. print the explain sql*/ + appendStringInfo(es->es_frs.str, "%s \n", esql); + + /* 3. get and print the plan */ + PGconn *conn = GetConnectionByFScanState(node, false); + PGresult *volatile res = pgfdw_exec_query(conn, esql); + if (PQresultStatus(res) != PGRES_TUPLES_OK) { + appendStringInfo(es->es_frs.str, + "failed to get remote plan, error massage is:\n%s\n", + PQresultErrorField(res, PG_DIAG_MESSAGE_PRIMARY)); + } else { + int numrows = PQntuples(res); + for (int i = 0; i < numrows; i++) { + appendStringInfo(es->es_frs.str, " %s\n", PQgetvalue(res, i, 0)); + } + } + + pfree(esql); + PQclear(res); + ReleaseConnection(conn); +} /* * estimate_path_cost_size - * Get cost and size estimates for a foreign scan + * Get cost and size estimates for a foreign scan on given foreign relation + * either a base relation or a join between foreign relations or an upper + * relation containing foreign relations. * - * We assume that all the baserestrictinfo clauses will be applied, plus - * any join clauses listed in join_conds. + * param_join_conds are the parameterization clauses with outer relations. + * pathkeys specify the expected sort order if any for given path being costed. + * fpextra specifies additional post-scan/join-processing steps such as the + * final sort and the LIMIT restriction. + * + * The function returns the cost and size estimates in p_rows, p_width, + * p_startup_cost and p_total_cost variables. */ -static void estimate_path_cost_size(PlannerInfo *root, RelOptInfo *baserel, List *join_conds, double *p_rows, - int *p_width, Cost *p_startup_cost, Cost *p_total_cost) +static void estimate_path_cost_size(PlannerInfo *root, RelOptInfo *foreignrel, List *param_join_conds, List *pathkeys, + PgFdwPathExtraData *fpextra, double *p_rows, int *p_width, Cost *p_startup_cost, Cost *p_total_cost) { - PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *)baserel->fdw_private; + PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *)foreignrel->fdw_private; double rows; double retrieved_rows; - int width = 0; + int width; Cost startup_cost; Cost total_cost; - Cost run_cost; - Cost cpu_per_tuple; /* * If the table or the server is configured to use remote estimates, @@ -1480,43 +1980,58 @@ static void estimate_path_cost_size(PlannerInfo *root, RelOptInfo *baserel, List * similar to ordinary tables. */ if (fpinfo->use_remote_estimate) { - List *remote_join_conds = NIL; - List *local_join_conds = NIL; + List *remote_param_join_conds = NIL; + List *local_param_join_conds = NIL; StringInfoData sql; - List *retrieved_attrs = NIL; + PGconn *conn = NULL; Selectivity local_sel; QualCost local_cost; + List *fdw_scan_tlist = NIL; + List *remote_conds = NIL; + + /* Required only to be passed to deparseSelectStmtForRel */ + List *retrieved_attrs = NIL; /* - * join_conds might contain both clauses that are safe to send across, - * and clauses that aren't. + * param_join_conds might contain both clauses that are safe to send + * across, and clauses that aren't. */ - classifyConditions(root, baserel, join_conds, &remote_join_conds, &local_join_conds); + classifyConditions(root, foreignrel, param_join_conds, &remote_param_join_conds, &local_param_join_conds); + + /* Build the list of columns to be fetched from the foreign server. */ + if (IS_JOIN_REL(foreignrel) || IS_UPPER_REL(foreignrel)) { + fdw_scan_tlist = build_tlist_to_deparse(foreignrel); + } else { + fdw_scan_tlist = NIL; + } + + /* + * The complete list of remote conditions includes everything from + * baserestrictinfo plus any extra join_conds relevant to this + * particular path. + */ + remote_conds = list_concat(remote_param_join_conds, fpinfo->remote_conds); /* * Construct EXPLAIN query including the desired SELECT, FROM, and - * WHERE clauses. Params and other-relation Vars are replaced by - * dummy values. + * WHERE clauses. Params and other-relation Vars are replaced by dummy + * values, so don't request params_list. */ initStringInfo(&sql); appendStringInfoString(&sql, "EXPLAIN "); - deparseSelectSql(&sql, root, baserel, fpinfo->attrs_used, &retrieved_attrs); - if (fpinfo->remote_conds) { - appendWhereClause(&sql, root, baserel, fpinfo->remote_conds, true, NULL); - } - if (remote_join_conds) { - appendWhereClause(&sql, root, baserel, remote_join_conds, (fpinfo->remote_conds == NIL), NULL); - } + deparseSelectStmtForRel(&sql, root, foreignrel, fdw_scan_tlist, remote_conds, pathkeys, + fpextra ? fpextra->has_final_sort : false, fpextra ? fpextra->has_limit : false, false, &retrieved_attrs, + NULL); /* Get the remote estimate */ - PGconn* conn = GetConnection(fpinfo->server, fpinfo->user, false); + conn = GetConnection(fpinfo->server, fpinfo->user, false); get_remote_estimate(sql.data, conn, &rows, &width, &startup_cost, &total_cost); ReleaseConnection(conn); retrieved_rows = rows; /* Factor in the selectivity of the locally-checked quals */ - local_sel = clauselist_selectivity(root, local_join_conds, baserel->relid, JOIN_INNER, NULL); + local_sel = clauselist_selectivity(root, local_param_join_conds, foreignrel->relid, JOIN_INNER, NULL); local_sel *= fpinfo->local_conds_sel; rows = clamp_row_est(rows * local_sel); @@ -1524,41 +2039,294 @@ static void estimate_path_cost_size(PlannerInfo *root, RelOptInfo *baserel, List /* Add in the eval cost of the locally-checked quals */ startup_cost += fpinfo->local_conds_cost.startup; total_cost += fpinfo->local_conds_cost.per_tuple * retrieved_rows; - cost_qual_eval(&local_cost, local_join_conds, root); + cost_qual_eval(&local_cost, local_param_join_conds, root); startup_cost += local_cost.startup; total_cost += local_cost.per_tuple * retrieved_rows; + + /* + * Add in tlist eval cost for each output row. In case of an + * aggregate, some of the tlist expressions such as grouping + * expressions will be evaluated remotely, so adjust the costs. + */ + if (IS_UPPER_REL(foreignrel)) { + QualCost tlist_cost; + + cost_qual_eval(&tlist_cost, fdw_scan_tlist, root); + startup_cost -= tlist_cost.startup; + total_cost -= tlist_cost.startup; + total_cost -= tlist_cost.per_tuple * rows; + } } else { + Cost run_cost = 0; + /* * We don't support join conditions in this mode (hence, no * parameterized paths can be made). */ - Assert(join_conds == NIL); - - /* Use rows/width estimates made by set_baserel_size_estimates. */ - rows = baserel->rows; - width = baserel->width; + Assert(param_join_conds == NIL); /* - * Back into an estimate of the number of retrieved rows. Just in - * case this is nuts, clamp to at most baserel->tuples. + * We will come here again and again with different set of pathkeys or + * additional post-scan/join-processing steps that caller wants to + * cost. We don't need to calculate the cost/size estimates for the + * underlying scan, join, or grouping each time. Instead, use those + * estimates if we have cached them already. */ - retrieved_rows = clamp_row_est(rows / fpinfo->local_conds_sel); - retrieved_rows = Min(retrieved_rows, baserel->tuples); + if (fpinfo->rel_startup_cost >= 0 && fpinfo->rel_total_cost >= 0) { + Assert(fpinfo->retrieved_rows >= 0); + + rows = fpinfo->rows; + retrieved_rows = fpinfo->retrieved_rows; + width = fpinfo->width; + startup_cost = fpinfo->rel_startup_cost; + run_cost = fpinfo->rel_total_cost - fpinfo->rel_startup_cost; + + /* + * If we estimate the costs of a foreign scan or a foreign join + * with additional post-scan/join-processing steps, the scan or + * join costs obtained from the cache wouldn't yet contain the + * eval costs for the final scan/join target, which would've been + * updated by apply_scanjoin_target_to_paths(); add the eval costs + * now. + */ + } else if (IS_JOIN_REL(foreignrel)) { + PgFdwRelationInfo *fpinfo_i; + PgFdwRelationInfo *fpinfo_o; + QualCost join_cost; + QualCost remote_conds_cost; + double nrows; + + /* Use rows/width estimates made by the core code. */ + rows = foreignrel->rows; + width = foreignrel->width; + + /* For join we expect inner and outer relations set */ + Assert(fpinfo->innerrel && fpinfo->outerrel); + + fpinfo_i = (PgFdwRelationInfo *)fpinfo->innerrel->fdw_private; + fpinfo_o = (PgFdwRelationInfo *)fpinfo->outerrel->fdw_private; + + /* Estimate of number of rows in cross product */ + nrows = fpinfo_i->rows * fpinfo_o->rows; + + /* + * Back into an estimate of the number of retrieved rows. Just in + * case this is nuts, clamp to at most nrows. + */ + retrieved_rows = clamp_row_est(rows / fpinfo->local_conds_sel); + retrieved_rows = Min(retrieved_rows, nrows); + + /* + * The cost of foreign join is estimated as cost of generating + * rows for the joining relations + cost for applying quals on the + * rows. + */ + + /* + * Calculate the cost of clauses pushed down to the foreign server + */ + cost_qual_eval(&remote_conds_cost, fpinfo->remote_conds, root); + /* Calculate the cost of applying join clauses */ + cost_qual_eval(&join_cost, fpinfo->joinclauses, root); + + /* + * Startup cost includes startup cost of joining relations and the + * startup cost for join and other clauses. We do not include the + * startup cost specific to join strategy (e.g. setting up hash + * tables) since we do not know what strategy the foreign server + * is going to use. + */ + startup_cost = fpinfo_i->rel_startup_cost + fpinfo_o->rel_startup_cost; + startup_cost += join_cost.startup; + startup_cost += remote_conds_cost.startup; + startup_cost += fpinfo->local_conds_cost.startup; + + /* + * Run time cost includes: + * + * 1. Run time cost (total_cost - startup_cost) of relations being + * joined + * + * 2. Run time cost of applying join clauses on the cross product + * of the joining relations. + * + * 3. Run time cost of applying pushed down other clauses on the + * result of join + * + * 4. Run time cost of applying nonpushable other clauses locally + * on the result fetched from the foreign server. + */ + run_cost = fpinfo_i->rel_total_cost - fpinfo_i->rel_startup_cost; + run_cost += fpinfo_o->rel_total_cost - fpinfo_o->rel_startup_cost; + run_cost += nrows * join_cost.per_tuple; + nrows = clamp_row_est(nrows * fpinfo->joinclause_sel); + run_cost += nrows * remote_conds_cost.per_tuple; + run_cost += fpinfo->local_conds_cost.per_tuple * retrieved_rows; + } else if (IS_UPPER_REL(foreignrel)) { + RelOptInfo *outerrel = fpinfo->outerrel; + PgFdwRelationInfo *ofpinfo; + AggClauseCosts aggcosts; + double input_rows; + int numGroupCols; + double numGroups = 1; + unsigned int numDatanodes; + + /* The upper relation should have its outer relation set */ + Assert(outerrel); + + /* + * This cost model is mixture of costing done for sorted and + * hashed aggregates in cost_agg(). We are not sure which + * strategy will be considered at remote side, thus for + * simplicity, we put all startup related costs in startup_cost + * and all finalization and run cost are added in total_cost. + */ + ofpinfo = (PgFdwRelationInfo *)outerrel->fdw_private; + + /* Get rows from input rel */ + input_rows = ofpinfo->rows; + + /* Collect statistics about aggregates for estimating costs. */ + MemSet(&aggcosts, 0, sizeof(AggClauseCosts)); + if (root->parse->hasAggs) { + count_agg_clauses(root, (Node*)foreignrel->reltargetlist, &aggcosts); + count_agg_clauses(root, root->parse->havingQual, &aggcosts); + } + + /* Get number of grouping columns and possible number of groups */ + numGroupCols = list_length(root->parse->groupClause); + numDatanodes = ng_get_dest_num_data_nodes(root, foreignrel); + numGroups = estimate_num_groups(root, + get_sortgrouplist_exprs(root->parse->groupClause, fpinfo->grouped_tlist), input_rows, numDatanodes); + + /* + * Get the retrieved_rows and rows estimates. If there are HAVING + * quals, account for their selectivity. + */ + if (root->parse->havingQual) { + /* + * Factor in the selectivity of the remotely-checked quals, + * openGauss not support calc selectivity of agg in having, so just using numGroups + */ + retrieved_rows = numGroups; + /* Factor in the selectivity of the locally-checked quals */ + rows = clamp_row_est(retrieved_rows * fpinfo->local_conds_sel); + } else { + rows = retrieved_rows = numGroups; + } + + /* Use width estimate made by the core code. */ + width = foreignrel->width; + + /* ----- + * Startup cost includes: + * 1. Startup cost for underneath input relation, adjusted for + * tlist replacement by apply_scanjoin_target_to_paths() + * 2. Cost of performing aggregation, per cost_agg() + * ----- + */ + startup_cost = ofpinfo->rel_startup_cost; + startup_cost += aggcosts.transCost.startup; + startup_cost += aggcosts.transCost.per_tuple * input_rows; + startup_cost += aggcosts.finalCost; + startup_cost += (u_sess->attr.attr_sql.cpu_operator_cost * numGroupCols) * input_rows; + + /* ----- + * Run time cost includes: + * 1. Run time cost of underneath input relation, adjusted for + * tlist replacement by apply_scanjoin_target_to_paths() + * 2. Run time cost of performing aggregation, per cost_agg() + * ----- + */ + run_cost = ofpinfo->rel_total_cost - ofpinfo->rel_startup_cost; + run_cost += aggcosts.finalCost * numGroups; + run_cost += u_sess->attr.attr_sql.cpu_tuple_cost * numGroups; + + /* Account for the eval cost of HAVING quals, if any */ + if (root->parse->havingQual) { + QualCost remote_cost; + + /* Add in the eval cost of the remotely-checked quals */ + cost_qual_eval(&remote_cost, fpinfo->remote_conds, root); + startup_cost += remote_cost.startup; + run_cost += remote_cost.per_tuple * numGroups; + /* Add in the eval cost of the locally-checked quals */ + startup_cost += fpinfo->local_conds_cost.startup; + run_cost += fpinfo->local_conds_cost.per_tuple * retrieved_rows; + } + } else { + Cost cpu_per_tuple; + + /* Use rows/width estimates made by set_baserel_size_estimates. */ + rows = foreignrel->rows; + width = foreignrel->width; + + /* + * Back into an estimate of the number of retrieved rows. Just in + * case this is nuts, clamp to at most foreignrel->tuples. + */ + retrieved_rows = clamp_row_est(rows / fpinfo->local_conds_sel); + retrieved_rows = Min(retrieved_rows, foreignrel->tuples); + + /* + * Cost as though this were a seqscan, which is pessimistic. We + * effectively imagine the local_conds are being evaluated + * remotely, too. + */ + startup_cost = 0; + run_cost = 0; + run_cost += u_sess->attr.attr_sql.seq_page_cost * foreignrel->pages; + + startup_cost += foreignrel->baserestrictcost.startup; + cpu_per_tuple = u_sess->attr.attr_sql.cpu_tuple_cost + foreignrel->baserestrictcost.per_tuple; + run_cost += cpu_per_tuple * foreignrel->tuples; + } /* - * Cost as though this were a seqscan, which is pessimistic. We - * effectively imagine the local_conds are being evaluated remotely, - * too. + * Without remote estimates, we have no real way to estimate the cost + * of generating sorted output. It could be free if the query plan + * the remote side would have chosen generates properly-sorted output + * anyway, but in most cases it will cost something. Estimate a value + * high enough that we won't pick the sorted path when the ordering + * isn't locally useful, but low enough that we'll err on the side of + * pushing down the ORDER BY clause when it's useful to do so. */ - startup_cost = 0; - run_cost = 0; - run_cost += u_sess->attr.attr_sql.seq_page_cost * baserel->pages; - - startup_cost += baserel->baserestrictcost.startup; - cpu_per_tuple = u_sess->attr.attr_sql.cpu_tuple_cost + baserel->baserestrictcost.per_tuple; - run_cost += cpu_per_tuple * baserel->tuples; + if (pathkeys != NIL) { + if (IS_UPPER_REL(foreignrel)) { + Assert(foreignrel->reloptkind == RELOPT_UPPER_REL && fpinfo->stage == UPPERREL_GROUP_AGG); + adjust_foreign_grouping_path_cost(root, pathkeys, retrieved_rows, width, fpextra->limit_tuples, + &startup_cost, &run_cost); + } else { + startup_cost *= DEFAULT_FDW_SORT_MULTIPLIER; + run_cost *= DEFAULT_FDW_SORT_MULTIPLIER; + } + } total_cost = startup_cost + run_cost; + + /* Adjust the cost estimates if we have LIMIT */ + if (fpextra && fpextra->has_limit) { + adjust_limit_rows_costs(&rows, &startup_cost, &total_cost, fpextra->offset_est, fpextra->count_est); + retrieved_rows = rows; + } + } + + /* + * Cache the retrieved rows and cost estimates for scans, joins, or + * groupings without any parameterization, pathkeys, or additional + * post-scan/join-processing steps, before adding the costs for + * transferring data from the foreign server. These estimates are useful + * for costing remote joins involving this relation or costing other + * remote operations on this relation such as remote sorts and remote + * LIMIT restrictions, when the costs can not be obtained from the foreign + * server. This function will be called at least once for every foreign + * relation without any parameterization, pathkeys, or additional + * post-scan/join-processing steps. + */ + if (pathkeys == NIL && param_join_conds == NIL && fpextra == NULL) { + fpinfo->retrieved_rows = retrieved_rows; + fpinfo->rel_startup_cost = startup_cost; + fpinfo->rel_total_cost = total_cost; } /* @@ -1572,6 +2340,26 @@ static void estimate_path_cost_size(PlannerInfo *root, RelOptInfo *baserel, List total_cost += fpinfo->fdw_tuple_cost * retrieved_rows; total_cost += u_sess->attr.attr_sql.cpu_tuple_cost * retrieved_rows; + /* + * If we have LIMIT, we should prefer performing the restriction remotely + * rather than locally, as the former avoids extra row fetches from the + * remote that the latter might cause. But since the core code doesn't + * account for such fetches when estimating the costs of the local + * restriction (see create_limit_path()), there would be no difference + * between the costs of the local restriction and the costs of the remote + * restriction estimated above if we don't use remote estimates (except + * for the case where the foreignrel is a grouping relation, the given + * pathkeys is not NIL, and the effects of a bounded sort for that rel is + * accounted for in costing the remote restriction). Tweak the costs of + * the remote restriction to ensure we'll prefer it if LIMIT is a useful + * one. + */ + if (!fpinfo->use_remote_estimate && fpextra && fpextra->has_limit && fpextra->limit_tuples > 0 && + fpextra->limit_tuples < fpinfo->rows) { + Assert(fpinfo->rows > 0); + total_cost -= (total_cost - startup_cost) * 0.05 * (fpinfo->rows - fpextra->limit_tuples) / fpinfo->rows; + } + /* Return results. */ *p_rows = rows; *p_width = width; @@ -1632,6 +2420,40 @@ static void get_remote_estimate(const char *sql, PGconn *conn, double *rows, int PG_END_TRY(); } +/* + * Adjust the cost estimates of a foreign grouping path to include the cost of + * generating properly-sorted output. + */ +static void adjust_foreign_grouping_path_cost(PlannerInfo *root, List *pathkeys, double retrieved_rows, double width, + double limit_tuples, Cost *p_startup_cost, Cost *p_run_cost) +{ + /* + * If the GROUP BY clause isn't sort-able, the plan chosen by the remote + * side is unlikely to generate properly-sorted output, so it would need + * an explicit sort; adjust the given costs with cost_sort(). Likewise, + * if the GROUP BY clause is sort-able but isn't a superset of the given + * pathkeys, adjust the costs with that function. Otherwise, adjust the + * costs by applying the same heuristic as for the scan or join case. + */ + if (!grouping_is_sortable(root->parse->groupClause) || !pathkeys_contained_in(pathkeys, root->group_pathkeys)) { + Path sort_path; /* dummy for result of cost_sort */ + + cost_sort(&sort_path, pathkeys, *p_startup_cost + *p_run_cost, retrieved_rows, width, 0.0, + u_sess->attr.attr_memory.work_mem, limit_tuples, root->glob->vectorized); + + *p_startup_cost = sort_path.startup_cost; + *p_run_cost = sort_path.total_cost - sort_path.startup_cost; + } else { + /* + * The default extra cost seems too large for foreign-grouping cases; + * add 1/4th of that default. + */ + double sort_multiplier = 1.0 + (DEFAULT_FDW_SORT_MULTIPLIER - 1.0) * 0.25; + + *p_startup_cost *= sort_multiplier; + *p_run_cost *= sort_multiplier; + } +} /* * Create cursor for node's query with current parameter values. @@ -1644,6 +2466,7 @@ static void create_cursor(ForeignScanState *node) const char **values = fsstate->param_values; PGconn *conn = fsstate->conn; StringInfoData buf; + PGresult *res = NULL; /* * Construct array of query parameter values in text format. We do the @@ -1651,38 +2474,13 @@ static void create_cursor(ForeignScanState *node) * memory leak over repeated scans. */ if (numParams > 0) { - int nestlevel; - int i; - ListCell *lc = NULL; + MemoryContext oldcontext; - MemoryContext oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory); + oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory); - nestlevel = set_transmission_modes(); + process_query_params(econtext, fsstate->param_flinfo, fsstate->param_exprs, values); - i = 0; - foreach (lc, fsstate->param_exprs) { - ExprState *expr_state = (ExprState *)lfirst(lc); - Datum expr_value; - bool isNull = false; - - /* Evaluate the parameter expression */ - expr_value = ExecEvalExpr(expr_state, econtext, &isNull, NULL); - - /* - * Get string representation of each parameter value by invoking - * type-specific output function, unless the value is null. - */ - if (isNull) { - values[i] = NULL; - } else { - values[i] = OutputFunctionCall(&fsstate->param_flinfo[i], expr_value); - } - i++; - } - - reset_transmission_modes(nestlevel); - - (void)MemoryContextSwitchTo(oldcontext); + MemoryContextSwitchTo(oldcontext); } /* Construct the DECLARE CURSOR command */ @@ -1706,7 +2504,7 @@ static void create_cursor(ForeignScanState *node) * We don't use a PG_TRY block here, so be careful not to throw error * without releasing the PGresult. */ - PGresult* res = pgfdw_get_result(conn, buf.data); + res = pgfdw_get_result(conn, buf.data); if (PQresultStatus(res) != PGRES_COMMAND_OK) { pgfdw_report_error(ERROR, res, conn, true, fsstate->query); } @@ -1731,6 +2529,7 @@ static void fetch_more_data(ForeignScanState *node) { PgFdwScanState *fsstate = (PgFdwScanState *)node->fdw_state; PGresult *volatile res = NULL; + MemoryContext oldcontext; /* * We'll store the tuples in the batch_cxt. First, flush the previous @@ -1738,23 +2537,19 @@ static void fetch_more_data(ForeignScanState *node) */ fsstate->tuples = NULL; MemoryContextReset(fsstate->batch_cxt); - MemoryContext oldcontext = MemoryContextSwitchTo(fsstate->batch_cxt); + oldcontext = MemoryContextSwitchTo(fsstate->batch_cxt); /* PGresult must be released before leaving this function. */ PG_TRY(); { PGconn *conn = fsstate->conn; - char sql[64]; - int fetch_size; int numrows; int i; + char sql[64] = {'\0'}; - /* The fetch size is arbitrary, but shouldn't be enormous. */ - fetch_size = 100; - - int rc = snprintf_s(sql, sizeof(sql), sizeof(sql) - 1, "FETCH %d FROM c%u", - fetch_size, fsstate->cursor_number); - securec_check_ss(rc, "", ""); + /* This is a regular synchronous fetch. */ + errno_t rc = snprintf_s(sql, sizeof(sql), sizeof(sql), "FETCH %d FROM c%u", fsstate->fetch_size, fsstate->cursor_number); + securec_check_ss(rc, "\0", "\0"); res = pgfdw_exec_query(conn, sql); /* On error, report the original query, not the FETCH. */ @@ -1769,8 +2564,10 @@ static void fetch_more_data(ForeignScanState *node) fsstate->next_tuple = 0; for (i = 0; i < numrows; i++) { + Assert(IsA(node->ss.ps.plan, ForeignScan)); + fsstate->tuples[i] = make_tuple_from_result_row(res, i, fsstate->rel, fsstate->attinmeta, - fsstate->retrieved_attrs, fsstate->temp_cxt); + fsstate->retrieved_attrs, node, fsstate->temp_cxt); } /* Update fetch_ct_2 */ @@ -1779,7 +2576,7 @@ static void fetch_more_data(ForeignScanState *node) } /* Must be EOF if we didn't get as many tuples as we asked for. */ - fsstate->eof_reached = (numrows < fetch_size); + fsstate->eof_reached = (numrows < fsstate->fetch_size); PQclear(res); res = NULL; @@ -1793,7 +2590,7 @@ static void fetch_more_data(ForeignScanState *node) } PG_END_TRY(); - (void)MemoryContextSwitchTo(oldcontext); + MemoryContextSwitchTo(oldcontext); } /* @@ -1846,7 +2643,6 @@ void reset_transmission_modes(int nestlevel) static void close_cursor(PGconn *conn, unsigned int cursor_number) { char sql[64]; - int rc = snprintf_s(sql, sizeof(sql), sizeof(sql) - 1, "CLOSE c%u", cursor_number); securec_check_ss(rc, "", ""); @@ -1868,7 +2664,6 @@ static void close_cursor(PGconn *conn, unsigned int cursor_number) static void prepare_foreign_modify(PgFdwModifyState *fmstate) { char prep_name[NAMEDATALEN]; - /* Construct name we'll use for the prepared statement. */ int rc = snprintf_s(prep_name, sizeof(prep_name), sizeof(prep_name) - 1, "pgsql_fdw_prep_%u", GetPrepStmtNumber(fmstate->conn)); @@ -1907,11 +2702,13 @@ static void prepare_foreign_modify(PgFdwModifyState *fmstate) * Create array of text strings representing parameter values * * tupleid is ctid to send, or NULL if none + * tableid is tableoid to send, or NULL if none * slot is slot to get remaining parameters from, or NULL if none * * Data is constructed in temp_cxt; caller should reset that after use. */ -static const char **convert_prep_stmt_params(PgFdwModifyState *fmstate, ItemPointer tupleid, TupleTableSlot *slot) +static const char **convert_prep_stmt_params(PgFdwModifyState *fmstate, ItemPointer tupleid, ItemPointer tableid, + TupleTableSlot *slot) { int pindex = 0; @@ -1925,6 +2722,12 @@ static const char **convert_prep_stmt_params(PgFdwModifyState *fmstate, ItemPoin p_values[pindex] = OutputFunctionCall(&fmstate->p_flinfo[pindex], PointerGetDatum(tupleid)); pindex++; } + /* 2sd parameter should be tableoid, if it's in use */ + if (tupleid != NULL) { + /* don't need set_transmission_modes for TABLEOID output */ + p_values[pindex] = OutputFunctionCall(&fmstate->p_flinfo[pindex], PointerGetDatum(tableid)); + pindex++; + } /* get following parameters from slot */ if (slot != NULL && fmstate->target_attrs != NIL) { @@ -1971,7 +2774,7 @@ static void store_returning_result(PgFdwModifyState *fmstate, TupleTableSlot *sl { HeapTuple newtup; - newtup = make_tuple_from_result_row(res, 0, fmstate->rel, fmstate->attinmeta, fmstate->retrieved_attrs, + newtup = make_tuple_from_result_row(res, 0, fmstate->rel, fmstate->attinmeta, fmstate->retrieved_attrs, NULL, fmstate->temp_cxt); /* tuple will be deleted when it is cleared from the slot */ (void)ExecStoreTuple(newtup, slot, InvalidBuffer, true); @@ -1986,6 +2789,81 @@ static void store_returning_result(PgFdwModifyState *fmstate, TupleTableSlot *sl PG_END_TRY(); } +/* + * Prepare for processing of parameters used in remote query. + */ +static void prepare_query_params(PlanState *node, List *fdw_exprs, int numParams, FmgrInfo **param_flinfo, + List **param_exprs, const char ***param_values) +{ + int i; + ListCell *lc = NULL; + + Assert(numParams > 0); + + /* Prepare for output conversion of parameters used in remote query. */ + *param_flinfo = (FmgrInfo *)palloc0(sizeof(FmgrInfo) * numParams); + + i = 0; + foreach (lc, fdw_exprs) { + Node *param_expr = (Node *)lfirst(lc); + Oid typefnoid; + bool isvarlena; + + getTypeOutputInfo(exprType(param_expr), &typefnoid, &isvarlena); + fmgr_info(typefnoid, &(*param_flinfo)[i]); + i++; + } + + /* + * Prepare remote-parameter expressions for evaluation. (Note: in + * practice, we expect that all these expressions will be just Params, so + * we could possibly do something more efficient than using the full + * expression-eval machinery for this. But probably there would be little + * benefit, and it'd require postgres_fdw to know more than is desirable + * about Param evaluation.) + */ + *param_exprs = ExecInitExprList(fdw_exprs, node); + + /* Allocate buffer for text form of query parameters. */ + *param_values = (const char **)palloc0(numParams * sizeof(char *)); +} + +/* + * Construct array of query parameter values in text format. + */ +static void process_query_params(ExprContext *econtext, FmgrInfo *param_flinfo, List *param_exprs, + const char **param_values) +{ + int i; + ListCell *lc = NULL; + + int nestlevel = set_transmission_modes(); + + i = 0; + foreach (lc, param_exprs) { + ExprState *expr_state = (ExprState *)lfirst(lc); + Datum expr_value; + bool isNull = false; + + /* Evaluate the parameter expression */ + expr_value = ExecEvalExpr(expr_state, econtext, &isNull, NULL); + + /* + * Get string representation of each parameter value by invoking + * type-specific output function, unless the value is null. + */ + if (isNull) { + param_values[i] = NULL; + } else { + param_values[i] = OutputFunctionCall(¶m_flinfo[i], expr_value); + } + + i++; + } + + reset_transmission_modes(nestlevel); +} + /* * postgresAnalyzeForeignTable * Test whether analyzing this foreign table is supported @@ -2117,7 +2995,7 @@ static int postgresAcquireSampleRowsFunc(Relation relation, int elevel, HeapTupl /* Retrieve and process rows a batch at a time. */ for (;;) { - char fetch_sql[64]; + char fetch_sql[64] = {'\0'}; int fetch_size; int numrows; int i; @@ -2239,7 +3117,7 @@ static void analyze_row_processor(PGresult *res, int row, PgFdwAnalyzeState *ast MemoryContext oldcontext = MemoryContextSwitchTo(astate->anl_cxt); astate->rows[pos] = make_tuple_from_result_row(res, row, astate->rel, astate->attinmeta, - astate->retrieved_attrs, astate->temp_cxt); + astate->retrieved_attrs, NULL, astate->temp_cxt); (void)MemoryContextSwitchTo(oldcontext); } @@ -2251,15 +3129,25 @@ static void analyze_row_processor(PGresult *res, int row, PgFdwAnalyzeState *ast * rel is the local representation of the foreign table, attinmeta is * conversion data for the rel's tupdesc, and retrieved_attrs is an * integer list of the table column numbers present in the PGresult. + * fsstate is the ForeignScan plan node's execution state. * temp_context is a working context that can be reset after each tuple. + * + * Note: either rel or fsstate, but not both, can be NULL. rel is NULL + * if we're processing a remote join, while fsstate is NULL in a non-query + * context such as ANALYZE, or if we're processing a non-scan query node. */ static HeapTuple make_tuple_from_result_row(PGresult *res, int row, Relation rel, AttInMetadata *attinmeta, - List *retrieved_attrs, MemoryContext temp_context) + List *retrieved_attrs, ForeignScanState *fsstate, MemoryContext temp_context) { - TupleDesc tupdesc = RelationGetDescr(rel); + HeapTuple tuple; + TupleDesc tupdesc; + Datum *values = NULL; + bool *nulls = NULL; ItemPointer ctid = NULL; + Oid oid = InvalidOid, tableoid = InvalidOid; ConversionLocation errpos; ErrorContextCallback errcallback; + MemoryContext oldcontext; ListCell *lc = NULL; int j; @@ -2270,19 +3158,31 @@ static HeapTuple make_tuple_from_result_row(PGresult *res, int row, Relation rel * This cleans up not only the data we have direct access to, but any * cruft the I/O functions might leak. */ - MemoryContext oldcontext = MemoryContextSwitchTo(temp_context); + oldcontext = MemoryContextSwitchTo(temp_context); - Datum* values = (Datum *)palloc0(tupdesc->natts * sizeof(Datum)); - bool* nulls = (bool *)palloc(tupdesc->natts * sizeof(bool)); + /* + * Get the tuple descriptor for the row. Use the rel's tupdesc if rel is + * provided, otherwise look to the scan node's ScanTupleSlot. + */ + if (rel) { + tupdesc = RelationGetDescr(rel); + } else { + Assert(fsstate); + tupdesc = fsstate->ss.ss_ScanTupleSlot->tts_tupleDescriptor; + } + + values = (Datum *)palloc0(tupdesc->natts * sizeof(Datum)); + nulls = (bool *)palloc(tupdesc->natts * sizeof(bool)); /* Initialize to nulls for any columns not present in result */ - int rc = memset_s(nulls, tupdesc->natts * sizeof(bool), true, tupdesc->natts * sizeof(bool)); - securec_check(rc, "", ""); + errno_t rc = memset_s(nulls, tupdesc->natts * sizeof(bool), true, tupdesc->natts * sizeof(bool)); + securec_check(rc, "\0", "\0"); /* * Set up and install callback to report where conversion error occurs. */ - errpos.rel = rel; errpos.cur_attno = 0; + errpos.rel = rel; + errpos.fsstate = fsstate; errcallback.callback = conversion_error_callback; errcallback.arg = (void *)&errpos; errcallback.previous = t_thrd.log_cxt.error_context_stack; @@ -2303,23 +3203,41 @@ static HeapTuple make_tuple_from_result_row(PGresult *res, int row, Relation rel valstr = PQgetvalue(res, row, j); } - /* convert value to internal representation */ + /* + * convert value to internal representation + * + * Note: we ignore system columns other than ctid and oid in result + */ + errpos.cur_attno = i; if (i > 0) { /* ordinary column */ Assert(i <= tupdesc->natts); nulls[i - 1] = (valstr == NULL); /* Apply the input function even to nulls, to support domains */ - errpos.cur_attno = i; values[i - 1] = InputFunctionCall(&attinmeta->attinfuncs[i - 1], valstr, attinmeta->attioparams[i - 1], attinmeta->atttypmods[i - 1]); - errpos.cur_attno = 0; } else if (i == SelfItemPointerAttributeNumber) { - /* ctid --- note we ignore any other system column in result */ + /* ctid */ if (valstr != NULL) { - Datum datum = DirectFunctionCall1(tidin, CStringGetDatum(valstr)); + Datum datum; + + datum = DirectFunctionCall1(tidin, CStringGetDatum(valstr)); ctid = (ItemPointer)DatumGetPointer(datum); } + } else if (i == ObjectIdAttributeNumber) { + /* oid */ + if (valstr != NULL) { + Datum datum = DirectFunctionCall1(oidin, CStringGetDatum(valstr)); + oid = DatumGetObjectId(datum); + } + } else if (i == TableOidAttributeNumber) { + /* tableoid */ + if (valstr != NULL) { + Datum datum = DirectFunctionCall1(oidin, CStringGetDatum(valstr)); + tableoid = DatumGetObjectId(datum); + } } + errpos.cur_attno = 0; j++; } @@ -2338,12 +3256,32 @@ static HeapTuple make_tuple_from_result_row(PGresult *res, int row, Relation rel /* * Build the result tuple in caller's memory context. */ - (void)MemoryContextSwitchTo(oldcontext); + MemoryContextSwitchTo(oldcontext); - HeapTuple tuple = heap_form_tuple(tupdesc, values, nulls); + tuple = heap_form_tuple(tupdesc, values, nulls); + /* + * If we have a CTID to return, install it in both t_self and t_ctid. + * t_self is the normal place, but if the tuple is converted to a + * composite Datum, t_self will be lost; setting t_ctid allows CTID to be + * preserved during EvalPlanQual re-evaluations (see ROW_MARK_COPY code). + */ if (ctid) { - tuple->t_self = *ctid; + tuple->t_self = tuple->t_data->t_ctid = *ctid; + } + + /* + * If we have an OID to return, install it. + */ + if (OidIsValid(oid)) { + HeapTupleSetOid(tuple, oid); + } + + /* + * If we have an TABLEOID to return, install it. + */ + if (OidIsValid(tableoid)) { + tuple->t_tableOid = tableoid; } /* Clean up */ @@ -2355,14 +3293,1444 @@ static HeapTuple make_tuple_from_result_row(PGresult *res, int row, Relation rel /* * Callback function which is called when error occurs during column value * conversion. Print names of column and relation. + * + * Note that this function mustn't do any catalog lookups, since we are in + * an already-failed transaction. Fortunately, we can get the needed info + * from the relation or the query's rangetable instead. */ static void conversion_error_callback(void *arg) { ConversionLocation *errpos = (ConversionLocation *)arg; - TupleDesc tupdesc = RelationGetDescr(errpos->rel); - if (errpos->cur_attno > 0 && errpos->cur_attno <= tupdesc->natts) { - errcontext("column \"%s\" of foreign table \"%s\"", NameStr(tupdesc->attrs[errpos->cur_attno - 1]->attname), - RelationGetRelationName(errpos->rel)); + Relation rel = errpos->rel; + ForeignScanState *fsstate = errpos->fsstate; + const char *attname = NULL; + const char *relname = NULL; + bool is_wholerow = false; + + /* + * If we're in a scan node, always use aliases from the rangetable, for + * consistency between the simple-relation and remote-join cases. Look at + * the relation's tupdesc only if we're not in a scan node. + */ + if (fsstate) { + /* ForeignScan case */ + ForeignScan *fsplan = castNode(ForeignScan, fsstate->ss.ps.plan); + int varno = 0; + AttrNumber colno = 0; + + if (fsplan->scan.scanrelid > 0) { + /* error occurred in a scan against a foreign table */ + varno = fsplan->scan.scanrelid; + colno = errpos->cur_attno; + } else { + /* error occurred in a scan against a foreign join */ + TargetEntry *tle = NULL; + + tle = list_nth_node(TargetEntry, fsplan->fdw_scan_tlist, errpos->cur_attno - 1); + + /* + * Target list can have Vars and expressions. For Vars, we can + * get some information, however for expressions we can't. Thus + * for expressions, just show generic context message. + */ + if (IsA(tle->expr, Var)) { + Var *var = (Var *)tle->expr; + + varno = var->varno; + colno = var->varattno; + } + } + + if (varno > 0) { + EState *estate = fsstate->ss.ps.state; + RangeTblEntry *rte = exec_rt_fetch(varno, estate); + + relname = rte->eref->aliasname; + + if (colno == 0) { + is_wholerow = true; + } else if (colno > 0 && colno <= list_length(rte->eref->colnames)) { + attname = strVal(list_nth(rte->eref->colnames, colno - 1)); + } else if (colno == SelfItemPointerAttributeNumber) { + attname = "ctid"; + } + } + } else if (rel) { + /* Non-ForeignScan case (we should always have a rel here) */ + TupleDesc tupdesc = RelationGetDescr(rel); + + relname = RelationGetRelationName(rel); + if (errpos->cur_attno > 0 && errpos->cur_attno <= tupdesc->natts) { + Form_pg_attribute attr = TupleDescAttr(tupdesc, errpos->cur_attno - 1); + + attname = NameStr(attr->attname); + } else if (errpos->cur_attno == SelfItemPointerAttributeNumber) { + attname = "ctid"; + } + } + + if (relname && is_wholerow){ + errcontext("whole-row reference to foreign table \"%s\"", relname); + } else if (relname && attname) { + errcontext("column \"%s\" of foreign table \"%s\"", attname, relname); + } else { + errcontext("processing expression at position %d in select list", errpos->cur_attno); } } +/* + * Given an EquivalenceClass and a foreign relation, find an EC member + * that can be used to sort the relation remotely according to a pathkey + * using this EC. + * + * If there is more than one suitable candidate, return an arbitrary + * one of them. If there is none, return NULL. + * + * This checks that the EC member expression uses only Vars from the given + * rel and is shippable. Caller must separately verify that the pathkey's + * ordering operator is shippable. + */ +EquivalenceMember *find_em_for_rel(PlannerInfo *root, EquivalenceClass *ec, RelOptInfo *rel) +{ + ListCell *lc = NULL; + + foreach (lc, ec->ec_members) { + EquivalenceMember *em = (EquivalenceMember *)lfirst(lc); + + /* + * Note we require !bms_is_empty, else we'd accept constant + * expressions which are not suitable for the purpose. + */ + if (bms_is_subset(em->em_relids, rel->relids) && + !bms_is_empty(em->em_relids) && + is_foreign_expr(root, rel, em->em_expr)) { + return em; + } + } + + return NULL; +} + +/* + * Find an EquivalenceClass member that is to be computed as a sort column + * in the given rel's reltarget, and is shippable. + * + * If there is more than one suitable candidate, return an arbitrary + * one of them. If there is none, return NULL. + * + * This checks that the EC member expression uses only Vars from the given + * rel and is shippable. Caller must separately verify that the pathkey's + * ordering operator is shippable. + */ +EquivalenceMember *find_em_for_rel_target(PlannerInfo *root, EquivalenceClass *ec, RelOptInfo *rel) +{ + PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *)rel->fdw_private; + List* target = fpinfo->complete_tlist; + ListCell *lc1 = NULL; + int i; + + i = 0; + foreach (lc1, target) { + TargetEntry* te = (TargetEntry*)lfirst(lc1); + Expr *expr = te->expr; + Index sgref = te->ressortgroupref; + ListCell *lc2; + + /* Ignore non-sort expressions */ + if (sgref == 0 || get_sortgroupref_clause_noerr(sgref, root->parse->sortClause) == NULL) { + i++; + continue; + } + + /* We ignore binary-compatible relabeling on both ends */ + while (expr && IsA(expr, RelabelType)) { + expr = ((RelabelType *)expr)->arg; + } + + /* Locate an EquivalenceClass member matching this expr, if any */ + foreach (lc2, ec->ec_members) { + EquivalenceMember *em = (EquivalenceMember *)lfirst(lc2); + Expr *em_expr = NULL; + + /* Don't match constants */ + if (em->em_is_const) { + continue; + } + + /* Ignore child members */ + if (em->em_is_child) { + continue; + } + + /* Match if same expression (after stripping relabel) */ + em_expr = em->em_expr; + while (em_expr && IsA(em_expr, RelabelType)) { + em_expr = ((RelabelType *)em_expr)->arg; + } + + if (!equal(em_expr, expr)) { + continue; + } + + /* Check that expression (including relabels!) is shippable */ + if (is_foreign_expr(root, rel, em->em_expr)) { + return em; + } + } + + i++; + } + + return NULL; +} + +/* + * Assess whether the join between inner and outer relations can be pushed down + * to the foreign server. As a side effect, save information we obtain in this + * function to PgFdwRelationInfo passed in. + */ +static bool foreign_join_ok(PlannerInfo *root, RelOptInfo *joinrel, JoinType jointype, RelOptInfo *outerrel, + RelOptInfo *innerrel, List* restrictlist) +{ + PgFdwRelationInfo *fpinfo = NULL; + PgFdwRelationInfo *fpinfo_o = NULL; + PgFdwRelationInfo *fpinfo_i = NULL; + ListCell *lc = NULL; + List *joinclauses = NIL; + + /* + * We support pushing down INNER, LEFT, RIGHT and FULL OUTER joins. + * Constructing queries representing SEMI and ANTI joins is hard, hence + * not considered right now. + */ + if (jointype != JOIN_INNER && jointype != JOIN_LEFT && jointype != JOIN_RIGHT && jointype != JOIN_FULL) { + return false; + } + + /* + * If either of the joining relations is marked as unsafe to pushdown, the + * join can not be pushed down. + */ + fpinfo = (PgFdwRelationInfo *)joinrel->fdw_private; + fpinfo_o = (PgFdwRelationInfo *)outerrel->fdw_private; + fpinfo_i = (PgFdwRelationInfo *)innerrel->fdw_private; + if (!fpinfo_o || !fpinfo_o->pushdown_safe || !fpinfo_i || !fpinfo_i->pushdown_safe) { + return false; + } + + /* + * If joining relations have local conditions, those conditions are + * required to be applied before joining the relations. Hence the join can + * not be pushed down. + */ + if (fpinfo_o->local_conds || fpinfo_i->local_conds) { + return false; + } + + /* + * Merge FDW options. We might be tempted to do this after we have deemed + * the foreign join to be OK. But we must do this beforehand so that we + * know which quals can be evaluated on the foreign server, which might + * depend on shippable_extensions. + */ + fpinfo->server = fpinfo_o->server; + merge_fdw_options(fpinfo, fpinfo_o, fpinfo_i); + + /* + * Separate restrict list into join quals and pushed-down (other) quals. + * + * Join quals belonging to an outer join must all be shippable, else we + * cannot execute the join remotely. Add such quals to 'joinclauses'. + * + * Add other quals to fpinfo->remote_conds if they are shippable, else to + * fpinfo->local_conds. In an inner join it's okay to execute conditions + * either locally or remotely; the same is true for pushed-down conditions + * at an outer join. + * + * Note we might return failure after having already scribbled on + * fpinfo->remote_conds and fpinfo->local_conds. That's okay because we + * won't consult those lists again if we deem the join unshippable. + */ + joinclauses = NIL; + foreach (lc, restrictlist) { + RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc); + bool is_remote_clause = is_foreign_expr(root, joinrel, rinfo->clause); + + if (IS_OUTER_JOIN(jointype) && !RINFO_IS_PUSHED_DOWN(rinfo, joinrel->relids)) { + if (!is_remote_clause) { + return false; + } + joinclauses = lappend(joinclauses, rinfo); + } else { + if (is_remote_clause) { + fpinfo->remote_conds = lappend(fpinfo->remote_conds, rinfo); + } else { + fpinfo->local_conds = lappend(fpinfo->local_conds, rinfo); + } + } + } + + + /* + * deparseExplicitTargetList() isn't smart enough to handle anything other + * than a Var. In particular, if there's some PlaceHolderVar that would + * need to be evaluated within this join tree (because there's an upper + * reference to a quantity that may go to NULL as a result of an outer + * join), then we can't try to push the join down because we'll fail when + * we get to deparseExplicitTargetList(). However, a PlaceHolderVar that + * needs to be evaluated *at the top* of this join tree is OK, because we + * can do that locally after fetching the results from the remote side. + */ + foreach (lc, root->placeholder_list) { + PlaceHolderInfo *phinfo = (PlaceHolderInfo*)lfirst(lc); + Relids relids; + + /* PlaceHolderInfo refers to parent relids, not child relids. */ + relids = joinrel->relids; + + if (bms_is_subset(phinfo->ph_eval_at, relids) && bms_nonempty_difference(relids, phinfo->ph_eval_at)) { + return false; + } + } + + /* Save the join clauses, for later use. */ + fpinfo->joinclauses = joinclauses; + + fpinfo->outerrel = outerrel; + fpinfo->innerrel = innerrel; + fpinfo->jointype = jointype; + + /* + * By default, both the input relations are not required to be deparsed as + * subqueries, but there might be some relations covered by the input + * relations that are required to be deparsed as subqueries, so save the + * relids of those relations for later use by the deparser. + */ + fpinfo->make_outerrel_subquery = false; + fpinfo->make_innerrel_subquery = false; + Assert(bms_is_subset(fpinfo_o->lower_subquery_rels, outerrel->relids)); + Assert(bms_is_subset(fpinfo_i->lower_subquery_rels, innerrel->relids)); + fpinfo->lower_subquery_rels = bms_union(fpinfo_o->lower_subquery_rels, fpinfo_i->lower_subquery_rels); + + /* + * Pull the other remote conditions from the joining relations into join + * clauses or other remote clauses (remote_conds) of this relation + * wherever possible. This avoids building subqueries at every join step. + * + * For an inner join, clauses from both the relations are added to the + * other remote clauses. For LEFT and RIGHT OUTER join, the clauses from + * the outer side are added to remote_conds since those can be evaluated + * after the join is evaluated. The clauses from inner side are added to + * the joinclauses, since they need to be evaluated while constructing the + * join. + * + * For a FULL OUTER JOIN, the other clauses from either relation can not + * be added to the joinclauses or remote_conds, since each relation acts + * as an outer relation for the other. + * + * The joining sides can not have local conditions, thus no need to test + * shippability of the clauses being pulled up. + * + * The foreign join path is not necessarily better than join foreign scan + * path, so we should use list_concat2. + */ + switch (jointype) { + case JOIN_INNER: + fpinfo->remote_conds = list_concat2(fpinfo->remote_conds, fpinfo_i->remote_conds); + fpinfo->remote_conds = list_concat2(fpinfo->remote_conds, fpinfo_o->remote_conds); + break; + + case JOIN_LEFT: + fpinfo->joinclauses = list_concat2(fpinfo->joinclauses, fpinfo_i->remote_conds); + fpinfo->remote_conds = list_concat2(fpinfo->remote_conds, fpinfo_o->remote_conds); + break; + + case JOIN_RIGHT: + fpinfo->joinclauses = list_concat2(fpinfo->joinclauses, fpinfo_o->remote_conds); + fpinfo->remote_conds = list_concat2(fpinfo->remote_conds, fpinfo_i->remote_conds); + break; + + case JOIN_FULL: + + /* + * In this case, if any of the input relations has conditions, we + * need to deparse that relation as a subquery so that the + * conditions can be evaluated before the join. Remember it in + * the fpinfo of this relation so that the deparser can take + * appropriate action. Also, save the relids of base relations + * covered by that relation for later use by the deparser. + */ + if (fpinfo_o->remote_conds) { + fpinfo->make_outerrel_subquery = true; + fpinfo->lower_subquery_rels = bms_add_members(fpinfo->lower_subquery_rels, outerrel->relids); + } + if (fpinfo_i->remote_conds) { + fpinfo->make_innerrel_subquery = true; + fpinfo->lower_subquery_rels = bms_add_members(fpinfo->lower_subquery_rels, innerrel->relids); + } + break; + + default: + /* Should not happen, we have just checked this above */ + elog(ERROR, "unsupported join type %d", jointype); + } + + /* + * For an inner join, all restrictions can be treated alike. Treating the + * pushed down conditions as join conditions allows a top level full outer + * join to be deparsed without requiring subqueries. + */ + if (jointype == JOIN_INNER) { + Assert(!fpinfo->joinclauses); + fpinfo->joinclauses = fpinfo->remote_conds; + fpinfo->remote_conds = NIL; + } + + /* Mark that this join can be pushed down safely */ + fpinfo->pushdown_safe = true; + + /* Get user mapping */ + if (fpinfo->use_remote_estimate) { + if (fpinfo_o->use_remote_estimate) { + fpinfo->user = fpinfo_o->user; + } else { + fpinfo->user = fpinfo_i->user; + } + } else { + fpinfo->user = NULL; + } + + /* + * Set # of retrieved rows and cached relation costs to some negative + * value, so that we can detect when they are set to some sensible values, + * during one (usually the first) of the calls to estimate_path_cost_size. + */ + fpinfo->retrieved_rows = -1; + fpinfo->rel_startup_cost = -1; + fpinfo->rel_total_cost = -1; + + /* + * Set the string describing this join relation to be used in EXPLAIN + * output of corresponding ForeignScan. Note that the decoration we add + * to the base relation names mustn't include any digits, or it'll confuse + * postgresExplainForeignScan. + */ + fpinfo->relation_name = psprintf("(%s) %s JOIN (%s)", fpinfo_o->relation_name, get_jointype_name(fpinfo->jointype), + fpinfo_i->relation_name); + + /* + * Set the relation index. This is defined as the position of this + * joinrel in the join_rel_list list plus the length of the rtable list. + * Note that since this joinrel is at the end of the join_rel_list list + * when we are called, we can get the position by list_length. + */ + Assert(fpinfo->relation_index == 0); /* shouldn't be set yet */ + fpinfo->relation_index = list_length(root->parse->rtable) + list_length(root->join_rel_list); + return true; +} + +static void add_paths_with_pathkeys_for_rel(PlannerInfo *root, RelOptInfo *rel, Path *epq_path) +{ + List *useful_pathkeys_list = NIL; /* List of all pathkeys */ + ListCell *lc = NULL; + + useful_pathkeys_list = get_useful_pathkeys_for_relation(root, rel); + + /* Create one path for each set of pathkeys we found above. */ + foreach (lc, useful_pathkeys_list) { + double rows; + int width; + Cost startup_cost; + Cost total_cost; + List *useful_pathkeys = (List *)lfirst(lc); + + estimate_path_cost_size(root, rel, NIL, useful_pathkeys, NULL, &rows, &width, &startup_cost, &total_cost); + + /* + * The EPQ path must be at least as well sorted as the path itself, in + * case it gets used as input to a mergejoin. + */ + /* but openGauss not support create_sort_path(). */ + Assert(epq_path == NULL); + + if (IS_SIMPLE_REL(rel)) { + add_path(NULL, rel, (Path *)create_foreignscan_path(root, rel, startup_cost, total_cost, + useful_pathkeys, rel->lateral_relids, epq_path, NIL)); + } else { + add_path(NULL, rel, (Path *)create_foreign_join_path(root, rel, NULL, rows, startup_cost, total_cost, + useful_pathkeys, rel->lateral_relids, epq_path, NIL)); + } + } +} + +/* + * Parse options from foreign server and apply them to fpinfo. + * + * New options might also require tweaking merge_fdw_options(). + */ +static void apply_server_options(PgFdwRelationInfo *fpinfo) +{ + ListCell *lc = NULL; + + foreach (lc, fpinfo->server->options) { + DefElem *def = (DefElem *)lfirst(lc); + + if (strcmp(def->defname, "use_remote_estimate") == 0) { + fpinfo->use_remote_estimate = defGetBoolean(def); + } else if (strcmp(def->defname, "fdw_startup_cost") == 0) { + (void)parse_real(defGetString(def), &fpinfo->fdw_startup_cost, 0, NULL); + } else if (strcmp(def->defname, "fdw_tuple_cost") == 0) { + (void)parse_real(defGetString(def), &fpinfo->fdw_tuple_cost, 0, NULL); + } else if (strcmp(def->defname, "fetch_size") == 0) { + (void)parse_int(defGetString(def), &fpinfo->fetch_size, 0, NULL); + } + } +} + +/* + * Parse options from foreign table and apply them to fpinfo. + * + * New options might also require tweaking merge_fdw_options(). + */ +static void apply_table_options(PgFdwRelationInfo *fpinfo) +{ + ListCell *lc = NULL; + + foreach (lc, fpinfo->table->options) { + DefElem *def = (DefElem *)lfirst(lc); + + if (strcmp(def->defname, "use_remote_estimate") == 0) { + fpinfo->use_remote_estimate = defGetBoolean(def); + } else if (strcmp(def->defname, "fetch_size") == 0) { + (void)parse_int(defGetString(def), &fpinfo->fetch_size, 0, NULL); + } + } +} + +/* + * Merge FDW options from input relations into a new set of options for a join + * or an upper rel. + * + * For a join relation, FDW-specific information about the inner and outer + * relations is provided using fpinfo_i and fpinfo_o. For an upper relation, + * fpinfo_o provides the information for the input relation; fpinfo_i is + * expected to NULL. + */ +static void merge_fdw_options(PgFdwRelationInfo *fpinfo, const PgFdwRelationInfo *fpinfo_o, + const PgFdwRelationInfo *fpinfo_i) +{ + /* We must always have fpinfo_o. */ + Assert(fpinfo_o); + + /* fpinfo_i may be NULL, but if present the servers must both match. */ + Assert(!fpinfo_i || fpinfo_i->server->serverid == fpinfo_o->server->serverid); + + /* + * Copy the server specific FDW options. (For a join, both relations come + * from the same server, so the server options should have the same value + * for both relations.) + */ + fpinfo->fdw_startup_cost = fpinfo_o->fdw_startup_cost; + fpinfo->fdw_tuple_cost = fpinfo_o->fdw_tuple_cost; + fpinfo->shippable_extensions = fpinfo_o->shippable_extensions; + fpinfo->use_remote_estimate = fpinfo_o->use_remote_estimate; + fpinfo->fetch_size = fpinfo_o->fetch_size; + fpinfo->async_capable = fpinfo_o->async_capable; + + /* Merge the table level options from either side of the join. */ + if (fpinfo_i) { + /* + * We'll prefer to use remote estimates for this join if any table + * from either side of the join is using remote estimates. This is + * most likely going to be preferred since they're already willing to + * pay the price of a round trip to get the remote EXPLAIN. In any + * case it's not entirely clear how we might otherwise handle this + * best. + */ + fpinfo->use_remote_estimate = fpinfo_o->use_remote_estimate || fpinfo_i->use_remote_estimate; + + /* + * Set fetch size to maximum of the joining sides, since we are + * expecting the rows returned by the join to be proportional to the + * relation sizes. + */ + fpinfo->fetch_size = Max(fpinfo_o->fetch_size, fpinfo_i->fetch_size); + + /* + * We'll prefer to consider this join async-capable if any table from + * either side of the join is considered async-capable. This would be + * reasonable because in that case the foreign server would have its + * own resources to scan that table asynchronously, and the join could + * also be computed asynchronously using the resources. + */ + fpinfo->async_capable = fpinfo_o->async_capable || fpinfo_i->async_capable; + } +} + +/* + * postgresGetForeignJoinPaths + * Add possible ForeignPath to joinrel, if join is safe to push down. + */ +static void postgresGetForeignJoinPaths(PlannerInfo *root, RelOptInfo *joinrel, RelOptInfo *outerrel, + RelOptInfo *innerrel, JoinType jointype, SpecialJoinInfo* sjinfo, List* restrictlist) +{ + PgFdwRelationInfo *fpinfo = NULL; + ForeignPath *joinpath = NULL; + double rows; + int width; + Cost startup_cost; + Cost total_cost; + + /* + * Skip if this join combination has been considered already. + */ + if (joinrel->fdw_private) { + return; + } + + /* + * This code does not work for joins with lateral references, since those + * must have parameterized paths, which we don't generate yet. + */ + if (!bms_is_empty(joinrel->lateral_relids)) { + return; + } + + /* + * Create unfinished PgFdwRelationInfo entry which is used to indicate + * that the join relation is already considered, so that we won't waste + * time in judging safety of join pushdown and adding the same paths again + * if found safe. Once we know that this join can be pushed down, we fill + * the entry. + */ + fpinfo = (PgFdwRelationInfo *)palloc0(sizeof(PgFdwRelationInfo)); + fpinfo->pushdown_safe = false; + joinrel->fdw_private = fpinfo; + /* attrs_used is only for base relations. */ + fpinfo->attrs_used = NULL; + + /* + * If there is a possibility that EvalPlanQual will be executed, we need + * to be able to reconstruct the row using scans of the base relations. + * GetExistingLocalJoinPath will find a suitable path for this purpose in + * the path list of the joinrel, if one exists. We must be careful to + * call it before adding any ForeignPath, since the ForeignPath might + * dominate the only suitable local path available. We also do it before + * calling foreign_join_ok(), since that function updates fpinfo and marks + * it as pushable if the join is found to be pushable. + * + * currently not support. + */ + if (root->parse->commandType == CMD_DELETE || root->parse->commandType == CMD_UPDATE || root->rowMarks) { + ereport(DEBUG3, (errmsg("could not push down foreign join because the EPQ maybe executed."))); + return; + } + + if (!foreign_join_ok(root, joinrel, jointype, outerrel, innerrel, restrictlist)) { + return; + } + + /* + * Compute the selectivity and cost of the local_conds, so we don't have + * to do it over again for each path. The best we can do for these + * conditions is to estimate selectivity on the basis of local statistics. + * The local conditions are applied after the join has been computed on + * the remote side like quals in WHERE clause, so pass jointype as + * JOIN_INNER. + */ + fpinfo->local_conds_sel = clauselist_selectivity(root, fpinfo->local_conds, 0, JOIN_INNER, NULL); + cost_qual_eval(&fpinfo->local_conds_cost, fpinfo->local_conds, root); + + /* + * If we are going to estimate costs locally, estimate the join clause + * selectivity here while we have special join info. + */ + if (!fpinfo->use_remote_estimate) { + fpinfo->joinclause_sel = clauselist_selectivity(root, fpinfo->joinclauses, 0, fpinfo->jointype, sjinfo); + } + + /* Estimate costs for bare join relation */ + estimate_path_cost_size(root, joinrel, NIL, NIL, NULL, &rows, &width, &startup_cost, &total_cost); + /* Now update this information in the joinrel */ + joinrel->rows = rows; + joinrel->width = width; + fpinfo->rows = rows; + fpinfo->width = width; + fpinfo->startup_cost = startup_cost; + fpinfo->total_cost = total_cost; + + /* + * Create a new join path and add it to the joinrel which represents a + * join between foreign tables. + */ + joinpath = create_foreign_join_path(root, joinrel, NULL, /* default pathtarget */ + rows, startup_cost, total_cost, NIL, /* no pathkeys */ + joinrel->lateral_relids, NULL, NIL); /* no fdw_private */ + + /* Add generated path into joinrel by add_path(). */ + add_path(NULL, joinrel, (Path *)joinpath); + + /* + * Consider pathkeys for the join relation. + */ + add_paths_with_pathkeys_for_rel(root, joinrel, NULL); + + /* XXX Consider parameterized paths for the join relation */ +} + +/* + * Assess whether the aggregation, grouping and having operations can be pushed + * down to the foreign server. As a side effect, save information we obtain in + * this function to PgFdwRelationInfo of the input relation. + */ +static bool foreign_grouping_ok(PlannerInfo *root, RelOptInfo *grouped_rel, Node *havingQual, + GroupPathExtraData *extra) +{ + Query *query = root->parse; + PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *)grouped_rel->fdw_private; + List *grouping_target = extra->targetList; + PgFdwRelationInfo *ofpinfo = NULL; + ListCell *lc = NULL; + int i; + List *tlist = NIL; + + /* We currently don't support pushing Grouping Sets. */ + if (query->groupingSets) { + return false; + } + + /* Get the fpinfo of the underlying scan relation. */ + ofpinfo = (PgFdwRelationInfo *)fpinfo->outerrel->fdw_private; + + /* + * If underlying scan relation has any local conditions, those conditions + * are required to be applied before performing aggregation. Hence the + * aggregate cannot be pushed down. + */ + if (ofpinfo->local_conds) { + return false; + } + + /* + * Examine grouping expressions, as well as other expressions we'd need to + * compute, and check whether they are safe to push down to the foreign + * server. All GROUP BY expressions will be part of the grouping target + * and thus there is no need to search for them separately. Add grouping + * expressions into target list which will be passed to foreign server. + * + * A tricky fine point is that we must not put any expression into the + * target list that is just a foreign param (that is, something that + * deparse.c would conclude has to be sent to the foreign server). If we + * do, the expression will also appear in the fdw_exprs list of the plan + * node, and setrefs.c will get confused and decide that the fdw_exprs + * entry is actually a reference to the fdw_scan_tlist entry, resulting in + * a broken plan. Somewhat oddly, it's OK if the expression contains such + * a node, as long as it's not at top level; then no match is possible. + */ + i = 0; + foreach (lc, grouping_target) { + TargetEntry* tle = (TargetEntry*)lfirst(lc); + Expr *expr = tle->expr; + Index sgref = tle->ressortgroupref; + ListCell *l = NULL; + + /* Check whether this expression is part of GROUP BY clause */ + if (sgref && get_sortgroupref_clause_noerr(sgref, query->groupClause)) { + /* + * If any GROUP BY expression is not shippable, then we cannot + * push down aggregation to the foreign server. + */ + if (!is_foreign_expr(root, grouped_rel, expr)) { + return false; + } + + /* + * If it would be a foreign param, we can't put it into the tlist, + * so we have to fail. + */ + if (is_foreign_param(root, grouped_rel, expr)) { + return false; + } + + /* + * Pushable, so add to tlist. We need to create a TLE for this + * expression and apply the sortgroupref to it. We cannot use + * add_to_flat_tlist() here because that avoids making duplicate + * entries in the tlist. If there are duplicate entries with + * distinct sortgrouprefs, we have to duplicate that situation in + * the output tlist. + */ + TargetEntry* new_tle = makeTargetEntry(expr, list_length(tlist) + 1, NULL, false); + new_tle->ressortgroupref = sgref; + tlist = lappend(tlist, new_tle); + } else { + /* + * Non-grouping expression we need to compute. Can we ship it + * as-is to the foreign server? + */ + if (is_foreign_expr(root, grouped_rel, expr) && !is_foreign_param(root, grouped_rel, expr)) { + /* Yes, so add to tlist as-is; OK to suppress duplicates */ + tlist = add_to_flat_tlist(tlist, list_make1(expr)); + } else { + /* Not pushable as a whole; extract its Vars and aggregates */ + List *aggvars = NIL; + + aggvars = pull_var_clause((Node *)expr, PVC_INCLUDE_AGGREGATES, PVC_REJECT_PLACEHOLDERS); + + /* + * If any aggregate expression is not shippable, then we + * cannot push down aggregation to the foreign server. (We + * don't have to check is_foreign_param, since that certainly + * won't return true for any such expression.) + */ + if (!is_foreign_expr(root, grouped_rel, (Expr *)aggvars)) { + return false; + } + + /* + * Add aggregates, if any, into the targetlist. Plain Vars + * outside an aggregate can be ignored, because they should be + * either same as some GROUP BY column or part of some GROUP + * BY expression. In either case, they are already part of + * the targetlist and thus no need to add them again. In fact + * including plain Vars in the tlist when they do not match a + * GROUP BY column would cause the foreign server to complain + * that the shipped query is invalid. + */ + foreach (l, aggvars) { + Expr *expr = (Expr *)lfirst(l); + + if (IsA(expr, Aggref)) { + tlist = add_to_flat_tlist(tlist, list_make1(expr)); + } + } + } + } + + i++; + } + + /* + * Classify the pushable and non-pushable HAVING clauses and save them in + * remote_conds and local_conds of the grouped rel's fpinfo. + */ + if (havingQual) { + ListCell *lc = NULL; + + foreach (lc, (List *)havingQual) { + Expr *expr = (Expr *)lfirst(lc); + RestrictInfo *rinfo = NULL; + + /* + * Currently, the core code doesn't wrap havingQuals in + * RestrictInfos, so we must make our own. + */ + Assert(!IsA(expr, RestrictInfo)); + rinfo = make_restrictinfo(expr, true, false, false, root->qualSecurityLevel, grouped_rel->relids, + NULL, NULL); + if (is_foreign_expr(root, grouped_rel, expr)) { + fpinfo->remote_conds = lappend(fpinfo->remote_conds, rinfo); + } else { + fpinfo->local_conds = lappend(fpinfo->local_conds, rinfo); + } + } + } + + /* + * If there are any local conditions, pull Vars and aggregates from it and + * check whether they are safe to pushdown or not. + */ + if (fpinfo->local_conds) { + List *aggvars = NIL; + ListCell *lc = NULL; + + foreach (lc, fpinfo->local_conds) { + RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc); + + aggvars = list_concat(aggvars, pull_var_clause((Node *)rinfo->clause, PVC_INCLUDE_AGGREGATES, PVC_REJECT_PLACEHOLDERS)); + } + + foreach (lc, aggvars) { + Expr *expr = (Expr *)lfirst(lc); + + /* + * If aggregates within local conditions are not safe to push + * down, then we cannot push down the query. Vars are already + * part of GROUP BY clause which are checked above, so no need to + * access them again here. Again, we need not check + * is_foreign_param for a foreign aggregate. + */ + if (IsA(expr, Aggref)) { + if (!is_foreign_expr(root, grouped_rel, expr)) { + return false; + } + + tlist = add_to_flat_tlist(tlist, list_make1(expr)); + } + } + } + + /* Store generated targetlist */ + fpinfo->grouped_tlist = tlist; + + /* Safe to pushdown */ + fpinfo->pushdown_safe = true; + + /* + * Set # of retrieved rows and cached relation costs to some negative + * value, so that we can detect when they are set to some sensible values, + * during one (usually the first) of the calls to estimate_path_cost_size. + */ + fpinfo->retrieved_rows = -1; + fpinfo->rel_startup_cost = -1; + fpinfo->rel_total_cost = -1; + + /* + * Set the string describing this grouped relation to be used in EXPLAIN + * output of corresponding ForeignScan. Note that the decoration we add + * to the base relation name mustn't include any digits, or it'll confuse + * postgresExplainForeignScan. + */ + fpinfo->relation_name = psprintf("Aggregate on (%s)", ofpinfo->relation_name); + + return true; +} + + +static void init_upperrel_paths_context(FDWUpperRelCxt* ufdwCxt, Plan* mainPlan) +{ + Assert(ufdwCxt->root != NULL && ufdwCxt->currentRel != NULL && ufdwCxt->spjExtra != NULL); + + PlannerInfo* root = ufdwCxt->root; + + if (root->parse->groupingSets) { + ufdwCxt->state = FDW_UPPER_REL_END; + return; + } + + /* if the spj rel is unsafe to push down, upper rel is unsafe alse. */ + PgFdwRelationInfo* fpinfo = (PgFdwRelationInfo *)ufdwCxt->currentRel->fdw_private; + if (!fpinfo->pushdown_safe) { + ufdwCxt->state = FDW_UPPER_REL_END; + return; + } + + fpinfo->stage = UPPERREL_INIT; + + /* the reltargetlist may bave been evaluted, we shoule adept it. */ + ufdwCxt->currentRel->reltargetlist = extract_target_from_tel(ufdwCxt, fpinfo); + + ufdwCxt->state = FDW_UPPER_REL_INIT; +} + +static void create_upperrel_plan_if_needed(FDWUpperRelCxt* ufdwCxt, Plan* mainPlan) +{ + ListCell* lc = NULL; + Path* bestPath = NULL; + set_cheapest(ufdwCxt->currentRel, ufdwCxt->root); + + foreach(lc, ufdwCxt->currentRel->cheapest_total_path) { + Path* p = (Path*)lfirst(lc); + if (bestPath == NULL || bestPath->total_cost > p->total_cost) { + bestPath = p; + } + } + + if (bestPath->total_cost < mainPlan->total_cost) { + ufdwCxt->resultPlan = create_plan(ufdwCxt->root, bestPath); + ufdwCxt->resultPathKeys = bestPath->pathkeys; + + apply_tlist_labeling(ufdwCxt->resultPlan->targetlist, mainPlan->targetlist); + } +} + +/* + * postgresGetForeignUpperPaths + * Add paths for post-join operations like aggregation, grouping etc. if + * corresponding operations are safe to push down. + */ +static void postgresGetForeignUpperPaths(FDWUpperRelCxt* ufdwCxt, UpperRelationKind stage, Plan* mainPlan) +{ + Assert(ufdwCxt != NULL && ufdwCxt->state != FDW_UPPER_REL_END); + Assert(ufdwCxt->currentRel && ufdwCxt->currentRel->fdwroutine && ufdwCxt->currentRel->fdwroutine->GetForeignUpperPaths); + + switch (stage) { + case UPPERREL_INIT: + init_upperrel_paths_context(ufdwCxt, mainPlan); + break; + case UPPERREL_GROUP_AGG: + case UPPERREL_ORDERED: + ufdwCxt->state = FDW_UPPER_REL_TRY; + _postgresGetForeignUpperPaths(ufdwCxt, stage); + break; + case UPPERREL_ROWMARKS: + case UPPERREL_LIMIT: + ufdwCxt->state = FDW_UPPER_REL_TRY; + break; + case UPPERREL_FINAL: + if (ufdwCxt->state == FDW_UPPER_REL_TRY) { + _postgresGetForeignUpperPaths(ufdwCxt, stage); + if (ufdwCxt->currentRel->pathlist != NIL) { + create_upperrel_plan_if_needed(ufdwCxt, mainPlan); + } + } + ufdwCxt->state = FDW_UPPER_REL_END; + break; + default: + ufdwCxt->state = FDW_UPPER_REL_END; + break; + } +} + +static void _postgresGetForeignUpperPaths(FDWUpperRelCxt *ufdw_cxt, UpperRelationKind stage) +{ + PgFdwRelationInfo *fpinfo = NULL; + RelOptInfo *input_rel = ufdw_cxt->currentRel; + RelOptInfo* output_rel = NULL; + + /* + * If input rel is not safe to pushdown, then simply return as we cannot + * perform any post-join operations on the foreign server. + */ + if (!input_rel->fdw_private || !((PgFdwRelationInfo *)input_rel->fdw_private)->pushdown_safe) { + return; + } + + fpinfo = (PgFdwRelationInfo *)palloc0(sizeof(PgFdwRelationInfo)); + fpinfo->pushdown_safe = false; + fpinfo->stage = stage; + + output_rel = make_upper_rel(ufdw_cxt, fpinfo); + + switch (stage) { + case UPPERREL_GROUP_AGG: + add_foreign_grouping_paths(ufdw_cxt->root, input_rel, output_rel, ufdw_cxt->groupExtra); + break; + case UPPERREL_ORDERED: + add_foreign_ordered_paths(ufdw_cxt->root, input_rel, output_rel, ufdw_cxt->orderExtra); + break; + case UPPERREL_FINAL: + add_foreign_final_paths(ufdw_cxt->root, input_rel, output_rel, ufdw_cxt->finalExtra); + break; + default: + elog(ERROR, "unexpected upper relation: %d", (int)stage); + break; + } + + ufdw_cxt->currentRel = output_rel; +} + +/* + * add_foreign_grouping_paths + * Add foreign path for grouping and/or aggregation. + * + * Given input_rel represents the underlying scan. The paths are added to the + * given grouped_rel. + */ +static void add_foreign_grouping_paths(PlannerInfo *root, RelOptInfo *input_rel, RelOptInfo *grouped_rel, + GroupPathExtraData *extra) +{ + Query *parse = root->parse; + PgFdwRelationInfo *ifpinfo = (PgFdwRelationInfo *)input_rel->fdw_private; + PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *)grouped_rel->fdw_private; + ForeignPath *grouppath = NULL; + double rows; + int width; + Cost startup_cost; + Cost total_cost; + + /* Nothing to be done, if there is no grouping or aggregation required. */ + if (!parse->groupClause && !parse->groupingSets && !parse->hasAggs && !root->hasHavingQual) { + return; + } + + /* save the input_rel as outerrel in fpinfo */ + fpinfo->outerrel = input_rel; + + /* + * Copy foreign table, foreign server, user mapping, FDW options etc. + * details from the input relation's fpinfo. + */ + fpinfo->table = ifpinfo->table; + fpinfo->server = ifpinfo->server; + fpinfo->user = ifpinfo->user; + merge_fdw_options(fpinfo, ifpinfo, NULL); + + /* + * Assess if it is safe to push down aggregation and grouping. + * + * Use HAVING qual from extra. In case of child partition, it will have + * translated Vars. + */ + if (!foreign_grouping_ok(root, grouped_rel, extra->havingQual, extra)) { + return; + } + + /* + * Compute the selectivity and cost of the local_conds, so we don't have + * to do it over again for each path. (Currently we create just a single + * path here, but in future it would be possible that we build more paths + * such as pre-sorted paths as in postgresGetForeignPaths and + * postgresGetForeignJoinPaths.) The best we can do for these conditions + * is to estimate selectivity on the basis of local statistics. + */ + fpinfo->local_conds_sel = clauselist_selectivity(root, fpinfo->local_conds, 0, JOIN_INNER, NULL, false); + + cost_qual_eval(&fpinfo->local_conds_cost, fpinfo->local_conds, root); + + /* Estimate the cost of push down */ + estimate_path_cost_size(root, grouped_rel, NIL, NIL, NULL, &rows, &width, &startup_cost, &total_cost); + + /* Now update this information in the fpinfo */ + fpinfo->rows = rows; + fpinfo->width = width; + fpinfo->startup_cost = startup_cost; + fpinfo->total_cost = total_cost; + + /* Create and add foreign path to the grouping relation. */ + grouppath = create_foreign_upper_path(root, grouped_rel, grouped_rel->reltargetlist, rows, startup_cost, total_cost, + NIL, /* no pathkeys */ + NULL, NIL); /* no fdw_private */ + + /* Add generated path into grouped_rel by add_path(). */ + add_path(root, grouped_rel, (Path *)grouppath); +} + +/* + * add_foreign_ordered_paths + * Add foreign paths for performing the final sort remotely. + * + * Given input_rel contains the source-data Paths. The paths are added to the + * given ordered_rel. + */ +static void add_foreign_ordered_paths(PlannerInfo *root, RelOptInfo *input_rel, RelOptInfo *ordered_rel, + OrderPathExtraData *extra) +{ + Query *parse = root->parse; + PgFdwRelationInfo *ifpinfo = (PgFdwRelationInfo *)input_rel->fdw_private; + PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *)ordered_rel->fdw_private; + PgFdwPathExtraData *fpextra = NULL; + double rows; + int width; + Cost startup_cost; + Cost total_cost; + List *fdw_private; + ForeignPath *ordered_path = NULL; + ListCell *lc = NULL; + + /* Shouldn't get here unless the query has ORDER BY */ + Assert(parse->sortClause); + + /* Save the input_rel as outerrel in fpinfo */ + fpinfo->outerrel = input_rel; + + /* + * Copy foreign table, foreign server, user mapping, FDW options etc. + * details from the input relation's fpinfo. + */ + fpinfo->table = ifpinfo->table; + fpinfo->server = ifpinfo->server; + fpinfo->user = ifpinfo->user; + merge_fdw_options(fpinfo, ifpinfo, NULL); + + /* + * If the input_rel is a base or join relation, we would already have + * considered pushing down the final sort to the remote server when + * creating pre-sorted foreign paths for that relation, because the + * query_pathkeys is set to the root->sort_pathkeys in that case (see + * standard_qp_callback()). + */ + if (input_rel->reloptkind == RELOPT_BASEREL || input_rel->reloptkind == RELOPT_JOINREL) { + Assert(equal(root->query_pathkeys, root->sort_pathkeys)); + + /* Safe to push down if the query_pathkeys is safe to push down */ + fpinfo->pushdown_safe = ifpinfo->qp_is_pushdown_safe; + + return; + } + + /* The input_rel should be a grouping relation */ + Assert(input_rel->reloptkind == RELOPT_UPPER_REL && ifpinfo->stage == UPPERREL_GROUP_AGG); + + /* + * We try to create a path below by extending a simple foreign path for + * the underlying grouping relation to perform the final sort remotely, + * which is stored into the fdw_private list of the resulting path. + */ + + /* Assess if it is safe to push down the final sort */ + foreach (lc, root->sort_pathkeys) { + PathKey *pathkey = (PathKey *)lfirst(lc); + EquivalenceClass *pathkey_ec = pathkey->pk_eclass; + + /* + * 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; + } + + /* + * Can't push down the sort if pathkey's opfamily is not shippable. + */ + if (!is_builtin(pathkey->pk_opfamily)) { + return; + } + + /* + * The EC must contain a shippable EM that is computed in input_rel's + * reltarget, else we can't push down the sort. + */ + if (find_em_for_rel_target(root, pathkey_ec, input_rel) == NULL) { + return; + } + } + + /* Safe to push down */ + fpinfo->pushdown_safe = true; + + /* Construct PgFdwPathExtraData */ + fpextra = (PgFdwPathExtraData *)palloc0(sizeof(PgFdwPathExtraData)); + fpextra->target = ordered_rel->reltargetlist; + fpextra->has_final_sort = true; + + /* Estimate the costs of performing the final sort remotely */ + estimate_path_cost_size(root, input_rel, NIL, root->sort_pathkeys, fpextra, &rows, &width, &startup_cost, + &total_cost); + + /* + * Build the fdw_private list that will be used by postgresGetForeignPlan. + * Items in the list must match order in enum FdwPathPrivateIndex. + */ + fdw_private = list_make2(makeInteger(1), makeInteger(0)); + + /* Create foreign ordering path */ + ordered_path = create_foreign_upper_path(root, input_rel, ordered_rel->reltargetlist, rows, startup_cost, + total_cost, root->sort_pathkeys, NULL, /* no extra plan */ + fdw_private); + + /* and add it to the ordered_rel */ + add_path(root, ordered_rel, (Path *)ordered_path); +} + +/* + * add_foreign_final_paths + * Add foreign paths for performing the final processing remotely. + * + * Given input_rel contains the source-data Paths. The paths are added to the + * given final_rel. + */ +static void add_foreign_final_paths(PlannerInfo *root, RelOptInfo *input_rel, RelOptInfo *final_rel, + FinalPathExtraData *extra) +{ + Query *parse = root->parse; + PgFdwRelationInfo *ifpinfo = (PgFdwRelationInfo *)input_rel->fdw_private; + PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *)final_rel->fdw_private; + bool has_final_sort = false; + List *pathkeys = NIL; + PgFdwPathExtraData *fpextra = NULL; + bool save_use_remote_estimate = false; + double rows; + int width; + Cost startup_cost; + Cost total_cost; + List *fdw_private = NIL; + ForeignPath *final_path = NULL; + + /* + * Currently, we only support this for SELECT commands + */ + if (parse->commandType != CMD_SELECT) { + return; + } + + /* + * No work if there is no FOR UPDATE/SHARE clause and if there is no need + * to add a LIMIT node, input rel pathlist is the result, just copy it + */ + if (!parse->rowMarks && !extra->limit_needed) { + ListCell* lc = NULL; + Path* path = NULL; + ForeignPath *final_path = NULL; + foreach(lc, input_rel->pathlist) { + path = (Path*)lfirst(lc); + if (IsA(path, ForeignPath)) { + final_path = create_foreign_upper_path(root, path->parent, extra->targetList, path->rows, + path->startup_cost, path->total_cost, path->pathkeys, NULL, /* no extra plan */ + ((ForeignPath *)path)->fdw_private); + + /* and add it to the final_rel */ + add_path(root, final_rel, (Path *)final_path); + + /* Safe to push down */ + fpinfo->pushdown_safe = true; + } + } + return; + } + + /* Save the input_rel as outerrel in fpinfo */ + fpinfo->outerrel = input_rel; + + /* + * Copy foreign table, foreign server, user mapping, FDW options etc. + * details from the input relation's fpinfo. + */ + fpinfo->table = ifpinfo->table; + fpinfo->server = ifpinfo->server; + fpinfo->user = ifpinfo->user; + merge_fdw_options(fpinfo, ifpinfo, NULL); + + /* + * If there is no need to add a LIMIT node, there might be a ForeignPath + * in the input_rel's pathlist that implements all behavior of the query. + * Note: we would already have accounted for the query's FOR UPDATE/SHARE + * (if any) before we get here. + */ + if (!extra->limit_needed) { + ListCell *lc = NULL; + + Assert(parse->rowMarks); + + /* + * Grouping and aggregation are not supported with FOR UPDATE/SHARE, + * so the input_rel should be a base, join, or ordered relation; and + * if it's an ordered relation, its input relation should be a base or + * join relation. + */ + Assert(input_rel->reloptkind == RELOPT_BASEREL || input_rel->reloptkind == RELOPT_JOINREL || + (input_rel->reloptkind == RELOPT_UPPER_REL && ifpinfo->stage == UPPERREL_ORDERED && + (ifpinfo->outerrel->reloptkind == RELOPT_BASEREL || ifpinfo->outerrel->reloptkind == RELOPT_JOINREL))); + + foreach (lc, input_rel->pathlist) { + Path *path = (Path *)lfirst(lc); + + /* + * apply_scanjoin_target_to_paths() uses create_projection_path() + * to adjust each of its input paths if needed, whereas + * create_ordered_paths() uses apply_projection_to_path() to do + * that. So the former might have put a ProjectionPath on top of + * the ForeignPath; look through ProjectionPath and see if the + * path underneath it is ForeignPath. + */ + if (IsA(path, ForeignPath)) { + /* + * Create foreign final path; this gets rid of a + * no-longer-needed outer plan (if any), which makes the + * EXPLAIN output look cleaner + */ + final_path = create_foreign_upper_path(root, path->parent, extra->targetList, path->rows, + path->startup_cost, path->total_cost, path->pathkeys, NULL, /* no extra plan */ + NULL); /* no fdw_private */ + + /* and add it to the final_rel */ + add_path(root, final_rel, (Path *)final_path); + + /* Safe to push down */ + fpinfo->pushdown_safe = true; + + return; + } + } + + /* + * If we get here it means no ForeignPaths; since we would already + * have considered pushing down all operations for the query to the + * remote server, give up on it. + */ + return; + } + + Assert(extra->limit_needed); + + /* + * If the input_rel is an ordered relation, replace the input_rel with its + * input relation + */ + if (input_rel->reloptkind == RELOPT_UPPER_REL && ifpinfo->stage == UPPERREL_ORDERED) { + input_rel = ifpinfo->outerrel; + ifpinfo = (PgFdwRelationInfo *)input_rel->fdw_private; + has_final_sort = true; + pathkeys = root->sort_pathkeys; + } + + /* The input_rel should be a base, join, or grouping relation */ + Assert(input_rel->reloptkind == RELOPT_BASEREL || input_rel->reloptkind == RELOPT_JOINREL || + (input_rel->reloptkind == RELOPT_UPPER_REL && ifpinfo->stage == UPPERREL_GROUP_AGG)); + + /* + * We try to create a path below by extending a simple foreign path for + * the underlying base, join, or grouping relation to perform the final + * sort (if has_final_sort) and the LIMIT restriction remotely, which is + * stored into the fdw_private list of the resulting path. (We + * re-estimate the costs of sorting the underlying relation, if + * has_final_sort.) + * + * Assess if it is safe to push down the LIMIT and OFFSET to the remote + * server + * + * If the underlying relation has any local conditions, the LIMIT/OFFSET + * cannot be pushed down. + */ + if (ifpinfo->local_conds) { + return; + } + + /* + * Also, the LIMIT/OFFSET cannot be pushed down, if their expressions are + * not safe to remote. + */ + if (!is_foreign_expr(root, input_rel, (Expr *)parse->limitOffset) || + !is_foreign_expr(root, input_rel, (Expr *)parse->limitCount)) { + return; + } + + /* Safe to push down */ + fpinfo->pushdown_safe = true; + + /* Construct PgFdwPathExtraData */ + fpextra = (PgFdwPathExtraData *)palloc0(sizeof(PgFdwPathExtraData)); + fpextra->target = extra->targetList; + fpextra->has_final_sort = has_final_sort; + fpextra->has_limit = extra->limit_needed; + fpextra->limit_tuples = extra->limit_tuples; + fpextra->count_est = extra->count_est; + fpextra->offset_est = extra->offset_est; + + /* + * Estimate the costs of performing the final sort and the LIMIT + * restriction remotely. If has_final_sort is false, we wouldn't need to + * execute EXPLAIN anymore if use_remote_estimate, since the costs can be + * roughly estimated using the costs we already have for the underlying + * relation, in the same way as when use_remote_estimate is false. Since + * it's pretty expensive to execute EXPLAIN, force use_remote_estimate to + * false in that case. + */ + if (!fpextra->has_final_sort) { + save_use_remote_estimate = ifpinfo->use_remote_estimate; + ifpinfo->use_remote_estimate = false; + } + estimate_path_cost_size(root, input_rel, NIL, pathkeys, fpextra, &rows, &width, &startup_cost, &total_cost); + if (!fpextra->has_final_sort) { + ifpinfo->use_remote_estimate = save_use_remote_estimate; + } + + /* + * Build the fdw_private list that will be used by postgresGetForeignPlan. + * Items in the list must match order in enum FdwPathPrivateIndex. + */ + fdw_private = list_make2(makeInteger(has_final_sort), makeInteger(extra->limit_needed)); + + /* + * Create foreign final path; this gets rid of a no-longer-needed outer + * plan (if any), which makes the EXPLAIN output look cleaner + */ + final_path = create_foreign_upper_path(root, input_rel, extra->targetList, rows, startup_cost, + total_cost, pathkeys, NULL, /* no extra plan */ + fdw_private); + + /* and add it to the final_rel */ + add_path(root, final_rel, (Path *)final_path); +} + diff --git a/contrib/postgres_fdw/postgres_fdw.h b/contrib/postgres_fdw/postgres_fdw.h index ed9d0b2b8..8dad0c6c7 100644 --- a/contrib/postgres_fdw/postgres_fdw.h +++ b/contrib/postgres_fdw/postgres_fdw.h @@ -18,15 +18,123 @@ #include "lib/stringinfo.h" #include "nodes/relation.h" #include "utils/rel.h" - #include "libpq/libpq-fe.h" +#include "nodes/execnodes.h" +#include "utils/relcache.h" + +/* + * FDW-specific planner information kept in RelOptInfo.fdw_private for a + * postgres_fdw foreign table. For a baserel, this struct is created by + * postgresGetForeignRelSize, although some fields are not filled till later. + * postgresGetForeignJoinPaths creates it for a joinrel, and + * postgresGetForeignUpperPaths creates it for an upperrel. + */ +typedef struct PgFdwRelationInfo { + /* + * True means that the relation can be pushed down. Always true for simple + * foreign scan. + */ + bool pushdown_safe; + + /* + * Restriction clauses, divided into safe and unsafe to pushdown subsets. + * All entries in these lists should have RestrictInfo wrappers; that + * improves efficiency of selectivity and cost estimation. + */ + List *remote_conds; + List *local_conds; + + /* Actual remote restriction clauses for scan (sans RestrictInfos) */ + List *final_remote_exprs; + + /* Bitmap of attr numbers we need to fetch from the remote server. */ + Bitmapset *attrs_used; + + /* True means that the query_pathkeys is safe to push down */ + bool qp_is_pushdown_safe; + + /* Cost and selectivity of local_conds. */ + QualCost local_conds_cost; + Selectivity local_conds_sel; + + /* Selectivity of join conditions */ + Selectivity joinclause_sel; + + /* Estimated size and cost for a scan with baserestrictinfo quals. */ + double rows; + int width; + Cost startup_cost; + Cost total_cost; + + /* + * Estimated number of rows fetched from the foreign server, and costs + * excluding costs for transferring those rows from the foreign server. + * These are only used by estimate_path_cost_size(). + */ + double retrieved_rows; + Cost rel_startup_cost; + Cost rel_total_cost; + + /* Options extracted from catalogs. */ + bool use_remote_estimate; + Cost fdw_startup_cost; + Cost fdw_tuple_cost; + List *shippable_extensions; /* OIDs of shippable extensions */ + bool async_capable; + + /* Cached catalog information. */ + ForeignTable *table; + ForeignServer *server; + UserMapping *user; /* only set in use_remote_estimate mode */ + + int fetch_size; /* fetch size for this remote table */ + + /* + * Name of the relation, for use while EXPLAINing ForeignScan. It is used + * for join and upper relations but is set for all relations. For a base + * relation, this is really just the RT index as a string; we convert that + * while producing EXPLAIN output. For join and upper relations, the name + * indicates which base foreign tables are included and the join type or + * aggregation type used. + */ + char *relation_name; + + /* Join information */ + RelOptInfo *outerrel; + RelOptInfo *innerrel; + JoinType jointype; + /* joinclauses contains only JOIN/ON conditions for an outer join */ + List *joinclauses; /* List of RestrictInfo */ + + /* Upper relation information */ + UpperRelationKind stage; + + /* Grouping information */ + List *grouped_tlist; + + /* Subquery information */ + bool make_outerrel_subquery; /* do we deparse outerrel as a subquery? */ + bool make_innerrel_subquery; /* do we deparse innerrel as a subquery? */ + Relids lower_subquery_rels; /* all relids appearing in lower subqueries */ + + /* + * Index of the relation. It is used to create an alias to a subquery + * representing the relation. + */ + int relation_index; + + /* target entry list from */ + List* complete_tlist; +} PgFdwRelationInfo; + /* in postgres_fdw.c */ extern int set_transmission_modes(void); extern void reset_transmission_modes(int nestlevel); /* in connection.c */ extern PGconn *GetConnection(ForeignServer *server, UserMapping *user, bool will_prep_stmt); +PGconn* GetConnectionByFScanState(ForeignScanState *node, bool will_prep_stmt); extern void ReleaseConnection(PGconn *conn); extern unsigned int GetCursorNumber(PGconn *conn); extern unsigned int GetPrepStmtNumber(PGconn *conn); @@ -41,10 +149,7 @@ extern int ExtractConnectionOptions(List *defelems, const char **keywords, const extern void classifyConditions(PlannerInfo *root, RelOptInfo *baserel, List *input_conds, List **remote_conds, List **local_conds); extern bool is_foreign_expr(PlannerInfo *root, RelOptInfo *baserel, Expr *expr); -extern void deparseSelectSql(StringInfo buf, PlannerInfo *root, RelOptInfo *baserel, Bitmapset *attrs_used, - List **retrieved_attrs); -extern void appendWhereClause(StringInfo buf, PlannerInfo *root, RelOptInfo *baserel, List *exprs, bool is_first, - List **params); +extern bool is_foreign_pathkey(PlannerInfo *root, RelOptInfo *baserel, PathKey *pathkey); extern void deparseInsertSql(StringInfo buf, RangeTblEntry *rte, Index rtindex, Relation rel, List *targetAttrs, List *returningList, List **retrieved_attrs); extern void deparseUpdateSql(StringInfo buf, RangeTblEntry *rte, Index rtindex, Relation rel, List *targetAttrs, @@ -53,6 +158,17 @@ extern void deparseDeleteSql(StringInfo buf, RangeTblEntry *rte, Index rtindex, List **retrieved_attrs); extern void deparseAnalyzeSizeSql(StringInfo buf, Relation rel); extern void deparseAnalyzeSql(StringInfo buf, Relation rel, List **retrieved_attrs); +extern void deparseSelectStmtForRel(StringInfo buf, PlannerInfo *root, RelOptInfo *foreignrel, List *tlist, + List *remote_conds, List *pathkeys, bool has_final_sort, bool has_limit, bool is_subquery, List **retrieved_attrs, + List **params_list); + +extern const char *get_jointype_name(JoinType jointype); +extern List *build_tlist_to_deparse(RelOptInfo *foreignrel); +extern bool is_foreign_param(PlannerInfo *root, RelOptInfo *baserel, Expr *expr); +extern EquivalenceMember *find_em_for_rel(PlannerInfo *root, EquivalenceClass *ec, RelOptInfo *rel); +extern EquivalenceMember *find_em_for_rel_target(PlannerInfo *root, EquivalenceClass *ec, RelOptInfo *rel); + +extern bool is_builtin(Oid oid); #endif /* POSTGRES_FDW_H */ diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql index 8350aeb45..966066008 100755 --- a/contrib/postgres_fdw/sql/postgres_fdw.sql +++ b/contrib/postgres_fdw/sql/postgres_fdw.sql @@ -1,122 +1,229 @@ --- =================================================================== --- create FDW objects --- =================================================================== +-- ====================================================================================================================================== +-- README: +-- "--nspt " means the feature that openGauss not supported. We don't remove it directly, leave it for incremental adaptation. +-- +-- The following test cases are available: +-- postgres_fdw : This test case is modified from the commit:164d174bbf9a3aba719c845497863cd3c49a3ad0 of PG14.4. +-- postgres_fdw_cstore : Test column orientation table. +-- postgres_fdw_partition : This is a beta feature in openGauss, open it by "set sql_beta_feature='partition_fdw_on'" +-- ====================================================================================================================================== +create database postgresfdw_test_db; +\c postgresfdw_test_db +set show_fdw_remote_plan = on; +-- ====================================================================================================================================== +-- TEST-MODULE: create FDW objects +-- -------------------------------------- +-- ====================================================================================================================================== CREATE EXTENSION postgres_fdw; CREATE SERVER testserver1 FOREIGN DATA WRAPPER postgres_fdw; -CREATE SERVER loopback FOREIGN DATA WRAPPER postgres_fdw - OPTIONS (dbname 'contrib_regression'); +DO $d$ + BEGIN + EXECUTE $$CREATE SERVER loopback FOREIGN DATA WRAPPER postgres_fdw + OPTIONS (dbname '$$||current_database()||$$', + port '$$||current_setting('port')||$$' + )$$; + EXECUTE $$CREATE SERVER loopback2 FOREIGN DATA WRAPPER postgres_fdw + OPTIONS (dbname '$$||current_database()||$$', + port '$$||current_setting('port')||$$' + )$$; + EXECUTE $$CREATE SERVER loopback3 FOREIGN DATA WRAPPER postgres_fdw + OPTIONS (dbname '$$||current_database()||$$', + port '$$||current_setting('port')||$$' + )$$; + END; +$d$; CREATE USER MAPPING FOR public SERVER testserver1 - OPTIONS (user 'value', password 'value'); + OPTIONS (user 'value', password 'value'); CREATE USER MAPPING FOR CURRENT_USER SERVER loopback; - --- =================================================================== --- create objects used through FDW loopback server --- =================================================================== +CREATE USER MAPPING FOR CURRENT_USER SERVER loopback2; +CREATE USER MAPPING FOR public SERVER loopback3; +-- ====================================================================================================================================== +-- TEST-MODULE: create objects used through FDW loopback server +-- -------------------------------------- +-- ====================================================================================================================================== CREATE TYPE user_enum AS ENUM ('foo', 'bar', 'buz'); CREATE SCHEMA "S 1"; CREATE TABLE "S 1"."T 1" ( - "C 1" int NOT NULL, - c2 int NOT NULL, - c3 text, - c4 timestamptz, - c5 timestamp, - c6 varchar(10), - c7 char(10), - c8 user_enum, - CONSTRAINT t1_pkey PRIMARY KEY ("C 1") + "C 1" int NOT NULL, + c2 int NOT NULL, + c3 text, + c4 timestamptz, + c5 timestamp, + c6 varchar(10), + c7 char(10), + c8 user_enum, + CONSTRAINT t1_pkey PRIMARY KEY ("C 1") ); CREATE TABLE "S 1"."T 2" ( - c1 int NOT NULL, - c2 text, - CONSTRAINT t2_pkey PRIMARY KEY (c1) + c1 int NOT NULL, + c2 text, + CONSTRAINT t2_pkey PRIMARY KEY (c1) +); +CREATE TABLE "S 1"."T 3" ( + c1 int NOT NULL, + c2 int NOT NULL, + c3 text, + CONSTRAINT t3_pkey PRIMARY KEY (c1) +); +CREATE TABLE "S 1"."T 4" ( + c1 int NOT NULL, + c2 int NOT NULL, + c3 text, + CONSTRAINT t4_pkey PRIMARY KEY (c1) ); +-- Disable autovacuum for these tables to avoid unexpected effects of that +ALTER TABLE "S 1"."T 1" SET (autovacuum_enabled = 'false'); +ALTER TABLE "S 1"."T 2" SET (autovacuum_enabled = 'false'); +ALTER TABLE "S 1"."T 3" SET (autovacuum_enabled = 'false'); +ALTER TABLE "S 1"."T 4" SET (autovacuum_enabled = 'false'); + INSERT INTO "S 1"."T 1" - SELECT id, - id % 10, - pg_catalog.to_char(id, 'FM00000'), - '1970-01-01'::timestamptz + ((id % 100) || ' days')::interval, - '1970-01-01'::timestamp + ((id % 100) || ' days')::interval, - id % 10, - id % 10, - 'foo'::user_enum - FROM pg_catalog.generate_series(1, 1000) id; + SELECT id, + id % 10, + to_char(id, 'FM00000'), + '1970-01-01'::timestamptz + ((id % 100) || ' days')::interval, + '1970-01-01'::timestamp + ((id % 100) || ' days')::interval, + id % 10, + id % 10, + 'foo'::user_enum + FROM generate_series(1, 1000) id; INSERT INTO "S 1"."T 2" - SELECT id, - 'AAA' || pg_catalog.to_char(id, 'FM000') - FROM pg_catalog.generate_series(1, 100) id; + SELECT id, + 'AAA' || to_char(id, 'FM000') + FROM generate_series(1, 100) id; +INSERT INTO "S 1"."T 3" + SELECT id, + id + 1, + 'AAA' || to_char(id, 'FM000') + FROM generate_series(1, 100) id; +DELETE FROM "S 1"."T 3" WHERE c1 % 2 != 0; -- delete for outer join tests +INSERT INTO "S 1"."T 4" + SELECT id, + id + 1, + 'AAA' || to_char(id, 'FM000') + FROM generate_series(1, 100) id; +DELETE FROM "S 1"."T 4" WHERE c1 % 3 != 0; -- delete for outer join tests ANALYZE "S 1"."T 1"; ANALYZE "S 1"."T 2"; - --- =================================================================== --- create foreign tables --- =================================================================== +ANALYZE "S 1"."T 3"; +ANALYZE "S 1"."T 4"; +-- ====================================================================================================================================== +-- TEST-MODULE: create foreign tables +-- -------------------------------------- +-- ====================================================================================================================================== CREATE FOREIGN TABLE ft1 ( - c0 int, - c1 int NOT NULL, - c2 int NOT NULL, - c3 text, - c4 timestamptz, - c5 timestamp, - c6 varchar(10), - c7 char(10) default 'ft1', - c8 user_enum + c0 int, + c1 int NOT NULL, + c2 int NOT NULL, + c3 text, + c4 timestamptz, + c5 timestamp, + c6 varchar(10), + c7 char(10) default 'ft1', + c8 user_enum ) SERVER loopback; ALTER FOREIGN TABLE ft1 DROP COLUMN c0; CREATE FOREIGN TABLE ft2 ( - c1 int NOT NULL, - c2 int NOT NULL, - cx int, - c3 text, - c4 timestamptz, - c5 timestamp, - c6 varchar(10), - c7 char(10) default 'ft2', - c8 user_enum + c1 int NOT NULL, + c2 int NOT NULL, + cx int, + c3 text, + c4 timestamptz, + c5 timestamp, + c6 varchar(10), + c7 char(10) default 'ft2', + c8 user_enum ) SERVER loopback; ALTER FOREIGN TABLE ft2 DROP COLUMN cx; --- =================================================================== --- tests for validator --- =================================================================== +CREATE FOREIGN TABLE ft4 ( + c1 int NOT NULL, + c2 int NOT NULL, + c3 text +) SERVER loopback OPTIONS (schema_name 'S 1', table_name 'T 3'); + +CREATE FOREIGN TABLE ft5 ( + c1 int NOT NULL, + c2 int NOT NULL, + c3 text +) SERVER loopback OPTIONS (schema_name 'S 1', table_name 'T 4'); + +CREATE FOREIGN TABLE ft6 ( + c1 int NOT NULL, + c2 int NOT NULL, + c3 text +) SERVER loopback2 OPTIONS (schema_name 'S 1', table_name 'T 4'); + +CREATE FOREIGN TABLE ft7 ( + c1 int NOT NULL, + c2 int NOT NULL, + c3 text +) SERVER loopback3 OPTIONS (schema_name 'S 1', table_name 'T 4'); +-- ====================================================================================================================================== +-- TEST-MODULE: tests for validator +-- -------------------------------------- +-- ====================================================================================================================================== -- requiressl and some other parameters are omitted because -- valid values for them depend on configure options ALTER SERVER testserver1 OPTIONS ( - use_remote_estimate 'false', - updatable 'true', - fdw_startup_cost '123.456', - fdw_tuple_cost '0.123', - service 'value', - connect_timeout 'value', - dbname 'value', - host 'value', - hostaddr 'value', - port 'value', - --client_encoding 'value', - application_name 'value', - --fallback_application_name 'value', - keepalives 'value', - keepalives_idle 'value', - keepalives_interval 'value', - -- requiressl 'value', - sslcompression 'value', - sslmode 'value', - sslcert 'value', - sslkey 'value', - sslrootcert 'value', - sslcrl 'value', - --requirepeer 'value', - krbsrvname 'value', - gsslib 'value' - --replication 'value' + use_remote_estimate 'false', + updatable 'true', + fdw_startup_cost '123.456', + fdw_tuple_cost '0.123', + service 'value', + connect_timeout 'value', + dbname 'value', + host 'value', + hostaddr 'value', + port 'value', + --client_encoding 'value', + application_name 'value', + --fallback_application_name 'value', + keepalives 'value', + keepalives_idle 'value', + keepalives_interval 'value', + --nspt tcp_user_timeout 'value', + -- requiressl 'value', + sslcompression 'value', + sslmode 'value', + sslcert 'value', + sslkey 'value', + sslrootcert 'value', + sslcrl 'value', + --requirepeer 'value', + krbsrvname 'value' + --nspt gsslib 'value' + --replication 'value' ); -ALTER USER MAPPING FOR public SERVER testserver1 - OPTIONS (DROP user, DROP password); + +-- Error, invalid list syntax +ALTER SERVER testserver1 OPTIONS (ADD extensions 'foo; bar'); --nspt + +-- OK but gets a warning +ALTER SERVER testserver1 OPTIONS (ADD extensions 'foo, bar'); --nspt +ALTER SERVER testserver1 OPTIONS (DROP extensions); --nspt + +ALTER USER MAPPING FOR public SERVER testserver1 OPTIONS (DROP user, DROP password); + +-- Attempt to add a valid option that's not allowed in a user mapping +ALTER USER MAPPING FOR public SERVER testserver1 --nspt + OPTIONS (ADD sslmode 'require'); --nspt + +-- But we can add valid ones fine +ALTER USER MAPPING FOR public SERVER testserver1 --nspt + OPTIONS (ADD sslpassword 'dummy'); --nspt + +-- Ensure valid options we haven't used in a user mapping yet are +-- permitted to check validation. +ALTER USER MAPPING FOR public SERVER testserver1 --nspt + OPTIONS (ADD sslkey 'value', ADD sslcert 'value'); --nspt + ALTER FOREIGN TABLE ft1 OPTIONS (schema_name 'S 1', table_name 'T 1'); ALTER FOREIGN TABLE ft2 OPTIONS (schema_name 'S 1', table_name 'T 1'); ALTER FOREIGN TABLE ft1 ALTER COLUMN c1 OPTIONS (column_name 'C 1'); @@ -151,32 +258,32 @@ SELECT c3, c4 FROM ft1 ORDER BY c3, c1 LIMIT 1; -- should work again -- and remote-estimate mode on ft2. ANALYZE ft1; ALTER FOREIGN TABLE ft2 OPTIONS (use_remote_estimate 'true'); - --- =================================================================== --- simple queries --- =================================================================== --- single table, with/without alias -EXPLAIN (COSTS false) SELECT * FROM ft1 ORDER BY c3, c1 OFFSET 100 LIMIT 10; +-- ====================================================================================================================================== +-- TEST-MODULE: simple queries +-- -------------------------------------- +-- +-- ====================================================================================================================================== +-- single table without alias +EXPLAIN (COSTS OFF) SELECT * FROM ft1 ORDER BY c3, c1 OFFSET 100 LIMIT 10; SELECT * FROM ft1 ORDER BY c3, c1 OFFSET 100 LIMIT 10; -EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; -SELECT * FROM ft1 t1 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; +--nspt single table with alias - also test that tableoid sort is not pushed to remote side +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 ORDER BY t1.c3, t1.c1, t1.tableoid OFFSET 100 LIMIT 10; +SELECT * FROM ft1 t1 ORDER BY t1.c3, t1.c1, t1.tableoid OFFSET 100 LIMIT 10; -- whole-row reference -EXPLAIN (VERBOSE, COSTS false) SELECT t1 FROM ft1 t1 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; +EXPLAIN (VERBOSE, COSTS OFF) SELECT t1 FROM ft1 t1 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; SELECT t1 FROM ft1 t1 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; -- empty result SELECT * FROM ft1 WHERE false; -- with WHERE clause -EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE t1.c1 = 101 AND t1.c6 = '1' AND t1.c7 >= '1'; +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE t1.c1 = 101 AND t1.c6 = '1' AND t1.c7 >= '1'; SELECT * FROM ft1 t1 WHERE t1.c1 = 101 AND t1.c6 = '1' AND t1.c7 >= '1'; -- with FOR UPDATE/SHARE -EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE c1 = 101 FOR UPDATE; +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 = 101 FOR UPDATE; SELECT * FROM ft1 t1 WHERE c1 = 101 FOR UPDATE; -EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE c1 = 102 FOR SHARE; +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 = 102 FOR SHARE; SELECT * FROM ft1 t1 WHERE c1 = 102 FOR SHARE; -- aggregate SELECT COUNT(*) FROM ft1 t1; --- join two tables -SELECT t1.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; -- subquery SELECT * FROM ft1 t1 WHERE t1.c3 IN (SELECT c3 FROM ft2 t2 WHERE c1 <= 10) ORDER BY c1; -- subquery+MAX @@ -185,114 +292,723 @@ SELECT * FROM ft1 t1 WHERE t1.c3 = (SELECT MAX(c3) FROM ft2 t2) ORDER BY c1; WITH t1 AS (SELECT * FROM ft1 WHERE c1 <= 10) SELECT t2.c1, t2.c2, t2.c3, t2.c4 FROM t1, ft2 t2 WHERE t1.c1 = t2.c1 ORDER BY t1.c1; -- fixed values SELECT 'fixed', NULL FROM ft1 t1 WHERE c1 = 1; +-- Test forcing the remote server to produce sorted data for a merge join. +SET enable_hashjoin TO false; +SET enable_nestloop TO false; +-- inner join; expressions in the clauses appear in the equivalence class list +EXPLAIN (VERBOSE, COSTS OFF) + SELECT t1.c1, t2."C 1" FROM ft2 t1 JOIN "S 1"."T 1" t2 ON (t1.c1 = t2."C 1") OFFSET 100 LIMIT 10; +SELECT t1.c1, t2."C 1" FROM ft2 t1 JOIN "S 1"."T 1" t2 ON (t1.c1 = t2."C 1") OFFSET 100 LIMIT 10; +-- outer join; expressions in the clauses do not appear in equivalence class +-- list but no output change as compared to the previous query +EXPLAIN (VERBOSE, COSTS OFF) + SELECT t1.c1, t2."C 1" FROM ft2 t1 LEFT JOIN "S 1"."T 1" t2 ON (t1.c1 = t2."C 1") OFFSET 100 LIMIT 10; +SELECT t1.c1, t2."C 1" FROM ft2 t1 LEFT JOIN "S 1"."T 1" t2 ON (t1.c1 = t2."C 1") OFFSET 100 LIMIT 10; +-- A join between local table and foreign join. ORDER BY clause is added to the +-- foreign join so that the local table can be joined using merge join strategy. +EXPLAIN (VERBOSE, COSTS OFF) + SELECT t1."C 1" FROM "S 1"."T 1" t1 left join ft1 t2 join ft2 t3 on (t2.c1 = t3.c1) on (t3.c1 = t1."C 1") OFFSET 100 LIMIT 10; +SELECT t1."C 1" FROM "S 1"."T 1" t1 left join ft1 t2 join ft2 t3 on (t2.c1 = t3.c1) on (t3.c1 = t1."C 1") OFFSET 100 LIMIT 10; +-- Test similar to above, except that the full join prevents any equivalence +-- classes from being merged. This produces single relation equivalence classes +-- included in join restrictions. +EXPLAIN (VERBOSE, COSTS OFF) + SELECT t1."C 1", t2.c1, t3.c1 FROM "S 1"."T 1" t1 left join ft1 t2 full join ft2 t3 on (t2.c1 = t3.c1) on (t3.c1 = t1."C 1") OFFSET 100 LIMIT 10; +SELECT t1."C 1", t2.c1, t3.c1 FROM "S 1"."T 1" t1 left join ft1 t2 full join ft2 t3 on (t2.c1 = t3.c1) on (t3.c1 = t1."C 1") OFFSET 100 LIMIT 10; +-- Test similar to above with all full outer joins +EXPLAIN (VERBOSE, COSTS OFF) + SELECT t1."C 1", t2.c1, t3.c1 FROM "S 1"."T 1" t1 full join ft1 t2 full join ft2 t3 on (t2.c1 = t3.c1) on (t3.c1 = t1."C 1") OFFSET 100 LIMIT 10; +SELECT t1."C 1", t2.c1, t3.c1 FROM "S 1"."T 1" t1 full join ft1 t2 full join ft2 t3 on (t2.c1 = t3.c1) on (t3.c1 = t1."C 1") OFFSET 100 LIMIT 10; +RESET enable_hashjoin; +RESET enable_nestloop; + +-- Test executing assertion in estimate_path_cost_size() that makes sure that +-- retrieved_rows for foreign rel re-used to cost pre-sorted foreign paths is +-- a sensible value even when the rel has tuples=0 +CREATE TABLE loct_empty (c1 int NOT NULL, c2 text); +CREATE FOREIGN TABLE ft_empty (c1 int NOT NULL, c2 text) + SERVER loopback OPTIONS (table_name 'loct_empty'); +INSERT INTO loct_empty + SELECT id, 'AAA' || to_char(id, 'FM000') FROM generate_series(1, 100) id; +DELETE FROM loct_empty; +ANALYZE ft_empty; +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft_empty ORDER BY c1; +-- ====================================================================================================================================== +-- TEST-MODULE: WHERE with remotely-executable conditions +-- -------------------------------------- +-- ====================================================================================================================================== +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE t1.c1 = 1; -- Var, OpExpr(b), Const +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE t1.c1 = 100 AND t1.c2 = 0; -- BoolExpr +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 IS NULL; -- NullTest +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 IS NOT NULL; -- NullTest +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE round(abs(c1), 0) = 1; -- FuncExpr +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 = -c1; -- OpExpr(l) +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE (c1 IS NOT NULL) IS DISTINCT FROM (c1 IS NOT NULL); -- DistinctExpr +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 = ANY(ARRAY[c2, 1, c1 + 0]); -- ScalarArrayOpExpr +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 = (ARRAY[c1,c2,3])[1]; -- SubscriptingRef +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c6 = E'foo''s\\bar'; -- check special chars +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c8 = 'foo'; -- can't be sent to remote +-- parameterized remote path for foreign table +EXPLAIN (VERBOSE, COSTS OFF) + SELECT * FROM "S 1"."T 1" a, ft2 b WHERE a."C 1" = 47 AND b.c1 = a.c2; +SELECT * FROM ft2 a, ft2 b WHERE a.c1 = 47 AND b.c1 = a.c2; + +-- check both safe and unsafe join conditions +EXPLAIN (VERBOSE, COSTS OFF) + SELECT * FROM ft2 a, ft2 b + WHERE a.c2 = 6 AND b.c1 = a.c1 AND a.c8 = 'foo' AND b.c7 = upper(a.c7); +SELECT * FROM ft2 a, ft2 b +WHERE a.c2 = 6 AND b.c1 = a.c1 AND a.c8 = 'foo' AND b.c7 = upper(a.c7); +-- bug before 9.3.5 due to sloppy handling of remote-estimate parameters +SELECT * FROM ft1 WHERE c1 = ANY (ARRAY(SELECT c1 FROM ft2 WHERE c1 < 5)); +SELECT * FROM ft2 WHERE c1 = ANY (ARRAY(SELECT c1 FROM ft1 WHERE c1 < 5)); +-- we should not push order by clause with volatile expressions or unsafe +-- collations +EXPLAIN (VERBOSE, COSTS OFF) + SELECT * FROM ft2 ORDER BY ft2.c1, random(); +EXPLAIN (VERBOSE, COSTS OFF) + SELECT * FROM ft2 ORDER BY ft2.c1, ft2.c3 collate "C"; + -- user-defined operator/function CREATE FUNCTION postgres_fdw_abs(int) RETURNS int AS $$ BEGIN -RETURN pg_catalog.abs($1); +RETURN abs($1); END $$ LANGUAGE plpgsql IMMUTABLE; CREATE OPERATOR === ( LEFTARG = int, RIGHTARG = int, PROCEDURE = int4eq, - COMMUTATOR = ===, - NEGATOR = !== + COMMUTATOR = === ); -EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE t1.c1 = postgres_fdw_abs(t1.c2); -EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE t1.c1 === t1.c2; -EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE t1.c1 = pg_catalog.abs(t1.c2); -EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE t1.c1 = t1.c2; --- =================================================================== --- WHERE with remotely-executable conditions --- =================================================================== -EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE t1.c1 = 1; -- Var, OpExpr(b), Const -EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE t1.c1 = 100 AND t1.c2 = 0; -- BoolExpr -EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE c1 IS NULL; -- NullTest -EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE c1 IS NOT NULL; -- NullTest -EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE pg_catalog.round(pg_catalog.abs(c1), 0) = 1; -- FuncExpr -EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE c1 = -c1; -- OpExpr(l) -EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE 1 = c1!; -- OpExpr(r) -EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE (c1 IS NOT NULL) IS DISTINCT FROM (c1 IS NOT NULL); -- DistinctExpr -EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE c1 = ANY(ARRAY[c2, 1, c1 + 0]); -- ScalarArrayOpExpr -EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE c1 = (ARRAY[c1,c2,3])[1]; -- ArrayRef -EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE c6 = E'foo''s\\bar'; -- check special chars -EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE c8 = 'foo'; -- can't be sent to remote --- parameterized remote path -EXPLAIN (VERBOSE, COSTS false) - SELECT * FROM ft2 a, ft2 b WHERE a.c1 = 47 AND b.c1 = a.c2; -SELECT * FROM ft2 a, ft2 b WHERE a.c1 = 47 AND b.c1 = a.c2; --- check both safe and unsafe join conditions -EXPLAIN (VERBOSE, COSTS false) - SELECT * FROM ft2 a, ft2 b - WHERE a.c2 = 6 AND b.c1 = a.c1 AND a.c8 = 'foo' AND b.c7 = pg_catalog.upper(a.c7); -SELECT * FROM ft2 a, ft2 b -WHERE a.c2 = 6 AND b.c1 = a.c1 AND a.c8 = 'foo' AND b.c7 = pg_catalog.upper(a.c7); --- bug before 9.3.5 due to sloppy handling of remote-estimate parameters -SELECT * FROM ft1 WHERE c1 = ANY (ARRAY(SELECT c1 FROM ft2 WHERE c1 < 5)); -SELECT * FROM ft2 WHERE c1 = ANY (ARRAY(SELECT c1 FROM ft1 WHERE c1 < 5)); - --- bug #15613: bad plan for foreign table scan with lateral reference +-- built-in operators and functions can be shipped for remote execution EXPLAIN (VERBOSE, COSTS OFF) -SELECT ref_0.c2, subq_1.* -FROM - "S 1"."T 1" AS ref_0, - LATERAL ( - SELECT ref_0."C 1" c1, subq_0.* - FROM (SELECT ref_0.c2, ref_1.c3 - FROM ft1 AS ref_1) AS subq_0 - RIGHT JOIN ft2 AS ref_3 ON (subq_0.c3 = ref_3.c3) - ) AS subq_1 -WHERE ref_0."C 1" < 10 AND subq_1.c3 = '00001' -ORDER BY ref_0."C 1"; + SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = abs(t1.c2); +SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = abs(t1.c2); +EXPLAIN (VERBOSE, COSTS OFF) + SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = t1.c2; +SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = t1.c2; -SELECT ref_0.c2, subq_1.* -FROM - "S 1"."T 1" AS ref_0, - LATERAL ( - SELECT ref_0."C 1" c1, subq_0.* - FROM (SELECT ref_0.c2, ref_1.c3 - FROM ft1 AS ref_1) AS subq_0 - RIGHT JOIN ft2 AS ref_3 ON (subq_0.c3 = ref_3.c3) - ) AS subq_1 -WHERE ref_0."C 1" < 10 AND subq_1.c3 = '00001' -ORDER BY ref_0."C 1"; +-- by default, user-defined ones cannot +EXPLAIN (VERBOSE, COSTS OFF) + SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = postgres_fdw_abs(t1.c2); +SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = postgres_fdw_abs(t1.c2); +EXPLAIN (VERBOSE, COSTS OFF) + SELECT count(c3) FROM ft1 t1 WHERE t1.c1 === t1.c2; +SELECT count(c3) FROM ft1 t1 WHERE t1.c1 === t1.c2; --- =================================================================== --- parameterized queries --- =================================================================== +-- ORDER BY can be shipped, though +EXPLAIN (VERBOSE, COSTS OFF) + SELECT * FROM ft1 t1 WHERE t1.c1 === t1.c2 order by t1.c2 limit 1; +SELECT * FROM ft1 t1 WHERE t1.c1 === t1.c2 order by t1.c2 limit 1; + +-- but let's put them in an extension ... +ALTER EXTENSION postgres_fdw ADD FUNCTION postgres_fdw_abs(int); +ALTER EXTENSION postgres_fdw ADD OPERATOR === (int, int); +ALTER SERVER loopback OPTIONS (ADD extensions 'postgres_fdw'); --nspt + +-- ... now they can be shipped +EXPLAIN (VERBOSE, COSTS OFF) + SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = postgres_fdw_abs(t1.c2); +SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = postgres_fdw_abs(t1.c2); +EXPLAIN (VERBOSE, COSTS OFF) + SELECT count(c3) FROM ft1 t1 WHERE t1.c1 === t1.c2; +SELECT count(c3) FROM ft1 t1 WHERE t1.c1 === t1.c2; + +-- and both ORDER BY and LIMIT can be shipped +EXPLAIN (VERBOSE, COSTS OFF) + SELECT * FROM ft1 t1 WHERE t1.c1 === t1.c2 order by t1.c2 limit 1; +SELECT * FROM ft1 t1 WHERE t1.c1 === t1.c2 order by t1.c2 limit 1; + +-- case when expr +explain (verbose, costs off) select * from ft1 where case c1 when 1 then 0 end = 0; +select * from ft1 where case c1 when 1 then 0 end = 0; + +-- ====================================================================================================================================== +-- TEST-MODULE: JOIN queries +-- -------------------------------------- +-- ====================================================================================================================================== +-- Analyze ft4 and ft5 so that we have better statistics. These tables do not +-- have use_remote_estimate set. +ANALYZE ft4; +ANALYZE ft5; + +-- join two tables +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; +-- join three tables +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) JOIN ft4 t3 ON (t3.c1 = t1.c1) ORDER BY t1.c3, t1.c1 OFFSET 10 LIMIT 10; +SELECT t1.c1, t2.c2, t3.c3 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) JOIN ft4 t3 ON (t3.c1 = t1.c1) ORDER BY t1.c3, t1.c1 OFFSET 10 LIMIT 10; +-- left outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; +SELECT t1.c1, t2.c1 FROM ft4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; +-- left outer join three tables +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) LEFT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) LEFT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +-- left outer join + placement of clauses. +-- clauses within the nullable side are not pulled up, but top level clause on +-- non-nullable side is pushed into non-nullable side +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t1.c2, t2.c1, t2.c2 FROM ft4 t1 LEFT JOIN (SELECT * FROM ft5 WHERE c1 < 10) t2 ON (t1.c1 = t2.c1) WHERE t1.c1 < 10; +SELECT t1.c1, t1.c2, t2.c1, t2.c2 FROM ft4 t1 LEFT JOIN (SELECT * FROM ft5 WHERE c1 < 10) t2 ON (t1.c1 = t2.c1) WHERE t1.c1 < 10; +-- clauses within the nullable side are not pulled up, but the top level clause +-- on nullable side is not pushed down into nullable side +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t1.c2, t2.c1, t2.c2 FROM ft4 t1 LEFT JOIN (SELECT * FROM ft5 WHERE c1 < 10) t2 ON (t1.c1 = t2.c1) + WHERE (t2.c1 < 10 OR t2.c1 IS NULL) AND t1.c1 < 10; +SELECT t1.c1, t1.c2, t2.c1, t2.c2 FROM ft4 t1 LEFT JOIN (SELECT * FROM ft5 WHERE c1 < 10) t2 ON (t1.c1 = t2.c1) + WHERE (t2.c1 < 10 OR t2.c1 IS NULL) AND t1.c1 < 10; +-- right outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft5 t1 RIGHT JOIN ft4 t2 ON (t1.c1 = t2.c1) ORDER BY t2.c1, t1.c1 OFFSET 10 LIMIT 10; +SELECT t1.c1, t2.c1 FROM ft5 t1 RIGHT JOIN ft4 t2 ON (t1.c1 = t2.c1) ORDER BY t2.c1, t1.c1 OFFSET 10 LIMIT 10; +-- right outer join three tables +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 RIGHT JOIN ft2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 RIGHT JOIN ft2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +-- full outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft4 t1 FULL JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 45 LIMIT 10; +SELECT t1.c1, t2.c1 FROM ft4 t1 FULL JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 45 LIMIT 10; +-- full outer join with restrictions on the joining relations +-- a. the joining relations are both base relations +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1; +SELECT t1.c1, t2.c1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1; +EXPLAIN (VERBOSE, COSTS OFF) +SELECT 1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t2 ON (TRUE) OFFSET 10 LIMIT 10; +SELECT 1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t2 ON (TRUE) OFFSET 10 LIMIT 10; +EXPLAIN (VERBOSE, COSTS OFF) +SELECT count(*) FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t2 ON (TRUE) OFFSET 10 LIMIT 2; +SELECT count(*) FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t2 ON (TRUE) OFFSET 10 LIMIT 2; +-- b. one of the joining relations is a base relation and the other is a join +-- relation +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, ss.a, ss.b FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT t2.c1, t3.c1 FROM ft4 t2 LEFT JOIN ft5 t3 ON (t2.c1 = t3.c1) WHERE (t2.c1 between 50 and 60)) ss(a, b) ON (t1.c1 = ss.a) ORDER BY t1.c1, ss.a, ss.b; +SELECT t1.c1, ss.a, ss.b FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT t2.c1, t3.c1 FROM ft4 t2 LEFT JOIN ft5 t3 ON (t2.c1 = t3.c1) WHERE (t2.c1 between 50 and 60)) ss(a, b) ON (t1.c1 = ss.a) ORDER BY t1.c1, ss.a, ss.b; +-- c. test deparsing the remote query as nested subqueries +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, ss.a, ss.b FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT t2.c1, t3.c1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t2 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t3 ON (t2.c1 = t3.c1) WHERE t2.c1 IS NULL OR t2.c1 IS NOT NULL) ss(a, b) ON (t1.c1 = ss.a) ORDER BY t1.c1, ss.a, ss.b; +SELECT t1.c1, ss.a, ss.b FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT t2.c1, t3.c1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t2 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t3 ON (t2.c1 = t3.c1) WHERE t2.c1 IS NULL OR t2.c1 IS NOT NULL) ss(a, b) ON (t1.c1 = ss.a) ORDER BY t1.c1, ss.a, ss.b; +-- d. test deparsing rowmarked relations as subqueries +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, ss.a, ss.b FROM (SELECT c1 FROM "S 1"."T 3" WHERE c1 = 50) t1 INNER JOIN (SELECT t2.c1, t3.c1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t2 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t3 ON (t2.c1 = t3.c1) WHERE t2.c1 IS NULL OR t2.c1 IS NOT NULL) ss(a, b) ON (TRUE) ORDER BY t1.c1, ss.a, ss.b FOR UPDATE OF t1; +SELECT t1.c1, ss.a, ss.b FROM (SELECT c1 FROM "S 1"."T 3" WHERE c1 = 50) t1 INNER JOIN (SELECT t2.c1, t3.c1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t2 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t3 ON (t2.c1 = t3.c1) WHERE t2.c1 IS NULL OR t2.c1 IS NOT NULL) ss(a, b) ON (TRUE) ORDER BY t1.c1, ss.a, ss.b FOR UPDATE OF t1; +-- full outer join + inner join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1, t3.c1 FROM ft4 t1 INNER JOIN ft5 t2 ON (t1.c1 = t2.c1 + 1 and t1.c1 between 50 and 60) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) ORDER BY t1.c1, t2.c1, t3.c1 LIMIT 10; +SELECT t1.c1, t2.c1, t3.c1 FROM ft4 t1 INNER JOIN ft5 t2 ON (t1.c1 = t2.c1 + 1 and t1.c1 between 50 and 60) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) ORDER BY t1.c1, t2.c1, t3.c1 LIMIT 10; +-- full outer join three tables +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +-- full outer join + right outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +-- right outer join + full outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 RIGHT JOIN ft2 t2 ON (t1.c1 = t2.c1) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 RIGHT JOIN ft2 t2 ON (t1.c1 = t2.c1) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +-- full outer join + left outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) LEFT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) LEFT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +-- left outer join + full outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +SET enable_memoize TO off; --nspt +-- right outer join + left outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 RIGHT JOIN ft2 t2 ON (t1.c1 = t2.c1) LEFT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 RIGHT JOIN ft2 t2 ON (t1.c1 = t2.c1) LEFT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +RESET enable_memoize; --nspt +-- left outer join + right outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +-- full outer join + WHERE clause, only matched rows +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft4 t1 FULL JOIN ft5 t2 ON (t1.c1 = t2.c1) WHERE (t1.c1 = t2.c1 OR t1.c1 IS NULL) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; +SELECT t1.c1, t2.c1 FROM ft4 t1 FULL JOIN ft5 t2 ON (t1.c1 = t2.c1) WHERE (t1.c1 = t2.c1 OR t1.c1 IS NULL) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; +-- full outer join + WHERE clause with shippable extensions set +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t1.c3 FROM ft1 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE postgres_fdw_abs(t1.c1) > 0 OFFSET 10 LIMIT 10; +ALTER SERVER loopback OPTIONS (DROP extensions); --nspt +-- full outer join + WHERE clause with shippable extensions not set +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t1.c3 FROM ft1 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE postgres_fdw_abs(t1.c1) > 0 OFFSET 10 LIMIT 10; +ALTER SERVER loopback OPTIONS (ADD extensions 'postgres_fdw'); --nspt +-- join two tables with FOR UPDATE clause +-- tests whole-row reference for row marks +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR UPDATE OF t1; +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR UPDATE OF t1; +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR UPDATE; +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR UPDATE; +-- join two tables with FOR SHARE clause +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR SHARE OF t1; +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR SHARE OF t1; +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR SHARE; +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR SHARE; +-- join in CTE +EXPLAIN (VERBOSE, COSTS OFF) +WITH t (c1_1, c1_3, c2_1) AS MATERIALIZED (SELECT t1.c1, t1.c3, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1)) SELECT c1_1, c2_1 FROM t ORDER BY c1_3, c1_1 OFFSET 100 LIMIT 10; +WITH t (c1_1, c1_3, c2_1) AS MATERIALIZED (SELECT t1.c1, t1.c3, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1)) SELECT c1_1, c2_1 FROM t ORDER BY c1_3, c1_1 OFFSET 100 LIMIT 10; +-- ctid with whole-row reference, currently, does not support system column +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.ctid, t1, t2, t1.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; +-- SEMI JOIN, not pushed down +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1 FROM ft1 t1 WHERE EXISTS (SELECT 1 FROM ft2 t2 WHERE t1.c1 = t2.c1) ORDER BY t1.c1 OFFSET 100 LIMIT 10; +SELECT t1.c1 FROM ft1 t1 WHERE EXISTS (SELECT 1 FROM ft2 t2 WHERE t1.c1 = t2.c1) ORDER BY t1.c1 OFFSET 100 LIMIT 10; +-- ANTI JOIN, not pushed down +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1 FROM ft1 t1 WHERE NOT EXISTS (SELECT 1 FROM ft2 t2 WHERE t1.c1 = t2.c2) ORDER BY t1.c1 OFFSET 100 LIMIT 10; +SELECT t1.c1 FROM ft1 t1 WHERE NOT EXISTS (SELECT 1 FROM ft2 t2 WHERE t1.c1 = t2.c2) ORDER BY t1.c1 OFFSET 100 LIMIT 10; +-- CROSS JOIN can be pushed down +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 CROSS JOIN ft2 t2 ORDER BY t1.c1, t2.c1 OFFSET 100 LIMIT 10; +SELECT t1.c1, t2.c1 FROM ft1 t1 CROSS JOIN ft2 t2 ORDER BY t1.c1, t2.c1 OFFSET 100 LIMIT 10; +-- different server, not pushed down. No result expected. +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft5 t1 JOIN ft6 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 100 LIMIT 10; +SELECT t1.c1, t2.c1 FROM ft5 t1 JOIN ft6 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 100 LIMIT 10; +-- unsafe join conditions (c8 has a UDT), not pushed down. Practically a CROSS +-- JOIN since c8 in both tables has same value. +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 LEFT JOIN ft2 t2 ON (t1.c8 = t2.c8) ORDER BY t1.c1, t2.c1 OFFSET 100 LIMIT 10; +SELECT t1.c1, t2.c1 FROM ft1 t1 LEFT JOIN ft2 t2 ON (t1.c8 = t2.c8) ORDER BY t1.c1, t2.c1 OFFSET 100 LIMIT 10; +-- unsafe conditions on one side (c8 has a UDT), not pushed down. +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE t1.c8 = 'foo' ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; +SELECT t1.c1, t2.c1 FROM ft1 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE t1.c8 = 'foo' ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; +-- join where unsafe to pushdown condition in WHERE clause has a column not +-- in the SELECT clause. In this test unsafe clause needs to have column +-- references from both joining sides so that the clause is not pushed down +-- into one of the joining sides. +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE t1.c8 = t2.c8 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE t1.c8 = t2.c8 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; +-- Aggregate after UNION, for testing setrefs +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1c1, avg(t1c1 + t2c1) FROM (SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) UNION SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1)) AS t (t1c1, t2c1) GROUP BY t1c1 ORDER BY t1c1 OFFSET 100 LIMIT 10; +SELECT t1c1, avg(t1c1 + t2c1) FROM (SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) UNION SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1)) AS t (t1c1, t2c1) GROUP BY t1c1 ORDER BY t1c1 OFFSET 100 LIMIT 10; +-- join with lateral reference +EXPLAIN (VERBOSE, COSTS OFF) -- openGauss not support LATERAL +SELECT t1."C 1" FROM "S 1"."T 1" t1, LATERAL (SELECT DISTINCT t2.c1, t3.c1 FROM ft1 t2, ft2 t3 WHERE t2.c1 = t3.c1 AND t2.c2 = t1.c2) q ORDER BY t1."C 1" OFFSET 10 LIMIT 10; +SELECT t1."C 1" FROM "S 1"."T 1" t1, LATERAL (SELECT DISTINCT t2.c1, t3.c1 FROM ft1 t2, ft2 t3 WHERE t2.c1 = t3.c1 AND t2.c2 = t1.c2) q ORDER BY t1."C 1" OFFSET 10 LIMIT 10; + +-- non-Var items in targetlist of the nullable rel of a join preventing +-- push-down in some cases +-- unable to push {ft1, ft2} +EXPLAIN (VERBOSE, COSTS OFF) +SELECT q.a, ft2.c1 FROM (SELECT 13 FROM ft1 WHERE c1 = 13) q(a) RIGHT JOIN ft2 ON (q.a = ft2.c1) WHERE ft2.c1 BETWEEN 10 AND 15; +SELECT q.a, ft2.c1 FROM (SELECT 13 FROM ft1 WHERE c1 = 13) q(a) RIGHT JOIN ft2 ON (q.a = ft2.c1) WHERE ft2.c1 BETWEEN 10 AND 15; + +-- ok to push {ft1, ft2} but not {ft1, ft2, ft4} +EXPLAIN (VERBOSE, COSTS OFF) +SELECT ft4.c1, q.* FROM ft4 LEFT JOIN (SELECT 13, ft1.c1, ft2.c1 FROM ft1 RIGHT JOIN ft2 ON (ft1.c1 = ft2.c1) WHERE ft1.c1 = 12) q(a, b, c) ON (ft4.c1 = q.b) WHERE ft4.c1 BETWEEN 10 AND 15; +SELECT ft4.c1, q.* FROM ft4 LEFT JOIN (SELECT 13, ft1.c1, ft2.c1 FROM ft1 RIGHT JOIN ft2 ON (ft1.c1 = ft2.c1) WHERE ft1.c1 = 12) q(a, b, c) ON (ft4.c1 = q.b) WHERE ft4.c1 BETWEEN 10 AND 15; + +-- join with nullable side with some columns with null values +UPDATE ft5 SET c3 = null where c1 % 9 = 0; +EXPLAIN (VERBOSE, COSTS OFF) +SELECT ft5, ft5.c1, ft5.c2, ft5.c3, ft4.c1, ft4.c2 FROM ft5 left join ft4 on ft5.c1 = ft4.c1 WHERE ft4.c1 BETWEEN 10 and 30 ORDER BY ft5.c1, ft4.c1; +SELECT ft5, ft5.c1, ft5.c2, ft5.c3, ft4.c1, ft4.c2 FROM ft5 left join ft4 on ft5.c1 = ft4.c1 WHERE ft4.c1 BETWEEN 10 and 30 ORDER BY ft5.c1, ft4.c1; + +-- multi-way join involving multiple merge joins +-- (this case used to have EPQ-related planning problems) +CREATE TABLE local_tbl (c1 int NOT NULL, c2 int NOT NULL, c3 text, CONSTRAINT local_tbl_pkey PRIMARY KEY (c1)); +INSERT INTO local_tbl SELECT id, id % 10, to_char(id, 'FM0000') FROM generate_series(1, 1000) id; +ANALYZE local_tbl; +SET enable_nestloop TO false; +SET enable_hashjoin TO false; +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM ft1, ft2, ft4, ft5, local_tbl WHERE ft1.c1 = ft2.c1 AND ft1.c2 = ft4.c1 + AND ft1.c2 = ft5.c1 AND ft1.c2 = local_tbl.c1 AND ft1.c1 < 100 AND ft2.c1 < 100 FOR UPDATE; +SELECT * FROM ft1, ft2, ft4, ft5, local_tbl WHERE ft1.c1 = ft2.c1 AND ft1.c2 = ft4.c1 + AND ft1.c2 = ft5.c1 AND ft1.c2 = local_tbl.c1 AND ft1.c1 < 100 AND ft2.c1 < 100 FOR UPDATE; +RESET enable_nestloop; +RESET enable_hashjoin; +DROP TABLE local_tbl; + +-- check join pushdown in situations where multiple userids are involved +CREATE ROLE regress_view_owner sysadmin password 'QWERT@12345'; +CREATE USER MAPPING FOR regress_view_owner SERVER loopback; +GRANT SELECT ON ft4 TO regress_view_owner; +GRANT SELECT ON ft5 TO regress_view_owner; + +CREATE VIEW v4 AS SELECT * FROM ft4; +CREATE VIEW v5 AS SELECT * FROM ft5; +ALTER VIEW v5 OWNER TO regress_view_owner; +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; -- can't be pushed down, different view owners +SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; +ALTER VIEW v4 OWNER TO regress_view_owner; +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; -- can be pushed down +SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; -- can't be pushed down, view owner not current user +SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; +ALTER VIEW v4 OWNER TO regress_view_owner; + +-- PlaceHolder +explain(verbose, costs off) select * from ft1 t1 left join (select c1, case when c2 is not null then 1 else 0 end as cc from ft2) t2 on t1.c1 = t2.c1; + +-- cleanup +DROP OWNED BY regress_view_owner; +DROP ROLE regress_view_owner; + +-- ====================================================================================================================================== +-- TEST-MODULE: Aggregate and grouping queries +-- -------------------------------------- +-- openGauss not support filter +-- ====================================================================================================================================== + +-- Simple aggregates +explain (verbose, costs off) +select count(c6), sum(c1), avg(c1), min(c2), max(c1), stddev(c2), sum(c1) * (random() <= 1)::int as sum2 from ft1 where c2 < 5 group by c2 order by 1, 2; +select count(c6), sum(c1), avg(c1), min(c2), max(c1), stddev(c2), sum(c1) * (random() <= 1)::int as sum2 from ft1 where c2 < 5 group by c2 order by 1, 2; + +explain (verbose, costs off) +select count(c6), sum(c1), avg(c1), min(c2), max(c1), stddev(c2), sum(c1) * (random() <= 1)::int as sum2 from ft1 where c2 < 5 group by c2 order by 1, 2 limit 1; +select count(c6), sum(c1), avg(c1), min(c2), max(c1), stddev(c2), sum(c1) * (random() <= 1)::int as sum2 from ft1 where c2 < 5 group by c2 order by 1, 2 limit 1; + +-- Aggregate is not pushed down as aggregation contains random() +explain (verbose, costs off) +select sum(c1 * (random() <= 1)::int) as sum, avg(c1) from ft1; + +-- Aggregate over join query +explain (verbose, costs off) +select count(*), sum(t1.c1), avg(t2.c1) from ft1 t1 inner join ft1 t2 on (t1.c2 = t2.c2) where t1.c2 = 6; +select count(*), sum(t1.c1), avg(t2.c1) from ft1 t1 inner join ft1 t2 on (t1.c2 = t2.c2) where t1.c2 = 6; + +-- Not pushed down due to local conditions present in underneath input rel +explain (verbose, costs off) +select sum(t1.c1), count(t2.c1) from ft1 t1 inner join ft2 t2 on (t1.c1 = t2.c1) where ((t1.c1 * t2.c1)/(t1.c1 * t2.c1)) * random() <= 1; + +-- GROUP BY clause having expressions +explain (verbose, costs off) +select c2/2, sum(c2) * (c2/2) from ft1 group by c2/2 order by c2/2; +select c2/2, sum(c2) * (c2/2) from ft1 group by c2/2 order by c2/2; + +-- Aggregates in subquery are pushed down. +explain (verbose, costs off) +select count(x.a), sum(x.a) from (select c2 a, sum(c1) b from ft1 group by c2, sqrt(c1) order by 1, 2) x; +select count(x.a), sum(x.a) from (select c2 a, sum(c1) b from ft1 group by c2, sqrt(c1) order by 1, 2) x; + +-- Aggregate is still pushed down by taking unshippable expression out +explain (verbose, costs off) +select c2 * (random() <= 1)::int as sum1, sum(c1) * c2 as sum2 from ft1 group by c2 order by 1, 2; +select c2 * (random() <= 1)::int as sum1, sum(c1) * c2 as sum2 from ft1 group by c2 order by 1, 2; + +-- Aggregate with unshippable GROUP BY clause are not pushed +explain (verbose, costs off) +select c2 * (random() <= 1)::int as c2 from ft2 group by c2 * (random() <= 1)::int order by 1; + +-- GROUP BY clause in various forms, cardinal, alias and constant expression +explain (verbose, costs off) +select count(c2) w, c2 x, 5 y, 7.0 z from ft1 group by 2, y, 9.0::int order by 2; +select count(c2) w, c2 x, 5 y, 7.0 z from ft1 group by 2, y, 9.0::int order by 2; + +-- GROUP BY clause referring to same column multiple times +-- Also, ORDER BY contains an aggregate function +explain (verbose, costs off) +select c2, c2 from ft1 where c2 > 6 group by 1, 2 order by sum(c1); +select c2, c2 from ft1 where c2 > 6 group by 1, 2 order by sum(c1); + +-- Testing HAVING clause shippability +explain (verbose, costs off) +select c2, sum(c1) from ft2 group by c2 having avg(c1) < 500 and sum(c1) < 49800 order by c2; +select c2, sum(c1) from ft2 group by c2 having avg(c1) < 500 and sum(c1) < 49800 order by c2; + +-- Unshippable HAVING clause will be evaluated locally, and other qual in HAVING clause is pushed down +explain (verbose, costs off) +select count(*) from (select c5, count(c1) from ft1 group by c5, sqrt(c2) having (avg(c1) / avg(c1)) * random() <= 1 and avg(c1) < 500) x; +select count(*) from (select c5, count(c1) from ft1 group by c5, sqrt(c2) having (avg(c1) / avg(c1)) * random() <= 1 and avg(c1) < 500) x; + +-- Aggregate in HAVING clause is not pushable, and thus aggregation is not pushed down +explain (verbose, costs off) +select sum(c1) from ft1 group by c2 having avg(c1 * (random() <= 1)::int) > 100 order by 1; + +-- Remote aggregate in combination with a local Param (for the output +-- of an initplan) can be trouble, per bug #15781 +explain (verbose, costs off) +select exists(select 1 from pg_enum), sum(c1) from ft1; +select exists(select 1 from pg_enum), sum(c1) from ft1; + +explain (verbose, costs off) +select exists(select 1 from pg_enum), sum(c1) from ft1 group by 1; +select exists(select 1 from pg_enum), sum(c1) from ft1 group by 1; + + +-- Testing ORDER BY, DISTINCT, FILTER, Ordered-sets and VARIADIC within aggregates + +-- ORDER BY within aggregate, same column used to order +explain (verbose, costs off) +select array_agg(c1 order by c1) from ft1 where c1 < 100 group by c2 order by 1; +select array_agg(c1 order by c1) from ft1 where c1 < 100 group by c2 order by 1; + +-- ORDER BY within aggregate, different column used to order also using DESC +explain (verbose, costs off) +select array_agg(c5 order by c1 desc) from ft2 where c2 = 6 and c1 < 50; +select array_agg(c5 order by c1 desc) from ft2 where c2 = 6 and c1 < 50; + +-- DISTINCT within aggregate +explain (verbose, costs off) +select array_agg(distinct (t1.c1)%5) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1; +select array_agg(distinct (t1.c1)%5) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1; + +-- DISTINCT combined with ORDER BY within aggregate +explain (verbose, costs off) +select array_agg(distinct (t1.c1)%5 order by (t1.c1)%5) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1; +select array_agg(distinct (t1.c1)%5 order by (t1.c1)%5) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1; + +explain (verbose, costs off) +select array_agg(distinct (t1.c1)%5 order by (t1.c1)%5 desc nulls last) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1; +select array_agg(distinct (t1.c1)%5 order by (t1.c1)%5 desc nulls last) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1; + +--nspt FILTER within aggregate +-- openGauss not support FILTER within aggregate, leve a err case, make others run as normal +explain (verbose, costs off) +select sum(c1) filter (where c1 < 100 and c2 > 5) from ft1 group by c2 order by 1 nulls last; +select sum(c1) filter (where c1 < 100 and c2 > 5) from ft1 group by c2 order by 1 nulls last; +explain (verbose, costs off) +select sum(c1)/* filter (where c1 < 100 and c2 > 5)*/ from ft1 group by c2 order by 1 nulls last; +select sum(c1)/* filter (where c1 < 100 and c2 > 5)*/ from ft1 group by c2 order by 1 nulls last; + +-- DISTINCT, ORDER BY and FILTER within aggregate +explain (verbose, costs off) +select sum(c1%3), sum(distinct c1%3 order by c1%3)/* filter (where c1%3 < 2)*/, c2 from ft1 where c2 = 6 group by c2; +select sum(c1%3), sum(distinct c1%3 order by c1%3)/* filter (where c1%3 < 2)*/, c2 from ft1 where c2 = 6 group by c2; + +-- Outer query is aggregation query +explain (verbose, costs off) +select distinct (select count(*) /*filter (where t2.c2 = 6 and t2.c1 < 10)*/ from ft1 t1 where t1.c1 = 6) from ft2 t2 where t2.c2 % 6 = 0 order by 1; +select distinct (select count(*) /*filter (where t2.c2 = 6 and t2.c1 < 10)*/ from ft1 t1 where t1.c1 = 6) from ft2 t2 where t2.c2 % 6 = 0 order by 1; +-- Inner query is aggregation query +explain (verbose, costs off) +select distinct (select count(t1.c1) /*filter (where t2.c2 = 6 and t2.c1 < 10)*/ from ft1 t1 where t1.c1 = 6) from ft2 t2 where t2.c2 % 6 = 0 order by 1; +select distinct (select count(t1.c1) /*filter (where t2.c2 = 6 and t2.c1 < 10)*/ from ft1 t1 where t1.c1 = 6) from ft2 t2 where t2.c2 % 6 = 0 order by 1; + +-- Aggregate not pushed down as FILTER condition is not pushable +explain (verbose, costs off) select sum(c1) /*filter (where (c1 / c1) * random() <= 1)*/ from ft1 group by c2 order by 1; +explain (verbose, costs off) select sum(c2) /*filter (where c2 in (select c2 from ft1 where c2 < 5))*/ from ft1; + +--nspt Ordered-sets within aggregate +--nspt Using multiple arguments within aggregates + +-- User defined function for user defined aggregate, VARIADIC +create function least_accum(anyelement, variadic anyarray) +returns anyelement language sql as + 'select least($1, min($2[i])) from generate_subscripts($2,1) g(i)'; +create aggregate least_agg(variadic items anyarray) ( + stype = anyelement, sfunc = least_accum +); + +-- Disable hash aggregation for plan stability. +set enable_hashagg to false; + +-- Not pushed down due to user defined aggregate +explain (verbose, costs off) +select c2, least_agg(c1) from ft1 group by c2 order by c2; + +-- Add function and aggregate into extension +alter extension postgres_fdw add function least_accum(anyelement, variadic anyarray); +alter extension postgres_fdw add aggregate least_agg(variadic items anyarray); +alter server loopback options (set extensions 'postgres_fdw'); + +-- Now aggregate will be pushed. Aggregate will display VARIADIC argument. +explain (verbose, costs off) +select c2, least_agg(c1) from ft1 where c2 < 100 group by c2 order by c2; +select c2, least_agg(c1) from ft1 where c2 < 100 group by c2 order by c2; + +-- Remove function and aggregate from extension +alter extension postgres_fdw drop function least_accum(anyelement, variadic anyarray); +alter extension postgres_fdw drop aggregate least_agg(variadic items anyarray); +alter server loopback options (set extensions 'postgres_fdw'); + +-- Not pushed down as we have dropped objects from extension. +explain (verbose, costs off) +select c2, least_agg(c1) from ft1 group by c2 order by c2; + +-- Cleanup +reset enable_hashagg; +drop aggregate least_agg(variadic items anyarray); +drop function least_accum(anyelement, variadic anyarray); + + +--nspt Testing USING OPERATOR() in ORDER BY within aggregate. +--nspt For this, we need user defined operators along with operator family and +--nspt operator class. Create those and then add them in extension. Note that +--nspt user defined objects are considered unshippable unless they are part of +--nspt the extension. +create operator public.<^ ( + leftarg = int4, + rightarg = int4, + procedure = int4eq +); + +create operator public.=^ ( + leftarg = int4, + rightarg = int4, + procedure = int4lt +); + +create operator public.>^ ( + leftarg = int4, + rightarg = int4, + procedure = int4gt +); + +create operator family my_op_family using btree; + +create function my_op_cmp(a int, b int) returns int as + $$begin return btint4cmp(a, b); end $$ language plpgsql; + +create operator class my_op_class for type int using btree family my_op_family as + operator 1 public.<^, + operator 3 public.=^, + operator 5 public.>^, + function 1 my_op_cmp(int, int); + +-- NOTICES: +-- openGauss not support create operator class. remove the case. If you want to restore the test case later, refer to README in the file header. + +-- This will not be pushed as sort operator is now removed from the extension. +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; + +-- Cleanup +drop operator class my_op_class using btree; +drop function my_op_cmp(a int, b int); +drop operator family my_op_family using btree; +drop operator public.>^(int, int); +drop operator public.=^(int, int); +drop operator public.<^(int, int); + +-- Input relation to aggregate push down hook is not safe to pushdown and thus +-- the aggregate cannot be pushed down to foreign server. +explain (verbose, costs off) +select count(t1.c3) from ft2 t1 left join ft2 t2 on (t1.c1 = random() * t2.c2); + +-- Subquery in FROM clause having aggregate +explain (verbose, costs off) +select count(*), x.b from ft1, (select c2 a, sum(c1) b from ft1 group by c2) x where ft1.c2 = x.a group by x.b order by 1, 2; +select count(*), x.b from ft1, (select c2 a, sum(c1) b from ft1 group by c2) x where ft1.c2 = x.a group by x.b order by 1, 2; + +-- FULL join with IS NULL check in HAVING +explain (verbose, costs off) +select avg(t1.c1), sum(t2.c1) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) group by t2.c1 having (avg(t1.c1) is null and sum(t2.c1) < 10) or sum(t2.c1) is null order by 1 nulls last, 2; +select avg(t1.c1), sum(t2.c1) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) group by t2.c1 having (avg(t1.c1) is null and sum(t2.c1) < 10) or sum(t2.c1) is null order by 1 nulls last, 2; + +-- Aggregate over FULL join needing to deparse the joining relations as +-- subqueries. +explain (verbose, costs off) +select count(*), sum(t1.c1), avg(t2.c1) from (select c1 from ft4 where c1 between 50 and 60) t1 full join (select c1 from ft5 where c1 between 50 and 60) t2 on (t1.c1 = t2.c1); +select count(*), sum(t1.c1), avg(t2.c1) from (select c1 from ft4 where c1 between 50 and 60) t1 full join (select c1 from ft5 where c1 between 50 and 60) t2 on (t1.c1 = t2.c1); + +-- ORDER BY expression is part of the target list but not pushed down to +-- foreign server. +explain (verbose, costs off) +select sum(c2) * (random() <= 1)::int as sum from ft1 order by 1; +select sum(c2) * (random() <= 1)::int as sum from ft1 order by 1; + +--nspt LATERAL join, with parameterization +-- NOTICES: +-- openGauss not support create operator class. remove the case. If you want to restore the test case later, refer to README in the file header. + +-- Check with placeHolderVars +explain (verbose, costs off) +select sum(q.a), count(q.b) from ft4 left join (select 13, avg(ft1.c1), sum(ft2.c1) from ft1 right join ft2 on (ft1.c1 = ft2.c1)) q(a, b, c) on (ft4.c1 <= q.b); +select sum(q.a), count(q.b) from ft4 left join (select 13, avg(ft1.c1), sum(ft2.c1) from ft1 right join ft2 on (ft1.c1 = ft2.c1)) q(a, b, c) on (ft4.c1 <= q.b); + + +-- Not supported cases +-- Grouping sets +explain (verbose, costs off) +select c2, sum(c1) from ft1 where c2 < 3 group by rollup(c2) order by 1 nulls last; +select c2, sum(c1) from ft1 where c2 < 3 group by rollup(c2) order by 1 nulls last; +explain (verbose, costs off) +select c2, sum(c1) from ft1 where c2 < 3 group by cube(c2) order by 1 nulls last; +select c2, sum(c1) from ft1 where c2 < 3 group by cube(c2) order by 1 nulls last; +explain (verbose, costs off) +select c2, c6, sum(c1) from ft1 where c2 < 3 group by grouping sets(c2, c6) order by 1 nulls last, 2 nulls last; +select c2, c6, sum(c1) from ft1 where c2 < 3 group by grouping sets(c2, c6) order by 1 nulls last, 2 nulls last; +explain (verbose, costs off) +select c2, sum(c1), grouping(c2) from ft1 where c2 < 3 group by c2 order by 1 nulls last; +select c2, sum(c1), grouping(c2) from ft1 where c2 < 3 group by c2 order by 1 nulls last; + +-- DISTINCT itself is not pushed down, whereas underneath aggregate is pushed +explain (verbose, costs off) +select distinct sum(c1)/1000 s from ft2 where c2 < 6 group by c2 order by 1; +select distinct sum(c1)/1000 s from ft2 where c2 < 6 group by c2 order by 1; + +-- WindowAgg +explain (verbose, costs off) +select c2, sum(c2), count(c2) over (partition by c2%2) from ft2 where c2 < 10 group by c2 order by 1; +select c2, sum(c2), count(c2) over (partition by c2%2) from ft2 where c2 < 10 group by c2 order by 1; +explain (verbose, costs off) +select c2, array_agg(c2) over (partition by c2%2 order by c2 desc) from ft1 where c2 < 10 group by c2 order by 1; +select c2, array_agg(c2) over (partition by c2%2 order by c2 desc) from ft1 where c2 < 10 group by c2 order by 1; +explain (verbose, costs off) +select c2, array_agg(c2) over (partition by c2%2 order by c2 range between current row and unbounded following) from ft1 where c2 < 10 group by c2 order by 1; +select c2, array_agg(c2) over (partition by c2%2 order by c2 range between current row and unbounded following) from ft1 where c2 < 10 group by c2 order by 1; + +-- Same group by +explain verbose select c1,c1,c1,count(*) from ft1 group by 1,1,1; +set enable_hashagg = off; +explain verbose select c1,c1,c1,count(*) from ft1 group by 1,1,1; +set enable_hashagg = on; + +-- ====================================================================================================================================== +-- TEST-MODULE: parameterized queries +-- -------------------------------------- +-- ====================================================================================================================================== -- simple join PREPARE st1(int, int) AS SELECT t1.c3, t2.c3 FROM ft1 t1, ft2 t2 WHERE t1.c1 = $1 AND t2.c1 = $2; -EXPLAIN (VERBOSE, COSTS false) EXECUTE st1(1, 2); +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st1(1, 2); EXECUTE st1(1, 1); EXECUTE st1(101, 101); -- subquery using stable function (can't be sent to remote) -PREPARE st2(int) AS SELECT * FROM ft1 t1 WHERE t1.c1 < $2 AND t1.c3 IN (SELECT c3 FROM ft2 t2 WHERE c1 > $1 AND pg_catalog.date(c4) = '1970-01-17'::date) ORDER BY c1; -EXPLAIN (VERBOSE, COSTS false) EXECUTE st2(10, 20); +PREPARE st2(int) AS SELECT * FROM ft1 t1 WHERE t1.c1 < $2 AND t1.c3 IN (SELECT c3 FROM ft2 t2 WHERE c1 > $1 AND "date"(c4) = '1970-01-17'::date) ORDER BY c1; +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st2(10, 20); EXECUTE st2(10, 20); EXECUTE st2(101, 121); -- subquery using immutable function (can be sent to remote) -PREPARE st3(int) AS SELECT * FROM ft1 t1 WHERE t1.c1 < $2 AND t1.c3 IN (SELECT c3 FROM ft2 t2 WHERE c1 > $1 AND pg_catalog.date(c5) = '1970-01-17'::date) ORDER BY c1; -EXPLAIN (VERBOSE, COSTS false) EXECUTE st3(10, 20); +PREPARE st3(int) AS SELECT * FROM ft1 t1 WHERE t1.c1 < $2 AND t1.c3 IN (SELECT c3 FROM ft2 t2 WHERE c1 > $1 AND "date"(c5) = '1970-01-17'::date) ORDER BY c1; +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st3(10, 20); EXECUTE st3(10, 20); EXECUTE st3(20, 30); -- custom plan should be chosen initially PREPARE st4(int) AS SELECT * FROM ft1 t1 WHERE t1.c1 = $1; -EXPLAIN (VERBOSE, COSTS false) EXECUTE st4(1); -EXPLAIN (VERBOSE, COSTS false) EXECUTE st4(1); -EXPLAIN (VERBOSE, COSTS false) EXECUTE st4(1); -EXPLAIN (VERBOSE, COSTS false) EXECUTE st4(1); -EXPLAIN (VERBOSE, COSTS false) EXECUTE st4(1); +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st4(1); +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st4(1); +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st4(1); +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st4(1); +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st4(1); -- once we try it enough times, should switch to generic plan -EXPLAIN (VERBOSE, COSTS false) EXECUTE st4(1); +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st4(1); -- value of $1 should not be sent to remote PREPARE st5(user_enum,int) AS SELECT * FROM ft1 t1 WHERE c8 = $1 and c1 = $2; -EXPLAIN (VERBOSE, COSTS false) EXECUTE st5('foo', 1); -EXPLAIN (VERBOSE, COSTS false) EXECUTE st5('foo', 1); -EXPLAIN (VERBOSE, COSTS false) EXECUTE st5('foo', 1); -EXPLAIN (VERBOSE, COSTS false) EXECUTE st5('foo', 1); -EXPLAIN (VERBOSE, COSTS false) EXECUTE st5('foo', 1); -EXPLAIN (VERBOSE, COSTS false) EXECUTE st5('foo', 1); +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st5('foo', 1); +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st5('foo', 1); +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st5('foo', 1); +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st5('foo', 1); +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st5('foo', 1); +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st5('foo', 1); EXECUTE st5('foo', 1); -- altering FDW options requires replanning @@ -303,11 +1019,18 @@ EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st7; ALTER TABLE "S 1"."T 1" RENAME TO "T 0"; ALTER FOREIGN TABLE ft1 OPTIONS (SET table_name 'T 0'); EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st6; -EXECUTE st6; +EXECUTE st6; --nspt EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st7; ALTER TABLE "S 1"."T 0" RENAME TO "T 1"; ALTER FOREIGN TABLE ft1 OPTIONS (SET table_name 'T 1'); +PREPARE st8 AS SELECT count(c3) FROM ft1 t1 WHERE t1.c1 === t1.c2; +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st8; +ALTER SERVER loopback OPTIONS (DROP extensions); +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st8; +EXECUTE st8; +ALTER SERVER loopback OPTIONS (ADD extensions 'postgres_fdw'); + -- cleanup DEALLOCATE st1; DEALLOCATE st2; @@ -316,27 +1039,30 @@ DEALLOCATE st4; DEALLOCATE st5; DEALLOCATE st6; DEALLOCATE st7; +DEALLOCATE st8; --- System columns, except ctid, should not be sent to remote -EXPLAIN (VERBOSE, COSTS false) +-- System columns, except ctid and oid, should not be sent to remote +-- openGauss NOT SUPPROT select system columns in ftable. +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE t1.tableoid = 'pg_class'::regclass LIMIT 1; SELECT * FROM ft1 t1 WHERE t1.tableoid = 'ft1'::regclass LIMIT 1; -EXPLAIN (VERBOSE, COSTS false) +EXPLAIN (VERBOSE, COSTS OFF) SELECT tableoid::regclass, * FROM ft1 t1 LIMIT 1; SELECT tableoid::regclass, * FROM ft1 t1 LIMIT 1; -EXPLAIN (VERBOSE, COSTS false) +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE t1.ctid = '(0,2)'; SELECT * FROM ft1 t1 WHERE t1.ctid = '(0,2)'; -EXPLAIN (VERBOSE, COSTS false) +EXPLAIN (VERBOSE, COSTS OFF) SELECT ctid, * FROM ft1 t1 LIMIT 1; SELECT ctid, * FROM ft1 t1 LIMIT 1; +-- ====================================================================================================================================== +-- TEST-MODULE: used in PL/pgSQL function +-- -------------------------------------- +-- ====================================================================================================================================== --- =================================================================== --- used in pl/pgsql function --- =================================================================== CREATE OR REPLACE FUNCTION f_test(p_c1 int) RETURNS int AS $$ DECLARE - v_c1 int; + v_c1 int; BEGIN SELECT c1 INTO v_c1 FROM ft1 WHERE c1 = p_c1 LIMIT 1; PERFORM c1 FROM ft1 WHERE c1 = p_c1 AND p_c1 = v_c1 LIMIT 1; @@ -345,18 +1071,51 @@ END; $$ LANGUAGE plpgsql; SELECT f_test(100); DROP FUNCTION f_test(int); +-- ====================================================================================================================================== +-- TEST-MODULE: REINDEX +-- -------------------------------------- +-- ====================================================================================================================================== +-- remote table is not created here +CREATE FOREIGN TABLE reindex_foreign (c1 int, c2 int) + SERVER loopback2 OPTIONS (table_name 'reindex_local'); +REINDEX TABLE reindex_foreign; -- error +REINDEX TABLE CONCURRENTLY reindex_foreign; -- error --nspt +DROP FOREIGN TABLE reindex_foreign; +-- partitions and foreign tables +CREATE TABLE reind_fdw_parent (c1 int) PARTITION BY RANGE (c1) ( + partition reind_fdw_0_10 values less than(11), + partition reind_fdw_10_20 values less than(21) +); +CREATE FOREIGN TABLE reind_fdw_10_20_fp(c1 int) + SERVER loopback OPTIONS (table_name 'reind_fdw_parent'); +CREATE FOREIGN TABLE reind_fdw_10_20_f1(c1 int) + SERVER loopback OPTIONS (table_name 'reind_fdw_parent partition(reind_fdw_10_20)'); +REINDEX TABLE reind_fdw_10_20_fp; -- error +REINDEX TABLE reind_fdw_10_20_f1; -- error +REINDEX TABLE reind_fdw_parent; -- ok +REINDEX TABLE CONCURRENTLY reind_fdw_parent; -- ok --nspt +DROP TABLE reind_fdw_parent; +DROP FOREIGN TABLE reind_fdw_10_20_fp; +DROP FOREIGN TABLE reind_fdw_10_20_f1; --- =================================================================== --- conversion error --- =================================================================== +-- ====================================================================================================================================== +-- TEST-MODULE: conversion error +-- -------------------------------------- +-- ====================================================================================================================================== ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 TYPE int; -SELECT * FROM ft1 WHERE c1 = 1; -- ERROR +SELECT * FROM ft1 ftx(x1,x2,x3,x4,x5,x6,x7,x8) WHERE x1 = 1; -- ERROR +SELECT ftx.x1, ft2.c2, ftx.x8 FROM ft1 ftx(x1,x2,x3,x4,x5,x6,x7,x8), ft2 + WHERE ftx.x1 = ft2.c1 AND ftx.x1 = 1; -- ERROR +SELECT ftx.x1, ft2.c2, ftx FROM ft1 ftx(x1,x2,x3,x4,x5,x6,x7,x8), ft2 + WHERE ftx.x1 = ft2.c1 AND ftx.x1 = 1; -- ERROR +SELECT sum(c2), array_agg(c8) FROM ft1 GROUP BY c8; -- ERROR +ANALYZE ft1; -- ERROR ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 TYPE user_enum; - --- =================================================================== --- subtransaction --- + local/remote error doesn't break cursor --- =================================================================== +-- ====================================================================================================================================== +-- TEST-MODULE: subtransaction +-- + local/remote error doesn't break cursor +-- -------------------------------------- +-- ====================================================================================================================================== BEGIN; DECLARE c CURSOR FOR SELECT * FROM ft1 ORDER BY c1; FETCH c; @@ -370,10 +1129,10 @@ ROLLBACK TO s; FETCH c; SELECT * FROM ft1 ORDER BY c1 LIMIT 1; COMMIT; - --- =================================================================== --- test handling of collations --- =================================================================== +-- ====================================================================================================================================== +-- TEST-MODULE: test handling of collations +-- -------------------------------------- +-- ====================================================================================================================================== create table loct3 (f1 text collate "C" unique, f2 text, f3 varchar(10) unique); create foreign table ft3 (f1 text collate "C", f2 text, f3 varchar(10)) server loopback options (table_name 'loct3', use_remote_estimate 'true'); @@ -392,39 +1151,125 @@ explain (verbose, costs off) select * from ft3 where f2 COLLATE "C" = 'foo'; explain (verbose, costs off) select * from ft3 where f2 = 'foo' COLLATE "C"; explain (verbose, costs off) select * from ft3 f, loct3 l where f.f3 = l.f3 COLLATE "POSIX" and l.f1 = 'foo'; - --- =================================================================== --- test writable foreign table stuff --- =================================================================== +-- ====================================================================================================================================== +-- TEST-MODULE: test writable foreign table stuff +-- -------------------------------------- +-- ====================================================================================================================================== EXPLAIN (verbose, costs off) INSERT INTO ft2 (c1,c2,c3) SELECT c1+1000,c2+100, c3 || c3 FROM ft2 LIMIT 20; INSERT INTO ft2 (c1,c2,c3) SELECT c1+1000,c2+100, c3 || c3 FROM ft2 LIMIT 20; INSERT INTO ft2 (c1,c2,c3) VALUES (1101,201,'aaa'), (1102,202,'bbb'), (1103,203,'ccc') RETURNING *; INSERT INTO ft2 (c1,c2,c3) VALUES (1104,204,'ddd'), (1105,205,'eee'); +EXPLAIN (verbose, costs off) +UPDATE ft2 SET c2 = c2 + 300, c3 = c3 || '_update3' WHERE c1 % 10 = 3; -- can be pushed down UPDATE ft2 SET c2 = c2 + 300, c3 = c3 || '_update3' WHERE c1 % 10 = 3; +EXPLAIN (verbose, costs off) +UPDATE ft2 SET c2 = c2 + 400, c3 = c3 || '_update7' WHERE c1 % 10 = 7 RETURNING *; -- can be pushed down UPDATE ft2 SET c2 = c2 + 400, c3 = c3 || '_update7' WHERE c1 % 10 = 7 RETURNING *; EXPLAIN (verbose, costs off) UPDATE ft2 SET c2 = ft2.c2 + 500, c3 = ft2.c3 || '_update9', c7 = DEFAULT - FROM ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 9; + FROM ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 9; -- can be pushed down UPDATE ft2 SET c2 = ft2.c2 + 500, c3 = ft2.c3 || '_update9', c7 = DEFAULT FROM ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 9; EXPLAIN (verbose, costs off) - DELETE FROM ft2 WHERE c1 % 10 = 5 RETURNING c1, c4; + DELETE FROM ft2 WHERE c1 % 10 = 5 RETURNING c1, c4; -- can be pushed down DELETE FROM ft2 WHERE c1 % 10 = 5 RETURNING c1, c4; EXPLAIN (verbose, costs off) -DELETE FROM ft2 USING ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 2; +DELETE FROM ft2 USING ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 2; -- can be pushed down DELETE FROM ft2 USING ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 2; SELECT c1,c2,c3,c4 FROM ft2 ORDER BY c1; + +--nspt openGauss not have tableoid, also not support select system columns. EXPLAIN (verbose, costs off) -INSERT INTO ft2 (c1,c2,c3) VALUES (9999,999,'foo') RETURNING tableoid::regclass; -INSERT INTO ft2 (c1,c2,c3) VALUES (9999,999,'foo') RETURNING tableoid::regclass; +INSERT INTO ft2 (c1,c2,c3) VALUES (1200,999,'foo') RETURNING tableoid::regclass; +INSERT INTO ft2 (c1,c2,c3) VALUES (1200,999,'foo') RETURNING tableoid::regclass; EXPLAIN (verbose, costs off) -UPDATE ft2 SET c3 = 'bar' WHERE c1 = 9999 RETURNING tableoid::regclass; -UPDATE ft2 SET c3 = 'bar' WHERE c1 = 9999 RETURNING tableoid::regclass; +UPDATE ft2 SET c3 = 'bar' WHERE c1 = 1200 RETURNING tableoid::regclass; -- can be pushed down +UPDATE ft2 SET c3 = 'bar' WHERE c1 = 1200 RETURNING tableoid::regclass; EXPLAIN (verbose, costs off) -DELETE FROM ft2 WHERE c1 = 9999 RETURNING tableoid::regclass; -DELETE FROM ft2 WHERE c1 = 9999 RETURNING tableoid::regclass; +DELETE FROM ft2 WHERE c1 = 1200 RETURNING tableoid::regclass; -- can be pushed down +DELETE FROM ft2 WHERE c1 = 1200 RETURNING tableoid::regclass; + +-- Test UPDATE/DELETE with RETURNING on a three-table join +INSERT INTO ft2 (c1,c2,c3) + SELECT id, id - 1200, to_char(id, 'FM00000') FROM generate_series(1201, 1300) id; +EXPLAIN (verbose, costs off) +UPDATE ft2 SET c3 = 'foo' + FROM ft4 INNER JOIN ft5 ON (ft4.c1 = ft5.c1) + WHERE ft2.c1 > 1200 AND ft2.c2 = ft4.c1 + RETURNING ft2, ft2.*, ft4, ft4.*; -- can be pushed down +UPDATE ft2 SET c3 = 'foo' + FROM ft4 INNER JOIN ft5 ON (ft4.c1 = ft5.c1) + WHERE ft2.c1 > 1200 AND ft2.c2 = ft4.c1 + RETURNING ft2, ft2.*, ft4, ft4.*; +EXPLAIN (verbose, costs off) +DELETE FROM ft2 + USING ft4 LEFT JOIN ft5 ON (ft4.c1 = ft5.c1) + WHERE ft2.c1 > 1200 AND ft2.c1 % 10 = 0 AND ft2.c2 = ft4.c1 + RETURNING 100; -- can be pushed down +DELETE FROM ft2 + USING ft4 LEFT JOIN ft5 ON (ft4.c1 = ft5.c1) + WHERE ft2.c1 > 1200 AND ft2.c1 % 10 = 0 AND ft2.c2 = ft4.c1 + RETURNING 100; +DELETE FROM ft2 WHERE ft2.c1 > 1200; + +-- Test UPDATE with a MULTIEXPR sub-select +-- (maybe someday this'll be remotely executable, but not today) +EXPLAIN (verbose, costs off) +UPDATE ft2 AS target SET (c2, c7) = ( + SELECT c2 * 10, c7 + FROM ft2 AS src + WHERE target.c1 = src.c1 +) WHERE c1 > 1100; +UPDATE ft2 AS target SET (c2, c7) = ( + SELECT c2 * 10, c7 + FROM ft2 AS src + WHERE target.c1 = src.c1 +) WHERE c1 > 1100; + +UPDATE ft2 AS target SET (c2) = ( + SELECT c2 / 10 + FROM ft2 AS src + WHERE target.c1 = src.c1 +) WHERE c1 > 1100; + +-- Test UPDATE involving a join that can be pushed down, +-- but a SET clause that can't be +EXPLAIN (VERBOSE, COSTS OFF) +UPDATE ft2 d SET c2 = CASE WHEN random() >= 0 THEN d.c2 ELSE 0 END + FROM ft2 AS t WHERE d.c1 = t.c1 AND d.c1 > 1000; +UPDATE ft2 d SET c2 = CASE WHEN random() >= 0 THEN d.c2 ELSE 0 END + FROM ft2 AS t WHERE d.c1 = t.c1 AND d.c1 > 1000; + +-- Test UPDATE/DELETE with WHERE or JOIN/ON conditions containing +-- user-defined operators/functions +ALTER SERVER loopback OPTIONS (DROP extensions); +INSERT INTO ft2 (c1,c2,c3) + SELECT id, id % 10, to_char(id, 'FM00000') FROM generate_series(2001, 2010) id; +EXPLAIN (verbose, costs off) +UPDATE ft2 SET c3 = 'bar' WHERE postgres_fdw_abs(c1) > 2000 RETURNING *; -- can't be pushed down +UPDATE ft2 SET c3 = 'bar' WHERE postgres_fdw_abs(c1) > 2000 RETURNING *; +EXPLAIN (verbose, costs off) +UPDATE ft2 SET c3 = 'baz' + FROM ft4 INNER JOIN ft5 ON (ft4.c1 = ft5.c1) + WHERE ft2.c1 > 2000 AND ft2.c2 === ft4.c1 + RETURNING ft2.*, ft4.*, ft5.*; -- can't be pushed down +UPDATE ft2 SET c3 = 'baz' + FROM ft4 INNER JOIN ft5 ON (ft4.c1 = ft5.c1) + WHERE ft2.c1 > 2000 AND ft2.c2 === ft4.c1 + RETURNING ft2.*, ft4.*, ft5.*; +EXPLAIN (verbose, costs off) +DELETE FROM ft2 + USING ft4 INNER JOIN ft5 ON (ft4.c1 === ft5.c1) + WHERE ft2.c1 > 2000 AND ft2.c2 = ft4.c1 + RETURNING ft2.c1, ft2.c2, ft2.c3; -- can't be pushed down +DELETE FROM ft2 + USING ft4 INNER JOIN ft5 ON (ft4.c1 === ft5.c1) + WHERE ft2.c1 > 2000 AND ft2.c2 = ft4.c1 + RETURNING ft2.c1, ft2.c2, ft2.c3; +DELETE FROM ft2 WHERE ft2.c1 > 2000; +ALTER SERVER loopback OPTIONS (ADD extensions 'postgres_fdw'); -- Test that trigger on remote table works as expected CREATE OR REPLACE FUNCTION "S 1".F_BRTRIG() RETURNS trigger AS $$ @@ -444,43 +1289,114 @@ UPDATE ft2 SET c2 = c2 + 600 WHERE c1 % 10 = 8 AND c1 < 1200 RETURNING *; ALTER TABLE "S 1"."T 1" ADD CONSTRAINT c2positive CHECK (c2 >= 0); INSERT INTO ft1(c1, c2) VALUES(11, 12); -- duplicate key +INSERT INTO ft1(c1, c2) VALUES(11, 12) ON DUPLICATE KEY UPDATE NOTHING; -- works +INSERT INTO ft1(c1, c2) VALUES(11, 12) ON DUPLICATE KEY UPDATE c3 = 'ffg'; -- unsupported INSERT INTO ft1(c1, c2) VALUES(1111, -2); -- c2positive UPDATE ft1 SET c2 = -c2 WHERE c1 = 1; -- c2positive -- Test savepoint/rollback behavior -select c2, pg_catalog.count(*) from ft2 where c2 < 500 group by 1 order by 1; -select c2, pg_catalog.count(*) from "S 1"."T 1" where c2 < 500 group by 1 order by 1; +select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; +select c2, count(*) from "S 1"."T 1" where c2 < 500 group by 1 order by 1; begin; update ft2 set c2 = 42 where c2 = 0; -select c2, pg_catalog.count(*) from ft2 where c2 < 500 group by 1 order by 1; +select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; savepoint s1; update ft2 set c2 = 44 where c2 = 4; -select c2, pg_catalog.count(*) from ft2 where c2 < 500 group by 1 order by 1; +select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; release savepoint s1; -select c2, pg_catalog.count(*) from ft2 where c2 < 500 group by 1 order by 1; +select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; savepoint s2; update ft2 set c2 = 46 where c2 = 6; -select c2, pg_catalog.count(*) from ft2 where c2 < 500 group by 1 order by 1; +select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; rollback to savepoint s2; -select c2, pg_catalog.count(*) from ft2 where c2 < 500 group by 1 order by 1; +select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; release savepoint s2; -select c2, pg_catalog.count(*) from ft2 where c2 < 500 group by 1 order by 1; +select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; savepoint s3; update ft2 set c2 = -2 where c2 = 42 and c1 = 10; -- fail on remote side rollback to savepoint s3; -select c2, pg_catalog.count(*) from ft2 where c2 < 500 group by 1 order by 1; +select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; release savepoint s3; -select c2, pg_catalog.count(*) from ft2 where c2 < 500 group by 1 order by 1; +select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; -- none of the above is committed yet remotely -select c2, pg_catalog.count(*) from "S 1"."T 1" where c2 < 500 group by 1 order by 1; +select c2, count(*) from "S 1"."T 1" where c2 < 500 group by 1 order by 1; commit; -select c2, pg_catalog.count(*) from ft2 where c2 < 500 group by 1 order by 1; -select c2, pg_catalog.count(*) from "S 1"."T 1" where c2 < 500 group by 1 order by 1; +select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; +select c2, count(*) from "S 1"."T 1" where c2 < 500 group by 1 order by 1; --- =================================================================== --- test serial columns (ie, sequence-based defaults) --- =================================================================== +VACUUM ANALYZE "S 1"."T 1"; + +-- Above DMLs add data with c6 as NULL in ft1, so test ORDER BY NULLS LAST and NULLs +-- FIRST behavior here. +-- ORDER BY DESC NULLS LAST options +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 ORDER BY c6 DESC NULLS LAST, c1 OFFSET 795 LIMIT 10; +SELECT * FROM ft1 ORDER BY c6 DESC NULLS LAST, c1 OFFSET 795 LIMIT 10; +-- ORDER BY DESC NULLS FIRST options +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 ORDER BY c6 DESC NULLS FIRST, c1 OFFSET 15 LIMIT 10; +SELECT * FROM ft1 ORDER BY c6 DESC NULLS FIRST, c1 OFFSET 15 LIMIT 10; +-- ORDER BY ASC NULLS FIRST options +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 ORDER BY c6 ASC NULLS FIRST, c1 OFFSET 15 LIMIT 10; +SELECT * FROM ft1 ORDER BY c6 ASC NULLS FIRST, c1 OFFSET 15 LIMIT 10; + +-- ====================================================================================================================================== +-- TEST-MODULE: check constraints +-- -------------------------------------- +-- openGauss not support to "ALTER FOREIGN TABLE ft1 ADD CONSTRAINT", so this test module +-- is unuseful. +-- ====================================================================================================================================== +-- Consistent check constraints provide consistent results +ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c2positive CHECK (c2 >= 0); +EXPLAIN (VERBOSE, COSTS OFF) SELECT count(*) FROM ft1 WHERE c2 < 0; +SELECT count(*) FROM ft1 WHERE c2 < 0; +SET constraint_exclusion = 'on'; +EXPLAIN (VERBOSE, COSTS OFF) SELECT count(*) FROM ft1 WHERE c2 < 0; +SELECT count(*) FROM ft1 WHERE c2 < 0; +RESET constraint_exclusion; +-- check constraint is enforced on the remote side, not locally +INSERT INTO ft1(c1, c2) VALUES(1111, -2); -- c2positive +UPDATE ft1 SET c2 = -c2 WHERE c1 = 1; -- c2positive +ALTER FOREIGN TABLE ft1 DROP CONSTRAINT ft1_c2positive; + +-- But inconsistent check constraints provide inconsistent results +ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c2negative CHECK (c2 < 0); +EXPLAIN (VERBOSE, COSTS OFF) SELECT count(*) FROM ft1 WHERE c2 >= 0; +SELECT count(*) FROM ft1 WHERE c2 >= 0; +SET constraint_exclusion = 'on'; +EXPLAIN (VERBOSE, COSTS OFF) SELECT count(*) FROM ft1 WHERE c2 >= 0; +SELECT count(*) FROM ft1 WHERE c2 >= 0; +RESET constraint_exclusion; +-- local check constraint is not actually enforced +INSERT INTO ft1(c1, c2) VALUES(1111, 2); +UPDATE ft1 SET c2 = c2 + 1 WHERE c1 = 1; +ALTER FOREIGN TABLE ft1 DROP CONSTRAINT ft1_c2negative; + +-- ====================================================================================================================================== +-- TEST-MODULE: WITH CHECK OPTION constraints +-- -------------------------------------- +-- openGauss not support WITH CHECK OPTION, so this test module is unuseful and cannot be fixed, remove it. +-- ====================================================================================================================================== +CREATE FUNCTION row_before_insupd_trigfunc() RETURNS trigger AS $$BEGIN NEW.a := NEW.a + 10; RETURN NEW; END$$ LANGUAGE plpgsql; + +CREATE TABLE base_tbl (a int, b int); +ALTER TABLE base_tbl SET (autovacuum_enabled = 'false'); +CREATE TRIGGER row_before_insupd_trigger BEFORE INSERT OR UPDATE ON base_tbl FOR EACH ROW EXECUTE PROCEDURE row_before_insupd_trigfunc(); +CREATE FOREIGN TABLE foreign_tbl (a int, b int) + SERVER loopback OPTIONS (table_name 'base_tbl'); +-- CREATE VIEW rw_view AS SELECT * FROM foreign_tbl +-- WHERE a < b WITH CHECK OPTION; +-- \d+ rw_view + +DROP FOREIGN TABLE foreign_tbl CASCADE; +DROP TRIGGER row_before_insupd_trigger ON base_tbl; +DROP TABLE base_tbl; + + +-- ====================================================================================================================================== +-- TEST-MODULE: test serial columns (ie, sequence-based defaults) +-- -------------------------------------- +-- ====================================================================================================================================== create table loc1 (f1 serial, f2 text); +alter table loc1 set (autovacuum_enabled = 'false'); create foreign table rem1 (f1 serial, f2 text) server loopback options(table_name 'loc1'); select pg_catalog.setval('rem1_f1_seq', 10, false); @@ -491,128 +1407,236 @@ insert into rem1(f2) values('bye remote'); select * from loc1; select * from rem1; --- =================================================================== --- test local triggers --- =================================================================== +-- ====================================================================================================================================== +-- TEST-MODULE: test generated columns +-- -------------------------------------- +-- CURRENTLY NOT SUPPORT +-- ====================================================================================================================================== +create table gloc1 ( + a int, + b int generated always as (a * 2) stored); +alter table gloc1 set (autovacuum_enabled = 'false'); +create foreign table grem1 ( + a int, + b int generated always as (a * 2) stored) + server loopback options(table_name 'gloc1'); +explain (verbose, costs off) +insert into grem1 (a) values (1), (2); --nspt +insert into grem1 (a) values (1), (2); --nspt +explain (verbose, costs off) +update grem1 set a = 22 where a = 2; +update grem1 set a = 22 where a = 2; +select * from gloc1; +select * from grem1; +delete from grem1; + +-- test copy from +copy grem1 from stdin; +1 +2 +\. +select * from gloc1; +select * from grem1; +delete from grem1; + +-- test batch insert +alter server loopback options (add batch_size '10'); +explain (verbose, costs off) +insert into grem1 (a) values (1), (2); +insert into grem1 (a) values (1), (2); +select * from gloc1; +select * from grem1; +delete from grem1; +alter server loopback options (drop batch_size); + +-- ====================================================================================================================================== +-- TEST-MODULE: test local triggers +-- -------------------------------------- +-- openGauss not support create trigger on foreign table, so this module is unuseful. +-- we adapt the test to create trigger on base table of foreign table. +-- ====================================================================================================================================== +create table tglog(id serial, context text); +create table previd(a int); +insert into previd values(0); + +create or replace function showtrigger(id out int, context out text) returns setof record LANGUAGE plpgsql as +$$ +DECLARE + r RECORD; + prev int; +BEGIN + select max(a) from previd into prev; + update previd set a = (select max(id) from tglog); + + FOR r IN SELECT * FROM tglog where id > prev ORDER BY id + LOOP + id := r.id; + context := r.context; + return next; + END LOOP; +END;$$; + + -- Trigger functions "borrowed" from triggers regress test. CREATE FUNCTION trigger_func() RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN - RAISE NOTICE 'trigger_func(%) called: action = %, when = %, level = %', - TG_ARGV[0], TG_OP, TG_WHEN, TG_LEVEL; - RETURN NULL; + insert into tglog(context) values( + format('trigger_func(%s) called: action = %s, when = %s, level = %s', + TG_ARGV[0], TG_OP, TG_WHEN, TG_LEVEL)); + RETURN NULL; END;$$; +-- error, openGauss not support create trigger on foreign table CREATE TRIGGER trig_stmt_before BEFORE DELETE OR INSERT OR UPDATE ON rem1 - FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func(); + FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func(); CREATE TRIGGER trig_stmt_after AFTER DELETE OR INSERT OR UPDATE ON rem1 - FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func(); + FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func(); + +-- success +CREATE TRIGGER trig_stmt_before BEFORE DELETE OR INSERT OR UPDATE ON loc1 + FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func(); +CREATE TRIGGER trig_stmt_after AFTER DELETE OR INSERT OR UPDATE ON loc1 + FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func(); CREATE OR REPLACE FUNCTION trigger_data() RETURNS trigger LANGUAGE plpgsql AS $$ declare - oldnew text[]; - relid text; + oldnew text[]; + relid text; argstr text; begin - relid := TG_relid::regclass; - argstr := ''; - for i in 0 .. TG_nargs - 1 loop - if i > 0 then - argstr := argstr || ', '; - end if; - argstr := argstr || TG_argv[i]; - end loop; + relid := TG_relid::regclass; + argstr := ''; + for i in 0 .. TG_nargs - 1 loop + if i > 0 then + argstr := argstr || ', '; + end if; + argstr := argstr || TG_argv[i]; + end loop; - RAISE NOTICE '%(%) % % % ON %', - tg_name, argstr, TG_when, TG_level, TG_OP, relid; + insert into tglog(context) values( + format('%s(%s) %s %s %s ON %s', + tg_name, argstr, TG_when, TG_level, TG_OP, relid)); oldnew := '{}'::text[]; - if TG_OP != 'INSERT' then - oldnew := pg_catalog.array_append(oldnew, pg_catalog.format('OLD: %s', OLD)); - end if; + if TG_OP != 'INSERT' then + oldnew := array_append(oldnew, format('OLD: %s', OLD)); + end if; - if TG_OP != 'DELETE' then - oldnew := pg_catalog.array_append(oldnew, pg_catalog.format('NEW: %s', NEW)); - end if; + if TG_OP != 'DELETE' then + oldnew := array_append(oldnew, format('NEW: %s', NEW)); + end if; - RAISE NOTICE '%', pg_catalog.array_to_string(oldnew, ','); + insert into tglog(context) values( + format('%s', + array_to_string(oldnew, ','))); - if TG_OP = 'DELETE' then - return OLD; - else - return NEW; - end if; + if TG_OP = 'DELETE' then + return OLD; + else + return NEW; + end if; end; $$; -- Test basic functionality CREATE TRIGGER trig_row_before -BEFORE INSERT OR UPDATE OR DELETE ON rem1 +BEFORE INSERT OR UPDATE OR DELETE ON loc1 FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); CREATE TRIGGER trig_row_after -AFTER INSERT OR UPDATE OR DELETE ON rem1 +AFTER INSERT OR UPDATE OR DELETE ON loc1 FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); delete from rem1; +select * from showtrigger(); insert into rem1 values(1,'insert'); +select * from showtrigger(); update rem1 set f2 = 'update' where f1 = 1; +select * from showtrigger(); update rem1 set f2 = f2 || f2; - +select * from showtrigger(); -- cleanup -DROP TRIGGER trig_row_before ON rem1; -DROP TRIGGER trig_row_after ON rem1; -DROP TRIGGER trig_stmt_before ON rem1; -DROP TRIGGER trig_stmt_after ON rem1; +DROP TRIGGER trig_row_before ON loc1; +DROP TRIGGER trig_row_after ON loc1; +DROP TRIGGER trig_stmt_before ON loc1; +DROP TRIGGER trig_stmt_after ON loc1; DELETE from rem1; +select * from showtrigger(); +-- Test multiple AFTER ROW triggers on a foreign table +CREATE TRIGGER trig_row_after1 +AFTER INSERT OR UPDATE OR DELETE ON loc1 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); + +CREATE TRIGGER trig_row_after2 +AFTER INSERT OR UPDATE OR DELETE ON loc1 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); + +insert into rem1 values(1,'insert'); +select * from showtrigger(); +update rem1 set f2 = 'update' where f1 = 1; +select * from showtrigger(); +update rem1 set f2 = f2 || f2; +select * from showtrigger(); +delete from rem1; +select * from showtrigger(); + +-- cleanup +DROP TRIGGER trig_row_after1 ON loc1; +DROP TRIGGER trig_row_after2 ON loc1; -- Test WHEN conditions CREATE TRIGGER trig_row_before_insupd -BEFORE INSERT OR UPDATE ON rem1 +BEFORE INSERT OR UPDATE ON loc1 FOR EACH ROW WHEN (NEW.f2 like '%update%') EXECUTE PROCEDURE trigger_data(23,'skidoo'); CREATE TRIGGER trig_row_after_insupd -AFTER INSERT OR UPDATE ON rem1 +AFTER INSERT OR UPDATE ON loc1 FOR EACH ROW WHEN (NEW.f2 like '%update%') EXECUTE PROCEDURE trigger_data(23,'skidoo'); -- Insert or update not matching: nothing happens INSERT INTO rem1 values(1, 'insert'); +select * from showtrigger(); UPDATE rem1 set f2 = 'test'; +select * from showtrigger(); -- Insert or update matching: triggers are fired INSERT INTO rem1 values(2, 'update'); +select * from showtrigger(); UPDATE rem1 set f2 = 'update update' where f1 = '2'; +select * from showtrigger(); CREATE TRIGGER trig_row_before_delete -BEFORE DELETE ON rem1 +BEFORE DELETE ON loc1 FOR EACH ROW WHEN (OLD.f2 like '%update%') EXECUTE PROCEDURE trigger_data(23,'skidoo'); CREATE TRIGGER trig_row_after_delete -AFTER DELETE ON rem1 +AFTER DELETE ON loc1 FOR EACH ROW WHEN (OLD.f2 like '%update%') EXECUTE PROCEDURE trigger_data(23,'skidoo'); -- Trigger is fired for f1=2, not for f1=1 DELETE FROM rem1; +select * from showtrigger(); -- cleanup -DROP TRIGGER trig_row_before_insupd ON rem1; -DROP TRIGGER trig_row_after_insupd ON rem1; -DROP TRIGGER trig_row_before_delete ON rem1; -DROP TRIGGER trig_row_after_delete ON rem1; +DROP TRIGGER trig_row_before_insupd ON loc1; +DROP TRIGGER trig_row_after_insupd ON loc1; +DROP TRIGGER trig_row_before_delete ON loc1; +DROP TRIGGER trig_row_after_delete ON loc1; -- Test various RETURN statements in BEFORE triggers. @@ -625,48 +1649,59 @@ CREATE FUNCTION trig_row_before_insupdate() RETURNS TRIGGER AS $$ $$ language plpgsql; CREATE TRIGGER trig_row_before_insupd -BEFORE INSERT OR UPDATE ON rem1 +BEFORE INSERT OR UPDATE ON loc1 FOR EACH ROW EXECUTE PROCEDURE trig_row_before_insupdate(); -- The new values should have 'triggered' appended INSERT INTO rem1 values(1, 'insert'); +select * from showtrigger(); SELECT * from loc1; INSERT INTO rem1 values(2, 'insert') RETURNING f2; +select * from showtrigger(); SELECT * from loc1; UPDATE rem1 set f2 = ''; +select * from showtrigger(); SELECT * from loc1; UPDATE rem1 set f2 = 'skidoo' RETURNING f2; +select * from showtrigger(); SELECT * from loc1; EXPLAIN (verbose, costs off) UPDATE rem1 set f1 = 10; -- all columns should be transmitted +select * from showtrigger(); UPDATE rem1 set f1 = 10; +select * from showtrigger(); SELECT * from loc1; DELETE FROM rem1; +select * from showtrigger(); -- Add a second trigger, to check that the changes are propagated correctly -- from trigger to trigger CREATE TRIGGER trig_row_before_insupd2 -BEFORE INSERT OR UPDATE ON rem1 +BEFORE INSERT OR UPDATE ON loc1 FOR EACH ROW EXECUTE PROCEDURE trig_row_before_insupdate(); INSERT INTO rem1 values(1, 'insert'); +select * from showtrigger(); SELECT * from loc1; INSERT INTO rem1 values(2, 'insert') RETURNING f2; +select * from showtrigger(); SELECT * from loc1; UPDATE rem1 set f2 = ''; +select * from showtrigger(); SELECT * from loc1; UPDATE rem1 set f2 = 'skidoo' RETURNING f2; +select * from showtrigger(); SELECT * from loc1; -DROP TRIGGER trig_row_before_insupd ON rem1; -DROP TRIGGER trig_row_before_insupd2 ON rem1; +DROP TRIGGER trig_row_before_insupd ON loc1; +DROP TRIGGER trig_row_before_insupd2 ON loc1; DELETE from rem1; - +select * from showtrigger(); INSERT INTO rem1 VALUES (1, 'test'); - +select * from showtrigger(); -- Test with a trigger returning NULL CREATE FUNCTION trig_null() RETURNS TRIGGER AS $$ BEGIN @@ -675,39 +1710,641 @@ CREATE FUNCTION trig_null() RETURNS TRIGGER AS $$ $$ language plpgsql; CREATE TRIGGER trig_null -BEFORE INSERT OR UPDATE OR DELETE ON rem1 +BEFORE INSERT OR UPDATE OR DELETE ON loc1 FOR EACH ROW EXECUTE PROCEDURE trig_null(); -- Nothing should have changed. INSERT INTO rem1 VALUES (2, 'test2'); - +select * from showtrigger(); SELECT * from loc1; UPDATE rem1 SET f2 = 'test2'; - +select * from showtrigger(); SELECT * from loc1; DELETE from rem1; - +select * from showtrigger(); SELECT * from loc1; -DROP TRIGGER trig_null ON rem1; +DROP TRIGGER trig_null ON loc1; DELETE from rem1; - +select * from showtrigger(); -- Test a combination of local and remote triggers CREATE TRIGGER trig_row_before -BEFORE INSERT OR UPDATE OR DELETE ON rem1 +BEFORE INSERT OR UPDATE OR DELETE ON loc1 FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); CREATE TRIGGER trig_row_after -AFTER INSERT OR UPDATE OR DELETE ON rem1 +AFTER INSERT OR UPDATE OR DELETE ON loc1 FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); CREATE TRIGGER trig_local_before BEFORE INSERT OR UPDATE ON loc1 FOR EACH ROW EXECUTE PROCEDURE trig_row_before_insupdate(); INSERT INTO rem1(f2) VALUES ('test'); +select * from showtrigger(); UPDATE rem1 SET f2 = 'testo'; +select * from showtrigger(); -- Test returning a system attribute INSERT INTO rem1(f2) VALUES ('test') RETURNING ctid; +select * from showtrigger(); +-- cleanup +DROP TRIGGER trig_row_before ON loc1; +DROP TRIGGER trig_row_after ON loc1; +DROP TRIGGER trig_local_before ON loc1; + + +-- Test direct foreign table modification functionality +EXPLAIN (verbose, costs off) +DELETE FROM rem1; -- can be pushed down +EXPLAIN (verbose, costs off) +DELETE FROM rem1 WHERE false; -- currently can't be pushed down + +-- Test with statement-level triggers +CREATE TRIGGER trig_stmt_before + BEFORE DELETE OR INSERT OR UPDATE ON loc1 + FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func(); +EXPLAIN (verbose, costs off) +UPDATE rem1 set f2 = ''; -- can be pushed down +EXPLAIN (verbose, costs off) +DELETE FROM rem1; -- can be pushed down +DROP TRIGGER trig_stmt_before ON loc1; + +CREATE TRIGGER trig_stmt_after + AFTER DELETE OR INSERT OR UPDATE ON loc1 + FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func(); +EXPLAIN (verbose, costs off) +UPDATE rem1 set f2 = ''; -- can be pushed down +EXPLAIN (verbose, costs off) +DELETE FROM rem1; -- can be pushed down +DROP TRIGGER trig_stmt_after ON loc1; + +-- Test with row-level ON INSERT triggers +CREATE TRIGGER trig_row_before_insert +BEFORE INSERT ON loc1 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); +EXPLAIN (verbose, costs off) +UPDATE rem1 set f2 = ''; -- can be pushed down +EXPLAIN (verbose, costs off) +DELETE FROM rem1; -- can be pushed down +DROP TRIGGER trig_row_before_insert ON loc1; + +CREATE TRIGGER trig_row_after_insert +AFTER INSERT ON loc1 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); +EXPLAIN (verbose, costs off) +UPDATE rem1 set f2 = ''; -- can be pushed down +EXPLAIN (verbose, costs off) +DELETE FROM rem1; -- can be pushed down +DROP TRIGGER trig_row_after_insert ON loc1; + +-- Test with row-level ON UPDATE triggers +CREATE TRIGGER trig_row_before_update +BEFORE UPDATE ON loc1 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); +EXPLAIN (verbose, costs off) +UPDATE rem1 set f2 = ''; -- can't be pushed down +EXPLAIN (verbose, costs off) +DELETE FROM rem1; -- can be pushed down +DROP TRIGGER trig_row_before_update ON loc1; + +CREATE TRIGGER trig_row_after_update +AFTER UPDATE ON loc1 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); +EXPLAIN (verbose, costs off) +UPDATE rem1 set f2 = ''; -- can't be pushed down +EXPLAIN (verbose, costs off) +DELETE FROM rem1; -- can be pushed down +DROP TRIGGER trig_row_after_update ON loc1; + +-- Test with row-level ON DELETE triggers +CREATE TRIGGER trig_row_before_delete +BEFORE DELETE ON loc1 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); +EXPLAIN (verbose, costs off) +UPDATE rem1 set f2 = ''; -- can be pushed down +EXPLAIN (verbose, costs off) +DELETE FROM rem1; -- can't be pushed down +DROP TRIGGER trig_row_before_delete ON loc1; + +CREATE TRIGGER trig_row_after_delete +AFTER DELETE ON loc1 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); +EXPLAIN (verbose, costs off) +UPDATE rem1 set f2 = ''; -- can be pushed down +EXPLAIN (verbose, costs off) +DELETE FROM rem1; -- can't be pushed down +DROP TRIGGER trig_row_after_delete ON loc1; + +-- ====================================================================================================================================== +-- TEST-MODULE: test inheritance features +-- -------------------------------------- +-- openGauss not support this feature. and cannot fix, remove all test case. +-- ====================================================================================================================================== + +-- ====================================================================================================================================== +-- TEST-MODULE: test tuple routing for foreign-table partitions +-- -------------------------------------- +-- partition table is different between openGauss and pg, and cannot fix these test cases, remove all. +-- ====================================================================================================================================== + + +-- ====================================================================================================================================== +-- TEST-MODULE: test COPY FROM +-- -------------------------------------- +-- ====================================================================================================================================== +create table loc2 (f1 int, f2 text); +alter table loc2 set (autovacuum_enabled = 'false'); +create foreign table rem2 (f1 int, f2 text) server loopback options(table_name 'loc2'); + +-- Test basic functionality +copy rem2 from stdin; +1 foo +2 bar +\. +select * from rem2; + +delete from rem2; + +-- Test check constraints +alter table loc2 add constraint loc2_f1positive check (f1 >= 0); +--nspt alter foreign table rem2 add constraint rem2_f1positive check (f1 >= 0); + +-- check constraint is enforced on the remote side, not locally +copy rem2 from stdin; +1 foo +2 bar +\. +copy rem2 from stdin; -- ERROR +-1 xyzzy +\. +select * from rem2; + +--nspt alter foreign table rem2 drop constraint rem2_f1positive; +alter table loc2 drop constraint loc2_f1positive; + +delete from rem2; + +-- Test local triggers +create trigger trig_stmt_before before insert on loc2 + for each statement execute procedure trigger_func(); +create trigger trig_stmt_after after insert on loc2 + for each statement execute procedure trigger_func(); +create trigger trig_row_before before insert on loc2 + for each row execute procedure trigger_data(23,'skidoo'); +create trigger trig_row_after after insert on loc2 + for each row execute procedure trigger_data(23,'skidoo'); + +copy rem2 from stdin; +1 foo +2 bar +\. +select * from showtrigger(); +select * from rem2; + +drop trigger trig_row_before on loc2; +drop trigger trig_row_after on loc2; +drop trigger trig_stmt_before on loc2; +drop trigger trig_stmt_after on loc2; + +delete from rem2; +select * from showtrigger(); + +create trigger trig_row_before_insert before insert on loc2 + for each row execute procedure trig_row_before_insupdate(); + +-- The new values are concatenated with ' triggered !' +copy rem2 from stdin; +1 foo +2 bar +\. +select * from showtrigger(); +select * from rem2; + +drop trigger trig_row_before_insert on loc2; + +delete from rem2; + +create trigger trig_null before insert on loc2 + for each row execute procedure trig_null(); + +-- Nothing happens +copy rem2 from stdin; +1 foo +2 bar +\. +select * from showtrigger(); +select * from rem2; + +drop trigger trig_null on loc2; + +delete from rem2; + +-- Test remote triggers +create trigger trig_row_before_insert before insert on loc2 + for each row execute procedure trig_row_before_insupdate(); + +-- The new values are concatenated with ' triggered !' +copy rem2 from stdin; +1 foo +2 bar +\. +select * from showtrigger(); +select * from rem2; + +drop trigger trig_row_before_insert on loc2; + +delete from rem2; + +create trigger trig_null before insert on loc2 + for each row execute procedure trig_null(); + +-- Nothing happens +copy rem2 from stdin; +1 foo +2 bar +\. +select * from showtrigger(); +select * from rem2; + +drop trigger trig_null on loc2; + +delete from rem2; + +-- Test a combination of local and remote triggers +create trigger rem2_trig_row_before before insert on loc2 + for each row execute procedure trigger_data(23,'skidoo'); +create trigger rem2_trig_row_after after insert on loc2 + for each row execute procedure trigger_data(23,'skidoo'); +create trigger loc2_trig_row_before_insert before insert on loc2 + for each row execute procedure trig_row_before_insupdate(); + +copy rem2 from stdin; +1 foo +2 bar +\. +select * from showtrigger(); +select * from rem2; + +drop trigger rem2_trig_row_before on loc2; +drop trigger rem2_trig_row_after on loc2; +drop trigger loc2_trig_row_before_insert on loc2; + +delete from rem2; + +-- test COPY FROM with foreign table created in the same transaction +create table loc3 (f1 int, f2 text); +begin; +create foreign table rem3 (f1 int, f2 text) + server loopback options(table_name 'loc3'); +copy rem3 from stdin; +1 foo +2 bar +\. +select * from showtrigger(); +commit; +select * from rem3; +drop foreign table rem3; +drop table loc3; + +-- ====================================================================================================================================== +-- TEST-MODULE: test for TRUNCATE +-- -------------------------------------- +-- openGauss not support this feature. However, some cases are still left for maintenance. +-- ====================================================================================================================================== +CREATE TABLE tru_rtable0 (id int primary key); +CREATE FOREIGN TABLE tru_ftable (id int) + SERVER loopback OPTIONS (table_name 'tru_rtable0'); +INSERT INTO tru_rtable0 (SELECT x FROM generate_series(1,10) x); + +CREATE TABLE tru_ptable (id int) PARTITION BY HASH(id)( + partition tru_ptable__p0, + partition tru_ptable__p1 +); +CREATE FOREIGN TABLE tru_p_ftable (id int) + SERVER loopback OPTIONS (table_name 'tru_ptable'); +INSERT INTO tru_ptable (SELECT x FROM generate_series(11,20) x); + +CREATE TABLE tru_pk_table(id int primary key); +CREATE TABLE tru_fk_table(fkey int references tru_pk_table(id)); +INSERT INTO tru_pk_table (SELECT x FROM generate_series(1,10) x); +INSERT INTO tru_fk_table (SELECT x % 10 + 1 FROM generate_series(5,25) x); +CREATE FOREIGN TABLE tru_pk_ftable (id int) + SERVER loopback OPTIONS (table_name 'tru_pk_table'); + +-- normal truncate +SELECT sum(id) FROM tru_ftable; -- 55 +TRUNCATE tru_ftable; -- nspt +SELECT count(*) FROM tru_rtable0; -- 10 +SELECT count(*) FROM tru_ftable; -- 10 + + +-- partitioned table with both local and foreign tables as partitions +SELECT sum(id) FROM tru_p_ftable; -- 155 +TRUNCATE tru_p_ftable; +SELECT count(*) FROM tru_p_ftable; -- 0 +SELECT count(*) FROM tru_p_ftable partition(tru_ptable__p0); -- 0 +SELECT count(*) FROM tru_ptable partition(tru_ftable__p1); -- 0 +SELECT count(*) FROM tru_ptable; -- 0 + +-- 'CASCADE' option +SELECT sum(id) FROM tru_pk_ftable; -- 55 +TRUNCATE tru_pk_ftable; -- nspt +TRUNCATE tru_pk_ftable CASCADE; +SELECT count(*) FROM tru_pk_ftable; -- 10 +SELECT count(*) FROM tru_fk_table; -- 21 + +-- truncate two tables at a command +INSERT INTO tru_ftable (SELECT x FROM generate_series(1,8) x); +INSERT INTO tru_pk_ftable (SELECT x FROM generate_series(3,10) x); +SELECT count(*) from tru_ftable; -- 8 +SELECT count(*) from tru_pk_ftable; -- 8 +TRUNCATE tru_ftable, tru_pk_ftable CASCADE; +SELECT count(*) from tru_ftable; -- 0 +SELECT count(*) from tru_pk_ftable; -- 0 + +-- cleanup +DROP FOREIGN TABLE tru_pk_ftable,tru_ftable__p1,tru_ftable; +DROP TABLE tru_rtable0, tru_ptable, tru_pk_table, tru_fk_table; + +-- ====================================================================================================================================== +-- TEST-MODULE: test IMPORT FOREIGN SCHEMA +-- -------------------------------------- +-- openGauss not support this feature. However, some cases are still left for maintenance. +-- ====================================================================================================================================== +CREATE SCHEMA import_source; +CREATE TABLE import_source.t1 (c1 int, c2 varchar NOT NULL); + +CREATE SCHEMA import_dest1; +IMPORT FOREIGN SCHEMA import_source FROM SERVER loopback INTO import_dest1; +\det+ import_dest1.* +\d import_dest1.* + +-- ====================================================================================================================================== +-- TEST-MODULE: test partitionwise joins +-- TEST-MODULE: test partitionwise aggregates +-- -------------------------------------- +-- partition table is different between openGauss and pg, and cannot fix these test cases, remove all. +-- ====================================================================================================================================== +-- nspt + +-- ====================================================================================================================================== +-- TEST-MODULE: access rights and superuser +-- -------------------------------------- +-- some privileges is different between og and pg +-- ====================================================================================================================================== +-- Non-superuser cannot create a FDW without a password in the connstr +CREATE ROLE regress_nosuper with password 'qwer@1234'; + +GRANT USAGE ON FOREIGN DATA WRAPPER postgres_fdw TO regress_nosuper; +grant all on schema public to regress_nosuper; + +SET SESSION AUTHORIZATION regress_nosuper PASSWORD 'qwer@1234'; + +SHOW is_superuser; --nspt + +-- This will be OK, we can create the FDW +DO $d$ + BEGIN + EXECUTE $$CREATE SERVER loopback_nopw FOREIGN DATA WRAPPER postgres_fdw + OPTIONS (dbname '$$||current_database()||$$', + port '$$||current_setting('port')||$$' + )$$; + END; +$d$; + +-- But creation of user mappings for non-superusers should fail +CREATE USER MAPPING FOR public SERVER loopback_nopw; +CREATE USER MAPPING FOR CURRENT_USER SERVER loopback_nopw; + +CREATE FOREIGN TABLE ft1_nopw ( + c1 int NOT NULL, + c2 int NOT NULL, + c3 text, + c4 timestamptz, + c5 timestamp, + c6 varchar(10), + c7 char(10) default 'ft1', + c8 text +) SERVER loopback_nopw OPTIONS (schema_name 'public', table_name 'ft1'); + +SELECT 1 FROM ft1_nopw LIMIT 1; + +-- If we add a password to the connstr it'll fail, because we don't allow passwords +-- in connstrs only in user mappings. + +DO $d$ + BEGIN + EXECUTE $$ALTER SERVER loopback_nopw OPTIONS (ADD password 'qwer@1234')$$; + END; +$d$; + +-- If we add a password for our user mapping instead, we should get a different +-- error because the password wasn't actually *used* when we run with trust auth. +-- +-- This won't work with installcheck, but neither will most of the FDW checks. + +ALTER USER MAPPING FOR CURRENT_USER SERVER loopback_nopw OPTIONS (ADD password 'qwer@1234'); + +SELECT 1 FROM ft1_nopw LIMIT 1; + +-- Unpriv user cannot make the mapping passwordless +ALTER USER MAPPING FOR CURRENT_USER SERVER loopback_nopw OPTIONS (ADD password_required 'false'); + + +SELECT 1 FROM ft1_nopw LIMIT 1; + +RESET ROLE; + +-- But the superuser can +ALTER USER MAPPING FOR regress_nosuper SERVER loopback_nopw OPTIONS (ADD password_required 'false'); + +SET ROLE regress_nosuper; + +-- Should finally work now +SELECT 1 FROM ft1_nopw LIMIT 1; + +-- unpriv user also cannot set sslcert / sslkey on the user mapping +-- first set password_required so we see the right error messages +ALTER USER MAPPING FOR CURRENT_USER SERVER loopback_nopw OPTIONS (SET password_required 'true'); +ALTER USER MAPPING FOR CURRENT_USER SERVER loopback_nopw OPTIONS (ADD sslcert 'foo.crt'); +ALTER USER MAPPING FOR CURRENT_USER SERVER loopback_nopw OPTIONS (ADD sslkey 'foo.key'); + +-- We're done with the role named after a specific user and need to check the +-- changes to the public mapping. +DROP USER MAPPING FOR CURRENT_USER SERVER loopback_nopw; + +-- This will fail again as it'll resolve the user mapping for public, which +-- lacks password_required=false +SELECT 1 FROM ft1_nopw LIMIT 1; + +\c + +-- The user mapping for public is passwordless and lacks the password_required=false +-- mapping option, but will work because the current user is a superuser. +SELECT 1 FROM ft1_nopw LIMIT 1; + +-- cleanup +DROP USER MAPPING FOR public SERVER loopback_nopw; +DROP OWNED BY regress_nosuper; +DROP ROLE regress_nosuper; + +-- Clean-up +RESET enable_partitionwise_aggregate; + +-- Two-phase transactions are not supported. +BEGIN; +SELECT count(*) FROM ft1; +-- error here +PREPARE TRANSACTION 'fdw_tpc'; +ROLLBACK; + +-- ====================================================================================================================================== +-- TEST-MODULE: reestablish new connection +-- TEST-MODULE: test connection invalidation cases and postgres_fdw_get_connections function +-- TEST-MODULE: test postgres_fdw_disconnect and postgres_fdw_disconnect_all functions +-- TEST-MODULE: test case for having multiple cached connections for a foreign server +-- TEST-MODULE: Test foreign server level option keep_connections +-- -------------------------------------- +-- openGauss not have the functions following: +-- postgres_fdw_abs +-- postgres_fdw_disconnect +-- postgres_fdw_disconnect_all +-- postgres_fdw_get_connections +-- Therefore, these test modules are temporarily unavailable. +-- ====================================================================================================================================== +\df postgres_fdw_abs +\df postgres_fdw_disconnect +\df postgres_fdw_disconnect_all +\df postgres_fdw_get_connections +\df postgres_fdw_handler +\df postgres_fdw_validator + +-- ====================================================================================================================================== +-- TEST-MODULE: batch insert +-- -------------------------------------- +-- openGauss not support this feature, it will run as normal +-- ====================================================================================================================================== + +BEGIN; + +CREATE SERVER batch10 FOREIGN DATA WRAPPER postgres_fdw OPTIONS( batch_size '10' ); + +SELECT count(*) +FROM pg_foreign_server +WHERE srvname = 'batch10' +AND srvoptions @> array['batch_size=10']; + +ALTER SERVER batch10 OPTIONS( SET batch_size '20' ); + +SELECT count(*) +FROM pg_foreign_server +WHERE srvname = 'batch10' +AND srvoptions @> array['batch_size=10']; + +SELECT count(*) +FROM pg_foreign_server +WHERE srvname = 'batch10' +AND srvoptions @> array['batch_size=20']; + +CREATE FOREIGN TABLE table30 ( x int ) SERVER batch10 OPTIONS ( batch_size '30' ); + +SELECT COUNT(*) +FROM pg_foreign_table +WHERE ftrelid = 'table30'::regclass +AND ftoptions @> array['batch_size=30']; + +ALTER FOREIGN TABLE table30 OPTIONS ( SET batch_size '40'); + +SELECT COUNT(*) +FROM pg_foreign_table +WHERE ftrelid = 'table30'::regclass +AND ftoptions @> array['batch_size=30']; + +SELECT COUNT(*) +FROM pg_foreign_table +WHERE ftrelid = 'table30'::regclass +AND ftoptions @> array['batch_size=40']; + +ROLLBACK; + +CREATE TABLE batch_table ( x int ); + +CREATE FOREIGN TABLE ftable ( x int ) SERVER loopback OPTIONS ( table_name 'batch_table'/*, batch_size '10' */); +EXPLAIN (VERBOSE, COSTS OFF) INSERT INTO ftable SELECT * FROM generate_series(1, 10) i; +INSERT INTO ftable SELECT * FROM generate_series(1, 10) i; +INSERT INTO ftable SELECT * FROM generate_series(11, 31) i; +INSERT INTO ftable VALUES (32); +INSERT INTO ftable VALUES (33), (34); +SELECT COUNT(*) FROM ftable; +TRUNCATE batch_table; +DROP FOREIGN TABLE ftable; + +-- try if large batches exceed max number of bind parameters +CREATE FOREIGN TABLE ftable ( x int ) SERVER loopback OPTIONS ( table_name 'batch_table'/*, batch_size '100000'*/ ); +INSERT INTO ftable SELECT * FROM generate_series(1, 70000) i; +SELECT COUNT(*) FROM ftable; +TRUNCATE batch_table; +DROP FOREIGN TABLE ftable; + +-- Disable batch insert +CREATE FOREIGN TABLE ftable ( x int ) SERVER loopback OPTIONS ( table_name 'batch_table'/*, batch_size '1'*/ ); +EXPLAIN (VERBOSE, COSTS OFF) INSERT INTO ftable VALUES (1), (2); +INSERT INTO ftable VALUES (1), (2); +SELECT COUNT(*) FROM ftable; + +-- Disable batch inserting into foreign tables with BEFORE ROW INSERT triggers +-- even if the batch_size option is enabled. +ALTER FOREIGN TABLE ftable OPTIONS ( SET batch_size '10' ); +CREATE TRIGGER trig_row_before BEFORE INSERT ON ftable +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); +EXPLAIN (VERBOSE, COSTS OFF) INSERT INTO ftable VALUES (3), (4); +INSERT INTO ftable VALUES (3), (4); +SELECT COUNT(*) FROM ftable; + +-- Clean up +DROP TRIGGER trig_row_before ON ftable; +DROP FOREIGN TABLE ftable; +DROP TABLE batch_table; + +-- Use partitioning +-- nspt remove it all + +ALTER SERVER loopback OPTIONS (DROP batch_size); +-- ====================================================================================================================================== +-- TEST-MODULE: test asynchronous execution +-- -------------------------------------- +-- openGauss not support this feature, therefore, some cases that can run properly after modification are retained, +-- and some cases that cannot be supported are directly deleted. +-- If you want to restore the test case later, refer to README in the file header. +-- ====================================================================================================================================== +ALTER SERVER loopback OPTIONS (DROP extensions); --nspt +ALTER SERVER loopback OPTIONS (ADD async_capable 'true'); --nspt +ALTER SERVER loopback2 OPTIONS (ADD async_capable 'true'); --nspt + + +-- ====================================================================================================================================== +-- TEST-MODULE: test invalid server and foreign table options +-- -------------------------------------- +-- ====================================================================================================================================== +-- Invalid fdw_startup_cost option +CREATE SERVER inv_scst FOREIGN DATA WRAPPER postgres_fdw + OPTIONS(fdw_startup_cost '100$%$#$#'); +-- Invalid fdw_tuple_cost option +CREATE SERVER inv_scst FOREIGN DATA WRAPPER postgres_fdw + OPTIONS(fdw_tuple_cost '100$%$#$#'); +-- Invalid fetch_size option +CREATE FOREIGN TABLE inv_fsz (c1 int ) + SERVER loopback OPTIONS (fetch_size '100$%$#$#'); +-- Invalid batch_size option +CREATE FOREIGN TABLE inv_bsz (c1 int ) + SERVER loopback OPTIONS (batch_size '100$%$#$#'); + +-- ====================================================================================================================================== +-- TEST-MODULE: clean up all the test data +-- -------------------------------------- +-- heihei! +-- ====================================================================================================================================== +\c regression +drop database postgresfdw_test_db; \ No newline at end of file diff --git a/contrib/postgres_fdw/sql/postgres_fdw_cstore.sql b/contrib/postgres_fdw/sql/postgres_fdw_cstore.sql new file mode 100644 index 000000000..c62d36103 --- /dev/null +++ b/contrib/postgres_fdw/sql/postgres_fdw_cstore.sql @@ -0,0 +1,1165 @@ +-- ====================================================================================================================================== +-- README: For details, see "postgres_fdw.sql". +-- This test case was written by us. Test for cstore. +-- ====================================================================================================================================== +create database postgresfdw_test_db_cstore; +\c postgresfdw_test_db_cstore +set show_fdw_remote_plan = on; + +-- ====================================================================================================================================== +-- TEST-MODULE: create FDW objects +-- -------------------------------------- +-- ====================================================================================================================================== +CREATE EXTENSION postgres_fdw; + +CREATE SERVER testserver1 FOREIGN DATA WRAPPER postgres_fdw; +DO $d$ + BEGIN + EXECUTE $$CREATE SERVER loopback FOREIGN DATA WRAPPER postgres_fdw + OPTIONS (dbname '$$||current_database()||$$', + port '$$||current_setting('port')||$$' + )$$; + EXECUTE $$CREATE SERVER loopback2 FOREIGN DATA WRAPPER postgres_fdw + OPTIONS (dbname '$$||current_database()||$$', + port '$$||current_setting('port')||$$' + )$$; + EXECUTE $$CREATE SERVER loopback3 FOREIGN DATA WRAPPER postgres_fdw + OPTIONS (dbname '$$||current_database()||$$', + port '$$||current_setting('port')||$$' + )$$; + END; +$d$; + +CREATE USER MAPPING FOR public SERVER testserver1 + OPTIONS (user 'value', password 'value'); +CREATE USER MAPPING FOR CURRENT_USER SERVER loopback; +CREATE USER MAPPING FOR CURRENT_USER SERVER loopback2; +CREATE USER MAPPING FOR public SERVER loopback3; +-- ====================================================================================================================================== +-- TEST-MODULE: create objects used through FDW loopback server +-- -------------------------------------- +-- ====================================================================================================================================== +CREATE TYPE user_enum AS ENUM ('foo', 'bar', 'buz'); +CREATE SCHEMA "S 1"; +CREATE TABLE "S 1"."T 1" ( + "C 1" int NOT NULL, + c2 int NOT NULL, + c3 text, + c4 timestamptz, + c5 timestamp, + c6 varchar(10), + c7 char(10), + c8 text, + CONSTRAINT t1_pkey PRIMARY KEY ("C 1") +) with (orientation=column); +CREATE TABLE "S 1"."T 2" ( + c1 int NOT NULL, + c2 text, + CONSTRAINT t2_pkey PRIMARY KEY (c1) +) with (orientation=column); +CREATE TABLE "S 1"."T 3" ( + c1 int NOT NULL, + c2 int NOT NULL, + c3 text, + CONSTRAINT t3_pkey PRIMARY KEY (c1) +) with (orientation=column); +CREATE TABLE "S 1"."T 4" ( + c1 int NOT NULL, + c2 int NOT NULL, + c3 text, + CONSTRAINT t4_pkey PRIMARY KEY (c1) +) with (orientation=column); + +-- Disable autovacuum for these tables to avoid unexpected effects of that +ALTER TABLE "S 1"."T 1" SET (autovacuum_enabled = 'false'); +ALTER TABLE "S 1"."T 2" SET (autovacuum_enabled = 'false'); +ALTER TABLE "S 1"."T 3" SET (autovacuum_enabled = 'false'); +ALTER TABLE "S 1"."T 4" SET (autovacuum_enabled = 'false'); + +INSERT INTO "S 1"."T 1" + SELECT id, + id % 10, + to_char(id, 'FM00000'), + '1970-01-01'::timestamptz + ((id % 100) || ' days')::interval, + '1970-01-01'::timestamp + ((id % 100) || ' days')::interval, + id % 10, + id % 10, + 'foo'::user_enum + FROM generate_series(1, 1000) id; +INSERT INTO "S 1"."T 2" + SELECT id, + 'AAA' || to_char(id, 'FM000') + FROM generate_series(1, 100) id; +INSERT INTO "S 1"."T 3" + SELECT id, + id + 1, + 'AAA' || to_char(id, 'FM000') + FROM generate_series(1, 100) id; +DELETE FROM "S 1"."T 3" WHERE c1 % 2 != 0; -- delete for outer join tests +INSERT INTO "S 1"."T 4" + SELECT id, + id + 1, + 'AAA' || to_char(id, 'FM000') + FROM generate_series(1, 100) id; +DELETE FROM "S 1"."T 4" WHERE c1 % 3 != 0; -- delete for outer join tests + +ANALYZE "S 1"."T 1"; +ANALYZE "S 1"."T 2"; +ANALYZE "S 1"."T 3"; +ANALYZE "S 1"."T 4"; + +-- ====================================================================================================================================== +-- TEST-MODULE: create foreign tables +-- -------------------------------------- +-- +-- ====================================================================================================================================== +CREATE FOREIGN TABLE ft1 ( + c0 int, + c1 int NOT NULL, + c2 int NOT NULL, + c3 text, + c4 timestamptz, + c5 timestamp, + c6 varchar(10), + c7 char(10) default 'ft1', + c8 user_enum +) SERVER loopback; +ALTER FOREIGN TABLE ft1 DROP COLUMN c0; + +CREATE FOREIGN TABLE ft2 ( + c1 int NOT NULL, + c2 int NOT NULL, + cx int, + c3 text, + c4 timestamptz, + c5 timestamp, + c6 varchar(10), + c7 char(10) default 'ft2', + c8 user_enum +) SERVER loopback; +ALTER FOREIGN TABLE ft2 DROP COLUMN cx; + +CREATE FOREIGN TABLE ft4 ( + c1 int NOT NULL, + c2 int NOT NULL, + c3 text +) SERVER loopback OPTIONS (schema_name 'S 1', table_name 'T 3'); + +CREATE FOREIGN TABLE ft5 ( + c1 int NOT NULL, + c2 int NOT NULL, + c3 text +) SERVER loopback OPTIONS (schema_name 'S 1', table_name 'T 4'); + +CREATE FOREIGN TABLE ft6 ( + c1 int NOT NULL, + c2 int NOT NULL, + c3 text +) SERVER loopback2 OPTIONS (schema_name 'S 1', table_name 'T 4'); + +CREATE FOREIGN TABLE ft7 ( + c1 int NOT NULL, + c2 int NOT NULL, + c3 text +) SERVER loopback3 OPTIONS (schema_name 'S 1', table_name 'T 4'); + +-- ====================================================================================================================================== +-- TEST-MODULE: tests for validator +-- -------------------------------------- +-- ====================================================================================================================================== +-- requiressl and some other parameters are omitted because +-- valid values for them depend on configure options +ALTER SERVER testserver1 OPTIONS ( + use_remote_estimate 'false', + updatable 'true', + fdw_startup_cost '123.456', + fdw_tuple_cost '0.123', + service 'value', + connect_timeout 'value', + dbname 'value', + host 'value', + hostaddr 'value', + port 'value', + application_name 'value', + keepalives 'value', + keepalives_idle 'value', + keepalives_interval 'value', + sslcompression 'value', + sslmode 'value', + sslcert 'value', + sslkey 'value', + sslrootcert 'value', + sslcrl 'value', + krbsrvname 'value' +); + +ALTER FOREIGN TABLE ft1 OPTIONS (schema_name 'S 1', table_name 'T 1'); +ALTER FOREIGN TABLE ft2 OPTIONS (schema_name 'S 1', table_name 'T 1'); +ALTER FOREIGN TABLE ft1 ALTER COLUMN c1 OPTIONS (column_name 'C 1'); +ALTER FOREIGN TABLE ft2 ALTER COLUMN c1 OPTIONS (column_name 'C 1'); +\det+ + + +-- Now we should be able to run ANALYZE. +-- To exercise multiple code paths, we use local stats on ft1 +-- and remote-estimate mode on ft2. +ANALYZE ft1; +ALTER FOREIGN TABLE ft2 OPTIONS (use_remote_estimate 'true'); +-- ====================================================================================================================================== +-- TEST-MODULE: simple queries +-- -------------------------------------- +-- ====================================================================================================================================== +-- single table without alias +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 ORDER BY c3, c1 OFFSET 100 LIMIT 10; +SELECT * FROM ft1 ORDER BY c3, c1 OFFSET 100 LIMIT 10; +-- whole-row reference +EXPLAIN (VERBOSE, COSTS OFF) SELECT t1 FROM ft1 t1 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; +SELECT t1 FROM ft1 t1 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; +-- empty result +SELECT * FROM ft1 WHERE false; +-- with WHERE clause +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE t1.c1 = 101 AND t1.c6 = '1' AND t1.c7 >= '1'; +SELECT * FROM ft1 t1 WHERE t1.c1 = 101 AND t1.c6 = '1' AND t1.c7 >= '1'; +-- with FOR UPDATE/SHARE +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 = 101 FOR UPDATE; +SELECT * FROM ft1 t1 WHERE c1 = 101 FOR UPDATE; +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 = 102 FOR SHARE; +SELECT * FROM ft1 t1 WHERE c1 = 102 FOR SHARE; +-- aggregate +SELECT COUNT(*) FROM ft1 t1; +-- subquery +SELECT * FROM ft1 t1 WHERE t1.c3 IN (SELECT c3 FROM ft2 t2 WHERE c1 <= 10) ORDER BY c1; +-- subquery+MAX +SELECT * FROM ft1 t1 WHERE t1.c3 = (SELECT MAX(c3) FROM ft2 t2) ORDER BY c1; +-- used in CTE +WITH t1 AS (SELECT * FROM ft1 WHERE c1 <= 10) SELECT t2.c1, t2.c2, t2.c3, t2.c4 FROM t1, ft2 t2 WHERE t1.c1 = t2.c1 ORDER BY t1.c1; +-- fixed values +SELECT 'fixed', NULL FROM ft1 t1 WHERE c1 = 1; +-- Test forcing the remote server to produce sorted data for a merge join. +SET enable_hashjoin TO false; +SET enable_nestloop TO false; +-- inner join; expressions in the clauses appear in the equivalence class list +EXPLAIN (VERBOSE, COSTS OFF) + SELECT t1.c1, t2."C 1" FROM ft2 t1 JOIN "S 1"."T 1" t2 ON (t1.c1 = t2."C 1") OFFSET 100 LIMIT 10; +SELECT t1.c1, t2."C 1" FROM ft2 t1 JOIN "S 1"."T 1" t2 ON (t1.c1 = t2."C 1") OFFSET 100 LIMIT 10; +-- outer join; expressions in the clauses do not appear in equivalence class +-- list but no output change as compared to the previous query +EXPLAIN (VERBOSE, COSTS OFF) + SELECT t1.c1, t2."C 1" FROM ft2 t1 LEFT JOIN "S 1"."T 1" t2 ON (t1.c1 = t2."C 1") OFFSET 100 LIMIT 10; +SELECT t1.c1, t2."C 1" FROM ft2 t1 LEFT JOIN "S 1"."T 1" t2 ON (t1.c1 = t2."C 1") OFFSET 100 LIMIT 10; +-- A join between local table and foreign join. ORDER BY clause is added to the +-- foreign join so that the local table can be joined using merge join strategy. +EXPLAIN (VERBOSE, COSTS OFF) + SELECT t1."C 1" FROM "S 1"."T 1" t1 left join ft1 t2 join ft2 t3 on (t2.c1 = t3.c1) on (t3.c1 = t1."C 1") OFFSET 100 LIMIT 10; +SELECT t1."C 1" FROM "S 1"."T 1" t1 left join ft1 t2 join ft2 t3 on (t2.c1 = t3.c1) on (t3.c1 = t1."C 1") OFFSET 100 LIMIT 10; +-- Test similar to above, except that the full join prevents any equivalence +-- classes from being merged. This produces single relation equivalence classes +-- included in join restrictions. +EXPLAIN (VERBOSE, COSTS OFF) + SELECT t1."C 1", t2.c1, t3.c1 FROM "S 1"."T 1" t1 left join ft1 t2 full join ft2 t3 on (t2.c1 = t3.c1) on (t3.c1 = t1."C 1") OFFSET 100 LIMIT 10; +SELECT t1."C 1", t2.c1, t3.c1 FROM "S 1"."T 1" t1 left join ft1 t2 full join ft2 t3 on (t2.c1 = t3.c1) on (t3.c1 = t1."C 1") OFFSET 100 LIMIT 10; +-- Test similar to above with all full outer joins +EXPLAIN (VERBOSE, COSTS OFF) + SELECT t1."C 1", t2.c1, t3.c1 FROM "S 1"."T 1" t1 full join ft1 t2 full join ft2 t3 on (t2.c1 = t3.c1) on (t3.c1 = t1."C 1") OFFSET 100 LIMIT 10; +SELECT t1."C 1", t2.c1, t3.c1 FROM "S 1"."T 1" t1 full join ft1 t2 full join ft2 t3 on (t2.c1 = t3.c1) on (t3.c1 = t1."C 1") OFFSET 100 LIMIT 10; +RESET enable_hashjoin; +RESET enable_nestloop; + +-- Test executing assertion in estimate_path_cost_size() that makes sure that +-- retrieved_rows for foreign rel re-used to cost pre-sorted foreign paths is +-- a sensible value even when the rel has tuples=0 +CREATE TABLE loct_empty (c1 int NOT NULL, c2 text) with (orientation=column); +CREATE FOREIGN TABLE ft_empty (c1 int NOT NULL, c2 text) + SERVER loopback OPTIONS (table_name 'loct_empty'); +INSERT INTO loct_empty + SELECT id, 'AAA' || to_char(id, 'FM000') FROM generate_series(1, 100) id; +DELETE FROM loct_empty; +ANALYZE ft_empty; +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft_empty ORDER BY c1; +-- ====================================================================================================================================== +-- TEST-MODULE: WHERE with remotely-executable conditions +-- -------------------------------------- +-- ====================================================================================================================================== +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE t1.c1 = 1; -- Var, OpExpr(b), Const +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE t1.c1 = 100 AND t1.c2 = 0; -- BoolExpr +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 IS NULL; -- NullTest +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 IS NOT NULL; -- NullTest +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE round(abs(c1), 0) = 1; -- FuncExpr +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 = -c1; -- OpExpr(l) +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE (c1 IS NOT NULL) IS DISTINCT FROM (c1 IS NOT NULL); -- DistinctExpr +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 = ANY(ARRAY[c2, 1, c1 + 0]); -- ScalarArrayOpExpr +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 = (ARRAY[c1,c2,3])[1]; -- SubscriptingRef +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c6 = E'foo''s\\bar'; -- check special chars +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c8 = 'foo'; -- can't be sent to remote +-- parameterized remote path for foreign table +EXPLAIN (VERBOSE, COSTS OFF) + SELECT * FROM "S 1"."T 1" a, ft2 b WHERE a."C 1" = 47 AND b.c1 = a.c2; +SELECT * FROM ft2 a, ft2 b WHERE a.c1 = 47 AND b.c1 = a.c2; + +-- check both safe and unsafe join conditions +EXPLAIN (VERBOSE, COSTS OFF) + SELECT * FROM ft2 a, ft2 b + WHERE a.c2 = 6 AND b.c1 = a.c1 AND a.c8 = 'foo' AND b.c7 = upper(a.c7); +SELECT * FROM ft2 a, ft2 b +WHERE a.c2 = 6 AND b.c1 = a.c1 AND a.c8 = 'foo' AND b.c7 = upper(a.c7); +-- bug before 9.3.5 due to sloppy handling of remote-estimate parameters +SELECT * FROM ft1 WHERE c1 = ANY (ARRAY(SELECT c1 FROM ft2 WHERE c1 < 5)); +SELECT * FROM ft2 WHERE c1 = ANY (ARRAY(SELECT c1 FROM ft1 WHERE c1 < 5)); +-- we should not push order by clause with volatile expressions or unsafe +-- collations +EXPLAIN (VERBOSE, COSTS OFF) + SELECT * FROM ft2 ORDER BY ft2.c1, random(); +EXPLAIN (VERBOSE, COSTS OFF) + SELECT * FROM ft2 ORDER BY ft2.c1, ft2.c3 collate "C"; + +-- user-defined operator/function +CREATE FUNCTION postgres_fdw_abs(int) RETURNS int AS $$ +BEGIN +RETURN abs($1); +END +$$ LANGUAGE plpgsql IMMUTABLE; +CREATE OPERATOR === ( + LEFTARG = int, + RIGHTARG = int, + PROCEDURE = int4eq, + COMMUTATOR = === +); + +-- built-in operators and functions can be shipped for remote execution +EXPLAIN (VERBOSE, COSTS OFF) + SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = abs(t1.c2); +SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = abs(t1.c2); +EXPLAIN (VERBOSE, COSTS OFF) + SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = t1.c2; +SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = t1.c2; + +-- by default, user-defined ones cannot +EXPLAIN (VERBOSE, COSTS OFF) + SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = postgres_fdw_abs(t1.c2); +SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = postgres_fdw_abs(t1.c2); +EXPLAIN (VERBOSE, COSTS OFF) + SELECT count(c3) FROM ft1 t1 WHERE t1.c1 === t1.c2; +SELECT count(c3) FROM ft1 t1 WHERE t1.c1 === t1.c2; + +-- ORDER BY can be shipped, though +EXPLAIN (VERBOSE, COSTS OFF) + SELECT * FROM ft1 t1 WHERE t1.c1 === t1.c2 order by t1.c2 limit 1; +SELECT * FROM ft1 t1 WHERE t1.c1 === t1.c2 order by t1.c2 limit 1; + +-- but let's put them in an extension ... +ALTER EXTENSION postgres_fdw ADD FUNCTION postgres_fdw_abs(int); +ALTER EXTENSION postgres_fdw ADD OPERATOR === (int, int); +SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = postgres_fdw_abs(t1.c2); +SELECT count(c3) FROM ft1 t1 WHERE t1.c1 === t1.c2; + +-- and both ORDER BY and LIMIT can be shipped +SELECT * FROM ft1 t1 WHERE t1.c1 === t1.c2 order by t1.c2 limit 1; + +-- case when expr +explain (verbose, costs off) select * from ft1 where case c1 when 1 then 0 end = 0; +select * from ft1 where case c1 when 1 then 0 end = 0; + +-- ====================================================================================================================================== +-- TEST-MODULE: JOIN queries +-- -------------------------------------- +-- ====================================================================================================================================== +-- Analyze ft4 and ft5 so that we have better statistics. These tables do not +-- have use_remote_estimate set. +ANALYZE ft4; +ANALYZE ft5; + +-- join two tables +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; -- Inaccurate +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; +-- join three tables +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) JOIN ft4 t3 ON (t3.c1 = t1.c1) ORDER BY t1.c3, t1.c1 OFFSET 10 LIMIT 10; -- Inaccurate +SELECT t1.c1, t2.c2, t3.c3 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) JOIN ft4 t3 ON (t3.c1 = t1.c1) ORDER BY t1.c3, t1.c1 OFFSET 10 LIMIT 10; +-- left outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; +SELECT t1.c1, t2.c1 FROM ft4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; +-- left outer join three tables +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) LEFT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) LEFT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +-- left outer join + placement of clauses. +-- clauses within the nullable side are not pulled up, but top level clause on +-- non-nullable side is pushed into non-nullable side +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t1.c2, t2.c1, t2.c2 FROM ft4 t1 LEFT JOIN (SELECT * FROM ft5 WHERE c1 < 10) t2 ON (t1.c1 = t2.c1) WHERE t1.c1 < 10; +SELECT t1.c1, t1.c2, t2.c1, t2.c2 FROM ft4 t1 LEFT JOIN (SELECT * FROM ft5 WHERE c1 < 10) t2 ON (t1.c1 = t2.c1) WHERE t1.c1 < 10; +-- clauses within the nullable side are not pulled up, but the top level clause +-- on nullable side is not pushed down into nullable side +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t1.c2, t2.c1, t2.c2 FROM ft4 t1 LEFT JOIN (SELECT * FROM ft5 WHERE c1 < 10) t2 ON (t1.c1 = t2.c1) + WHERE (t2.c1 < 10 OR t2.c1 IS NULL) AND t1.c1 < 10; +SELECT t1.c1, t1.c2, t2.c1, t2.c2 FROM ft4 t1 LEFT JOIN (SELECT * FROM ft5 WHERE c1 < 10) t2 ON (t1.c1 = t2.c1) + WHERE (t2.c1 < 10 OR t2.c1 IS NULL) AND t1.c1 < 10; +-- right outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft5 t1 RIGHT JOIN ft4 t2 ON (t1.c1 = t2.c1) ORDER BY t2.c1, t1.c1 OFFSET 10 LIMIT 10; +SELECT t1.c1, t2.c1 FROM ft5 t1 RIGHT JOIN ft4 t2 ON (t1.c1 = t2.c1) ORDER BY t2.c1, t1.c1 OFFSET 10 LIMIT 10; +-- right outer join three tables +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 RIGHT JOIN ft2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 RIGHT JOIN ft2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +-- full outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft4 t1 FULL JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 45 LIMIT 10; -- Inaccurate +SELECT t1.c1, t2.c1 FROM ft4 t1 FULL JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 45 LIMIT 10; +-- full outer join with restrictions on the joining relations +-- a. the joining relations are both base relations +EXPLAIN (VERBOSE, COSTS OFF) -- Inaccurate +SELECT t1.c1, t2.c1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1; +SELECT t1.c1, t2.c1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1; +EXPLAIN (VERBOSE, COSTS OFF) +SELECT 1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t2 ON (TRUE) OFFSET 10 LIMIT 10; +SELECT 1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t2 ON (TRUE) OFFSET 10 LIMIT 10; +EXPLAIN (VERBOSE, COSTS OFF) +SELECT count(*) FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t2 ON (TRUE) OFFSET 10 LIMIT 2; +SELECT count(*) FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t2 ON (TRUE) OFFSET 10 LIMIT 2; +-- b. one of the joining relations is a base relation and the other is a join +-- relation +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, ss.a, ss.b FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT t2.c1, t3.c1 FROM ft4 t2 LEFT JOIN ft5 t3 ON (t2.c1 = t3.c1) WHERE (t2.c1 between 50 and 60)) ss(a, b) ON (t1.c1 = ss.a) ORDER BY t1.c1, ss.a, ss.b; +SELECT t1.c1, ss.a, ss.b FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT t2.c1, t3.c1 FROM ft4 t2 LEFT JOIN ft5 t3 ON (t2.c1 = t3.c1) WHERE (t2.c1 between 50 and 60)) ss(a, b) ON (t1.c1 = ss.a) ORDER BY t1.c1, ss.a, ss.b; +-- c. test deparsing the remote query as nested subqueries +EXPLAIN (VERBOSE, COSTS OFF) -- Inaccurate +SELECT t1.c1, ss.a, ss.b FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT t2.c1, t3.c1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t2 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t3 ON (t2.c1 = t3.c1) WHERE t2.c1 IS NULL OR t2.c1 IS NOT NULL) ss(a, b) ON (t1.c1 = ss.a) ORDER BY t1.c1, ss.a, ss.b; +SELECT t1.c1, ss.a, ss.b FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT t2.c1, t3.c1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t2 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t3 ON (t2.c1 = t3.c1) WHERE t2.c1 IS NULL OR t2.c1 IS NOT NULL) ss(a, b) ON (t1.c1 = ss.a) ORDER BY t1.c1, ss.a, ss.b; +-- d. test deparsing rowmarked relations as subqueries +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, ss.a, ss.b FROM (SELECT c1 FROM "S 1"."T 3" WHERE c1 = 50) t1 INNER JOIN (SELECT t2.c1, t3.c1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t2 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t3 ON (t2.c1 = t3.c1) WHERE t2.c1 IS NULL OR t2.c1 IS NOT NULL) ss(a, b) ON (TRUE) ORDER BY t1.c1, ss.a, ss.b FOR UPDATE OF t1; +SELECT t1.c1, ss.a, ss.b FROM (SELECT c1 FROM "S 1"."T 3" WHERE c1 = 50) t1 INNER JOIN (SELECT t2.c1, t3.c1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t2 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t3 ON (t2.c1 = t3.c1) WHERE t2.c1 IS NULL OR t2.c1 IS NOT NULL) ss(a, b) ON (TRUE) ORDER BY t1.c1, ss.a, ss.b FOR UPDATE OF t1; +-- full outer join + inner join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1, t3.c1 FROM ft4 t1 INNER JOIN ft5 t2 ON (t1.c1 = t2.c1 + 1 and t1.c1 between 50 and 60) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) ORDER BY t1.c1, t2.c1, t3.c1 LIMIT 10; +SELECT t1.c1, t2.c1, t3.c1 FROM ft4 t1 INNER JOIN ft5 t2 ON (t1.c1 = t2.c1 + 1 and t1.c1 between 50 and 60) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) ORDER BY t1.c1, t2.c1, t3.c1 LIMIT 10; +-- full outer join three tables +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +-- full outer join + right outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +-- right outer join + full outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 RIGHT JOIN ft2 t2 ON (t1.c1 = t2.c1) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 RIGHT JOIN ft2 t2 ON (t1.c1 = t2.c1) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +-- full outer join + left outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) LEFT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) LEFT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +-- left outer join + full outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +-- right outer join + left outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 RIGHT JOIN ft2 t2 ON (t1.c1 = t2.c1) LEFT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 RIGHT JOIN ft2 t2 ON (t1.c1 = t2.c1) LEFT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +-- left outer join + right outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +-- full outer join + WHERE clause, only matched rows +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft4 t1 FULL JOIN ft5 t2 ON (t1.c1 = t2.c1) WHERE (t1.c1 = t2.c1 OR t1.c1 IS NULL) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; +SELECT t1.c1, t2.c1 FROM ft4 t1 FULL JOIN ft5 t2 ON (t1.c1 = t2.c1) WHERE (t1.c1 = t2.c1 OR t1.c1 IS NULL) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; +-- full outer join + WHERE clause with shippable extensions set +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t1.c3 FROM ft1 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE postgres_fdw_abs(t1.c1) > 0 OFFSET 10 LIMIT 10; +-- full outer join + WHERE clause with shippable extensions not set +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t1.c3 FROM ft1 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE postgres_fdw_abs(t1.c1) > 0 OFFSET 10 LIMIT 10; +-- join two tables with FOR UPDATE clause +-- tests whole-row reference for row marks +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR UPDATE OF t1; +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR UPDATE OF t1; +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR UPDATE; +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR UPDATE; +-- join two tables with FOR SHARE clause +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR SHARE OF t1; +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR SHARE OF t1; +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR SHARE; +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR SHARE; +-- join in CTE +EXPLAIN (VERBOSE, COSTS OFF) -- Inaccurate +WITH t (c1_1, c1_3, c2_1) AS MATERIALIZED (SELECT t1.c1, t1.c3, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1)) SELECT c1_1, c2_1 FROM t ORDER BY c1_3, c1_1 OFFSET 100 LIMIT 10; +WITH t (c1_1, c1_3, c2_1) AS MATERIALIZED (SELECT t1.c1, t1.c3, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1)) SELECT c1_1, c2_1 FROM t ORDER BY c1_3, c1_1 OFFSET 100 LIMIT 10; +-- ctid with whole-row reference, currently, does not support system column +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.ctid, t1, t2, t1.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; +-- SEMI JOIN, not pushed down +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1 FROM ft1 t1 WHERE EXISTS (SELECT 1 FROM ft2 t2 WHERE t1.c1 = t2.c1) ORDER BY t1.c1 OFFSET 100 LIMIT 10; +SELECT t1.c1 FROM ft1 t1 WHERE EXISTS (SELECT 1 FROM ft2 t2 WHERE t1.c1 = t2.c1) ORDER BY t1.c1 OFFSET 100 LIMIT 10; +-- ANTI JOIN, not pushed down +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1 FROM ft1 t1 WHERE NOT EXISTS (SELECT 1 FROM ft2 t2 WHERE t1.c1 = t2.c2) ORDER BY t1.c1 OFFSET 100 LIMIT 10; +SELECT t1.c1 FROM ft1 t1 WHERE NOT EXISTS (SELECT 1 FROM ft2 t2 WHERE t1.c1 = t2.c2) ORDER BY t1.c1 OFFSET 100 LIMIT 10; +-- CROSS JOIN can be pushed down +EXPLAIN (VERBOSE, COSTS OFF) -- Inaccurate +SELECT t1.c1, t2.c1 FROM ft1 t1 CROSS JOIN ft2 t2 ORDER BY t1.c1, t2.c1 OFFSET 100 LIMIT 10; +SELECT t1.c1, t2.c1 FROM ft1 t1 CROSS JOIN ft2 t2 ORDER BY t1.c1, t2.c1 OFFSET 100 LIMIT 10; +-- different server, not pushed down. No result expected. +EXPLAIN (VERBOSE, COSTS OFF) -- Inaccurate +SELECT t1.c1, t2.c1 FROM ft5 t1 JOIN ft6 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 100 LIMIT 10; +SELECT t1.c1, t2.c1 FROM ft5 t1 JOIN ft6 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 100 LIMIT 10; +-- unsafe join conditions (c8 has a UDT), not pushed down. Practically a CROSS +-- JOIN since c8 in both tables has same value. +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 LEFT JOIN ft2 t2 ON (t1.c8 = t2.c8) ORDER BY t1.c1, t2.c1 OFFSET 100 LIMIT 10; +SELECT t1.c1, t2.c1 FROM ft1 t1 LEFT JOIN ft2 t2 ON (t1.c8 = t2.c8) ORDER BY t1.c1, t2.c1 OFFSET 100 LIMIT 10; +-- unsafe conditions on one side (c8 has a UDT), not pushed down. +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE t1.c8 = 'foo' ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; +SELECT t1.c1, t2.c1 FROM ft1 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE t1.c8 = 'foo' ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; +-- join where unsafe to pushdown condition in WHERE clause has a column not +-- in the SELECT clause. In this test unsafe clause needs to have column +-- references from both joining sides so that the clause is not pushed down +-- into one of the joining sides. +EXPLAIN (VERBOSE, COSTS OFF) -- Inaccurate +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE t1.c8 = t2.c8 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE t1.c8 = t2.c8 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; +-- Aggregate after UNION, for testing setrefs +EXPLAIN (VERBOSE, COSTS OFF) -- Inaccurate +SELECT t1c1, avg(t1c1 + t2c1) FROM (SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) UNION SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1)) AS t (t1c1, t2c1) GROUP BY t1c1 ORDER BY t1c1 OFFSET 100 LIMIT 10; +SELECT t1c1, avg(t1c1 + t2c1) FROM (SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) UNION SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1)) AS t (t1c1, t2c1) GROUP BY t1c1 ORDER BY t1c1 OFFSET 100 LIMIT 10; + +-- non-Var items in targetlist of the nullable rel of a join preventing +-- push-down in some cases +-- unable to push {ft1, ft2} +EXPLAIN (VERBOSE, COSTS OFF) +SELECT q.a, ft2.c1 FROM (SELECT 13 FROM ft1 WHERE c1 = 13) q(a) RIGHT JOIN ft2 ON (q.a = ft2.c1) WHERE ft2.c1 BETWEEN 10 AND 15; +SELECT q.a, ft2.c1 FROM (SELECT 13 FROM ft1 WHERE c1 = 13) q(a) RIGHT JOIN ft2 ON (q.a = ft2.c1) WHERE ft2.c1 BETWEEN 10 AND 15; + +-- ok to push {ft1, ft2} but not {ft1, ft2, ft4} +EXPLAIN (VERBOSE, COSTS OFF) -- Inaccurate +SELECT ft4.c1, q.* FROM ft4 LEFT JOIN (SELECT 13, ft1.c1, ft2.c1 FROM ft1 RIGHT JOIN ft2 ON (ft1.c1 = ft2.c1) WHERE ft1.c1 = 12) q(a, b, c) ON (ft4.c1 = q.b) WHERE ft4.c1 BETWEEN 10 AND 15; +SELECT ft4.c1, q.* FROM ft4 LEFT JOIN (SELECT 13, ft1.c1, ft2.c1 FROM ft1 RIGHT JOIN ft2 ON (ft1.c1 = ft2.c1) WHERE ft1.c1 = 12) q(a, b, c) ON (ft4.c1 = q.b) WHERE ft4.c1 BETWEEN 10 AND 15; + +-- join with nullable side with some columns with null values +UPDATE ft5 SET c3 = null where c1 % 9 = 0; +UPDATE "S 1"."T 4" SET c3 = null where c1 % 9 = 0; +EXPLAIN (VERBOSE, COSTS OFF) -- Inaccurate +SELECT ft5, ft5.c1, ft5.c2, ft5.c3, ft4.c1, ft4.c2 FROM ft5 left join ft4 on ft5.c1 = ft4.c1 WHERE ft4.c1 BETWEEN 10 and 30 ORDER BY ft5.c1, ft4.c1; +SELECT ft5, ft5.c1, ft5.c2, ft5.c3, ft4.c1, ft4.c2 FROM ft5 left join ft4 on ft5.c1 = ft4.c1 WHERE ft4.c1 BETWEEN 10 and 30 ORDER BY ft5.c1, ft4.c1; + +-- multi-way join involving multiple merge joins +-- (this case used to have EPQ-related planning problems) +CREATE TABLE local_tbl (c1 int NOT NULL, c2 int NOT NULL, c3 text, CONSTRAINT local_tbl_pkey PRIMARY KEY (c1)) with (orientation=column); +INSERT INTO local_tbl SELECT id, id % 10, to_char(id, 'FM0000') FROM generate_series(1, 1000) id; +ANALYZE local_tbl; +SET enable_nestloop TO false; +SET enable_hashjoin TO false; +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM ft1, ft2, ft4, ft5, local_tbl WHERE ft1.c1 = ft2.c1 AND ft1.c2 = ft4.c1 + AND ft1.c2 = ft5.c1 AND ft1.c2 = local_tbl.c1 AND ft1.c1 < 100 AND ft2.c1 < 100 FOR UPDATE; +SELECT * FROM ft1, ft2, ft4, ft5, local_tbl WHERE ft1.c1 = ft2.c1 AND ft1.c2 = ft4.c1 + AND ft1.c2 = ft5.c1 AND ft1.c2 = local_tbl.c1 AND ft1.c1 < 100 AND ft2.c1 < 100 FOR UPDATE; +RESET enable_nestloop; +RESET enable_hashjoin; +DROP TABLE local_tbl; + +-- check join pushdown in situations where multiple userids are involved +CREATE ROLE regress_view_owner sysadmin password 'QWERT@12345'; +CREATE USER MAPPING FOR regress_view_owner SERVER loopback; +GRANT SELECT ON ft4 TO regress_view_owner; +GRANT SELECT ON ft5 TO regress_view_owner; + +CREATE VIEW v4 AS SELECT * FROM ft4; +CREATE VIEW v5 AS SELECT * FROM ft5; +ALTER VIEW v5 OWNER TO regress_view_owner; +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; -- can't be pushed down, different view owners +SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; +ALTER VIEW v4 OWNER TO regress_view_owner; +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; -- can be pushed down +SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; -- can't be pushed down, view owner not current user +SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; +ALTER VIEW v4 OWNER TO regress_view_owner; + +-- PlaceHolder +explain(verbose, costs off) select * from ft1 t1 left join (select c1, case when c2 is not null then 1 else 0 end as cc from ft2) t2 on t1.c1 = t2.c1; + +-- cleanup +DROP OWNED BY regress_view_owner; +DROP ROLE regress_view_owner; + +-- ====================================================================================================================================== +-- TEST-MODULE: Aggregate and grouping queries +-- -------------------------------------- +-- openGauss not support filter +-- ====================================================================================================================================== + +-- Simple aggregates +explain (verbose, costs off) +select count(c6), sum(c1), avg(c1), min(c2), max(c1), stddev(c2), sum(c1) * (random() <= 1)::int as sum2 from ft1 where c2 < 5 group by c2 order by 1, 2; +select count(c6), sum(c1), avg(c1), min(c2), max(c1), stddev(c2), sum(c1) * (random() <= 1)::int as sum2 from ft1 where c2 < 5 group by c2 order by 1, 2; + +explain (verbose, costs off) +select count(c6), sum(c1), avg(c1), min(c2), max(c1), stddev(c2), sum(c1) * (random() <= 1)::int as sum2 from ft1 where c2 < 5 group by c2 order by 1, 2 limit 1; +select count(c6), sum(c1), avg(c1), min(c2), max(c1), stddev(c2), sum(c1) * (random() <= 1)::int as sum2 from ft1 where c2 < 5 group by c2 order by 1, 2 limit 1; + +-- Aggregate is not pushed down as aggregation contains random() +explain (verbose, costs off) +select sum(c1 * (random() <= 1)::int) as sum, avg(c1) from ft1; + +-- Aggregate over join query +explain (verbose, costs off) +select count(*), sum(t1.c1), avg(t2.c1) from ft1 t1 inner join ft1 t2 on (t1.c2 = t2.c2) where t1.c2 = 6; +select count(*), sum(t1.c1), avg(t2.c1) from ft1 t1 inner join ft1 t2 on (t1.c2 = t2.c2) where t1.c2 = 6; + +-- Not pushed down due to local conditions present in underneath input rel +explain (verbose, costs off) -- Inaccurate +select sum(t1.c1), count(t2.c1) from ft1 t1 inner join ft2 t2 on (t1.c1 = t2.c1) where ((t1.c1 * t2.c1)/(t1.c1 * t2.c1)) * random() <= 1; + +-- GROUP BY clause having expressions +explain (verbose, costs off) +select c2/2, sum(c2) * (c2/2) from ft1 group by c2/2 order by c2/2; +select c2/2, sum(c2) * (c2/2) from ft1 group by c2/2 order by c2/2; + +-- Aggregates in subquery are pushed down. +explain (verbose, costs off) +select count(x.a), sum(x.a) from (select c2 a, sum(c1) b from ft1 group by c2, sqrt(c1) order by 1, 2) x; +select count(x.a), sum(x.a) from (select c2 a, sum(c1) b from ft1 group by c2, sqrt(c1) order by 1, 2) x; + +-- Aggregate is still pushed down by taking unshippable expression out +explain (verbose, costs off) +select c2 * (random() <= 1)::int as sum1, sum(c1) * c2 as sum2 from ft1 group by c2 order by 1, 2; +select c2 * (random() <= 1)::int as sum1, sum(c1) * c2 as sum2 from ft1 group by c2 order by 1, 2; + +-- Aggregate with unshippable GROUP BY clause are not pushed +explain (verbose, costs off) +select c2 * (random() <= 1)::int as c2 from ft2 group by c2 * (random() <= 1)::int order by 1; + +-- GROUP BY clause in various forms, cardinal, alias and constant expression +explain (verbose, costs off) +select count(c2) w, c2 x, 5 y, 7.0 z from ft1 group by 2, y, 9.0::int order by 2; +select count(c2) w, c2 x, 5 y, 7.0 z from ft1 group by 2, y, 9.0::int order by 2; + +-- GROUP BY clause referring to same column multiple times +-- Also, ORDER BY contains an aggregate function +explain (verbose, costs off) +select c2, c2 from ft1 where c2 > 6 group by 1, 2 order by sum(c1); +select c2, c2 from ft1 where c2 > 6 group by 1, 2 order by sum(c1); + +-- Testing HAVING clause shippability +explain (verbose, costs off) +select c2, sum(c1) from ft2 group by c2 having avg(c1) < 500 and sum(c1) < 49800 order by c2; +select c2, sum(c1) from ft2 group by c2 having avg(c1) < 500 and sum(c1) < 49800 order by c2; + +-- Unshippable HAVING clause will be evaluated locally, and other qual in HAVING clause is pushed down +explain (verbose, costs off) +select count(*) from (select c5, count(c1) from ft1 group by c5, sqrt(c2) having (avg(c1) / avg(c1)) * random() <= 1 and avg(c1) < 500) x; +select count(*) from (select c5, count(c1) from ft1 group by c5, sqrt(c2) having (avg(c1) / avg(c1)) * random() <= 1 and avg(c1) < 500) x; + +-- Aggregate in HAVING clause is not pushable, and thus aggregation is not pushed down +explain (verbose, costs off) +select sum(c1) from ft1 group by c2 having avg(c1 * (random() <= 1)::int) > 100 order by 1; + +-- Remote aggregate in combination with a local Param (for the output +-- of an initplan) can be trouble, per bug #15781 +explain (verbose, costs off) +select exists(select 1 from pg_enum), sum(c1) from ft1; +select exists(select 1 from pg_enum), sum(c1) from ft1; + +explain (verbose, costs off) +select exists(select 1 from pg_enum), sum(c1) from ft1 group by 1; +select exists(select 1 from pg_enum), sum(c1) from ft1 group by 1; + + +-- Testing ORDER BY, DISTINCT, FILTER, Ordered-sets and VARIADIC within aggregates + +-- ORDER BY within aggregate, same column used to order +explain (verbose, costs off) +select array_agg(c1 order by c1) from ft1 where c1 < 100 group by c2 order by 1; +select array_agg(c1 order by c1) from ft1 where c1 < 100 group by c2 order by 1; + +-- ORDER BY within aggregate, different column used to order also using DESC +explain (verbose, costs off) +select array_agg(c5 order by c1 desc) from ft2 where c2 = 6 and c1 < 50; +select array_agg(c5 order by c1 desc) from ft2 where c2 = 6 and c1 < 50; + +-- DISTINCT within aggregate +explain (verbose, costs off) +select array_agg(distinct (t1.c1)%5) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1; +select array_agg(distinct (t1.c1)%5) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1; + +-- DISTINCT combined with ORDER BY within aggregate +explain (verbose, costs off) +select array_agg(distinct (t1.c1)%5 order by (t1.c1)%5) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1; +select array_agg(distinct (t1.c1)%5 order by (t1.c1)%5) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1; + +explain (verbose, costs off) +select array_agg(distinct (t1.c1)%5 order by (t1.c1)%5 desc nulls last) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1; +select array_agg(distinct (t1.c1)%5 order by (t1.c1)%5 desc nulls last) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1; + +-- Input relation to aggregate push down hook is not safe to pushdown and thus +-- the aggregate cannot be pushed down to foreign server. +explain (verbose, costs off) +select count(t1.c3) from ft2 t1 left join ft2 t2 on (t1.c1 = random() * t2.c2); + +-- Subquery in FROM clause having aggregate +explain (verbose, costs off) +select count(*), x.b from ft1, (select c2 a, sum(c1) b from ft1 group by c2) x where ft1.c2 = x.a group by x.b order by 1, 2; +select count(*), x.b from ft1, (select c2 a, sum(c1) b from ft1 group by c2) x where ft1.c2 = x.a group by x.b order by 1, 2; + +-- FULL join with IS NULL check in HAVING +explain (verbose, costs off) +select avg(t1.c1), sum(t2.c1) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) group by t2.c1 having (avg(t1.c1) is null and sum(t2.c1) < 10) or sum(t2.c1) is null order by 1 nulls last, 2; +select avg(t1.c1), sum(t2.c1) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) group by t2.c1 having (avg(t1.c1) is null and sum(t2.c1) < 10) or sum(t2.c1) is null order by 1 nulls last, 2; + +-- Aggregate over FULL join needing to deparse the joining relations as +-- subqueries. +explain (verbose, costs off) +select count(*), sum(t1.c1), avg(t2.c1) from (select c1 from ft4 where c1 between 50 and 60) t1 full join (select c1 from ft5 where c1 between 50 and 60) t2 on (t1.c1 = t2.c1); +select count(*), sum(t1.c1), avg(t2.c1) from (select c1 from ft4 where c1 between 50 and 60) t1 full join (select c1 from ft5 where c1 between 50 and 60) t2 on (t1.c1 = t2.c1); + +-- ORDER BY expression is part of the target list but not pushed down to +-- foreign server. +explain (verbose, costs off) +select sum(c2) * (random() <= 1)::int as sum from ft1 order by 1; +select sum(c2) * (random() <= 1)::int as sum from ft1 order by 1; + + +-- Check with placeHolderVars +explain (verbose, costs off) +select sum(q.a), count(q.b) from ft4 left join (select 13, avg(ft1.c1), sum(ft2.c1) from ft1 right join ft2 on (ft1.c1 = ft2.c1)) q(a, b, c) on (ft4.c1 <= q.b); +select sum(q.a), count(q.b) from ft4 left join (select 13, avg(ft1.c1), sum(ft2.c1) from ft1 right join ft2 on (ft1.c1 = ft2.c1)) q(a, b, c) on (ft4.c1 <= q.b); + + +-- Not supported cases +-- Grouping sets +explain (verbose, costs off) +select c2, sum(c1) from ft1 where c2 < 3 group by rollup(c2) order by 1 nulls last; +select c2, sum(c1) from ft1 where c2 < 3 group by rollup(c2) order by 1 nulls last; +explain (verbose, costs off) +select c2, sum(c1) from ft1 where c2 < 3 group by cube(c2) order by 1 nulls last; +select c2, sum(c1) from ft1 where c2 < 3 group by cube(c2) order by 1 nulls last; +explain (verbose, costs off) +select c2, c6, sum(c1) from ft1 where c2 < 3 group by grouping sets(c2, c6) order by 1 nulls last, 2 nulls last; +select c2, c6, sum(c1) from ft1 where c2 < 3 group by grouping sets(c2, c6) order by 1 nulls last, 2 nulls last; +explain (verbose, costs off) +select c2, sum(c1), grouping(c2) from ft1 where c2 < 3 group by c2 order by 1 nulls last; +select c2, sum(c1), grouping(c2) from ft1 where c2 < 3 group by c2 order by 1 nulls last; + +-- DISTINCT itself is not pushed down, whereas underneath aggregate is pushed +explain (verbose, costs off) +select distinct sum(c1)/1000 s from ft2 where c2 < 6 group by c2 order by 1; +select distinct sum(c1)/1000 s from ft2 where c2 < 6 group by c2 order by 1; + +-- WindowAgg +explain (verbose, costs off) +select c2, sum(c2), count(c2) over (partition by c2%2) from ft2 where c2 < 10 group by c2 order by 1; +select c2, sum(c2), count(c2) over (partition by c2%2) from ft2 where c2 < 10 group by c2 order by 1; +explain (verbose, costs off) +select c2, array_agg(c2) over (partition by c2%2 order by c2 desc) from ft1 where c2 < 10 group by c2 order by 1; +select c2, array_agg(c2) over (partition by c2%2 order by c2 desc) from ft1 where c2 < 10 group by c2 order by 1; +explain (verbose, costs off) +select c2, array_agg(c2) over (partition by c2%2 order by c2 range between current row and unbounded following) from ft1 where c2 < 10 group by c2 order by 1; +select c2, array_agg(c2) over (partition by c2%2 order by c2 range between current row and unbounded following) from ft1 where c2 < 10 group by c2 order by 1; + +-- Same group by +explain verbose select c1,c1,c1,count(*) from ft1 group by 1,1,1; +set enable_hashagg = off; +explain verbose select c1,c1,c1,count(*) from ft1 group by 1,1,1; +set enable_hashagg = on; + +-- ====================================================================================================================================== +-- TEST-MODULE: parameterized queries +-- -------------------------------------- +-- ====================================================================================================================================== +-- simple join +PREPARE st1(int, int) AS SELECT t1.c3, t2.c3 FROM ft1 t1, ft2 t2 WHERE t1.c1 = $1 AND t2.c1 = $2; +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st1(1, 2); +EXECUTE st1(1, 1); +EXECUTE st1(101, 101); +-- subquery using stable function (can't be sent to remote) +PREPARE st2(int) AS SELECT * FROM ft1 t1 WHERE t1.c1 < $2 AND t1.c3 IN (SELECT c3 FROM ft2 t2 WHERE c1 > $1 AND "date"(c4) = '1970-01-17'::date) ORDER BY c1; +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st2(10, 20); +EXECUTE st2(10, 20); +EXECUTE st2(101, 121); +-- subquery using immutable function (can be sent to remote) +PREPARE st3(int) AS SELECT * FROM ft1 t1 WHERE t1.c1 < $2 AND t1.c3 IN (SELECT c3 FROM ft2 t2 WHERE c1 > $1 AND "date"(c5) = '1970-01-17'::date) ORDER BY c1; +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st3(10, 20); +EXECUTE st3(10, 20); +EXECUTE st3(20, 30); +-- custom plan should be chosen initially +PREPARE st4(int) AS SELECT * FROM ft1 t1 WHERE t1.c1 = $1; +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st4(1); +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st4(1); +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st4(1); +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st4(1); +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st4(1); +-- once we try it enough times, should switch to generic plan +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st4(1); +-- value of $1 should not be sent to remote +PREPARE st5(user_enum,int) AS SELECT * FROM ft1 t1 WHERE c8 = $1 and c1 = $2; +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st5('foo', 1); +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st5('foo', 1); +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st5('foo', 1); +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st5('foo', 1); +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st5('foo', 1); +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st5('foo', 1); +EXECUTE st5('foo', 1); + +-- altering FDW options requires replanning +PREPARE st6 AS SELECT * FROM ft1 t1 WHERE t1.c1 = t1.c2; +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st6; +PREPARE st7 AS INSERT INTO ft1 (c1,c2,c3) VALUES (1001,101,'foo'); +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st7; +ALTER TABLE "S 1"."T 1" RENAME TO "T 0"; +ALTER FOREIGN TABLE ft1 OPTIONS (SET table_name 'T 0'); +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st6; +EXECUTE st6; +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st7; +ALTER TABLE "S 1"."T 0" RENAME TO "T 1"; +ALTER FOREIGN TABLE ft1 OPTIONS (SET table_name 'T 1'); + +PREPARE st8 AS SELECT count(c3) FROM ft1 t1 WHERE t1.c1 === t1.c2; +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st8; +ALTER SERVER loopback OPTIONS (DROP extensions); +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st8; +EXECUTE st8; +ALTER SERVER loopback OPTIONS (ADD extensions 'postgres_fdw'); + +-- cleanup +DEALLOCATE st1; +DEALLOCATE st2; +DEALLOCATE st3; +DEALLOCATE st4; +DEALLOCATE st5; +DEALLOCATE st6; +DEALLOCATE st7; +DEALLOCATE st8; + +-- ====================================================================================================================================== +-- TEST-MODULE: used in PL/pgSQL function +-- -------------------------------------- +-- ====================================================================================================================================== + +CREATE OR REPLACE FUNCTION f_test(p_c1 int) RETURNS int AS $$ +DECLARE + v_c1 int; +BEGIN + SELECT c1 INTO v_c1 FROM ft1 WHERE c1 = p_c1 LIMIT 1; + PERFORM c1 FROM ft1 WHERE c1 = p_c1 AND p_c1 = v_c1 LIMIT 1; + RETURN v_c1; +END; +$$ LANGUAGE plpgsql; +SELECT f_test(100); +DROP FUNCTION f_test(int); +-- ====================================================================================================================================== +-- TEST-MODULE: REINDEX +-- -------------------------------------- +-- ====================================================================================================================================== +-- remote table is not created here +CREATE FOREIGN TABLE reindex_foreign (c1 int, c2 int) + SERVER loopback2 OPTIONS (table_name 'reindex_local'); +REINDEX TABLE reindex_foreign; -- error +DROP FOREIGN TABLE reindex_foreign; +-- partitions and foreign tables +CREATE TABLE reind_fdw_parent (c1 int) with (orientation=column) PARTITION BY RANGE (c1) ( + partition reind_fdw_0_10 values less than(11), + partition reind_fdw_10_20 values less than(21) +); +CREATE FOREIGN TABLE reind_fdw_10_20_fp(c1 int) + SERVER loopback OPTIONS (table_name 'reind_fdw_parent'); +CREATE FOREIGN TABLE reind_fdw_10_20_f1(c1 int) + SERVER loopback OPTIONS (table_name 'reind_fdw_parent partition(reind_fdw_10_20)'); +REINDEX TABLE reind_fdw_10_20_fp; -- error +REINDEX TABLE reind_fdw_10_20_f1; -- error +REINDEX TABLE reind_fdw_parent; -- ok +DROP TABLE reind_fdw_parent; +DROP FOREIGN TABLE reind_fdw_10_20_fp; +DROP FOREIGN TABLE reind_fdw_10_20_f1; + +-- ====================================================================================================================================== +-- TEST-MODULE: conversion error +-- -------------------------------------- +-- ====================================================================================================================================== +ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 TYPE int; +SELECT * FROM ft1 ftx(x1,x2,x3,x4,x5,x6,x7,x8) WHERE x1 = 1; -- ERROR +EXPLAIN (VERBOSE, COSTS OFF) SELECT ftx.x1, ft2.c2, ftx.x8 FROM ft1 ftx(x1,x2,x3,x4,x5,x6,x7,x8), ft2 + WHERE ftx.x1 = ft2.c1 AND ftx.x1 = 1; +SELECT ftx.x1, ft2.c2, ftx.x8 FROM ft1 ftx(x1,x2,x3,x4,x5,x6,x7,x8), ft2 + WHERE ftx.x1 = ft2.c1 AND ftx.x1 = 1; -- ERROR +EXPLAIN (VERBOSE, COSTS OFF) SELECT ftx.x1, ft2.c2, ftx FROM ft1 ftx(x1,x2,x3,x4,x5,x6,x7,x8), ft2 + WHERE ftx.x1 = ft2.c1 AND ftx.x1 = 1; +SELECT ftx.x1, ft2.c2, ftx FROM ft1 ftx(x1,x2,x3,x4,x5,x6,x7,x8), ft2 + WHERE ftx.x1 = ft2.c1 AND ftx.x1 = 1; -- ERROR +SELECT sum(c2), array_agg(c8) FROM ft1 GROUP BY c8; -- ERROR +ANALYZE ft1; -- ERROR +ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 TYPE user_enum; +-- ====================================================================================================================================== +-- TEST-MODULE: subtransaction +-- + local/remote error doesn't break cursor +-- -------------------------------------- +-- ====================================================================================================================================== +BEGIN; +DECLARE c CURSOR FOR SELECT * FROM ft1 ORDER BY c1; +FETCH c; +SAVEPOINT s; +ERROR OUT; -- ERROR +ROLLBACK TO s; +FETCH c; +SAVEPOINT s; +SELECT * FROM ft1 WHERE 1 / (c1 - 1) > 0; -- ERROR +ROLLBACK TO s; +FETCH c; +SELECT * FROM ft1 ORDER BY c1 LIMIT 1; +COMMIT; +-- ====================================================================================================================================== +-- TEST-MODULE: test handling of collations +-- -------------------------------------- +-- ====================================================================================================================================== +create table loct3 (f1 text collate "C" unique, f2 text, f3 varchar(10) unique) with (orientation=column); +create foreign table ft3 (f1 text collate "C", f2 text, f3 varchar(10)) + server loopback options (table_name 'loct3', use_remote_estimate 'true'); + +-- can be sent to remote +explain (verbose, costs off) select * from ft3 where f1 = 'foo'; +explain (verbose, costs off) select * from ft3 where f1 COLLATE "C" = 'foo'; +explain (verbose, costs off) select * from ft3 where f2 = 'foo'; +explain (verbose, costs off) select * from ft3 where f3 = 'foo'; +explain (verbose, costs off) select * from ft3 f, loct3 l + where f.f3 = l.f3 and l.f1 = 'foo'; +-- can't be sent to remote +explain (verbose, costs off) select * from ft3 where f1 COLLATE "POSIX" = 'foo'; +explain (verbose, costs off) select * from ft3 where f1 = 'foo' COLLATE "C"; +explain (verbose, costs off) select * from ft3 where f2 COLLATE "C" = 'foo'; +explain (verbose, costs off) select * from ft3 where f2 = 'foo' COLLATE "C"; +explain (verbose, costs off) select * from ft3 f, loct3 l + where f.f3 = l.f3 COLLATE "POSIX" and l.f1 = 'foo'; +-- ====================================================================================================================================== +-- TEST-MODULE: test writable foreign table stuff +-- -------------------------------------- +-- currently openGauss not support to update and delete from a foreign table built on a column table. +-- so it is unusefull. +-- ====================================================================================================================================== +EXPLAIN (verbose, costs off) +INSERT INTO ft2 (c1,c2,c3) SELECT c1+1000,c2+100, c3 || c3 FROM ft2 LIMIT 20; +INSERT INTO ft2 (c1,c2,c3) SELECT c1+1000,c2+100, c3 || c3 FROM ft2 LIMIT 20; +INSERT INTO ft2 (c1,c2,c3) + VALUES (1101,201,'aaa'), (1102,202,'bbb'), (1103,203,'ccc') RETURNING *; +INSERT INTO ft2 (c1,c2,c3) VALUES (1104,204,'ddd'), (1105,205,'eee'); +EXPLAIN (verbose, costs off) +UPDATE ft2 SET c2 = c2 + 300, c3 = c3 || '_update3' WHERE c1 % 10 = 3; +UPDATE ft2 SET c2 = c2 + 300, c3 = c3 || '_update3' WHERE c1 % 10 = 3; +EXPLAIN (verbose, costs off) +UPDATE ft2 SET c2 = c2 + 400, c3 = c3 || '_update7' WHERE c1 % 10 = 7 RETURNING *; +UPDATE ft2 SET c2 = c2 + 400, c3 = c3 || '_update7' WHERE c1 % 10 = 7 RETURNING *; +EXPLAIN (verbose, costs off) +UPDATE ft2 SET c2 = ft2.c2 + 500, c3 = ft2.c3 || '_update9', c7 = DEFAULT + FROM ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 9; +UPDATE ft2 SET c2 = ft2.c2 + 500, c3 = ft2.c3 || '_update9', c7 = DEFAULT + FROM ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 9; +EXPLAIN (verbose, costs off) + DELETE FROM ft2 WHERE c1 % 10 = 5 RETURNING c1, c4; +DELETE FROM ft2 WHERE c1 % 10 = 5 RETURNING c1, c4; +EXPLAIN (verbose, costs off) +DELETE FROM ft2 USING ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 2; +DELETE FROM ft2 USING ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 2; +SELECT c1,c2,c3,c4 FROM ft2 ORDER BY c1; + +-- ====================================================================================================================================== +-- TEST-MODULE: check constraints +-- -------------------------------------- +-- openGauss not support to "ALTER FOREIGN TABLE ft1 ADD CONSTRAINT", so this test module +-- is unuseful. +-- ====================================================================================================================================== +-- Consistent check constraints provide consistent results +ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c2positive CHECK (c2 >= 0); +EXPLAIN (VERBOSE, COSTS OFF) SELECT count(*) FROM ft1 WHERE c2 < 0; +SELECT count(*) FROM ft1 WHERE c2 < 0; +SET constraint_exclusion = 'on'; +EXPLAIN (VERBOSE, COSTS OFF) SELECT count(*) FROM ft1 WHERE c2 < 0; +SELECT count(*) FROM ft1 WHERE c2 < 0; +RESET constraint_exclusion; +-- check constraint is enforced on the remote side, not locally +INSERT INTO ft1(c1, c2) VALUES(1111, -2); -- c2positive +UPDATE ft1 SET c2 = -c2 WHERE c1 = 1; -- c2positive +ALTER FOREIGN TABLE ft1 DROP CONSTRAINT ft1_c2positive; + +-- But inconsistent check constraints provide inconsistent results +ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c2negative CHECK (c2 < 0); +EXPLAIN (VERBOSE, COSTS OFF) SELECT count(*) FROM ft1 WHERE c2 >= 0; +SELECT count(*) FROM ft1 WHERE c2 >= 0; +SET constraint_exclusion = 'on'; +EXPLAIN (VERBOSE, COSTS OFF) SELECT count(*) FROM ft1 WHERE c2 >= 0; +SELECT count(*) FROM ft1 WHERE c2 >= 0; +RESET constraint_exclusion; +-- local check constraint is not actually enforced +INSERT INTO ft1(c1, c2) VALUES(1111, 2); +UPDATE ft1 SET c2 = c2 + 1 WHERE c1 = 1; +ALTER FOREIGN TABLE ft1 DROP CONSTRAINT ft1_c2negative; + +-- ====================================================================================================================================== +-- TEST-MODULE: WITH CHECK OPTION constraints +-- -------------------------------------- +-- openGauss not support WITH CHECK OPTION, so this test module +-- is unuseful. +-- ====================================================================================================================================== + +CREATE FUNCTION row_before_insupd_trigfunc() RETURNS trigger AS $$BEGIN NEW.a := NEW.a + 10; RETURN NEW; END$$ LANGUAGE plpgsql; + +CREATE TABLE base_tbl (a int, b int) with (orientation=column); +ALTER TABLE base_tbl SET (autovacuum_enabled = 'false'); +CREATE TRIGGER row_before_insupd_trigger BEFORE INSERT OR UPDATE ON base_tbl FOR EACH ROW EXECUTE PROCEDURE row_before_insupd_trigfunc(); +CREATE FOREIGN TABLE foreign_tbl (a int, b int) + SERVER loopback OPTIONS (table_name 'base_tbl'); +-- CREATE VIEW rw_view AS SELECT * FROM foreign_tbl +-- WHERE a < b WITH CHECK OPTION; +-- \d+ rw_view + +DROP FOREIGN TABLE foreign_tbl CASCADE; +DROP TRIGGER row_before_insupd_trigger ON base_tbl; +DROP TABLE base_tbl; + +-- ====================================================================================================================================== +-- TEST-MODULE: test serial columns (ie, sequence-based defaults) +-- -------------------------------------- +-- ====================================================================================================================================== +create table loc1 (f1 serial, f2 text) with (orientation=column); +alter table loc1 set (autovacuum_enabled = 'false'); +create foreign table rem1 (f1 serial, f2 text) + server loopback options(table_name 'loc1'); +select pg_catalog.setval('rem1_f1_seq', 10, false); +insert into loc1(f2) values('hi'); +insert into rem1(f2) values('hi remote'); +insert into loc1(f2) values('bye'); +insert into rem1(f2) values('bye remote'); +select * from loc1; +select * from rem1; + +-- ====================================================================================================================================== +-- TEST-MODULE: test generated columns +-- -------------------------------------- +-- openGauss not support generated columns in column table. +-- ====================================================================================================================================== +create table gloc1 ( + a int, + b int generated always as (a * 2) stored) +with (orientation=column); + + +-- ====================================================================================================================================== +-- TEST-MODULE: test local triggers +-- -------------------------------------- +-- openGauss not support create trigger on foreign table and column table. +-- ====================================================================================================================================== +create table tglog(id serial, context text) with (orientation=column); +create table previd(a int) with (orientation=column); +insert into previd values(0); + +create or replace function showtrigger(id out int, context out text) returns setof record LANGUAGE plpgsql as +$$ +DECLARE + r RECORD; + prev int; +BEGIN + select max(a) from previd into prev; + update previd set a = (select max(id) from tglog); + + FOR r IN SELECT * FROM tglog where id > prev ORDER BY id + LOOP + id := r.id; + context := r.context; + return next; + END LOOP; +END;$$; + + + +-- Trigger functions "borrowed" from triggers regress test. +CREATE FUNCTION trigger_func() RETURNS trigger LANGUAGE plpgsql AS $$ +BEGIN + insert into tglog(context) values( + format('trigger_func(%s) called: action = %s, when = %s, level = %s', + TG_ARGV[0], TG_OP, TG_WHEN, TG_LEVEL)); + RETURN NULL; +END;$$; + +-- error, openGauss not support create trigger on foreign table +CREATE TRIGGER trig_stmt_before BEFORE DELETE OR INSERT OR UPDATE ON rem1 + FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func(); +CREATE TRIGGER trig_stmt_after AFTER DELETE OR INSERT OR UPDATE ON rem1 + FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func(); + +-- success +CREATE TRIGGER trig_stmt_before BEFORE DELETE OR INSERT OR UPDATE ON loc1 --nspt + FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func(); +CREATE TRIGGER trig_stmt_after AFTER DELETE OR INSERT OR UPDATE ON loc1 --nspt + FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func(); + +-- ====================================================================================================================================== +-- TEST-MODULE: test inheritance features +-- TEST-MODULE: test tuple routing for foreign-table partitions +-- -------------------------------------- +-- unsupport feature of openGauss +-- ====================================================================================================================================== + +-- ====================================================================================================================================== +-- TEST-MODULE: test COPY FROM +-- -------------------------------------- +-- ====================================================================================================================================== +create table loc2 (f1 int primary key, f2 text) with (orientation=column); +alter table loc2 set (autovacuum_enabled = 'false'); +create foreign table rem2 (f1 int, f2 text) server loopback options(table_name 'loc2'); + +-- Test basic functionality +copy rem2 from stdin; +1 foo +2 bar +\. +select * from rem2; + +delete from rem2; + +-- check constraint is enforced on the remote side, not locally +copy rem2 from stdin; +3 foo +2 bar +\. +select * from rem2; + +drop foreign table rem2; +drop table loc2; + +-- ====================================================================================================================================== +-- TEST-MODULE: test for TRUNCATE +-- TEST-MODULE: test IMPORT FOREIGN SCHEMA +-- TEST-MODULE: test partitionwise joins +-- TEST-MODULE: test partitionwise aggregates +-- TEST-MODULE: access rights and superuser +-- TEST-MODULE: reestablish new connection +-- TEST-MODULE: batch insert +-- TEST-MODULE: test asynchronous execution +-- TEST-MODULE: test invalid server and foreign table options +-- -------------------------------------- +-- openGauss not support this feature, therefore, some cases that can run properly after modification are retained, +-- and some cases that cannot be supported are directly deleted. +-- If you want to restore the test case later, refer to README in the file header. +-- ====================================================================================================================================== + + +-- ====================================================================================================================================== +-- TEST-MODULE: clean up all the test data +-- -------------------------------------- +-- heihei! +-- ====================================================================================================================================== +\c regression +drop database postgresfdw_test_db_cstore; \ No newline at end of file diff --git a/contrib/postgres_fdw/sql/postgres_fdw_partition.sql b/contrib/postgres_fdw/sql/postgres_fdw_partition.sql new file mode 100644 index 000000000..19e4f5e69 --- /dev/null +++ b/contrib/postgres_fdw/sql/postgres_fdw_partition.sql @@ -0,0 +1,1315 @@ +-- ====================================================================================================================================== +-- README: +-- For details, see "postgres_fdw.sql". +-- ====================================================================================================================================== +create database postgresfdw_test_db_partition; +\c postgresfdw_test_db_partition +set show_fdw_remote_plan = on; + +-- ====================================================================================================================================== +-- TEST-MODULE: create FDW objects +-- -------------------------------------- +-- ====================================================================================================================================== +CREATE EXTENSION postgres_fdw; + +CREATE SERVER testserver1 FOREIGN DATA WRAPPER postgres_fdw; +DO $d$ + BEGIN + EXECUTE $$CREATE SERVER loopback FOREIGN DATA WRAPPER postgres_fdw + OPTIONS (dbname '$$||current_database()||$$', + port '$$||current_setting('port')||$$' + )$$; + EXECUTE $$CREATE SERVER loopback2 FOREIGN DATA WRAPPER postgres_fdw + OPTIONS (dbname '$$||current_database()||$$', + port '$$||current_setting('port')||$$' + )$$; + EXECUTE $$CREATE SERVER loopback3 FOREIGN DATA WRAPPER postgres_fdw + OPTIONS (dbname '$$||current_database()||$$', + port '$$||current_setting('port')||$$' + )$$; + END; +$d$; + +CREATE USER MAPPING FOR public SERVER testserver1 + OPTIONS (user 'value', password 'value'); +CREATE USER MAPPING FOR CURRENT_USER SERVER loopback; +CREATE USER MAPPING FOR CURRENT_USER SERVER loopback2; +CREATE USER MAPPING FOR public SERVER loopback3; +-- ====================================================================================================================================== +-- TEST-MODULE: create objects used through FDW loopback server +-- -------------------------------------- +-- ====================================================================================================================================== +CREATE TYPE user_enum AS ENUM ('foo', 'bar', 'buz'); +CREATE SCHEMA "S 1"; +CREATE TABLE "S 1"."T 1" ( + "C 1" int NOT NULL, + c2 int NOT NULL, + c3 text, + c4 timestamptz, + c5 timestamp, + c6 varchar(10), + c7 char(10), + c8 user_enum, + CONSTRAINT t1_pkey PRIMARY KEY ("C 1") +) PARTITION BY RANGE ("C 1") ( + partition ptb1 values less than(500), + partition ptb2 values less than(maxvalue) +); +CREATE TABLE "S 1"."T 2" ( + c1 int NOT NULL, + c2 text, + CONSTRAINT t2_pkey PRIMARY KEY (c1) +) with (orientation=column) +PARTITION BY RANGE (c1) ( + partition ptb1 values less than(50), + partition ptb2 values less than(maxvalue) +); +CREATE TABLE "S 1"."T 3" ( + c1 int NOT NULL, + c2 int NOT NULL, + c3 text, + CONSTRAINT t3_pkey PRIMARY KEY (c1) +) PARTITION BY hash (c1) ( + partition ptb1, + partition ptb2 +); +CREATE TABLE "S 1"."T 4" ( + c1 int NOT NULL, + c2 int NOT NULL, + c3 text, + CONSTRAINT t4_pkey PRIMARY KEY (c1) +) PARTITION BY RANGE (c1) SUBPARTITION BY HASH (c2) ( + partition ptb1 values less than(50) + ( + subpartition ptb11, + subpartition ptb12 + ), + partition ptb2 values less than(maxvalue) + ( + subpartition ptb21, + subpartition ptb22 + ) +); + +-- Disable autovacuum for these tables to avoid unexpected effects of that +ALTER TABLE "S 1"."T 1" SET (autovacuum_enabled = 'false'); +ALTER TABLE "S 1"."T 2" SET (autovacuum_enabled = 'false'); +ALTER TABLE "S 1"."T 3" SET (autovacuum_enabled = 'false'); +ALTER TABLE "S 1"."T 4" SET (autovacuum_enabled = 'false'); + +INSERT INTO "S 1"."T 1" + SELECT id, + id % 10, + to_char(id, 'FM00000'), + '1970-01-01'::timestamptz + ((id % 100) || ' days')::interval, + '1970-01-01'::timestamp + ((id % 100) || ' days')::interval, + id % 10, + id % 10, + 'foo'::user_enum + FROM generate_series(1, 1000) id; +INSERT INTO "S 1"."T 2" + SELECT id, + 'AAA' || to_char(id, 'FM000') + FROM generate_series(1, 100) id; +INSERT INTO "S 1"."T 3" + SELECT id, + id + 1, + 'AAA' || to_char(id, 'FM000') + FROM generate_series(1, 100) id; +DELETE FROM "S 1"."T 3" WHERE c1 % 2 != 0; -- delete for outer join tests +INSERT INTO "S 1"."T 4" + SELECT id, + id + 1, + 'AAA' || to_char(id, 'FM000') + FROM generate_series(1, 100) id; +DELETE FROM "S 1"."T 4" WHERE c1 % 3 != 0; -- delete for outer join tests + +ANALYZE "S 1"."T 1"; +ANALYZE "S 1"."T 2"; +ANALYZE "S 1"."T 3"; +ANALYZE "S 1"."T 4"; + +-- ====================================================================================================================================== +-- TEST-MODULE: create foreign tables +-- -------------------------------------- +-- +-- ====================================================================================================================================== +CREATE FOREIGN TABLE ft1 ( + c0 int, + c1 int NOT NULL, + c2 int NOT NULL, + c3 text, + c4 timestamptz, + c5 timestamp, + c6 varchar(10), + c7 char(10) default 'ft1', + c8 user_enum +) SERVER loopback; +ALTER FOREIGN TABLE ft1 DROP COLUMN c0; + +CREATE FOREIGN TABLE ft2 ( + c1 int NOT NULL, + c2 int NOT NULL, + cx int, + c3 text, + c4 timestamptz, + c5 timestamp, + c6 varchar(10), + c7 char(10) default 'ft2', + c8 user_enum +) SERVER loopback; +ALTER FOREIGN TABLE ft2 DROP COLUMN cx; + +CREATE FOREIGN TABLE ft4 ( + c1 int NOT NULL, + c2 int NOT NULL, + c3 text +) SERVER loopback OPTIONS (schema_name 'S 1', table_name 'T 3'); + +CREATE FOREIGN TABLE ft5 ( + c1 int NOT NULL, + c2 int NOT NULL, + c3 text +) SERVER loopback OPTIONS (schema_name 'S 1', table_name 'T 4'); + +CREATE FOREIGN TABLE ft6 ( + c1 int NOT NULL, + c2 int NOT NULL, + c3 text +) SERVER loopback2 OPTIONS (schema_name 'S 1', table_name 'T 4'); + +CREATE FOREIGN TABLE ft7 ( + c1 int NOT NULL, + c2 int NOT NULL, + c3 text +) SERVER loopback3 OPTIONS (schema_name 'S 1', table_name 'T 4'); + +-- ====================================================================================================================================== +-- TEST-MODULE: tests for validator and prepare params +-- -------------------------------------- +-- ====================================================================================================================================== +-- requiressl and some other parameters are omitted because +-- valid values for them depend on configure options +ALTER SERVER testserver1 OPTIONS ( + use_remote_estimate 'false', + updatable 'true', + fdw_startup_cost '123.456', + fdw_tuple_cost '0.123', + service 'value', + connect_timeout 'value', + dbname 'value', + host 'value', + hostaddr 'value', + port 'value', + application_name 'value', + keepalives 'value', + keepalives_idle 'value', + keepalives_interval 'value', + sslcompression 'value', + sslmode 'value', + sslcert 'value', + sslkey 'value', + sslrootcert 'value', + sslcrl 'value', + krbsrvname 'value' +); + +ALTER FOREIGN TABLE ft1 OPTIONS (schema_name 'S 1', table_name 'T 1'); +ALTER FOREIGN TABLE ft2 OPTIONS (schema_name 'S 1', table_name 'T 1'); +ALTER FOREIGN TABLE ft1 ALTER COLUMN c1 OPTIONS (column_name 'C 1'); +ALTER FOREIGN TABLE ft2 ALTER COLUMN c1 OPTIONS (column_name 'C 1'); +\det+ + + +-- Now we should be able to run ANALYZE. +-- To exercise multiple code paths, we use local stats on ft1 +-- and remote-estimate mode on ft2. +ANALYZE ft1; +ALTER FOREIGN TABLE ft2 OPTIONS (use_remote_estimate 'true'); + + +-- now open the guc. +SHOW sql_beta_feature; +SET sql_beta_feature TO 'partition_fdw_on'; +EXPLAIN (COSTS OFF) SELECT * FROM ft6; --err +SELECT * FROM ft6; + +SHOW sql_beta_feature; +SET sql_beta_feature TO 'partition_fdw_on'; +EXPLAIN (COSTS OFF) SELECT * FROM ft6; --suc +SELECT * FROM ft6; + +-- ====================================================================================================================================== +-- TEST-MODULE: simple queries +-- -------------------------------------- +-- +-- ====================================================================================================================================== +-- single table without alias +EXPLAIN (COSTS OFF) SELECT * FROM ft1 ORDER BY c3, c1 OFFSET 100 LIMIT 10; +SELECT * FROM ft1 ORDER BY c3, c1 OFFSET 100 LIMIT 10; +--nspt single table with alias - also test that tableoid sort is not pushed to remote side +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 ORDER BY t1.c3, t1.c1, t1.tableoid OFFSET 100 LIMIT 10; +SELECT * FROM ft1 t1 ORDER BY t1.c3, t1.c1, t1.tableoid OFFSET 100 LIMIT 10; +-- whole-row reference +EXPLAIN (VERBOSE, COSTS OFF) SELECT t1 FROM ft1 t1 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; +SELECT t1 FROM ft1 t1 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; +-- empty result +SELECT * FROM ft1 WHERE false; +-- with WHERE clause +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE t1.c1 = 101 AND t1.c6 = '1' AND t1.c7 >= '1'; +SELECT * FROM ft1 t1 WHERE t1.c1 = 101 AND t1.c6 = '1' AND t1.c7 >= '1'; +-- with FOR UPDATE/SHARE +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 = 101 FOR UPDATE; +SELECT * FROM ft1 t1 WHERE c1 = 101 FOR UPDATE; +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 = 102 FOR SHARE; +SELECT * FROM ft1 t1 WHERE c1 = 102 FOR SHARE; +-- aggregate +SELECT COUNT(*) FROM ft1 t1; +-- subquery +SELECT * FROM ft1 t1 WHERE t1.c3 IN (SELECT c3 FROM ft2 t2 WHERE c1 <= 10) ORDER BY c1; +-- subquery+MAX +SELECT * FROM ft1 t1 WHERE t1.c3 = (SELECT MAX(c3) FROM ft2 t2) ORDER BY c1; +-- used in CTE +WITH t1 AS (SELECT * FROM ft1 WHERE c1 <= 10) SELECT t2.c1, t2.c2, t2.c3, t2.c4 FROM t1, ft2 t2 WHERE t1.c1 = t2.c1 ORDER BY t1.c1; +-- fixed values +SELECT 'fixed', NULL FROM ft1 t1 WHERE c1 = 1; +-- Test forcing the remote server to produce sorted data for a merge join. +SET enable_hashjoin TO false; +SET enable_nestloop TO false; +-- inner join; expressions in the clauses appear in the equivalence class list +EXPLAIN (VERBOSE, COSTS OFF) + SELECT t1.c1, t2."C 1" FROM ft2 t1 JOIN "S 1"."T 1" t2 ON (t1.c1 = t2."C 1") OFFSET 100 LIMIT 10; +SELECT t1.c1, t2."C 1" FROM ft2 t1 JOIN "S 1"."T 1" t2 ON (t1.c1 = t2."C 1") OFFSET 100 LIMIT 10; +-- outer join; expressions in the clauses do not appear in equivalence class +-- list but no output change as compared to the previous query +EXPLAIN (VERBOSE, COSTS OFF) + SELECT t1.c1, t2."C 1" FROM ft2 t1 LEFT JOIN "S 1"."T 1" t2 ON (t1.c1 = t2."C 1") OFFSET 100 LIMIT 10; +SELECT t1.c1, t2."C 1" FROM ft2 t1 LEFT JOIN "S 1"."T 1" t2 ON (t1.c1 = t2."C 1") OFFSET 100 LIMIT 10; +-- A join between local table and foreign join. ORDER BY clause is added to the +-- foreign join so that the local table can be joined using merge join strategy. +EXPLAIN (VERBOSE, COSTS OFF) + SELECT t1."C 1" FROM "S 1"."T 1" t1 left join ft1 t2 join ft2 t3 on (t2.c1 = t3.c1) on (t3.c1 = t1."C 1") OFFSET 100 LIMIT 10; +SELECT t1."C 1" FROM "S 1"."T 1" t1 left join ft1 t2 join ft2 t3 on (t2.c1 = t3.c1) on (t3.c1 = t1."C 1") OFFSET 100 LIMIT 10; +-- Test similar to above, except that the full join prevents any equivalence +-- classes from being merged. This produces single relation equivalence classes +-- included in join restrictions. +EXPLAIN (VERBOSE, COSTS OFF) + SELECT t1."C 1", t2.c1, t3.c1 FROM "S 1"."T 1" t1 left join ft1 t2 full join ft2 t3 on (t2.c1 = t3.c1) on (t3.c1 = t1."C 1") OFFSET 100 LIMIT 10; +SELECT t1."C 1", t2.c1, t3.c1 FROM "S 1"."T 1" t1 left join ft1 t2 full join ft2 t3 on (t2.c1 = t3.c1) on (t3.c1 = t1."C 1") OFFSET 100 LIMIT 10; +-- Test similar to above with all full outer joins +EXPLAIN (VERBOSE, COSTS OFF) + SELECT t1."C 1", t2.c1, t3.c1 FROM "S 1"."T 1" t1 full join ft1 t2 full join ft2 t3 on (t2.c1 = t3.c1) on (t3.c1 = t1."C 1") OFFSET 100 LIMIT 10; +SELECT t1."C 1", t2.c1, t3.c1 FROM "S 1"."T 1" t1 full join ft1 t2 full join ft2 t3 on (t2.c1 = t3.c1) on (t3.c1 = t1."C 1") OFFSET 100 LIMIT 10; +RESET enable_hashjoin; +RESET enable_nestloop; + +-- Test executing assertion in estimate_path_cost_size() that makes sure that +-- retrieved_rows for foreign rel re-used to cost pre-sorted foreign paths is +-- a sensible value even when the rel has tuples=0 +CREATE TABLE loct_empty (c1 int NOT NULL, c2 text); +CREATE FOREIGN TABLE ft_empty (c1 int NOT NULL, c2 text) + SERVER loopback OPTIONS (table_name 'loct_empty'); +INSERT INTO loct_empty + SELECT id, 'AAA' || to_char(id, 'FM000') FROM generate_series(1, 100) id; +DELETE FROM loct_empty; +ANALYZE ft_empty; +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft_empty ORDER BY c1; +-- ====================================================================================================================================== +-- TEST-MODULE: WHERE with remotely-executable conditions +-- -------------------------------------- +-- ====================================================================================================================================== +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE t1.c1 = 1; -- Var, OpExpr(b), Const +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE t1.c1 = 100 AND t1.c2 = 0; -- BoolExpr +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 IS NULL; -- NullTest +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 IS NOT NULL; -- NullTest +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE round(abs(c1), 0) = 1; -- FuncExpr +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 = -c1; -- OpExpr(l) +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE (c1 IS NOT NULL) IS DISTINCT FROM (c1 IS NOT NULL); -- DistinctExpr +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 = ANY(ARRAY[c2, 1, c1 + 0]); -- ScalarArrayOpExpr +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 = (ARRAY[c1,c2,3])[1]; -- SubscriptingRef +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c6 = E'foo''s\\bar'; -- check special chars +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c8 = 'foo'; -- can't be sent to remote +-- parameterized remote path for foreign table +EXPLAIN (VERBOSE, COSTS OFF) + SELECT * FROM "S 1"."T 1" a, ft2 b WHERE a."C 1" = 47 AND b.c1 = a.c2; +SELECT * FROM ft2 a, ft2 b WHERE a.c1 = 47 AND b.c1 = a.c2; + +-- check both safe and unsafe join conditions +EXPLAIN (VERBOSE, COSTS OFF) + SELECT * FROM ft2 a, ft2 b + WHERE a.c2 = 6 AND b.c1 = a.c1 AND a.c8 = 'foo' AND b.c7 = upper(a.c7); +SELECT * FROM ft2 a, ft2 b +WHERE a.c2 = 6 AND b.c1 = a.c1 AND a.c8 = 'foo' AND b.c7 = upper(a.c7); +-- bug before 9.3.5 due to sloppy handling of remote-estimate parameters +SELECT * FROM ft1 WHERE c1 = ANY (ARRAY(SELECT c1 FROM ft2 WHERE c1 < 5)); +SELECT * FROM ft2 WHERE c1 = ANY (ARRAY(SELECT c1 FROM ft1 WHERE c1 < 5)); +-- we should not push order by clause with volatile expressions or unsafe +-- collations +EXPLAIN (VERBOSE, COSTS OFF) + SELECT * FROM ft2 ORDER BY ft2.c1, random(); +EXPLAIN (VERBOSE, COSTS OFF) + SELECT * FROM ft2 ORDER BY ft2.c1, ft2.c3 collate "C"; + +-- user-defined operator/function +CREATE FUNCTION postgres_fdw_abs(int) RETURNS int AS $$ +BEGIN +RETURN abs($1); +END +$$ LANGUAGE plpgsql IMMUTABLE; +CREATE OPERATOR === ( + LEFTARG = int, + RIGHTARG = int, + PROCEDURE = int4eq, + COMMUTATOR = === +); + +-- built-in operators and functions can be shipped for remote execution +EXPLAIN (VERBOSE, COSTS OFF) + SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = abs(t1.c2); +SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = abs(t1.c2); +EXPLAIN (VERBOSE, COSTS OFF) + SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = t1.c2; +SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = t1.c2; + +-- by default, user-defined ones cannot +EXPLAIN (VERBOSE, COSTS OFF) + SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = postgres_fdw_abs(t1.c2); +SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = postgres_fdw_abs(t1.c2); +EXPLAIN (VERBOSE, COSTS OFF) + SELECT count(c3) FROM ft1 t1 WHERE t1.c1 === t1.c2; +SELECT count(c3) FROM ft1 t1 WHERE t1.c1 === t1.c2; + +-- ORDER BY can be shipped, though +EXPLAIN (VERBOSE, COSTS OFF) + SELECT * FROM ft1 t1 WHERE t1.c1 === t1.c2 order by t1.c2 limit 1; +SELECT * FROM ft1 t1 WHERE t1.c1 === t1.c2 order by t1.c2 limit 1; + +-- but let's put them in an extension ... +ALTER EXTENSION postgres_fdw ADD FUNCTION postgres_fdw_abs(int); +ALTER EXTENSION postgres_fdw ADD OPERATOR === (int, int); +ALTER SERVER loopback OPTIONS (ADD extensions 'postgres_fdw'); --nspt + +-- ... now they can be shipped +EXPLAIN (VERBOSE, COSTS OFF) + SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = postgres_fdw_abs(t1.c2); +SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = postgres_fdw_abs(t1.c2); +EXPLAIN (VERBOSE, COSTS OFF) + SELECT count(c3) FROM ft1 t1 WHERE t1.c1 === t1.c2; +SELECT count(c3) FROM ft1 t1 WHERE t1.c1 === t1.c2; + +-- and both ORDER BY and LIMIT can be shipped +EXPLAIN (VERBOSE, COSTS OFF) + SELECT * FROM ft1 t1 WHERE t1.c1 === t1.c2 order by t1.c2 limit 1; +SELECT * FROM ft1 t1 WHERE t1.c1 === t1.c2 order by t1.c2 limit 1; + +-- case when expr +explain (verbose, costs off) select * from ft1 where case c1 when 1 then 0 end = 0; +select * from ft1 where case c1 when 1 then 0 end = 0; + +-- ====================================================================================================================================== +-- TEST-MODULE: JOIN queries +-- -------------------------------------- +-- ====================================================================================================================================== +-- Analyze ft4 and ft5 so that we have better statistics. These tables do not +-- have use_remote_estimate set. +ANALYZE ft4; +ANALYZE ft5; + +-- join two tables +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; +-- join three tables +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) JOIN ft4 t3 ON (t3.c1 = t1.c1) ORDER BY t1.c3, t1.c1 OFFSET 10 LIMIT 10; +SELECT t1.c1, t2.c2, t3.c3 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) JOIN ft4 t3 ON (t3.c1 = t1.c1) ORDER BY t1.c3, t1.c1 OFFSET 10 LIMIT 10; +-- left outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; +SELECT t1.c1, t2.c1 FROM ft4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; +-- left outer join three tables +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) LEFT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) LEFT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +-- left outer join + placement of clauses. +-- clauses within the nullable side are not pulled up, but top level clause on +-- non-nullable side is pushed into non-nullable side +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t1.c2, t2.c1, t2.c2 FROM ft4 t1 LEFT JOIN (SELECT * FROM ft5 WHERE c1 < 10) t2 ON (t1.c1 = t2.c1) WHERE t1.c1 < 10; +SELECT t1.c1, t1.c2, t2.c1, t2.c2 FROM ft4 t1 LEFT JOIN (SELECT * FROM ft5 WHERE c1 < 10) t2 ON (t1.c1 = t2.c1) WHERE t1.c1 < 10; +-- clauses within the nullable side are not pulled up, but the top level clause +-- on nullable side is not pushed down into nullable side +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t1.c2, t2.c1, t2.c2 FROM ft4 t1 LEFT JOIN (SELECT * FROM ft5 WHERE c1 < 10) t2 ON (t1.c1 = t2.c1) + WHERE (t2.c1 < 10 OR t2.c1 IS NULL) AND t1.c1 < 10; +SELECT t1.c1, t1.c2, t2.c1, t2.c2 FROM ft4 t1 LEFT JOIN (SELECT * FROM ft5 WHERE c1 < 10) t2 ON (t1.c1 = t2.c1) + WHERE (t2.c1 < 10 OR t2.c1 IS NULL) AND t1.c1 < 10; +-- right outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft5 t1 RIGHT JOIN ft4 t2 ON (t1.c1 = t2.c1) ORDER BY t2.c1, t1.c1 OFFSET 10 LIMIT 10; +SELECT t1.c1, t2.c1 FROM ft5 t1 RIGHT JOIN ft4 t2 ON (t1.c1 = t2.c1) ORDER BY t2.c1, t1.c1 OFFSET 10 LIMIT 10; +-- right outer join three tables +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 RIGHT JOIN ft2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 RIGHT JOIN ft2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +-- full outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft4 t1 FULL JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 45 LIMIT 10; +SELECT t1.c1, t2.c1 FROM ft4 t1 FULL JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 45 LIMIT 10; +-- full outer join with restrictions on the joining relations +-- a. the joining relations are both base relations +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1; +SELECT t1.c1, t2.c1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1; +EXPLAIN (VERBOSE, COSTS OFF) +SELECT 1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t2 ON (TRUE) OFFSET 10 LIMIT 10; +SELECT 1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t2 ON (TRUE) OFFSET 10 LIMIT 10; +EXPLAIN (VERBOSE, COSTS OFF) +SELECT count(*) FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t2 ON (TRUE) OFFSET 10 LIMIT 2; +SELECT count(*) FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t2 ON (TRUE) OFFSET 10 LIMIT 2; +-- b. one of the joining relations is a base relation and the other is a join +-- relation +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, ss.a, ss.b FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT t2.c1, t3.c1 FROM ft4 t2 LEFT JOIN ft5 t3 ON (t2.c1 = t3.c1) WHERE (t2.c1 between 50 and 60)) ss(a, b) ON (t1.c1 = ss.a) ORDER BY t1.c1, ss.a, ss.b; +SELECT t1.c1, ss.a, ss.b FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT t2.c1, t3.c1 FROM ft4 t2 LEFT JOIN ft5 t3 ON (t2.c1 = t3.c1) WHERE (t2.c1 between 50 and 60)) ss(a, b) ON (t1.c1 = ss.a) ORDER BY t1.c1, ss.a, ss.b; +-- c. test deparsing the remote query as nested subqueries +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, ss.a, ss.b FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT t2.c1, t3.c1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t2 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t3 ON (t2.c1 = t3.c1) WHERE t2.c1 IS NULL OR t2.c1 IS NOT NULL) ss(a, b) ON (t1.c1 = ss.a) ORDER BY t1.c1, ss.a, ss.b; +SELECT t1.c1, ss.a, ss.b FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT t2.c1, t3.c1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t2 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t3 ON (t2.c1 = t3.c1) WHERE t2.c1 IS NULL OR t2.c1 IS NOT NULL) ss(a, b) ON (t1.c1 = ss.a) ORDER BY t1.c1, ss.a, ss.b; +-- d. test deparsing rowmarked relations as subqueries +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, ss.a, ss.b FROM (SELECT c1 FROM "S 1"."T 3" WHERE c1 = 50) t1 INNER JOIN (SELECT t2.c1, t3.c1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t2 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t3 ON (t2.c1 = t3.c1) WHERE t2.c1 IS NULL OR t2.c1 IS NOT NULL) ss(a, b) ON (TRUE) ORDER BY t1.c1, ss.a, ss.b FOR UPDATE OF t1; +SELECT t1.c1, ss.a, ss.b FROM (SELECT c1 FROM "S 1"."T 3" WHERE c1 = 50) t1 INNER JOIN (SELECT t2.c1, t3.c1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t2 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t3 ON (t2.c1 = t3.c1) WHERE t2.c1 IS NULL OR t2.c1 IS NOT NULL) ss(a, b) ON (TRUE) ORDER BY t1.c1, ss.a, ss.b FOR UPDATE OF t1; +-- full outer join + inner join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1, t3.c1 FROM ft4 t1 INNER JOIN ft5 t2 ON (t1.c1 = t2.c1 + 1 and t1.c1 between 50 and 60) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) ORDER BY t1.c1, t2.c1, t3.c1 LIMIT 10; +SELECT t1.c1, t2.c1, t3.c1 FROM ft4 t1 INNER JOIN ft5 t2 ON (t1.c1 = t2.c1 + 1 and t1.c1 between 50 and 60) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) ORDER BY t1.c1, t2.c1, t3.c1 LIMIT 10; +-- full outer join three tables +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +-- full outer join + right outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +-- right outer join + full outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 RIGHT JOIN ft2 t2 ON (t1.c1 = t2.c1) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 RIGHT JOIN ft2 t2 ON (t1.c1 = t2.c1) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +-- full outer join + left outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) LEFT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) LEFT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +-- left outer join + full outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +SET enable_memoize TO off; --nspt +-- right outer join + left outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 RIGHT JOIN ft2 t2 ON (t1.c1 = t2.c1) LEFT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 RIGHT JOIN ft2 t2 ON (t1.c1 = t2.c1) LEFT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +RESET enable_memoize; --nspt +-- left outer join + right outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +-- full outer join + WHERE clause, only matched rows +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft4 t1 FULL JOIN ft5 t2 ON (t1.c1 = t2.c1) WHERE (t1.c1 = t2.c1 OR t1.c1 IS NULL) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; +SELECT t1.c1, t2.c1 FROM ft4 t1 FULL JOIN ft5 t2 ON (t1.c1 = t2.c1) WHERE (t1.c1 = t2.c1 OR t1.c1 IS NULL) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; +-- full outer join + WHERE clause with shippable extensions set +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t1.c3 FROM ft1 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE postgres_fdw_abs(t1.c1) > 0 OFFSET 10 LIMIT 10; +ALTER SERVER loopback OPTIONS (DROP extensions); --nspt +-- full outer join + WHERE clause with shippable extensions not set +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t1.c3 FROM ft1 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE postgres_fdw_abs(t1.c1) > 0 OFFSET 10 LIMIT 10; +ALTER SERVER loopback OPTIONS (ADD extensions 'postgres_fdw'); --nspt +-- join two tables with FOR UPDATE clause +-- tests whole-row reference for row marks +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR UPDATE OF t1; +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR UPDATE OF t1; +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR UPDATE; +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR UPDATE; +-- join two tables with FOR SHARE clause +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR SHARE OF t1; +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR SHARE OF t1; +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR SHARE; +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR SHARE; +-- join in CTE +EXPLAIN (VERBOSE, COSTS OFF) +WITH t (c1_1, c1_3, c2_1) AS MATERIALIZED (SELECT t1.c1, t1.c3, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1)) SELECT c1_1, c2_1 FROM t ORDER BY c1_3, c1_1 OFFSET 100 LIMIT 10; +WITH t (c1_1, c1_3, c2_1) AS MATERIALIZED (SELECT t1.c1, t1.c3, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1)) SELECT c1_1, c2_1 FROM t ORDER BY c1_3, c1_1 OFFSET 100 LIMIT 10; +-- ctid with whole-row reference, currently, does not support system column +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.ctid, t1, t2, t1.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; +-- SEMI JOIN, not pushed down +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1 FROM ft1 t1 WHERE EXISTS (SELECT 1 FROM ft2 t2 WHERE t1.c1 = t2.c1) ORDER BY t1.c1 OFFSET 100 LIMIT 10; +SELECT t1.c1 FROM ft1 t1 WHERE EXISTS (SELECT 1 FROM ft2 t2 WHERE t1.c1 = t2.c1) ORDER BY t1.c1 OFFSET 100 LIMIT 10; +-- ANTI JOIN, not pushed down +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1 FROM ft1 t1 WHERE NOT EXISTS (SELECT 1 FROM ft2 t2 WHERE t1.c1 = t2.c2) ORDER BY t1.c1 OFFSET 100 LIMIT 10; +SELECT t1.c1 FROM ft1 t1 WHERE NOT EXISTS (SELECT 1 FROM ft2 t2 WHERE t1.c1 = t2.c2) ORDER BY t1.c1 OFFSET 100 LIMIT 10; +-- CROSS JOIN can be pushed down +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 CROSS JOIN ft2 t2 ORDER BY t1.c1, t2.c1 OFFSET 100 LIMIT 10; +SELECT t1.c1, t2.c1 FROM ft1 t1 CROSS JOIN ft2 t2 ORDER BY t1.c1, t2.c1 OFFSET 100 LIMIT 10; +-- different server, not pushed down. No result expected. +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft5 t1 JOIN ft6 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 100 LIMIT 10; +SELECT t1.c1, t2.c1 FROM ft5 t1 JOIN ft6 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 100 LIMIT 10; +-- unsafe join conditions (c8 has a UDT), not pushed down. Practically a CROSS +-- JOIN since c8 in both tables has same value. +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 LEFT JOIN ft2 t2 ON (t1.c8 = t2.c8) ORDER BY t1.c1, t2.c1 OFFSET 100 LIMIT 10; +SELECT t1.c1, t2.c1 FROM ft1 t1 LEFT JOIN ft2 t2 ON (t1.c8 = t2.c8) ORDER BY t1.c1, t2.c1 OFFSET 100 LIMIT 10; +-- unsafe conditions on one side (c8 has a UDT), not pushed down. +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE t1.c8 = 'foo' ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; +SELECT t1.c1, t2.c1 FROM ft1 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE t1.c8 = 'foo' ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; +-- join where unsafe to pushdown condition in WHERE clause has a column not +-- in the SELECT clause. In this test unsafe clause needs to have column +-- references from both joining sides so that the clause is not pushed down +-- into one of the joining sides. +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE t1.c8 = t2.c8 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE t1.c8 = t2.c8 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; +-- Aggregate after UNION, for testing setrefs +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1c1, avg(t1c1 + t2c1) FROM (SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) UNION SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1)) AS t (t1c1, t2c1) GROUP BY t1c1 ORDER BY t1c1 OFFSET 100 LIMIT 10; +SELECT t1c1, avg(t1c1 + t2c1) FROM (SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) UNION SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1)) AS t (t1c1, t2c1) GROUP BY t1c1 ORDER BY t1c1 OFFSET 100 LIMIT 10; +-- join with lateral reference +EXPLAIN (VERBOSE, COSTS OFF) -- openGauss not support LATERAL +SELECT t1."C 1" FROM "S 1"."T 1" t1, LATERAL (SELECT DISTINCT t2.c1, t3.c1 FROM ft1 t2, ft2 t3 WHERE t2.c1 = t3.c1 AND t2.c2 = t1.c2) q ORDER BY t1."C 1" OFFSET 10 LIMIT 10; +SELECT t1."C 1" FROM "S 1"."T 1" t1, LATERAL (SELECT DISTINCT t2.c1, t3.c1 FROM ft1 t2, ft2 t3 WHERE t2.c1 = t3.c1 AND t2.c2 = t1.c2) q ORDER BY t1."C 1" OFFSET 10 LIMIT 10; + +-- non-Var items in targetlist of the nullable rel of a join preventing +-- push-down in some cases +-- unable to push {ft1, ft2} +EXPLAIN (VERBOSE, COSTS OFF) +SELECT q.a, ft2.c1 FROM (SELECT 13 FROM ft1 WHERE c1 = 13) q(a) RIGHT JOIN ft2 ON (q.a = ft2.c1) WHERE ft2.c1 BETWEEN 10 AND 15; +SELECT q.a, ft2.c1 FROM (SELECT 13 FROM ft1 WHERE c1 = 13) q(a) RIGHT JOIN ft2 ON (q.a = ft2.c1) WHERE ft2.c1 BETWEEN 10 AND 15; + +-- ok to push {ft1, ft2} but not {ft1, ft2, ft4} +EXPLAIN (VERBOSE, COSTS OFF) +SELECT ft4.c1, q.* FROM ft4 LEFT JOIN (SELECT 13, ft1.c1, ft2.c1 FROM ft1 RIGHT JOIN ft2 ON (ft1.c1 = ft2.c1) WHERE ft1.c1 = 12) q(a, b, c) ON (ft4.c1 = q.b) WHERE ft4.c1 BETWEEN 10 AND 15; +SELECT ft4.c1, q.* FROM ft4 LEFT JOIN (SELECT 13, ft1.c1, ft2.c1 FROM ft1 RIGHT JOIN ft2 ON (ft1.c1 = ft2.c1) WHERE ft1.c1 = 12) q(a, b, c) ON (ft4.c1 = q.b) WHERE ft4.c1 BETWEEN 10 AND 15; + +-- join with nullable side with some columns with null values +UPDATE ft5 SET c3 = null where c1 % 9 = 0; +EXPLAIN (VERBOSE, COSTS OFF) +SELECT ft5, ft5.c1, ft5.c2, ft5.c3, ft4.c1, ft4.c2 FROM ft5 left join ft4 on ft5.c1 = ft4.c1 WHERE ft4.c1 BETWEEN 10 and 30 ORDER BY ft5.c1, ft4.c1; +SELECT ft5, ft5.c1, ft5.c2, ft5.c3, ft4.c1, ft4.c2 FROM ft5 left join ft4 on ft5.c1 = ft4.c1 WHERE ft4.c1 BETWEEN 10 and 30 ORDER BY ft5.c1, ft4.c1; + +-- multi-way join involving multiple merge joins +-- (this case used to have EPQ-related planning problems) +CREATE TABLE local_tbl (c1 int NOT NULL, c2 int NOT NULL, c3 text, CONSTRAINT local_tbl_pkey PRIMARY KEY (c1)); +INSERT INTO local_tbl SELECT id, id % 10, to_char(id, 'FM0000') FROM generate_series(1, 1000) id; +ANALYZE local_tbl; +SET enable_nestloop TO false; +SET enable_hashjoin TO false; +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM ft1, ft2, ft4, ft5, local_tbl WHERE ft1.c1 = ft2.c1 AND ft1.c2 = ft4.c1 + AND ft1.c2 = ft5.c1 AND ft1.c2 = local_tbl.c1 AND ft1.c1 < 100 AND ft2.c1 < 100 FOR UPDATE; +SELECT * FROM ft1, ft2, ft4, ft5, local_tbl WHERE ft1.c1 = ft2.c1 AND ft1.c2 = ft4.c1 + AND ft1.c2 = ft5.c1 AND ft1.c2 = local_tbl.c1 AND ft1.c1 < 100 AND ft2.c1 < 100 FOR UPDATE; +RESET enable_nestloop; +RESET enable_hashjoin; +DROP TABLE local_tbl; + +-- check join pushdown in situations where multiple userids are involved +CREATE ROLE regress_view_owner sysadmin password 'QWERT@12345'; +CREATE USER MAPPING FOR regress_view_owner SERVER loopback; +GRANT SELECT ON ft4 TO regress_view_owner; +GRANT SELECT ON ft5 TO regress_view_owner; + +CREATE VIEW v4 AS SELECT * FROM ft4; +CREATE VIEW v5 AS SELECT * FROM ft5; +ALTER VIEW v5 OWNER TO regress_view_owner; +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; -- can't be pushed down, different view owners +SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; +ALTER VIEW v4 OWNER TO regress_view_owner; +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; -- can be pushed down +SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; -- can't be pushed down, view owner not current user +SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; +ALTER VIEW v4 OWNER TO regress_view_owner; + +-- PlaceHolder +explain(verbose, costs off) select * from ft1 t1 left join (select c1, case when c2 is not null then 1 else 0 end as cc from ft2) t2 on t1.c1 = t2.c1; + +-- cleanup +DROP OWNED BY regress_view_owner; +DROP ROLE regress_view_owner; + +-- ====================================================================================================================================== +-- TEST-MODULE: Aggregate and grouping queries +-- -------------------------------------- +-- openGauss not support filter +-- ====================================================================================================================================== + +-- Simple aggregates +explain (verbose, costs off) +select count(c6), sum(c1), avg(c1), min(c2), max(c1), stddev(c2), sum(c1) * (random() <= 1)::int as sum2 from ft1 where c2 < 5 group by c2 order by 1, 2; +select count(c6), sum(c1), avg(c1), min(c2), max(c1), stddev(c2), sum(c1) * (random() <= 1)::int as sum2 from ft1 where c2 < 5 group by c2 order by 1, 2; + +explain (verbose, costs off) +select count(c6), sum(c1), avg(c1), min(c2), max(c1), stddev(c2), sum(c1) * (random() <= 1)::int as sum2 from ft1 where c2 < 5 group by c2 order by 1, 2 limit 1; +select count(c6), sum(c1), avg(c1), min(c2), max(c1), stddev(c2), sum(c1) * (random() <= 1)::int as sum2 from ft1 where c2 < 5 group by c2 order by 1, 2 limit 1; + +-- Aggregate is not pushed down as aggregation contains random() +explain (verbose, costs off) +select sum(c1 * (random() <= 1)::int) as sum, avg(c1) from ft1; + +-- Aggregate over join query +explain (verbose, costs off) +select count(*), sum(t1.c1), avg(t2.c1) from ft1 t1 inner join ft1 t2 on (t1.c2 = t2.c2) where t1.c2 = 6; +select count(*), sum(t1.c1), avg(t2.c1) from ft1 t1 inner join ft1 t2 on (t1.c2 = t2.c2) where t1.c2 = 6; + +-- Not pushed down due to local conditions present in underneath input rel +explain (verbose, costs off) +select sum(t1.c1), count(t2.c1) from ft1 t1 inner join ft2 t2 on (t1.c1 = t2.c1) where ((t1.c1 * t2.c1)/(t1.c1 * t2.c1)) * random() <= 1; + +-- GROUP BY clause having expressions +explain (verbose, costs off) +select c2/2, sum(c2) * (c2/2) from ft1 group by c2/2 order by c2/2; +select c2/2, sum(c2) * (c2/2) from ft1 group by c2/2 order by c2/2; + +-- Aggregates in subquery are pushed down. +explain (verbose, costs off) +select count(x.a), sum(x.a) from (select c2 a, sum(c1) b from ft1 group by c2, sqrt(c1) order by 1, 2) x; +select count(x.a), sum(x.a) from (select c2 a, sum(c1) b from ft1 group by c2, sqrt(c1) order by 1, 2) x; + +-- Aggregate is still pushed down by taking unshippable expression out +explain (verbose, costs off) +select c2 * (random() <= 1)::int as sum1, sum(c1) * c2 as sum2 from ft1 group by c2 order by 1, 2; +select c2 * (random() <= 1)::int as sum1, sum(c1) * c2 as sum2 from ft1 group by c2 order by 1, 2; + +-- Aggregate with unshippable GROUP BY clause are not pushed +explain (verbose, costs off) +select c2 * (random() <= 1)::int as c2 from ft2 group by c2 * (random() <= 1)::int order by 1; + +-- GROUP BY clause in various forms, cardinal, alias and constant expression +explain (verbose, costs off) +select count(c2) w, c2 x, 5 y, 7.0 z from ft1 group by 2, y, 9.0::int order by 2; +select count(c2) w, c2 x, 5 y, 7.0 z from ft1 group by 2, y, 9.0::int order by 2; + +-- GROUP BY clause referring to same column multiple times +-- Also, ORDER BY contains an aggregate function +explain (verbose, costs off) +select c2, c2 from ft1 where c2 > 6 group by 1, 2 order by sum(c1); +select c2, c2 from ft1 where c2 > 6 group by 1, 2 order by sum(c1); + +-- Testing HAVING clause shippability +explain (verbose, costs off) +select c2, sum(c1) from ft2 group by c2 having avg(c1) < 500 and sum(c1) < 49800 order by c2; +select c2, sum(c1) from ft2 group by c2 having avg(c1) < 500 and sum(c1) < 49800 order by c2; + +-- Unshippable HAVING clause will be evaluated locally, and other qual in HAVING clause is pushed down +explain (verbose, costs off) +select count(*) from (select c5, count(c1) from ft1 group by c5, sqrt(c2) having (avg(c1) / avg(c1)) * random() <= 1 and avg(c1) < 500) x; +select count(*) from (select c5, count(c1) from ft1 group by c5, sqrt(c2) having (avg(c1) / avg(c1)) * random() <= 1 and avg(c1) < 500) x; + +-- Aggregate in HAVING clause is not pushable, and thus aggregation is not pushed down +explain (verbose, costs off) +select sum(c1) from ft1 group by c2 having avg(c1 * (random() <= 1)::int) > 100 order by 1; + +-- Remote aggregate in combination with a local Param (for the output +-- of an initplan) can be trouble, per bug #15781 +explain (verbose, costs off) +select exists(select 1 from pg_enum), sum(c1) from ft1; +select exists(select 1 from pg_enum), sum(c1) from ft1; + +explain (verbose, costs off) +select exists(select 1 from pg_enum), sum(c1) from ft1 group by 1; +select exists(select 1 from pg_enum), sum(c1) from ft1 group by 1; + + +-- Testing ORDER BY, DISTINCT, FILTER, Ordered-sets and VARIADIC within aggregates + +-- ORDER BY within aggregate, same column used to order +explain (verbose, costs off) +select array_agg(c1 order by c1) from ft1 where c1 < 100 group by c2 order by 1; +select array_agg(c1 order by c1) from ft1 where c1 < 100 group by c2 order by 1; + +-- ORDER BY within aggregate, different column used to order also using DESC +explain (verbose, costs off) +select array_agg(c5 order by c1 desc) from ft2 where c2 = 6 and c1 < 50; +select array_agg(c5 order by c1 desc) from ft2 where c2 = 6 and c1 < 50; + +-- DISTINCT within aggregate +explain (verbose, costs off) +select array_agg(distinct (t1.c1)%5) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1; +select array_agg(distinct (t1.c1)%5) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1; + +-- DISTINCT combined with ORDER BY within aggregate +explain (verbose, costs off) +select array_agg(distinct (t1.c1)%5 order by (t1.c1)%5) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1; +select array_agg(distinct (t1.c1)%5 order by (t1.c1)%5) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1; + +explain (verbose, costs off) +select array_agg(distinct (t1.c1)%5 order by (t1.c1)%5 desc nulls last) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1; +select array_agg(distinct (t1.c1)%5 order by (t1.c1)%5 desc nulls last) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1; + +--nspt FILTER within aggregate +-- openGauss not support FILTER within aggregate, leve a err case, make others run as normal +explain (verbose, costs off) +select sum(c1) filter (where c1 < 100 and c2 > 5) from ft1 group by c2 order by 1 nulls last; +select sum(c1) filter (where c1 < 100 and c2 > 5) from ft1 group by c2 order by 1 nulls last; +explain (verbose, costs off) +select sum(c1)/* filter (where c1 < 100 and c2 > 5)*/ from ft1 group by c2 order by 1 nulls last; +select sum(c1)/* filter (where c1 < 100 and c2 > 5)*/ from ft1 group by c2 order by 1 nulls last; + +-- DISTINCT, ORDER BY and FILTER within aggregate +explain (verbose, costs off) +select sum(c1%3), sum(distinct c1%3 order by c1%3)/* filter (where c1%3 < 2)*/, c2 from ft1 where c2 = 6 group by c2; +select sum(c1%3), sum(distinct c1%3 order by c1%3)/* filter (where c1%3 < 2)*/, c2 from ft1 where c2 = 6 group by c2; + +-- Outer query is aggregation query +explain (verbose, costs off) +select distinct (select count(*) /*filter (where t2.c2 = 6 and t2.c1 < 10)*/ from ft1 t1 where t1.c1 = 6) from ft2 t2 where t2.c2 % 6 = 0 order by 1; +select distinct (select count(*) /*filter (where t2.c2 = 6 and t2.c1 < 10)*/ from ft1 t1 where t1.c1 = 6) from ft2 t2 where t2.c2 % 6 = 0 order by 1; +-- Inner query is aggregation query +explain (verbose, costs off) +select distinct (select count(t1.c1) /*filter (where t2.c2 = 6 and t2.c1 < 10)*/ from ft1 t1 where t1.c1 = 6) from ft2 t2 where t2.c2 % 6 = 0 order by 1; +select distinct (select count(t1.c1) /*filter (where t2.c2 = 6 and t2.c1 < 10)*/ from ft1 t1 where t1.c1 = 6) from ft2 t2 where t2.c2 % 6 = 0 order by 1; + +-- Aggregate not pushed down as FILTER condition is not pushable +explain (verbose, costs off) select sum(c1) /*filter (where (c1 / c1) * random() <= 1)*/ from ft1 group by c2 order by 1; +explain (verbose, costs off) select sum(c2) /*filter (where c2 in (select c2 from ft1 where c2 < 5))*/ from ft1; + +--nspt Ordered-sets within aggregate +--nspt Using multiple arguments within aggregates + +-- User defined function for user defined aggregate, VARIADIC +create function least_accum(anyelement, variadic anyarray) +returns anyelement language sql as + 'select least($1, min($2[i])) from generate_subscripts($2,1) g(i)'; +create aggregate least_agg(variadic items anyarray) ( + stype = anyelement, sfunc = least_accum +); + +-- Disable hash aggregation for plan stability. +set enable_hashagg to false; + +-- Not pushed down due to user defined aggregate +explain (verbose, costs off) +select c2, least_agg(c1) from ft1 group by c2 order by c2; + +-- Add function and aggregate into extension +alter extension postgres_fdw add function least_accum(anyelement, variadic anyarray); +alter extension postgres_fdw add aggregate least_agg(variadic items anyarray); +alter server loopback options (set extensions 'postgres_fdw'); + +-- Now aggregate will be pushed. Aggregate will display VARIADIC argument. +explain (verbose, costs off) +select c2, least_agg(c1) from ft1 where c2 < 100 group by c2 order by c2; +select c2, least_agg(c1) from ft1 where c2 < 100 group by c2 order by c2; + +-- Remove function and aggregate from extension +alter extension postgres_fdw drop function least_accum(anyelement, variadic anyarray); +alter extension postgres_fdw drop aggregate least_agg(variadic items anyarray); +alter server loopback options (set extensions 'postgres_fdw'); + +-- Not pushed down as we have dropped objects from extension. +explain (verbose, costs off) +select c2, least_agg(c1) from ft1 group by c2 order by c2; + +-- Cleanup +reset enable_hashagg; +drop aggregate least_agg(variadic items anyarray); +drop function least_accum(anyelement, variadic anyarray); + + +--nspt Testing USING OPERATOR() in ORDER BY within aggregate. +--nspt For this, we need user defined operators along with operator family and +--nspt operator class. Create those and then add them in extension. Note that +--nspt user defined objects are considered unshippable unless they are part of +--nspt the extension. +create operator public.<^ ( + leftarg = int4, + rightarg = int4, + procedure = int4eq +); + +create operator public.=^ ( + leftarg = int4, + rightarg = int4, + procedure = int4lt +); + +create operator public.>^ ( + leftarg = int4, + rightarg = int4, + procedure = int4gt +); + +create operator family my_op_family using btree; + +create function my_op_cmp(a int, b int) returns int as + $$begin return btint4cmp(a, b); end $$ language plpgsql; + +create operator class my_op_class for type int using btree family my_op_family as + operator 1 public.<^, + operator 3 public.=^, + operator 5 public.>^, + function 1 my_op_cmp(int, int); + +-- NOTICES: +-- openGauss not support create operator class. remove the case. If you want to restore the test case later, refer to README in the file header. + +-- This will not be pushed as sort operator is now removed from the extension. +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; + +-- Cleanup +drop operator class my_op_class using btree; +drop function my_op_cmp(a int, b int); +drop operator family my_op_family using btree; +drop operator public.>^(int, int); +drop operator public.=^(int, int); +drop operator public.<^(int, int); + +-- Input relation to aggregate push down hook is not safe to pushdown and thus +-- the aggregate cannot be pushed down to foreign server. +explain (verbose, costs off) +select count(t1.c3) from ft2 t1 left join ft2 t2 on (t1.c1 = random() * t2.c2); + +-- Subquery in FROM clause having aggregate +explain (verbose, costs off) +select count(*), x.b from ft1, (select c2 a, sum(c1) b from ft1 group by c2) x where ft1.c2 = x.a group by x.b order by 1, 2; +select count(*), x.b from ft1, (select c2 a, sum(c1) b from ft1 group by c2) x where ft1.c2 = x.a group by x.b order by 1, 2; + +-- FULL join with IS NULL check in HAVING +explain (verbose, costs off) +select avg(t1.c1), sum(t2.c1) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) group by t2.c1 having (avg(t1.c1) is null and sum(t2.c1) < 10) or sum(t2.c1) is null order by 1 nulls last, 2; +select avg(t1.c1), sum(t2.c1) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) group by t2.c1 having (avg(t1.c1) is null and sum(t2.c1) < 10) or sum(t2.c1) is null order by 1 nulls last, 2; + +-- Aggregate over FULL join needing to deparse the joining relations as +-- subqueries. +explain (verbose, costs off) +select count(*), sum(t1.c1), avg(t2.c1) from (select c1 from ft4 where c1 between 50 and 60) t1 full join (select c1 from ft5 where c1 between 50 and 60) t2 on (t1.c1 = t2.c1); +select count(*), sum(t1.c1), avg(t2.c1) from (select c1 from ft4 where c1 between 50 and 60) t1 full join (select c1 from ft5 where c1 between 50 and 60) t2 on (t1.c1 = t2.c1); + +-- ORDER BY expression is part of the target list but not pushed down to +-- foreign server. +explain (verbose, costs off) +select sum(c2) * (random() <= 1)::int as sum from ft1 order by 1; +select sum(c2) * (random() <= 1)::int as sum from ft1 order by 1; + +--nspt LATERAL join, with parameterization +-- NOTICES: +-- openGauss not support create operator class. remove the case. If you want to restore the test case later, refer to README in the file header. + +-- Check with placeHolderVars +explain (verbose, costs off) +select sum(q.a), count(q.b) from ft4 left join (select 13, avg(ft1.c1), sum(ft2.c1) from ft1 right join ft2 on (ft1.c1 = ft2.c1)) q(a, b, c) on (ft4.c1 <= q.b); +select sum(q.a), count(q.b) from ft4 left join (select 13, avg(ft1.c1), sum(ft2.c1) from ft1 right join ft2 on (ft1.c1 = ft2.c1)) q(a, b, c) on (ft4.c1 <= q.b); + + +-- Not supported cases +-- Grouping sets +explain (verbose, costs off) +select c2, sum(c1) from ft1 where c2 < 3 group by rollup(c2) order by 1 nulls last; +select c2, sum(c1) from ft1 where c2 < 3 group by rollup(c2) order by 1 nulls last; +explain (verbose, costs off) +select c2, sum(c1) from ft1 where c2 < 3 group by cube(c2) order by 1 nulls last; +select c2, sum(c1) from ft1 where c2 < 3 group by cube(c2) order by 1 nulls last; +explain (verbose, costs off) +select c2, c6, sum(c1) from ft1 where c2 < 3 group by grouping sets(c2, c6) order by 1 nulls last, 2 nulls last; +select c2, c6, sum(c1) from ft1 where c2 < 3 group by grouping sets(c2, c6) order by 1 nulls last, 2 nulls last; +explain (verbose, costs off) +select c2, sum(c1), grouping(c2) from ft1 where c2 < 3 group by c2 order by 1 nulls last; +select c2, sum(c1), grouping(c2) from ft1 where c2 < 3 group by c2 order by 1 nulls last; + +-- DISTINCT itself is not pushed down, whereas underneath aggregate is pushed +explain (verbose, costs off) +select distinct sum(c1)/1000 s from ft2 where c2 < 6 group by c2 order by 1; +select distinct sum(c1)/1000 s from ft2 where c2 < 6 group by c2 order by 1; + +-- WindowAgg +explain (verbose, costs off) +select c2, sum(c2), count(c2) over (partition by c2%2) from ft2 where c2 < 10 group by c2 order by 1; +select c2, sum(c2), count(c2) over (partition by c2%2) from ft2 where c2 < 10 group by c2 order by 1; +explain (verbose, costs off) +select c2, array_agg(c2) over (partition by c2%2 order by c2 desc) from ft1 where c2 < 10 group by c2 order by 1; +select c2, array_agg(c2) over (partition by c2%2 order by c2 desc) from ft1 where c2 < 10 group by c2 order by 1; +explain (verbose, costs off) +select c2, array_agg(c2) over (partition by c2%2 order by c2 range between current row and unbounded following) from ft1 where c2 < 10 group by c2 order by 1; +select c2, array_agg(c2) over (partition by c2%2 order by c2 range between current row and unbounded following) from ft1 where c2 < 10 group by c2 order by 1; + +-- Same group by +explain verbose select c1,c1,c1,count(*) from ft1 group by 1,1,1; +set enable_hashagg = off; +explain verbose select c1,c1,c1,count(*) from ft1 group by 1,1,1; +set enable_hashagg = on; + +-- ====================================================================================================================================== +-- TEST-MODULE: parameterized queries +-- -------------------------------------- +-- ====================================================================================================================================== +-- simple join +PREPARE st1(int, int) AS SELECT t1.c3, t2.c3 FROM ft1 t1, ft2 t2 WHERE t1.c1 = $1 AND t2.c1 = $2; +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st1(1, 2); +EXECUTE st1(1, 1); +EXECUTE st1(101, 101); +-- subquery using stable function (can't be sent to remote) +PREPARE st2(int) AS SELECT * FROM ft1 t1 WHERE t1.c1 < $2 AND t1.c3 IN (SELECT c3 FROM ft2 t2 WHERE c1 > $1 AND "date"(c4) = '1970-01-17'::date) ORDER BY c1; +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st2(10, 20); +EXECUTE st2(10, 20); +EXECUTE st2(101, 121); +-- subquery using immutable function (can be sent to remote) +PREPARE st3(int) AS SELECT * FROM ft1 t1 WHERE t1.c1 < $2 AND t1.c3 IN (SELECT c3 FROM ft2 t2 WHERE c1 > $1 AND "date"(c5) = '1970-01-17'::date) ORDER BY c1; +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st3(10, 20); +EXECUTE st3(10, 20); +EXECUTE st3(20, 30); +-- custom plan should be chosen initially +PREPARE st4(int) AS SELECT * FROM ft1 t1 WHERE t1.c1 = $1; +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st4(1); +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st4(1); +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st4(1); +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st4(1); +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st4(1); +-- once we try it enough times, should switch to generic plan +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st4(1); +-- value of $1 should not be sent to remote +PREPARE st5(user_enum,int) AS SELECT * FROM ft1 t1 WHERE c8 = $1 and c1 = $2; +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st5('foo', 1); +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st5('foo', 1); +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st5('foo', 1); +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st5('foo', 1); +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st5('foo', 1); +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st5('foo', 1); +EXECUTE st5('foo', 1); + +-- altering FDW options requires replanning +PREPARE st6 AS SELECT * FROM ft1 t1 WHERE t1.c1 = t1.c2; +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st6; +PREPARE st7 AS INSERT INTO ft1 (c1,c2,c3) VALUES (1001,101,'foo'); +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st7; +ALTER TABLE "S 1"."T 1" RENAME TO "T 0"; +ALTER FOREIGN TABLE ft1 OPTIONS (SET table_name 'T 0'); +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st6; +EXECUTE st6; --nspt +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st7; +ALTER TABLE "S 1"."T 0" RENAME TO "T 1"; +ALTER FOREIGN TABLE ft1 OPTIONS (SET table_name 'T 1'); + +PREPARE st8 AS SELECT count(c3) FROM ft1 t1 WHERE t1.c1 === t1.c2; +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st8; +ALTER SERVER loopback OPTIONS (DROP extensions); +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st8; +EXECUTE st8; +ALTER SERVER loopback OPTIONS (ADD extensions 'postgres_fdw'); + +-- cleanup +DEALLOCATE st1; +DEALLOCATE st2; +DEALLOCATE st3; +DEALLOCATE st4; +DEALLOCATE st5; +DEALLOCATE st6; +DEALLOCATE st7; +DEALLOCATE st8; + +-- System columns, except ctid and oid, should not be sent to remote +-- openGauss NOT SUPPROT select system columns in ftable. +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM ft1 t1 WHERE t1.tableoid = 'pg_class'::regclass LIMIT 1; +SELECT * FROM ft1 t1 WHERE t1.tableoid = 'ft1'::regclass LIMIT 1; +EXPLAIN (VERBOSE, COSTS OFF) +SELECT tableoid::regclass, * FROM ft1 t1 LIMIT 1; +SELECT tableoid::regclass, * FROM ft1 t1 LIMIT 1; +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM ft1 t1 WHERE t1.ctid = '(0,2)'; +SELECT * FROM ft1 t1 WHERE t1.ctid = '(0,2)'; +EXPLAIN (VERBOSE, COSTS OFF) +SELECT ctid, * FROM ft1 t1 LIMIT 1; +SELECT ctid, * FROM ft1 t1 LIMIT 1; +-- ====================================================================================================================================== +-- TEST-MODULE: used in PL/pgSQL function +-- -------------------------------------- +-- ====================================================================================================================================== + +CREATE OR REPLACE FUNCTION f_test(p_c1 int) RETURNS int AS $$ +DECLARE + v_c1 int; +BEGIN + SELECT c1 INTO v_c1 FROM ft1 WHERE c1 = p_c1 LIMIT 1; + PERFORM c1 FROM ft1 WHERE c1 = p_c1 AND p_c1 = v_c1 LIMIT 1; + RETURN v_c1; +END; +$$ LANGUAGE plpgsql; +SELECT f_test(100); +DROP FUNCTION f_test(int); + +-- ====================================================================================================================================== +-- TEST-MODULE: test writable foreign table stuff +-- -------------------------------------- +-- ====================================================================================================================================== +EXPLAIN (verbose, costs off) +INSERT INTO ft2 (c1,c2,c3) SELECT c1+1000,c2+100, c3 || c3 FROM ft2 LIMIT 20; +INSERT INTO ft2 (c1,c2,c3) SELECT c1+1000,c2+100, c3 || c3 FROM ft2 LIMIT 20; +INSERT INTO ft2 (c1,c2,c3) + VALUES (1101,201,'aaa'), (1102,202,'bbb'), (1103,203,'ccc') RETURNING *; +INSERT INTO ft2 (c1,c2,c3) VALUES (1104,204,'ddd'), (1105,205,'eee'); +EXPLAIN (verbose, costs off) +UPDATE ft2 SET c2 = c2 + 300, c3 = c3 || '_update3' WHERE c1 % 10 = 3; -- can be pushed down +UPDATE ft2 SET c2 = c2 + 300, c3 = c3 || '_update3' WHERE c1 % 10 = 3; +EXPLAIN (verbose, costs off) +UPDATE ft2 SET c2 = c2 + 400, c3 = c3 || '_update7' WHERE c1 % 10 = 7 RETURNING *; -- can be pushed down +UPDATE ft2 SET c2 = c2 + 400, c3 = c3 || '_update7' WHERE c1 % 10 = 7 RETURNING *; +EXPLAIN (verbose, costs off) +UPDATE ft2 SET c2 = ft2.c2 + 500, c3 = ft2.c3 || '_update9', c7 = DEFAULT + FROM ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 9; -- can be pushed down +UPDATE ft2 SET c2 = ft2.c2 + 500, c3 = ft2.c3 || '_update9', c7 = DEFAULT + FROM ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 9; +EXPLAIN (verbose, costs off) + DELETE FROM ft2 WHERE c1 % 10 = 5 RETURNING c1, c4; -- can be pushed down +DELETE FROM ft2 WHERE c1 % 10 = 5 RETURNING c1, c4; +EXPLAIN (verbose, costs off) +DELETE FROM ft2 USING ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 2; -- can be pushed down +DELETE FROM ft2 USING ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 2; +SELECT c1,c2,c3,c4 FROM ft2 ORDER BY c1; + +--nspt openGauss not have tableoid, also not support select system columns. +EXPLAIN (verbose, costs off) +INSERT INTO ft2 (c1,c2,c3) VALUES (1200,999,'foo') RETURNING tableoid::regclass; +INSERT INTO ft2 (c1,c2,c3) VALUES (1200,999,'foo') RETURNING tableoid::regclass; +EXPLAIN (verbose, costs off) +UPDATE ft2 SET c3 = 'bar' WHERE c1 = 1200 RETURNING tableoid::regclass; -- can be pushed down +UPDATE ft2 SET c3 = 'bar' WHERE c1 = 1200 RETURNING tableoid::regclass; +EXPLAIN (verbose, costs off) +DELETE FROM ft2 WHERE c1 = 1200 RETURNING tableoid::regclass; -- can be pushed down +DELETE FROM ft2 WHERE c1 = 1200 RETURNING tableoid::regclass; + +-- Test UPDATE/DELETE with RETURNING on a three-table join +INSERT INTO ft2 (c1,c2,c3) + SELECT id, id - 1200, to_char(id, 'FM00000') FROM generate_series(1201, 1300) id; +EXPLAIN (verbose, costs off) +UPDATE ft2 SET c3 = 'foo' + FROM ft4 INNER JOIN ft5 ON (ft4.c1 = ft5.c1) + WHERE ft2.c1 > 1200 AND ft2.c2 = ft4.c1 + RETURNING ft2, ft2.*, ft4, ft4.*; -- can be pushed down +UPDATE ft2 SET c3 = 'foo' + FROM ft4 INNER JOIN ft5 ON (ft4.c1 = ft5.c1) + WHERE ft2.c1 > 1200 AND ft2.c2 = ft4.c1 + RETURNING ft2, ft2.*, ft4, ft4.*; +EXPLAIN (verbose, costs off) +DELETE FROM ft2 + USING ft4 LEFT JOIN ft5 ON (ft4.c1 = ft5.c1) + WHERE ft2.c1 > 1200 AND ft2.c1 % 10 = 0 AND ft2.c2 = ft4.c1 + RETURNING 100; -- can be pushed down +DELETE FROM ft2 + USING ft4 LEFT JOIN ft5 ON (ft4.c1 = ft5.c1) + WHERE ft2.c1 > 1200 AND ft2.c1 % 10 = 0 AND ft2.c2 = ft4.c1 + RETURNING 100; +DELETE FROM ft2 WHERE ft2.c1 > 1200; + +-- Test UPDATE with a MULTIEXPR sub-select +-- (maybe someday this'll be remotely executable, but not today) +EXPLAIN (verbose, costs off) +UPDATE ft2 AS target SET (c2, c7) = ( + SELECT c2 * 10, c7 + FROM ft2 AS src + WHERE target.c1 = src.c1 +) WHERE c1 > 1100; +UPDATE ft2 AS target SET (c2, c7) = ( + SELECT c2 * 10, c7 + FROM ft2 AS src + WHERE target.c1 = src.c1 +) WHERE c1 > 1100; + +UPDATE ft2 AS target SET (c2) = ( + SELECT c2 / 10 + FROM ft2 AS src + WHERE target.c1 = src.c1 +) WHERE c1 > 1100; + +-- Test UPDATE involving a join that can be pushed down, +-- but a SET clause that can't be +EXPLAIN (VERBOSE, COSTS OFF) +UPDATE ft2 d SET c2 = CASE WHEN random() >= 0 THEN d.c2 ELSE 0 END + FROM ft2 AS t WHERE d.c1 = t.c1 AND d.c1 > 1000; +UPDATE ft2 d SET c2 = CASE WHEN random() >= 0 THEN d.c2 ELSE 0 END + FROM ft2 AS t WHERE d.c1 = t.c1 AND d.c1 > 1000; + +-- Test UPDATE/DELETE with WHERE or JOIN/ON conditions containing +-- user-defined operators/functions +ALTER SERVER loopback OPTIONS (DROP extensions); +INSERT INTO ft2 (c1,c2,c3) + SELECT id, id % 10, to_char(id, 'FM00000') FROM generate_series(2001, 2010) id; +EXPLAIN (verbose, costs off) +UPDATE ft2 SET c3 = 'bar' WHERE postgres_fdw_abs(c1) > 2000 RETURNING *; -- can't be pushed down +UPDATE ft2 SET c3 = 'bar' WHERE postgres_fdw_abs(c1) > 2000 RETURNING *; +EXPLAIN (verbose, costs off) +UPDATE ft2 SET c3 = 'baz' + FROM ft4 INNER JOIN ft5 ON (ft4.c1 = ft5.c1) + WHERE ft2.c1 > 2000 AND ft2.c2 === ft4.c1 + RETURNING ft2.*, ft4.*, ft5.*; -- can't be pushed down +UPDATE ft2 SET c3 = 'baz' + FROM ft4 INNER JOIN ft5 ON (ft4.c1 = ft5.c1) + WHERE ft2.c1 > 2000 AND ft2.c2 === ft4.c1 + RETURNING ft2.*, ft4.*, ft5.*; +EXPLAIN (verbose, costs off) +DELETE FROM ft2 + USING ft4 INNER JOIN ft5 ON (ft4.c1 === ft5.c1) + WHERE ft2.c1 > 2000 AND ft2.c2 = ft4.c1 + RETURNING ft2.c1, ft2.c2, ft2.c3; -- can't be pushed down +DELETE FROM ft2 + USING ft4 INNER JOIN ft5 ON (ft4.c1 === ft5.c1) + WHERE ft2.c1 > 2000 AND ft2.c2 = ft4.c1 + RETURNING ft2.c1, ft2.c2, ft2.c3; +DELETE FROM ft2 WHERE ft2.c1 > 2000; +ALTER SERVER loopback OPTIONS (ADD extensions 'postgres_fdw'); + +-- Test that trigger on remote table works as expected +CREATE OR REPLACE FUNCTION "S 1".F_BRTRIG() RETURNS trigger AS $$ +BEGIN + NEW.c3 = NEW.c3 || '_trig_update'; + RETURN NEW; +END; +$$ LANGUAGE plpgsql; +CREATE TRIGGER t1_br_insert BEFORE INSERT OR UPDATE + ON "S 1"."T 1" FOR EACH ROW EXECUTE PROCEDURE "S 1".F_BRTRIG(); + +INSERT INTO ft2 (c1,c2,c3) VALUES (1208, 818, 'fff') RETURNING *; +INSERT INTO ft2 (c1,c2,c3,c6) VALUES (1218, 818, 'ggg', '(--;') RETURNING *; +UPDATE ft2 SET c2 = c2 + 600 WHERE c1 % 10 = 8 AND c1 < 1200 RETURNING *; + +-- Test errors thrown on remote side during update +ALTER TABLE "S 1"."T 1" ADD CONSTRAINT c2positive CHECK (c2 >= 0); + +INSERT INTO ft1(c1, c2) VALUES(11, 12); -- duplicate key +INSERT INTO ft1(c1, c2) VALUES(11, 12) ON DUPLICATE KEY UPDATE NOTHING; -- works +INSERT INTO ft1(c1, c2) VALUES(11, 12) ON DUPLICATE KEY UPDATE c3 = 'ffg'; -- unsupported +INSERT INTO ft1(c1, c2) VALUES(1111, -2); -- c2positive +UPDATE ft1 SET c2 = -c2 WHERE c1 = 1; -- c2positive + +-- Test savepoint/rollback behavior +select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; +select c2, count(*) from "S 1"."T 1" where c2 < 500 group by 1 order by 1; +begin; +update ft2 set c2 = 42 where c2 = 0; +select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; +savepoint s1; +update ft2 set c2 = 44 where c2 = 4; +select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; +release savepoint s1; +select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; +savepoint s2; +update ft2 set c2 = 46 where c2 = 6; +select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; +rollback to savepoint s2; +select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; +release savepoint s2; +select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; +savepoint s3; +update ft2 set c2 = -2 where c2 = 42 and c1 = 10; -- fail on remote side +rollback to savepoint s3; +select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; +release savepoint s3; +select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; +-- none of the above is committed yet remotely +select c2, count(*) from "S 1"."T 1" where c2 < 500 group by 1 order by 1; +commit; +select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; +select c2, count(*) from "S 1"."T 1" where c2 < 500 group by 1 order by 1; + +VACUUM ANALYZE "S 1"."T 1"; + +-- Above DMLs add data with c6 as NULL in ft1, so test ORDER BY NULLS LAST and NULLs +-- FIRST behavior here. +-- ORDER BY DESC NULLS LAST options +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 ORDER BY c6 DESC NULLS LAST, c1 OFFSET 795 LIMIT 10; +SELECT * FROM ft1 ORDER BY c6 DESC NULLS LAST, c1 OFFSET 795 LIMIT 10; +-- ORDER BY DESC NULLS FIRST options +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 ORDER BY c6 DESC NULLS FIRST, c1 OFFSET 15 LIMIT 10; +SELECT * FROM ft1 ORDER BY c6 DESC NULLS FIRST, c1 OFFSET 15 LIMIT 10; +-- ORDER BY ASC NULLS FIRST options +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 ORDER BY c6 ASC NULLS FIRST, c1 OFFSET 15 LIMIT 10; +SELECT * FROM ft1 ORDER BY c6 ASC NULLS FIRST, c1 OFFSET 15 LIMIT 10; + +-- ====================================================================================================================================== +-- TEST-MODULE: check constraints +-- -------------------------------------- +-- openGauss not support to "ALTER FOREIGN TABLE ft1 ADD CONSTRAINT", so this test module +-- is unuseful. +-- ====================================================================================================================================== +-- Consistent check constraints provide consistent results +ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c2positive CHECK (c2 >= 0); +EXPLAIN (VERBOSE, COSTS OFF) SELECT count(*) FROM ft1 WHERE c2 < 0; +SELECT count(*) FROM ft1 WHERE c2 < 0; +SET constraint_exclusion = 'on'; +EXPLAIN (VERBOSE, COSTS OFF) SELECT count(*) FROM ft1 WHERE c2 < 0; +SELECT count(*) FROM ft1 WHERE c2 < 0; +RESET constraint_exclusion; +-- check constraint is enforced on the remote side, not locally +INSERT INTO ft1(c1, c2) VALUES(1111, -2); -- c2positive +UPDATE ft1 SET c2 = -c2 WHERE c1 = 1; -- c2positive +ALTER FOREIGN TABLE ft1 DROP CONSTRAINT ft1_c2positive; + +-- But inconsistent check constraints provide inconsistent results +ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c2negative CHECK (c2 < 0); +EXPLAIN (VERBOSE, COSTS OFF) SELECT count(*) FROM ft1 WHERE c2 >= 0; +SELECT count(*) FROM ft1 WHERE c2 >= 0; +SET constraint_exclusion = 'on'; +EXPLAIN (VERBOSE, COSTS OFF) SELECT count(*) FROM ft1 WHERE c2 >= 0; +SELECT count(*) FROM ft1 WHERE c2 >= 0; +RESET constraint_exclusion; +-- local check constraint is not actually enforced +INSERT INTO ft1(c1, c2) VALUES(1111, 2); +UPDATE ft1 SET c2 = c2 + 1 WHERE c1 = 1; +ALTER FOREIGN TABLE ft1 DROP CONSTRAINT ft1_c2negative; + + +-- ====================================================================================================================================== +-- TEST-MODULE: Incorrect Usage +-- -------------------------------------- +-- ====================================================================================================================================== +CREATE FOREIGN TABLE ftx ( + c1 int NOT NULL, + c2 int NOT NULL, + c3 text +) SERVER loopback OPTIONS (schema_name 'S 1', table_name 'T 4'); + + +EXPLAIN (VERBOSE, COSTS OFF)select * from ftx; +select * from ftx; +EXPLAIN (VERBOSE, COSTS OFF)select * from ftx partition(ptb1); --ERR +select * from ftx partition(ptb1); +EXPLAIN (VERBOSE, COSTS OFF)select * from ftx partition(ptb1) subpartition(ptb11); --ERR +select * from ftx partition(ptb1) subpartition(ptb11); + +DROP FOREIGN TABLE ftx; + + +CREATE FOREIGN TABLE ftx ( + c1 int NOT NULL, + c2 int NOT NULL, + c3 text +) SERVER loopback OPTIONS (schema_name 'S 1', table_name 'T 3 partition(ptb1)'); + +--ERR +EXPLAIN (VERBOSE, COSTS OFF)select * from ftx; +select * from ftx; + +DROP FOREIGN TABLE ftx; + +-- ====================================================================================================================================== +-- TEST-MODULE: clean up all the test data +-- -------------------------------------- +-- heihei! +-- ====================================================================================================================================== +\c regression +drop database postgresfdw_test_db_partition; \ No newline at end of file diff --git a/src/bin/gs_guc/cluster_guc.conf b/src/bin/gs_guc/cluster_guc.conf index 06927bb0a..07b162e91 100755 --- a/src/bin/gs_guc/cluster_guc.conf +++ b/src/bin/gs_guc/cluster_guc.conf @@ -448,6 +448,7 @@ shared_buffers|int|16,1073741823|kB|NULL| pca_shared_buffers|int|8,1073741823|kB|NULL| shared_preload_libraries|string|0,0|NULL|NULL| show_acce_estimate_detail|bool|0,0|NULL|NULL| +show_fdw_remote_plan|bool|0,0|NULL|NULL| skew_option|enum|normal,lazy,off|NULL|NULL| sql_inheritance|bool|0,0|NULL|NULL| ssl|bool|0,0|NULL|NULL| diff --git a/src/common/backend/nodes/copyfuncs.cpp b/src/common/backend/nodes/copyfuncs.cpp index a2606ad64..052b6e98c 100644 --- a/src/common/backend/nodes/copyfuncs.cpp +++ b/src/common/backend/nodes/copyfuncs.cpp @@ -959,6 +959,13 @@ static ForeignScan* _copyForeignScan(const ForeignScan* from) } } + COPY_SCALAR_FIELD(operation); + COPY_SCALAR_FIELD(resultRelation); + COPY_SCALAR_FIELD(fs_server); + COPY_BITMAPSET_FIELD(fs_relids); + COPY_NODE_FIELD(fdw_scan_tlist); + COPY_NODE_FIELD(fdw_recheck_quals); + return newnode; } @@ -1831,6 +1838,13 @@ static VecForeignScan* _copyVecForeignScan(const VecForeignScan* from) } } + COPY_SCALAR_FIELD(operation); + COPY_SCALAR_FIELD(resultRelation); + COPY_SCALAR_FIELD(fs_server); + COPY_BITMAPSET_FIELD(fs_relids); + COPY_NODE_FIELD(fdw_scan_tlist); + COPY_NODE_FIELD(fdw_recheck_quals); + return newnode; } diff --git a/src/common/backend/nodes/list.cpp b/src/common/backend/nodes/list.cpp index 7576024ea..f514334b1 100644 --- a/src/common/backend/nodes/list.cpp +++ b/src/common/backend/nodes/list.cpp @@ -541,6 +541,31 @@ List* list_concat(List* list1, List* list2) return list1; } +/* make new listcell to concat data of list2, not distory the struction of list2. */ +List* list_concat2(List* list1, List* list2) +{ + if (list2 == NIL) { + return list1; + } + if (list1 == list2) { + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("cannot list_concat2() a list to itself"))); + } + + Assert(list1 == NIL || list1->type == list2->type); + + ListCell* lc = NULL; + foreach(lc, list2) { + /* no matter the type, use "lfirst" */ + list1 = lappend(list1, lfirst(lc)); + } + + list1->type = list2->type; + + check_list_invariants(list1); + check_list_invariants(list2); + return list1; +} + /* * Truncate 'list' to contain no more than 'new_size' elements. This * modifies the list in-place! Despite this, callers should use the diff --git a/src/common/backend/nodes/outfuncs.cpp b/src/common/backend/nodes/outfuncs.cpp index 9e1fac243..e092c6935 100755 --- a/src/common/backend/nodes/outfuncs.cpp +++ b/src/common/backend/nodes/outfuncs.cpp @@ -1482,6 +1482,15 @@ static void _outCommonForeignScanPart(StringInfo str, T* node) WRITE_NODE_FIELD(bloomFilterSet[i]); } WRITE_BOOL_FIELD(in_compute_pool); + + if (t_thrd.proc->workingVersionNum >= FDW_SUPPORT_JOIN_AGG_VERSION_NUM) { + WRITE_ENUM_FIELD(operation, CmdType); + WRITE_UINT_FIELD(resultRelation); + WRITE_OID_FIELD(fs_server); + WRITE_BITMAPSET_FIELD(fs_relids); + WRITE_NODE_FIELD(fdw_scan_tlist); + WRITE_NODE_FIELD(fdw_recheck_quals); + } } static void _outForeignScan(StringInfo str, ForeignScan* node) { @@ -3289,6 +3298,9 @@ static void _outRelOptInfo(StringInfo str, RelOptInfo* node) WRITE_INT_FIELD(partItrs); WRITE_NODE_FIELD(subplan); WRITE_NODE_FIELD(subroot); + WRITE_OID_FIELD(serverid); + WRITE_OID_FIELD(userid); + WRITE_BOOL_FIELD(useridiscurrent); /* we don't try to print fdwroutine or fdw_private */ WRITE_NODE_FIELD(baserestrictinfo); WRITE_UINT_FIELD(baserestrict_min_security); diff --git a/src/common/backend/nodes/readfuncs.cpp b/src/common/backend/nodes/readfuncs.cpp index 0a2cc9b21..bc7a238b0 100755 --- a/src/common/backend/nodes/readfuncs.cpp +++ b/src/common/backend/nodes/readfuncs.cpp @@ -4512,6 +4512,16 @@ static ForeignScan* _readForeignScan(ForeignScan* local_node) } READ_BOOL_FIELD(in_compute_pool); + + IF_EXIST(operation) { + READ_ENUM_FIELD(operation, CmdType); + READ_UINT_FIELD(resultRelation); + READ_OID_FIELD(fs_server); + READ_BITMAPSET_FIELD(fs_relids); + READ_NODE_FIELD(fdw_scan_tlist); + READ_NODE_FIELD(fdw_recheck_quals); + } + READ_DONE(); } @@ -5046,6 +5056,14 @@ static VecForeignScan* _readVecForeignScan(VecForeignScan* local_node) } READ_BOOL_FIELD(in_compute_pool); + IF_EXIST(operation) { + READ_ENUM_FIELD(operation, CmdType); + READ_UINT_FIELD(resultRelation); + READ_OID_FIELD(fs_server); + READ_BITMAPSET_FIELD(fs_relids); + READ_NODE_FIELD(fdw_scan_tlist); + READ_NODE_FIELD(fdw_recheck_quals); + } READ_DONE(); } diff --git a/src/common/backend/utils/adt/ruleutils.cpp b/src/common/backend/utils/adt/ruleutils.cpp index eb4642aaa..bc9cdb3b2 100644 --- a/src/common/backend/utils/adt/ruleutils.cpp +++ b/src/common/backend/utils/adt/ruleutils.cpp @@ -5142,6 +5142,8 @@ static void set_deparse_planstate(deparse_namespace* dpns, PlanState* ps) /* index_tlist is set only if it's an IndexOnlyScan */ if (IsA(ps->plan, IndexOnlyScan)) dpns->index_tlist = ((IndexOnlyScan*)ps->plan)->indextlist; + else if (IsA(ps->plan, ForeignScan)) + dpns->index_tlist = ((ForeignScan *)ps->plan)->fdw_scan_tlist; else if (IsA(ps->plan, ExtensiblePlan)) dpns->index_tlist = ((ExtensiblePlan*)ps->plan)->extensible_plan_tlist; else diff --git a/src/common/backend/utils/init/globals.cpp b/src/common/backend/utils/init/globals.cpp index 7197fb4e9..2eb01d0d8 100644 --- a/src/common/backend/utils/init/globals.cpp +++ b/src/common/backend/utils/init/globals.cpp @@ -59,7 +59,7 @@ bool open_join_children = true; bool will_shutdown = false; /* hard-wired binary version number */ -const uint32 GRAND_VERSION_NUM = 92838; +const uint32 GRAND_VERSION_NUM = 92839; const uint32 SELECT_INTO_VAR_VERSION_NUM = 92834; const uint32 DOLPHIN_ENABLE_DROP_NUM = 92830; @@ -164,6 +164,8 @@ const uint32 MAT_VIEW_RECURSIVE_VERSION_NUM = 92833; const uint32 NEGETIVE_BOOL_VERSION_NUM = 92835; +const uint32 FDW_SUPPORT_JOIN_AGG_VERSION_NUM = 92839; + #ifdef PGXC bool useLocalXid = false; #endif diff --git a/src/common/backend/utils/misc/guc.cpp b/src/common/backend/utils/misc/guc.cpp index 923ffd9e8..09edd6757 100755 --- a/src/common/backend/utils/misc/guc.cpp +++ b/src/common/backend/utils/misc/guc.cpp @@ -1921,6 +1921,18 @@ static void InitConfigureNamesBool() NULL, NULL }, + {{"show_fdw_remote_plan", + PGC_USERSET, + NODE_SINGLENODE, + UNGROUPED, + gettext_noop("show remote plan of a foreign scan."), + NULL}, + &u_sess->attr.attr_common.show_fdw_remote_plan, + false, + NULL, + NULL, + NULL + }, /* End-of-list marker */ {{NULL, (GucContext)0, diff --git a/src/gausskernel/cbb/extension/foreign/foreign.cpp b/src/gausskernel/cbb/extension/foreign/foreign.cpp index 525fe2cc3..0b7753f19 100644 --- a/src/gausskernel/cbb/extension/foreign/foreign.cpp +++ b/src/gausskernel/cbb/extension/foreign/foreign.cpp @@ -33,6 +33,8 @@ #include "utils/syscache.h" #include "cipher.h" #include "utils/knl_relcache.h" +#include "optimizer/planmain.h" +#include "optimizer/pathnode.h" extern Datum pg_options_to_table(PG_FUNCTION_ARGS); extern Datum postgresql_fdw_validator(PG_FUNCTION_ARGS); @@ -215,6 +217,11 @@ bool IsSpecifiedFDW(const char* ServerName, const char* SepcifiedType) */ bool IsSpecifiedFDWFromRelid(Oid relId, const char* SepcifiedType) { + if (!OidIsValid(relId)) { + ereport(ERROR, (errmsg("Invalid relid is used to verify fdw type."), + errhint("This may be a join relationship, or has aggregation, etc."))); + } + ForeignTable* ftbl = NULL; ForeignServer* fsvr = NULL; bool IsSpecifiedTable = false; @@ -248,10 +255,10 @@ bool IsSpecifiedFDWFromRelid(Oid relId, const char* SepcifiedType) /** * @Description: Jude whether type of the foreign table support SELECT/INSERT/UPDATE/DELETE/COPY - * @in relId: The foreign table Oid. + * @in oid: The foreign table Oid or The foreign server Oid. * @return Rreturn true if the foreign table support those DML. */ -bool CheckSupportedFDWType(Oid relId) +bool CheckSupportedFDWType(Oid oid, bool byServerId) { static const char* supportFDWType[] = {MOT_FDW, MYSQL_FDW, ORACLE_FDW, POSTGRES_FDW}; int size = sizeof(supportFDWType) / sizeof(supportFDWType[0]); @@ -268,8 +275,8 @@ bool CheckSupportedFDWType(Oid relId) } MemoryContext oldContext = MemoryContextSwitchTo(u_sess->opt_cxt.ft_context); - ForeignTable* ftbl = GetForeignTable(relId); - ForeignServer* fsvr = GetForeignServer(ftbl->serverid); + Oid serverid = byServerId ? oid : GetForeignTable(oid)->serverid; + ForeignServer* fsvr = GetForeignServer(serverid); ForeignDataWrapper* fdw = GetForeignDataWrapper(fsvr->fdwid); for (int i = 0; i < size; i++) { @@ -286,6 +293,11 @@ bool CheckSupportedFDWType(Oid relId) bool isSpecifiedSrvTypeFromRelId(Oid relId, const char* SepcifiedType) { + if (!OidIsValid(relId)) { + ereport(ERROR, (errmsg("Invalid relid is used to verify foreign server type."), + errhint("This may be a join relationship, or has aggregation, etc."))); + } + ForeignTable* ftbl = NULL; ForeignServer* fsrv = NULL; bool ret = false; @@ -494,6 +506,26 @@ FdwRoutine* GetFdwRoutine(Oid fdwhandler) return routine; } +/* + * GetForeignServerIdByRelId - look up the foreign server + * for the given foreign table, and return its OID. + */ +Oid GetForeignServerIdByRelId(Oid relid) +{ + HeapTuple tp; + Form_pg_foreign_table tableform; + Oid serverid; + + tp = SearchSysCache1(FOREIGNTABLEREL, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(tp)) + elog(ERROR, "cache lookup failed for foreign table %u", relid); + tableform = (Form_pg_foreign_table)GETSTRUCT(tp); + serverid = tableform->ftserver; + ReleaseSysCache(tp); + + return serverid; +} + /* * GetFdwRoutineByRelId - look up the handler of the foreign-data wrapper * for the given foreign table, and retrieve its FdwRoutine struct. @@ -996,6 +1028,10 @@ ObsOptions* getObsOptions(Oid foreignTableId) */ ServerTypeOption getServerType(Oid foreignTableId) { + if (!OidIsValid(foreignTableId)) { + ereport(ERROR, (errmsg("Invalid foreignTableId is used to get server type."), + errhint("This may be a join relationship, or has aggregation, etc."))); + } char* optionValue = HdfsGetOptionValue(foreignTableId, "type"); ServerTypeOption srvType = T_INVALID; @@ -1698,3 +1734,38 @@ void CheckFoldernameOrFilenamesOrCfgPtah(const char *OptStr, char *OptType) } } +FDWUpperRelCxt* InitFDWUpperPlan(PlannerInfo* root, RelOptInfo* baseRel, Plan* localPlan) +{ + if (baseRel == NULL || baseRel->fdwroutine == NULL || + baseRel->fdwroutine->GetForeignUpperPaths == NULL) { + return NULL; + } + + if (IS_STREAM_PLAN) { + return NULL; + } + + FDWUpperRelCxt* ufdwCxt = (FDWUpperRelCxt*)palloc0(sizeof(FDWUpperRelCxt)); + ufdwCxt->root = root; + ufdwCxt->currentRel = baseRel; + + /* these will definitely be used, create it early. */ + ufdwCxt->spjExtra = (SPJPathExtraData*)palloc0(sizeof(SPJPathExtraData)); + ufdwCxt->finalExtra = (FinalPathExtraData*)palloc0(sizeof(FinalPathExtraData)); + + ufdwCxt->spjExtra->targetList = localPlan->targetlist; + AdvanceFDWUpperPlan(ufdwCxt, UPPERREL_INIT, localPlan); + + return ufdwCxt; +} + +void AdvanceFDWUpperPlan(FDWUpperRelCxt* ufdwCxt, UpperRelationKind stage, Plan* localPlan) +{ + if (ufdwCxt->currentRel == NULL || ufdwCxt->currentRel->fdwroutine == NULL || + ufdwCxt->currentRel->fdwroutine->GetForeignUpperPaths == NULL) { + ufdwCxt->state = FDW_UPPER_REL_END; + return; + } + + ufdwCxt->currentRel->fdwroutine->GetForeignUpperPaths(ufdwCxt, stage, localPlan); +} diff --git a/src/gausskernel/optimizer/commands/explain.cpp b/src/gausskernel/optimizer/commands/explain.cpp index 51b218c02..fad0d6351 100755 --- a/src/gausskernel/optimizer/commands/explain.cpp +++ b/src/gausskernel/optimizer/commands/explain.cpp @@ -567,6 +567,7 @@ void ExplainInitState(ExplainState* es) es->indent = 0; es->pindent = 0; es->wlm_statistics_plan_max_digit = NULL; + es->es_frs.parent = es; /* Reset flag for plan_table. */ IsExplainPlanStmt = false; IsExplainPlanSelectForUpdateStmt = false; @@ -1211,6 +1212,13 @@ void ExplainOnePlan( } u_sess->exec_cxt.remotequery_list = NIL; } + + /* we have get all plan of foreignscan remote sql, append it */ + if (es->es_frs.node_num > 0) { + appendStringInfo(es->str, "%s\n", es->es_frs.str->data); + pfree_ext(es->es_frs.str->data); + } + /* * Close down the query and free resources. Include time for this in the * total runtime (although it should be pretty minimal). @@ -2102,7 +2110,9 @@ static void ExplainNode( case T_WorkTableScan: case T_ForeignScan: case T_VecForeignScan: - ExplainScanTarget((Scan*)plan, es); + if (((Scan *) plan)->scanrelid > 0) { + ExplainScanTarget((Scan*)plan, es); + } break; case T_TrainModel: appendStringInfo(es->str, " - %s", sname); @@ -3330,7 +3340,7 @@ static void show_plan_tlist(PlanState* planstate, List* ancestors, ExplainState* if (IsA(plan, ForeignScan) || IsA(plan, VecForeignScan)) { ForeignScan* fscan = (ForeignScan*)plan; - if (IsSpecifiedFDWFromRelid(fscan->scan_relid, GC_FDW)) { + if (OidIsValid(fscan->scan_relid) && IsSpecifiedFDWFromRelid(fscan->scan_relid, GC_FDW)) { List* str_targetlist = get_str_targetlist(fscan->fdw_private); if (str_targetlist != NULL) result = str_targetlist; @@ -7449,9 +7459,33 @@ static void show_foreignscan_info(ForeignScanState* fsstate, ExplainState* es) { FdwRoutine* fdwroutine = fsstate->fdwroutine; + /* if we want to know what the remote plan like, numbering this node to find it quickly later. */ + if (u_sess->attr.attr_common.show_fdw_remote_plan) { + es->es_frs.node_num++; + ExplainPropertyInteger("Node ID", es->es_frs.node_num , es); + } + /* Let the FDW emit whatever fields it wants */ if (NULL != fdwroutine && NULL != fdwroutine->ExplainForeignScan) fdwroutine->ExplainForeignScan(fsstate, es); + + /* Let the FDW get the remote plan */ + if (u_sess->attr.attr_common.show_fdw_remote_plan) { + /* Set a title */ + if (es->es_frs.node_num == 1) { + es->es_frs.str=makeStringInfo(); + appendStringInfo(es->es_frs.str, "\nFDW remote plans:\n"); + } + + appendStringInfo(es->es_frs.str, "Node %d: ", es->es_frs.node_num); + + /* Let the FDW emit whatever fields it wants */ + if (NULL != fdwroutine && NULL != fdwroutine->ExplainForeignScanRemote) { + fdwroutine->ExplainForeignScanRemote(fsstate, es); + } else { + appendStringInfo(es->es_frs.str, "No remote plan information.\n"); + } + } } /* diff --git a/src/gausskernel/optimizer/commands/tablecmds.cpp b/src/gausskernel/optimizer/commands/tablecmds.cpp index b6d1ea971..655c66f7c 100644 --- a/src/gausskernel/optimizer/commands/tablecmds.cpp +++ b/src/gausskernel/optimizer/commands/tablecmds.cpp @@ -17810,13 +17810,11 @@ static void ATExecGenericOptions(Relation rel, List* options) simple_heap_update(ftrel, &tuple->t_self, tuple); CatalogUpdateIndexes(ftrel, tuple); -#ifdef ENABLE_MOT /* * Invalidate relcache so that all sessions will refresh any cached plans * that might depend on the old options. */ CacheInvalidateRelcache(rel); -#endif heap_close(ftrel, RowExclusiveLock); diff --git a/src/gausskernel/optimizer/path/joinpath.cpp b/src/gausskernel/optimizer/path/joinpath.cpp index 2454b94d9..977c80f6e 100755 --- a/src/gausskernel/optimizer/path/joinpath.cpp +++ b/src/gausskernel/optimizer/path/joinpath.cpp @@ -14,6 +14,7 @@ * ------------------------------------------------------------------------- */ #include "postgres.h" +#include "foreign/fdwapi.h" #include "knl/knl_variable.h" #include @@ -338,6 +339,15 @@ void add_paths_to_joinrel(PlannerInfo* root, RelOptInfo* joinrel, RelOptInfo* ou list_free_ext(mergejoin_hint); list_free_ext(hashjoin_hint); + /* + * 5. If inner and outer relations are foreign tables (or joins) belonging + * to the same server and assigned to the same user to check access + * permissions as, give the FDW a chance to push down joins. + */ + if (joinrel->fdwroutine && joinrel->fdwroutine->GetForeignJoinPaths) { + joinrel->fdwroutine->GetForeignJoinPaths(root, joinrel, outerrel, innerrel, jointype, sjinfo, restrictlist); + } + /* * Finally, give extensions a change to manipulate the path list. */ diff --git a/src/gausskernel/optimizer/path/pathkeys.cpp b/src/gausskernel/optimizer/path/pathkeys.cpp index 2231c5f46..ee680c27a 100644 --- a/src/gausskernel/optimizer/path/pathkeys.cpp +++ b/src/gausskernel/optimizer/path/pathkeys.cpp @@ -32,8 +32,6 @@ #include "utils/lsyscache.h" static PathKey* makePathKey(EquivalenceClass* eclass, Oid opfamily, int strategy, bool nulls_first); -static PathKey* make_canonical_pathkey( - PlannerInfo* root, EquivalenceClass* eclass, Oid opfamily, int strategy, bool nulls_first); static bool pathkey_is_redundant(PathKey* new_pathkey, List* pathkeys, bool predpush = false); static bool right_merge_direction(PlannerInfo* root, PathKey* pathkey); @@ -68,7 +66,7 @@ static PathKey* makePathKey(EquivalenceClass* eclass, Oid opfamily, int strategy * Note that this function must not be used until after we have completed * merging EquivalenceClasses. */ -static PathKey* make_canonical_pathkey( +PathKey* make_canonical_pathkey( PlannerInfo* root, EquivalenceClass* eclass, Oid opfamily, int strategy, bool nulls_first) { PathKey* pk = NULL; diff --git a/src/gausskernel/optimizer/plan/createplan.cpp b/src/gausskernel/optimizer/plan/createplan.cpp index 333cf3c02..6a127a5a0 100755 --- a/src/gausskernel/optimizer/plan/createplan.cpp +++ b/src/gausskernel/optimizer/plan/createplan.cpp @@ -3632,13 +3632,23 @@ static ForeignScan* create_foreignscan_plan(PlannerInfo* root, ForeignPath* best Index scan_relid = rel->relid; RangeTblEntry* rte = NULL; RangeTblEntry* target_rte = NULL; + Plan* outer_plan = NULL; int i; - /* it should be a base rel... */ - Assert(scan_relid > 0); - Assert(rel->rtekind == RTE_RELATION); - rte = planner_rt_fetch(scan_relid, root); - Assert(rte->rtekind == RTE_RELATION); + /* transform the child path if any */ + if (best_path->fdw_outerpath) { + outer_plan = create_plan_recurse(root, best_path->fdw_outerpath); + } + + /* + * If we're scanning a base relation, fetch its OID. (Irrelevant if + * scanning a join relation.) + */ + if (scan_relid > 0) { + Assert(rel->rtekind == RTE_RELATION); + rte = planner_rt_fetch(scan_relid, root); + Assert(rte->rtekind == RTE_RELATION); + } /* * Sort clauses into best execution order. We do this first since the FDW @@ -3655,7 +3665,7 @@ static ForeignScan* create_foreignscan_plan(PlannerInfo* root, ForeignPath* best * For now, error table only support insert statement. */ target_rte = rte; - if (root->parse->commandType == CMD_INSERT) { + if (scan_relid > 0 && root->parse->commandType == CMD_INSERT) { // Confirm whether exists error table. DefElem* def = GetForeignTableOptionByName(rte->relid, optErrorRel); if (def != NULL) { @@ -3686,19 +3696,36 @@ static ForeignScan* create_foreignscan_plan(PlannerInfo* root, ForeignPath* best * Assign task to the datanodes where target table exists so that * the error information will be saved only in these nodes. */ - scan_plan = rel->fdwroutine->GetForeignPlan(root, rel, target_rte->relid, best_path, tlist, scan_clauses); - scan_plan->scan_relid = rte->relid; + scan_plan = rel->fdwroutine->GetForeignPlan(root, rel, + target_rte == NULL ? scan_relid : target_rte->relid, + best_path, tlist, scan_clauses, outer_plan); - char locator_type = GetLocatorType(rte->relid); + scan_plan->scan_relid = (rte == NULL ? scan_relid : rte->relid); + /* Copy foreign server OID; likewise, no need to make FDW do this */ + scan_plan->fs_server = rel->serverid; + + /* + * Likewise, copy the relids that are represented by this foreign scan. An + * upper rel doesn't have relids set, but it covers all the base relations + * participating in the underlying scan, so use root's all_baserels. + */ + if (rel->reloptkind == RELOPT_UPPER_REL) { + scan_plan->fs_relids = root->all_baserels; + } else { + scan_plan->fs_relids = best_path->path.parent->relids; + } + + char locator_type = (rte == NULL ? LOCATOR_TYPE_NONE : GetLocatorType(rte->relid)); /* * In order to support error table in multi-nodegroup situation, * execute foreign scan only on DNs where the insert-targeted table exists. * Secondly, find the DNs where the target table lies and set them as the exec_nodes. */ - exec_nodes = GetRelationNodesByQuals( - (void*)root->parse, target_rte->relid, scan_relid, (Node*)scan_clauses, RELATION_ACCESS_READ, NULL); - + if (scan_relid > 0) { + exec_nodes = GetRelationNodesByQuals((void *)root->parse, target_rte->relid, + scan_relid, (Node *)scan_clauses, RELATION_ACCESS_READ, NULL); + } if (exec_nodes == NULL) { /* foreign scan is executed on installation group */ exec_nodes = ng_get_installation_group_exec_node(); @@ -3723,6 +3750,15 @@ static ForeignScan* create_foreignscan_plan(PlannerInfo* root, ForeignPath* best /* Copy cost data from Path to Plan; no need to make FDW do this */ copy_path_costsize(&scan_plan->scan.plan, &best_path->path); + /* + * If this is a foreign join, and to make it valid to push down we had to + * assume that the current user is the same as some user explicitly named + * in the query, mark the finished plan as depending on the current user. + */ + if (rel->useridiscurrent) { + root->glob->dependsOnRole = true; + } + /* * Replace any outer-relation variables with nestloop params in the qual * and fdw_exprs expressions. We do this last so that the FDW doesn't @@ -3731,8 +3767,9 @@ static ForeignScan* create_foreignscan_plan(PlannerInfo* root, ForeignPath* best * wouldn't work.) */ if (best_path->path.param_info) { - scan_plan->scan.plan.qual = (List*)replace_nestloop_params(root, (Node*)scan_plan->scan.plan.qual); - scan_plan->fdw_exprs = (List*)replace_nestloop_params(root, (Node*)scan_plan->fdw_exprs); + scan_plan->scan.plan.qual = (List *)replace_nestloop_params(root, (Node *)scan_plan->scan.plan.qual); + scan_plan->fdw_exprs = (List *)replace_nestloop_params(root, (Node *)scan_plan->fdw_exprs); + scan_plan->fdw_recheck_quals = (List *)replace_nestloop_params(root, (Node *)scan_plan->fdw_recheck_quals); } /* @@ -3741,6 +3778,10 @@ static ForeignScan* create_foreignscan_plan(PlannerInfo* root, ForeignPath* best * out of the API presented to FDWs. */ scan_plan->fsSystemCol = false; + if (scan_relid == 0) { + return scan_plan; + } + for (i = rel->min_attr; i < 0; i++) { if (!bms_is_empty(rel->attr_needed[i - rel->min_attr])) { scan_plan->fsSystemCol = true; @@ -5921,8 +5962,8 @@ static WorkTableScan* make_worktablescan(List* qptlist, List* qpqual, Index scan return node; } -ForeignScan* make_foreignscan( - List* qptlist, List* qpqual, Index scanrelid, List* fdw_exprs, List* fdw_private, RemoteQueryExecType type) +ForeignScan *make_foreignscan(List *qptlist, List *qpqual, Index scanrelid, List *fdw_exprs, List *fdw_private, + List *fdw_scan_tlist, List *fdw_recheck_quals, Plan *outer_plan, RemoteQueryExecType type) { ForeignScan* node = makeNode(ForeignScan); @@ -5931,17 +5972,29 @@ ForeignScan* make_foreignscan( /* cost will be filled in by create_foreignscan_plan */ plan->targetlist = qptlist; plan->qual = qpqual; - plan->lefttree = NULL; + plan->lefttree = outer_plan; plan->righttree = NULL; plan->exec_type = type; plan->distributed_keys = NIL; #ifdef ENABLE_MULTIPLE_NODES - plan->distributed_keys = lappend(plan->distributed_keys, makeVar(0, InvalidAttrNumber, InvalidOid, -1, InvalidOid, 0)); #endif node->scan.scanrelid = scanrelid; + + /* these may be overridden by the FDW's PlanDirectModify callback. */ + node->operation = CMD_SELECT; + node->resultRelation = 0; + + /* fs_server will be filled in by create_foreignscan_plan */ + node->fs_server = InvalidOid; node->fdw_exprs = fdw_exprs; node->fdw_private = fdw_private; + node->fdw_scan_tlist = fdw_scan_tlist; + node->fdw_recheck_quals = fdw_recheck_quals; + + /* fs_relids will be filled in by create_foreignscan_plan */ + node->fs_relids = NULL; + /* fsSystemCol will be filled in by create_foreignscan_plan */ node->fsSystemCol = false; @@ -8298,8 +8351,10 @@ static Plan* FindForeignScan(Plan* plan) switch (nodeTag(plan)) { case T_ForeignScan: { ForeignScan* fscan = (ForeignScan*)plan; - if (isObsOrHdfsTableFormTblOid(fscan->scan_relid) || IS_LOGFDW_FOREIGN_TABLE(fscan->scan_relid) || - IS_POSTGRESFDW_FOREIGN_TABLE(fscan->scan_relid)) { + if (fscan->scan_relid > 0 && + (isObsOrHdfsTableFormTblOid(fscan->scan_relid) || + IS_LOGFDW_FOREIGN_TABLE(fscan->scan_relid) || + IS_POSTGRESFDW_FOREIGN_TABLE(fscan->scan_relid))) { return NULL; } return plan; @@ -8709,7 +8764,7 @@ ModifyTable* make_modifytable(CmdType operation, bool canSetTag, List* resultRel Plan* subplan = (Plan*)(linitial(subplans)); ForeignScan* fscan = NULL; if ((fscan = (ForeignScan*)FindForeignScan(subplan)) != NULL) { - if (!CheckSupportedFDWType(fscan->scan_relid)) + if (!CheckSupportedFDWType(fscan->fs_server, true)) ereport(ERROR, (errmodule(MOD_OPT), errcode(ERRCODE_FEATURE_NOT_SUPPORTED), diff --git a/src/gausskernel/optimizer/plan/planner.cpp b/src/gausskernel/optimizer/plan/planner.cpp index 8fcee43e3..54e1345ae 100755 --- a/src/gausskernel/optimizer/plan/planner.cpp +++ b/src/gausskernel/optimizer/plan/planner.cpp @@ -1191,6 +1191,22 @@ void recover_set_hint(int savedNestLevel) u_sess->attr.attr_common.node_name = ""; } +static bool has_foreign_table_in_rtable(Query* query) +{ + ListCell* lc = NULL; + RangeTblEntry* rte = NULL; + foreach(lc, query->rtable) { + rte = (RangeTblEntry*)lfirst(lc); + if (rte->rtekind != RTE_RELATION) { + continue; + } + if (rte->relkind == RELKIND_FOREIGN_TABLE) { + return true; + } + } + return false; +} + /* -------------------- * subquery_planner * Invokes the planner on a subquery. We recurse to here for each @@ -1757,6 +1773,9 @@ Plan* subquery_planner(PlannerGlobal* glob, Query* parse, PlannerInfo* parent_ro context.func_exprs = NIL; support_rewrite = false; } + if (has_foreign_table_in_rtable(root->parse)) { + support_rewrite = false; + } if (support_rewrite) { reduce_inequality_fulljoins(root); DEBUG_QRW("After full join conversion"); @@ -2628,6 +2647,7 @@ static Plan* grouping_planner(PlannerInfo* root, double tuple_fraction) char PlanContextName[NAMEDATALEN] = {0}; MemoryContext PlanGenerateContext = NULL; MemoryContext oldcontext = NULL; + FDWUpperRelCxt* ufdwCxt = NULL; errno_t rc = EOK; /* @@ -2723,6 +2743,7 @@ static Plan* grouping_planner(PlannerInfo* root, double tuple_fraction) List* sub_tlist = NIL; double sub_limit_tuples; AttrNumber* groupColIdx = NULL; + bool need_try_fdw_plan = false; bool need_tlist_eval = true; Path* cheapest_path = NULL; Path* sorted_path = NULL; @@ -3095,6 +3116,11 @@ static Plan* grouping_planner(PlannerInfo* root, double tuple_fraction) rel_info = best_path->parent; + /* if it is an foreign rel, try to create foreign plan continue */ + if (rel_info != NULL && rel_info->fdwroutine != NULL) { + need_try_fdw_plan = true; + } + /* * For dummy plan, we should return it quickly. Meanwhile, we should * eliminate agg node, or an error will thrown out later @@ -3229,6 +3255,10 @@ static Plan* grouping_planner(PlannerInfo* root, double tuple_fraction) locate_grouping_columns(root, tlist, result_plan->targetlist, groupColIdx); } + + if (need_try_fdw_plan) { + ufdwCxt = InitFDWUpperPlan(root, rel_info, result_plan); + } #ifdef ENABLE_MULTIPLE_NODES /* shuffle to another node group in FORCE mode (CNG_MODE_FORCE) */ if (IS_STREAM_PLAN && !parse->hasForUpdate && @@ -3838,6 +3868,18 @@ static Plan* grouping_planner(PlannerInfo* root, double tuple_fraction) result_plan = (Plan*)make_append(plans, tlist); } } + + if (parse->hasAggs || parse->groupClause != NIL || parse->groupingSets || parse->havingQual != NULL) { + if (ufdwCxt != NULL && ufdwCxt->state != FDW_UPPER_REL_END) { + GroupPathExtraData *extra = (GroupPathExtraData*)palloc0(sizeof(GroupPathExtraData)); + extra->havingQual = parse->havingQual; + extra->targetList = parse->targetList; + extra->partial_costs_set = false; + ufdwCxt->groupExtra = extra; + AdvanceFDWUpperPlan(ufdwCxt, UPPERREL_GROUP_AGG, result_plan); + } + } + #ifdef PGXC /* * Grouping will certainly not increase the number of rows @@ -4000,6 +4042,10 @@ static Plan* grouping_planner(PlannerInfo* root, double tuple_fraction) } #endif } + + if (ufdwCxt != NULL && ufdwCxt->state != FDW_UPPER_REL_END) { + AdvanceFDWUpperPlan(ufdwCxt, UPPERREL_WINDOW, result_plan); + } } (void)MemoryContextSwitchTo(oldcontext); } /* end of if (setOperations) */ @@ -4194,6 +4240,10 @@ static Plan* grouping_planner(PlannerInfo* root, double tuple_fraction) } #endif } + + if (ufdwCxt != NULL && ufdwCxt->state != FDW_UPPER_REL_END) { + AdvanceFDWUpperPlan(ufdwCxt, UPPERREL_WINDOW, result_plan); + } } /* @@ -4218,6 +4268,10 @@ static Plan* grouping_planner(PlannerInfo* root, double tuple_fraction) #ifndef PGXC current_pathkeys = NIL; #endif + + if (ufdwCxt != NULL && ufdwCxt->state != FDW_UPPER_REL_END) { + AdvanceFDWUpperPlan(ufdwCxt, UPPERREL_ROWMARKS, result_plan); + } } /* @@ -4246,6 +4300,12 @@ static Plan* grouping_planner(PlannerInfo* root, double tuple_fraction) #endif /* PGXC */ current_pathkeys = root->sort_pathkeys; } + + if (ufdwCxt != NULL && ufdwCxt->state != FDW_UPPER_REL_END) { + ufdwCxt->orderExtra = (OrderPathExtraData*)palloc(sizeof(OrderPathExtraData)); + ufdwCxt->orderExtra->targetList = result_plan->targetlist; + AdvanceFDWUpperPlan(ufdwCxt, UPPERREL_ORDERED, result_plan); + } } /* @@ -4275,6 +4335,27 @@ static Plan* grouping_planner(PlannerInfo* root, double tuple_fraction) if (IS_PGXC_COORDINATOR && !IsConnFromCoord() && !IS_STREAM) result_plan = (Plan*)create_remotelimit_plan(root, result_plan); #endif /* PGXC */ + + if (ufdwCxt != NULL && ufdwCxt->state != FDW_UPPER_REL_END) { + ufdwCxt->finalExtra->limit_needed = true; + ufdwCxt->finalExtra->limit_tuples = limit_tuples; + ufdwCxt->finalExtra->count_est = count_est; + ufdwCxt->finalExtra->offset_est = offset_est; + AdvanceFDWUpperPlan(ufdwCxt, UPPERREL_LIMIT, result_plan); + } + } + + if (ufdwCxt != NULL && ufdwCxt->state != FDW_UPPER_REL_END) { + Assert(!IS_STREAM_PLAN); + ufdwCxt->finalExtra->targetList = result_plan->targetlist; + + AdvanceFDWUpperPlan(ufdwCxt, UPPERREL_FINAL, result_plan); + + /* apply plan. */ + if (ufdwCxt->resultPlan != NULL) { + result_plan = ufdwCxt->resultPlan; + current_pathkeys = ufdwCxt->resultPathKeys; + } } #ifdef STREAMPLAN @@ -9033,6 +9114,12 @@ static bool CheckWindowsAggExpr(Plan* resultPlan, bool check_rescan, VectorPlanC static bool CheckForeignScanExpr(Plan* resultPlan, VectorPlanContext* planContext) { ForeignScan* fscan = (ForeignScan*)resultPlan; + + /* only support foreign scan for base rel, not support for join\agg\etc. */ + if (!OidIsValid(fscan->scan_relid)) { + return false; + } + if (IsSpecifiedFDWFromRelid(fscan->scan_relid, GC_FDW) || IsSpecifiedFDWFromRelid(fscan->scan_relid, LOG_FDW)) { resultPlan->vec_output = false; diff --git a/src/gausskernel/optimizer/plan/setrefs.cpp b/src/gausskernel/optimizer/plan/setrefs.cpp index bb5bdaf2c..14214f249 100644 --- a/src/gausskernel/optimizer/plan/setrefs.cpp +++ b/src/gausskernel/optimizer/plan/setrefs.cpp @@ -133,6 +133,9 @@ static bool extract_query_dependencies_walker(Node* node, PlannerInfo* context); static void fix_skew_quals(PlannerInfo* root, Plan* plan, indexed_tlist* subplan_itlist, int rtoffset); static void fix_assign_targetlists(ModifyTable* plan, Plan* subplan, List* resultRelation); +static Relids offset_relid_set(Relids relids, int rtoffset); +static void set_foreignscan_references(PlannerInfo *root, ForeignScan *fscan, int rtoffset); + #ifdef PGXC /* References for remote plans */ static List* fix_remote_expr(PlannerInfo* root, List* clauses, indexed_tlist* base_itlist, Index newrelid, int rtoffset, @@ -496,22 +499,7 @@ static Plan* set_plan_refs(PlannerInfo* root, Plan* plan, int rtoffset) break; case T_ForeignScan: case T_VecForeignScan: { - ForeignScan* splan = (ForeignScan*)plan; - - splan->scan.scanrelid += rtoffset; - splan->scan.plan.targetlist = fix_scan_list(root, splan->scan.plan.targetlist, rtoffset); - if (splan->scan.plan.distributed_keys != NIL) { - splan->scan.plan.distributed_keys = fix_scan_list(root, splan->scan.plan.distributed_keys, rtoffset); - } - splan->scan.plan.var_list = fix_scan_list(root, splan->scan.plan.var_list, rtoffset); - splan->scan.plan.qual = fix_scan_list(root, splan->scan.plan.qual, rtoffset); - splan->fdw_exprs = fix_scan_list(root, splan->fdw_exprs, rtoffset); - - if (isObsOrHdfsTableFormTblOid(splan->scan_relid)) { - DefElem* private_data = (DefElem*)linitial(splan->fdw_private); - DfsPrivateItem* item = (DfsPrivateItem*)private_data->arg; - fix_dfs_private_item(root, rtoffset, item); - } + set_foreignscan_references(root, (ForeignScan*) plan, rtoffset); } break; case T_NestLoop: case T_VecNestLoop: @@ -2727,3 +2715,75 @@ void pgxc_set_agg_references(PlannerInfo* root, Agg* aggplan) return; } #endif /* PGXC */ + +/* + * offset_relid_set + * Apply rtoffset to the members of a Relids set. + */ +static Relids offset_relid_set(Relids relids, int rtoffset) +{ + Relids result = NULL; + int rtindex; + + /* If there's no offset to apply, we needn't recompute the value */ + if (rtoffset == 0) { + return relids; + } + rtindex = -1; + while ((rtindex = bms_next_member(relids, rtindex)) >= 0) { + result = bms_add_member(result, rtindex + rtoffset); + } + return result; +} + +/* + * set_foreignscan_references + * Do set_plan_references processing on a ForeignScan + */ +static void set_foreignscan_references(PlannerInfo *root, ForeignScan *fscan, int rtoffset) +{ + /* Adjust scanrelid if it's valid */ + if (fscan->scan.scanrelid > 0) { + fscan->scan.scanrelid += rtoffset; + } + + if (fscan->fdw_scan_tlist != NIL || fscan->scan.scanrelid == 0) { + /* + * Adjust tlist, qual, fdw_exprs, fdw_recheck_quals to reference + * foreign scan tuple + */ + indexed_tlist *itlist = build_tlist_index(fscan->fdw_scan_tlist); + + fscan->scan.plan.targetlist = + (List *)fix_upper_expr(root, (Node *)fscan->scan.plan.targetlist, itlist, INDEX_VAR, rtoffset); + fscan->scan.plan.qual = + (List *)fix_upper_expr(root, (Node *)fscan->scan.plan.qual, itlist, INDEX_VAR, rtoffset); + fscan->fdw_exprs = (List *)fix_upper_expr(root, (Node *)fscan->fdw_exprs, itlist, INDEX_VAR, rtoffset); + fscan->fdw_recheck_quals = + (List *)fix_upper_expr(root, (Node *)fscan->fdw_recheck_quals, itlist, INDEX_VAR, rtoffset); + pfree(itlist); + /* fdw_scan_tlist itself just needs fix_scan_list() adjustments */ + fscan->fdw_scan_tlist = fix_scan_list(root, fscan->fdw_scan_tlist, rtoffset); + } else { + /* + * Adjust tlist, qual, fdw_exprs, fdw_recheck_quals in the standard + * way + */ + fscan->scan.plan.targetlist = fix_scan_list(root, fscan->scan.plan.targetlist, rtoffset); + fscan->scan.plan.qual = fix_scan_list(root, fscan->scan.plan.qual, rtoffset); + fscan->fdw_exprs = fix_scan_list(root, fscan->fdw_exprs, rtoffset); + fscan->fdw_recheck_quals = fix_scan_list(root, fscan->fdw_recheck_quals, rtoffset); + } + + if (fscan->scan.plan.distributed_keys != NIL) { + fscan->scan.plan.distributed_keys = fix_scan_list(root, fscan->scan.plan.distributed_keys, rtoffset); + } + + fscan->fs_relids = offset_relid_set(fscan->fs_relids, rtoffset); + + if (fscan->scan_relid > 0 && isObsOrHdfsTableFormTblOid(fscan->scan_relid)) { + DefElem* private_data = (DefElem*)linitial(fscan->fdw_private); + DfsPrivateItem* item = (DfsPrivateItem*)private_data->arg; + fix_dfs_private_item(root, rtoffset, item); + } +} diff --git a/src/gausskernel/optimizer/plan/streamplan_utils.cpp b/src/gausskernel/optimizer/plan/streamplan_utils.cpp index 6f31a0231..333d0bc0c 100755 --- a/src/gausskernel/optimizer/plan/streamplan_utils.cpp +++ b/src/gausskernel/optimizer/plan/streamplan_utils.cpp @@ -56,9 +56,12 @@ List* check_op_list_template(Plan* result_plan, List* (*check_eval)(Node*)) case T_ForeignScan: case T_VecForeignScan: { ForeignScan* foreignScan = (ForeignScan*)result_plan; + if (!OidIsValid(foreignScan->scan_relid)) { + break; + } + ForeignTable* ftbl = NULL; ForeignServer* fsvr = NULL; - ftbl = GetForeignTable(foreignScan->scan_relid); AssertEreport(NULL != ftbl, MOD_OPT, "The foreign table is NULL"); fsvr = GetForeignServer(ftbl->serverid); diff --git a/src/gausskernel/optimizer/util/pathnode.cpp b/src/gausskernel/optimizer/util/pathnode.cpp index 0195c831f..6670f5c5b 100755 --- a/src/gausskernel/optimizer/util/pathnode.cpp +++ b/src/gausskernel/optimizer/util/pathnode.cpp @@ -3779,7 +3779,7 @@ Path* create_worktablescan_path(PlannerInfo* root, RelOptInfo* rel) * way to calculate them in core. */ ForeignPath* create_foreignscan_path(PlannerInfo* root, RelOptInfo* rel, Cost startup_cost, Cost total_cost, - List* pathkeys, Relids required_outer, List* fdw_private, int dop) + List* pathkeys, Relids required_outer, Path* fdw_outerpath, List* fdw_private, int dop) { ForeignPath* pathnode = makeNode(ForeignPath); @@ -3793,14 +3793,14 @@ ForeignPath* create_foreignscan_path(PlannerInfo* root, RelOptInfo* rel, Cost st pathnode->path.locator_type = rel->locator_type; pathnode->path.exec_type = SetBasePathExectype(root, rel); pathnode->path.stream_cost = 0; - + pathnode->fdw_outerpath = fdw_outerpath; pathnode->fdw_private = fdw_private; pathnode->path.dop = 1; dop = SET_DOP(dop); /* Create a parallel foreignscan path. */ - if (root->parse && dop > 1) { + if (root->parse && dop > 1 && IS_SIMPLE_REL(rel)) { RangeTblEntry* source = rt_fetch(rel->relid, root->parse->rtable); AssertEreport(NULL != source, MOD_OPT_JOIN, "There should be rtable in table list"); @@ -3824,6 +3824,8 @@ ForeignPath* create_foreignscan_path(PlannerInfo* root, RelOptInfo* rel, Cost st * It is comfortable to add smp foreign scan for this scenario. * HDFS Server: we don't add smp feature for this kind of server. No reason. * Others: Keep constant with the original logic. + * + * Notice: Only Base scan is supported, join\agg is not supported. */ if (T_OBS_SERVER == serverType) { if ((CMD_SELECT == root->parse->commandType || CMD_INSERT == root->parse->commandType) && diff --git a/src/gausskernel/optimizer/util/plancat.cpp b/src/gausskernel/optimizer/util/plancat.cpp index f91a0e608..e7d8b9ada 100755 --- a/src/gausskernel/optimizer/util/plancat.cpp +++ b/src/gausskernel/optimizer/util/plancat.cpp @@ -716,10 +716,13 @@ void get_relation_info(PlannerInfo* root, Oid relationObjectId, bool inhparent, setRelStoreInfo(rel, relation); /* Grab the fdwroutine info using the relcache, while we have it */ - if (relation->rd_rel->relkind == RELKIND_FOREIGN_TABLE || relation->rd_rel->relkind == RELKIND_STREAM) + if (relation->rd_rel->relkind == RELKIND_FOREIGN_TABLE || relation->rd_rel->relkind == RELKIND_STREAM) { + rel->serverid = GetForeignServerIdByRelId(RelationGetRelid(relation)); rel->fdwroutine = GetFdwRoutineForRelation(relation, true); - else + } else { + rel->serverid = InvalidOid; rel->fdwroutine = NULL; + } heap_close(relation, NoLock); diff --git a/src/gausskernel/optimizer/util/relnode.cpp b/src/gausskernel/optimizer/util/relnode.cpp index e03ac0539..23bd0919a 100755 --- a/src/gausskernel/optimizer/util/relnode.cpp +++ b/src/gausskernel/optimizer/util/relnode.cpp @@ -201,6 +201,9 @@ RelOptInfo* build_simple_rel(PlannerInfo* root, int relid, RelOptKind reloptkind rel->subplan = NULL; rel->subroot = NULL; rel->subplan_params = NIL; + rel->serverid = InvalidOid; + rel->userid = rte->checkAsUser; + rel->useridiscurrent = false; rel->fdwroutine = NULL; rel->fdw_private = NULL; rel->baserestrictinfo = NIL; @@ -455,6 +458,45 @@ RelOptInfo* find_join_rel(PlannerInfo* root, Relids relids) return NULL; } +/* + * set_foreign_rel_properties + * Set up foreign-join fields if outer and inner relation are foreign + * tables (or joins) belonging to the same server and assigned to the same + * user to check access permissions as. + * + * In addition to an exact match of userid, we allow the case where one side + * has zero userid (implying current user) and the other side has explicit + * userid that happens to equal the current user; but in that case, pushdown of + * the join is only valid for the current user. The useridiscurrent field + * records whether we had to make such an assumption for this join or any + * sub-join. + * + * Otherwise these fields are left invalid, so GetForeignJoinPaths will not be + * called for the join relation. + * + */ +static void set_foreign_rel_properties(RelOptInfo *joinrel, RelOptInfo *outer_rel, RelOptInfo *inner_rel) +{ + if (OidIsValid(outer_rel->serverid) && inner_rel->serverid == outer_rel->serverid) { + if (inner_rel->userid == outer_rel->userid) { + joinrel->serverid = outer_rel->serverid; + joinrel->userid = outer_rel->userid; + joinrel->useridiscurrent = outer_rel->useridiscurrent || inner_rel->useridiscurrent; + joinrel->fdwroutine = outer_rel->fdwroutine; + } else if (!OidIsValid(inner_rel->userid) && outer_rel->userid == GetUserId()) { + joinrel->serverid = outer_rel->serverid; + joinrel->userid = outer_rel->userid; + joinrel->useridiscurrent = true; + joinrel->fdwroutine = outer_rel->fdwroutine; + } else if (!OidIsValid(outer_rel->userid) && inner_rel->userid == GetUserId()) { + joinrel->serverid = outer_rel->serverid; + joinrel->userid = inner_rel->userid; + joinrel->useridiscurrent = true; + joinrel->fdwroutine = outer_rel->fdwroutine; + } + } +} + void remove_join_rel(PlannerInfo *root, RelOptInfo *rel) { root->join_rel_level[root->join_cur_level] = @@ -631,10 +673,14 @@ RelOptInfo* build_join_rel(PlannerInfo* root, Relids joinrelids, RelOptInfo* out joinrel->joininfo = NIL; joinrel->has_eclass_joins = false; joinrel->varratio = NIL; - if (IsLocatorReplicated(inner_rel->locator_type) && IsLocatorReplicated(outer_rel->locator_type)) + if (IsLocatorReplicated(inner_rel->locator_type) && IsLocatorReplicated(outer_rel->locator_type)) { joinrel->locator_type = LOCATOR_TYPE_REPLICATED; - else + } else { joinrel->locator_type = LOCATOR_TYPE_NONE; + } + + /* Compute information relevant to the foreign relations. */ + set_foreign_rel_properties(joinrel, outer_rel, inner_rel); /* * Create a new tlist containing just the vars that need to be output from diff --git a/src/gausskernel/runtime/executor/execScan.cpp b/src/gausskernel/runtime/executor/execScan.cpp index b1bfd81fc..bf4ebf240 100644 --- a/src/gausskernel/runtime/executor/execScan.cpp +++ b/src/gausskernel/runtime/executor/execScan.cpp @@ -43,8 +43,17 @@ static TupleTableSlot* ExecScanFetch(ScanState* node, ExecScanAccessMtd access_m */ Index scan_rel_id = ((Scan*)node->ps.plan)->scanrelid; - Assert(scan_rel_id > 0); - if (estate->es_epqTupleSet[scan_rel_id - 1]) { + if (scan_rel_id == 0) { + /* + * This is a ForeignScan which has pushed down a + * join to the remote side. The recheck method is responsible not + * only for rechecking the scan/join quals but also for storing + * the correct tuple in the slot. + * + * currently not support. + */ + Assert(false); + } else if (estate->es_epqTupleSet[scan_rel_id - 1]) { TupleTableSlot* slot = node->ss_ScanTupleSlot; /* Return empty slot if we already returned a tuple */ diff --git a/src/gausskernel/runtime/executor/nodeForeignscan.cpp b/src/gausskernel/runtime/executor/nodeForeignscan.cpp index a07ae9deb..b04b858ec 100644 --- a/src/gausskernel/runtime/executor/nodeForeignscan.cpp +++ b/src/gausskernel/runtime/executor/nodeForeignscan.cpp @@ -71,7 +71,8 @@ static TupleTableSlot* ForeignNext(ForeignScanState* node) if (plan->fsSystemCol && !TupIsNull(slot)) { HeapTuple tup = ExecMaterializeSlot(slot); - tup->t_tableOid = RelationGetRelid(node->ss.ss_currentRelation); + if (!OidIsValid(tup->t_tableOid)) + tup->t_tableOid = RelationGetRelid(node->ss.ss_currentRelation); tup->t_bucketId = RelationGetBktid(node->ss.ss_currentRelation); #ifdef PGXC tup->t_xc_node_id = u_sess->pgxc_cxt.PGXCNodeIdentifier; @@ -86,7 +87,6 @@ static TupleTableSlot* ForeignNext(ForeignScanState* node) */ static bool ForeignRecheck(ForeignScanState* node, TupleTableSlot* slot) { - /* There are no access-method-specific conditions to recheck. */ return true; } @@ -111,8 +111,10 @@ TupleTableSlot* ExecForeignScan(ForeignScanState* node) ForeignScanState* ExecInitForeignScan(ForeignScan* node, EState* estate, int eflags) { ForeignScanState* scanstate = NULL; - Relation currentRelation; + Relation currentRelation = NULL; FdwRoutine* fdwroutine = NULL; + Index scanrelid = node->scan.scanrelid; + int tlistvarno; errno_t rc; /* check for unsupported flags */ @@ -159,25 +161,65 @@ ForeignScanState* ExecInitForeignScan(ForeignScan* node, EState* estate, int efl /* * open the base relation and acquire appropriate lock on it. + * or it is a foreign join, we should recreate resultdesc and project info */ - if (node->rel == NULL) { - currentRelation = ExecOpenScanRelation(estate, node->scan.scanrelid); - } else { - if (node->in_compute_pool == false) { + if (scanrelid > 0) { + if (node->rel == NULL) { currentRelation = ExecOpenScanRelation(estate, node->scan.scanrelid); } else { - currentRelation = get_rel_from_meta(node->rel); - scanstate->options = node->options; + if (node->in_compute_pool == false) { + currentRelation = ExecOpenScanRelation(estate, node->scan.scanrelid); + } else { + currentRelation = get_rel_from_meta(node->rel); + scanstate->options = node->options; + } } + scanstate->ss.ss_currentRelation = currentRelation; + + /* + * Acquire function pointers from the FDW's handler, and init fdw_state. + */ + if (!node->rel) { + fdwroutine = GetFdwRoutineForRelation(currentRelation, true); + } else { + ForeignDataWrapper* fdw = NULL; + if (node->options->stype == T_OBS_SERVER || node->options->stype == T_HDFS_SERVER) { + fdw = GetForeignDataWrapperByName(HDFS_FDW, false); + } else { + fdw = GetForeignDataWrapperByName(DIST_FDW, false); + } + fdwroutine = GetFdwRoutine(fdw->fdwhandler); + + /* Save the data for later reuse in LocalMyDBCacheMemCxt */ + FdwRoutine* cfdwroutine = (FdwRoutine*)MemoryContextAlloc(LocalMyDBCacheMemCxt(), sizeof(FdwRoutine)); + rc = memcpy_s(cfdwroutine, sizeof(FdwRoutine), fdwroutine, sizeof(FdwRoutine)); + securec_check(rc, "\0", "\0"); + currentRelation->rd_fdwroutine = cfdwroutine; + } + } else { + /* We can't use the relcache, so get fdwroutine the hard way */ + fdwroutine = GetFdwRoutineByServerId(node->fs_server); } - scanstate->ss.ss_currentRelation = currentRelation; - /* - * get the scan type from the relation descriptor. (XXX at some point we - * might want to let the FDW editorialize on the scan tupdesc.) + * Determine the scan tuple type. If the FDW provided a targetlist + * describing the scan tuples, use that; else use base relation's rowtype. */ - ExecAssignScanType(&scanstate->ss, RelationGetDescr(currentRelation)); + if (node->fdw_scan_tlist != NIL || currentRelation == NULL) { + TupleDesc scan_tupdesc; + + scan_tupdesc = ExecTypeFromTL(node->fdw_scan_tlist, false); + ExecAssignScanType(&scanstate->ss, scan_tupdesc); + tlistvarno = INDEX_VAR; + } else { + TupleDesc scan_tupdesc; + + /* don't trust FDWs to return tuples fulfilling NOT NULL constraints */ + scan_tupdesc = CreateTupleDescCopy(RelationGetDescr(currentRelation)); + ExecAssignScanType(&scanstate->ss, scan_tupdesc); + /* Node's targetlist will contain Vars with varno = scanrelid */ + tlistvarno = scanrelid; + } /* * Initialize result tuple type and projection info. @@ -185,34 +227,16 @@ ForeignScanState* ExecInitForeignScan(ForeignScan* node, EState* estate, int efl ExecAssignResultTypeFromTL( &scanstate->ss.ps, scanstate->ss.ss_ScanTupleSlot->tts_tupleDescriptor->tdTableAmType); - ExecAssignScanProjectionInfo(&scanstate->ss); - Assert(scanstate->ss.ps.ps_ResultTupleSlot->tts_tupleDescriptor->tdTableAmType != TAM_INVALID); - - /* - * Acquire function pointers from the FDW's handler, and init fdw_state. - */ - if (!node->rel) { - fdwroutine = GetFdwRoutineForRelation(currentRelation, true); - } else { - ForeignDataWrapper* fdw = NULL; - if (node->options->stype == T_OBS_SERVER || node->options->stype == T_HDFS_SERVER) { - fdw = GetForeignDataWrapperByName(HDFS_FDW, false); - } else { - fdw = GetForeignDataWrapperByName(DIST_FDW, false); - } - - fdwroutine = GetFdwRoutine(fdw->fdwhandler); - - /* Save the data for later reuse in LocalMyDBCacheMemCxt */ - FdwRoutine* cfdwroutine = (FdwRoutine*)MemoryContextAlloc(LocalMyDBCacheMemCxt(), sizeof(FdwRoutine)); - rc = memcpy_s(cfdwroutine, sizeof(FdwRoutine), fdwroutine, sizeof(FdwRoutine)); - securec_check(rc, "\0", "\0"); - currentRelation->rd_fdwroutine = cfdwroutine; - } + ExecAssignScanProjectionInfoWithVarno(&scanstate->ss, tlistvarno); scanstate->fdwroutine = fdwroutine; scanstate->fdw_state = NULL; + /* Initialize any outer plan. */ + if (outerPlan(node)) { + outerPlanState(scanstate) = ExecInitNode(outerPlan(node), estate, eflags); + } + #ifdef ENABLE_MOT if ((estate->mot_jit_context == NULL) || IS_PGXC_COORDINATOR || !JitExec::IsMotCodegenEnabled()) { #endif @@ -250,6 +274,10 @@ void ExecEndForeignScan(ForeignScanState* node) } #endif + /* Shut down any outer plan. */ + if (outerPlanState(node)) + ExecEndNode(outerPlanState(node)); + /* Free the exprcontext */ ExecFreeExprContext(&node->ss.ps); @@ -259,11 +287,13 @@ void ExecEndForeignScan(ForeignScanState* node) /* close the relation. */ ForeignScan* scan = (ForeignScan*)node->ss.ps.plan; - if (NULL == scan->rel) { - ExecCloseScanRelation(node->ss.ss_currentRelation); - } else { - if (false == scan->in_compute_pool) { + if (node->ss.ss_currentRelation != NULL) { + if (NULL == scan->rel) { ExecCloseScanRelation(node->ss.ss_currentRelation); + } else { + if (false == scan->in_compute_pool) { + ExecCloseScanRelation(node->ss.ss_currentRelation); + } } } diff --git a/src/gausskernel/storage/bulkload/foreignroutine.cpp b/src/gausskernel/storage/bulkload/foreignroutine.cpp index ce506a572..fc3657346 100644 --- a/src/gausskernel/storage/bulkload/foreignroutine.cpp +++ b/src/gausskernel/storage/bulkload/foreignroutine.cpp @@ -1198,7 +1198,7 @@ void distImportGetPaths(PlannerInfo *root, RelOptInfo *baserel, Oid foreigntable */ add_path(root, baserel, (Path *)create_foreignscan_path(root, baserel, startup_cost, total_cost, NIL, /* no pathkeys */ - NULL, /* no outer rel either */ + NULL, NULL, /* no outer rel and path either */ coptions, u_sess->opt_cxt.query_dop)); } @@ -1207,7 +1207,7 @@ void distImportGetPaths(PlannerInfo *root, RelOptInfo *baserel, Oid foreigntable * Create a ForeignScan plan node for scanning the foreign table */ ForeignScan *distImportGetPlan(PlannerInfo *root, RelOptInfo *baserel, Oid foreigntableid, ForeignPath *best_path, - List *tlist, List *scan_clauses) + List *tlist, List *scan_clauses, Plan *outer_plan) { Index scan_relid = baserel->relid; DistImportPlanState *planstate = (DistImportPlanState *)baserel->fdw_private; @@ -1252,7 +1252,7 @@ ForeignScan *distImportGetPlan(PlannerInfo *root, RelOptInfo *baserel, Oid forei /* Create the ForeignScan node */ ForeignScan *fScan = make_foreignscan(tlist, scan_clauses, scan_relid, NIL, /* no expressions to evaluate */ - fdw_data, EXEC_ON_DATANODES); /* no private state either */ + fdw_data, NIL, NIL, NULL, EXEC_ON_DATANODES); /* no private state either */ fScan->objectNum = fileNum; if (root->parse->commandType != CMD_INSERT && is_obs_protocol(HdfsGetOptionValue(foreigntableid, optLocation))) { diff --git a/src/gausskernel/storage/mot/fdw_adapter/mot_fdw.cpp b/src/gausskernel/storage/mot/fdw_adapter/mot_fdw.cpp index 4c8320839..ead65d0aa 100644 --- a/src/gausskernel/storage/mot/fdw_adapter/mot_fdw.cpp +++ b/src/gausskernel/storage/mot/fdw_adapter/mot_fdw.cpp @@ -127,7 +127,7 @@ PG_FUNCTION_INFO_V1(mot_fdw_validator); static void MOTGetForeignRelSize(PlannerInfo* root, RelOptInfo* baserel, Oid foreigntableid); static void MOTGetForeignPaths(PlannerInfo* root, RelOptInfo* baserel, Oid foreigntableid); static ForeignScan* MOTGetForeignPlan(PlannerInfo* root, RelOptInfo* baserel, Oid foreigntableid, - ForeignPath* best_path, List* tlist, List* scan_clauses); + ForeignPath* best_path, List* tlist, List* scan_clauses, Plan *outer_plan); static void MOTExplainForeignScan(ForeignScanState* node, ExplainState* es); static void MOTBeginForeignScan(ForeignScanState* node, int eflags); static TupleTableSlot* MOTIterateForeignScan(ForeignScanState* node); @@ -621,7 +621,8 @@ static void MOTGetForeignPaths(PlannerInfo* root, RelOptInfo* baserel, Oid forei planstate->m_startupCost, planstate->m_totalCost, usablePathkeys, - nullptr, /* no outer rel either */ + nullptr, /* no outer rel either */ + nullptr, /* no outer path either */ nullptr, // private data will be assigned later 0); @@ -691,6 +692,7 @@ static void MOTGetForeignPaths(PlannerInfo* root, RelOptInfo* baserel, Oid forei planstate->m_totalCost, usablePathkeys, nullptr, /* no outer rel either */ + nullptr, /* no outer path either */ nullptr, // private data will be assigned later 0); @@ -721,8 +723,8 @@ static void MOTGetForeignPaths(PlannerInfo* root, RelOptInfo* baserel, Oid forei /* * */ -static ForeignScan* MOTGetForeignPlan( - PlannerInfo* root, RelOptInfo* baserel, Oid foreigntableid, ForeignPath* best_path, List* tlist, List* scan_clauses) +static ForeignScan *MOTGetForeignPlan(PlannerInfo *root, RelOptInfo *baserel, Oid foreigntableid, + ForeignPath *best_path, List *tlist, List *scan_clauses, Plan *outer_plan) { ListCell* lc = nullptr; ::Index scanRelid = baserel->relid; @@ -789,7 +791,10 @@ static ForeignScan* MOTGetForeignPlan( quals, scanRelid, remote, /* no expressions to evaluate */ - (List*)SerializeFdwState(planstate) + (List*)SerializeFdwState(planstate), + NIL, + NIL, + NULL #if PG_VERSION_NUM >= 90500 , nullptr, diff --git a/src/include/bulkload/foreignroutine.h b/src/include/bulkload/foreignroutine.h index 0eeb7ae87..1940ed93f 100644 --- a/src/include/bulkload/foreignroutine.h +++ b/src/include/bulkload/foreignroutine.h @@ -83,7 +83,7 @@ extern void ProcessDistImportOptions(DistImportPlanState *planstate, List *optio extern void distImportGetRelSize(PlannerInfo* root, RelOptInfo* baserel, Oid foreigntableid); extern void distImportGetPaths(PlannerInfo* root, RelOptInfo* baserel, Oid foreigntableid); extern ForeignScan* distImportGetPlan(PlannerInfo* root, RelOptInfo* baserel, Oid foreigntableid, - ForeignPath* best_path, List* tlist, List* scan_clauses); + ForeignPath* best_path, List* tlist, List* scan_clauses, Plan *outer_plan); extern void distImportExplain(ForeignScanState* node, ExplainState* es); diff --git a/src/include/commands/explain.h b/src/include/commands/explain.h index bec17578a..64330128c 100644 --- a/src/include/commands/explain.h +++ b/src/include/commands/explain.h @@ -472,6 +472,7 @@ typedef struct ExplainState { char* statement_id; /* statement_id for EXPLAIN PLAN */ bool is_explain_gplan; char* opt_model_name; + ExplainFRSqlState es_frs; /* explain state for remote sql of foreign scan. */ } ExplainState; /* Hook for plugins to get control in explain_get_index_name() */ diff --git a/src/include/foreign/fdwapi.h b/src/include/foreign/fdwapi.h index 675efe2df..2614fe51c 100644 --- a/src/include/foreign/fdwapi.h +++ b/src/include/foreign/fdwapi.h @@ -30,7 +30,7 @@ typedef void (*GetForeignRelSize_function)(PlannerInfo* root, RelOptInfo* basere typedef void (*GetForeignPaths_function)(PlannerInfo* root, RelOptInfo* baserel, Oid foreigntableid); typedef ForeignScan* (*GetForeignPlan_function)(PlannerInfo* root, RelOptInfo* baserel, Oid foreigntableid, - ForeignPath* best_path, List* tlist, List* scan_clauses); + ForeignPath* best_path, List* tlist, List* scan_clauses, Plan *outer_plan); typedef void (*BeginForeignScan_function)(ForeignScanState* node, int eflags); @@ -40,6 +40,11 @@ typedef void (*ReScanForeignScan_function)(ForeignScanState* node); typedef void (*EndForeignScan_function)(ForeignScanState* node); +typedef void (*GetForeignJoinPaths_function)(PlannerInfo *root, RelOptInfo *joinrel, RelOptInfo *outerrel, + RelOptInfo *innerrel, JoinType jointype, SpecialJoinInfo* sjinfo, List* restrictlist); + +typedef void (*GetForeignUpperPaths_function)(FDWUpperRelCxt* ufdwCxt, UpperRelationKind stage, Plan* mainPlan); + typedef void (*AddForeignUpdateTargets_function)(Query* parsetree, RangeTblEntry* target_rte, Relation target_relation); typedef List* (*PlanForeignModify_function)( @@ -63,6 +68,8 @@ typedef int (*IsForeignRelUpdatable_function)(Relation rel); typedef void (*ExplainForeignScan_function)(ForeignScanState* node, struct ExplainState* es); +typedef void (*ExplainForeignScanRemote_function)(ForeignScanState* node, struct ExplainState* es); + typedef void (*ExplainForeignModify_function)( ModifyTableState* mtstate, ResultRelInfo* rinfo, List* fdw_private, int subplan_index, struct ExplainState* es); @@ -140,6 +147,12 @@ typedef struct FdwRoutine { * These functions are optional. Set the pointer to NULL for any that are * not provided. */ + + /* Functions for remote-join planning */ + GetForeignJoinPaths_function GetForeignJoinPaths; + + /* Functions for remote upper-relation (post scan/join) planning */ + GetForeignUpperPaths_function GetForeignUpperPaths; /* Functions for updating foreign tables */ AddForeignUpdateTargets_function AddForeignUpdateTargets; @@ -154,6 +167,7 @@ typedef struct FdwRoutine { /* Support functions for EXPLAIN */ ExplainForeignScan_function ExplainForeignScan; ExplainForeignModify_function ExplainForeignModify; + ExplainForeignScanRemote_function ExplainForeignScanRemote; /* @hdfs Support functions for ANALYZE */ AnalyzeForeignTable_function AnalyzeForeignTable; @@ -202,5 +216,6 @@ extern FdwRoutine* GetFdwRoutine(Oid fdwhandler); extern FdwRoutine* GetFdwRoutineByRelId(Oid relid, bool missHandlerOk = false); extern FdwRoutine* GetFdwRoutineByServerId(Oid serverid); extern FdwRoutine* GetFdwRoutineForRelation(Relation relation, bool makecopy); +extern Oid GetForeignServerIdByRelId(Oid relid); #endif /* FDWAPI_H */ diff --git a/src/include/foreign/foreign.h b/src/include/foreign/foreign.h index e89078820..01bea24e9 100644 --- a/src/include/foreign/foreign.h +++ b/src/include/foreign/foreign.h @@ -16,6 +16,7 @@ #include "access/obs/obs_am.h" #include "commands/defrem.h" #include "nodes/parsenodes.h" +#include "nodes/relation.h" #ifndef OBS_SERVER #define OBS_SERVER "obs" @@ -223,7 +224,7 @@ extern bool IsSpecifiedFDWFromRelid(Oid relId, const char* SepcifiedType); * @in relId: The foreign table Oid. * @return Rreturn true if the foreign table support those DML. */ -extern bool CheckSupportedFDWType(Oid relId); +extern bool CheckSupportedFDWType(Oid oid, bool byServerId = false); /** * @Description: Get the all options for the OBS foreign table. @@ -304,11 +305,101 @@ void CheckGetServerIpAndPort(const char *Address, List **AddrList, bool IsCheck, bool isWriteOnlyFt(Oid relid); +/* + * state for explain foreign remote sql + */ +typedef struct ExplainFRSqlState { + struct ExplainState* parent; + int node_num; + StringInfo str; /* output buffer */ +} ExplainFRSqlState; + +typedef struct SPJPathExtraData { + List *targetList; +} SPJPathExtraData; + +/* + * Struct for extra information passed to subroutines of create_grouping_paths + * + * flags indicating what kinds of grouping are possible. + * partial_costs_set is true if the agg_partial_costs and agg_final_costs + * have been initialized. + * agg_partial_costs gives partial aggregation costs. + * agg_final_costs gives finalization costs. + * target_parallel_safe is true if target is parallel safe. + * havingQual gives list of quals to be applied after aggregation. + * targetList gives list of columns to be projected. + * patype is the type of partitionwise aggregation that is being performed. + */ +typedef struct GroupPathExtraData { + /* Data which remains constant once set. */ + int flags; + bool partial_costs_set; + AggClauseCosts agg_partial_costs; + AggClauseCosts agg_final_costs; + + /* Data which may differ across partitions. */ + Node *havingQual; + List *targetList; +} GroupPathExtraData; + +typedef double Cardinality; /* (estimated) number of rows or other integer count */ + + +typedef struct OrderPathExtraData { + List *targetList; + List *irel_tel; // target entry list of lefttree +} OrderPathExtraData; + +/* + * Struct for extra information passed to subroutines of grouping_planner + * + * limit_needed is true if we actually need a Limit plan node. + * limit_tuples is an estimated bound on the number of output tuples, + * or -1 if no LIMIT or couldn't estimate. + * count_est and offset_est are the estimated values of the LIMIT and OFFSET + * expressions computed by preprocess_limit() (see comments for + * preprocess_limit() for more information). + */ +typedef struct FinalPathExtraData { + bool limit_needed; + Cardinality limit_tuples; + int64 count_est; + int64 offset_est; + + List* targetList; +} FinalPathExtraData; + +typedef enum FDWUpperRelState { + FDW_UPPER_REL_INIT, + FDW_UPPER_REL_TRY, + FDW_UPPER_REL_END +} FDWUpperRelState; + +typedef struct FDWUpperRelCxt { + FDWUpperRelState state; + PlannerInfo* root; + RelOptInfo* currentRel; // current rel to create path + RelOptInfo* upperRels[UPPERREL_FINAL + 1]; // the rel that we using to create foreign path + SPJPathExtraData* spjExtra; + GroupPathExtraData* groupExtra; + OrderPathExtraData* orderExtra; + FinalPathExtraData* finalExtra; + + /* result */ + Plan* resultPlan; + List* resultPathKeys; +} FDWUpperRelCxt; + +#define FDWUpperPlanContinue(cxt) ((cxt) != NULL && (cxt)->stage != FDW_UPPER_REL_END) +extern FDWUpperRelCxt* InitFDWUpperPlan(PlannerInfo* root, RelOptInfo* baseRel, Plan* localPlan); +extern void AdvanceFDWUpperPlan(FDWUpperRelCxt* ufdwCxt, UpperRelationKind stage, Plan* localPlan); + #define isObsOrHdfsTableFormTblOid(relId) \ - (isSpecifiedSrvTypeFromRelId(relId, HDFS) || isSpecifiedSrvTypeFromRelId(relId, OBS)) + (OidIsValid(relId) && (isSpecifiedSrvTypeFromRelId(relId, HDFS) || isSpecifiedSrvTypeFromRelId(relId, OBS))) #define isMOTFromTblOid(relId) \ - (IsSpecifiedFDWFromRelid(relId, MOT_FDW)) + (OidIsValid(relId) && IsSpecifiedFDWFromRelid(relId, MOT_FDW)) #define isObsOrHdfsTableFormSrvName(srvName) \ (isSpecifiedSrvTypeFromSrvName(srvName, HDFS) || isSpecifiedSrvTypeFromSrvName(srvName, OBS)) @@ -320,16 +411,18 @@ bool isWriteOnlyFt(Oid relid); (IsSpecifiedFDW(srvName, POSTGRES_FDW)) #define isMysqlFDWFromTblOid(relId) \ - (IsSpecifiedFDWFromRelid(relId, MYSQL_FDW)) + (OidIsValid(relId) && IsSpecifiedFDWFromRelid(relId, MYSQL_FDW)) #define isOracleFDWFromTblOid(relId) \ - (IsSpecifiedFDWFromRelid(relId, ORACLE_FDW)) + (OidIsValid(relId) && IsSpecifiedFDWFromRelid(relId, ORACLE_FDW)) #define isPostgresFDWFromTblOid(relId) \ - (IsSpecifiedFDWFromRelid(relId, POSTGRES_FDW)) + (OidIsValid(relId) && IsSpecifiedFDWFromRelid(relId, POSTGRES_FDW)) #define IS_OBS_CSV_TXT_FOREIGN_TABLE(relId) \ - (IsSpecifiedFDWFromRelid(relId, DIST_FDW) && (is_obs_protocol(HdfsGetOptionValue(relId, optLocation)))) + (OidIsValid(relId) && \ + IsSpecifiedFDWFromRelid(relId, DIST_FDW) && \ + (is_obs_protocol(HdfsGetOptionValue(relId, optLocation)))) #define CAN_BUILD_INFORMATIONAL_CONSTRAINT_BY_RELID(relId) \ (isObsOrHdfsTableFormTblOid(relId) || IS_OBS_CSV_TXT_FOREIGN_TABLE(relId)) @@ -340,9 +433,9 @@ bool isWriteOnlyFt(Oid relid); ? false \ : is_obs_protocol(getFTOptionValue(stmt->options, optLocation)))) -#define IS_LOGFDW_FOREIGN_TABLE(relId) (IsSpecifiedFDWFromRelid(relId, LOG_FDW)) +#define IS_LOGFDW_FOREIGN_TABLE(relId) (OidIsValid(relId) && IsSpecifiedFDWFromRelid(relId, LOG_FDW)) -#define IS_POSTGRESFDW_FOREIGN_TABLE(relId) (IsSpecifiedFDWFromRelid(relId, GC_FDW)) +#define IS_POSTGRESFDW_FOREIGN_TABLE(relId) (OidIsValid(relId) && IsSpecifiedFDWFromRelid(relId, GC_FDW)) #define ENCRYPT_STR_PREFIX "encryptstr" #define MIN_ENCRYPTED_PASSWORD_LENGTH 54 diff --git a/src/include/knl/knl_guc/knl_session_attr_common.h b/src/include/knl/knl_guc/knl_session_attr_common.h index 57042dad6..1a223e95e 100644 --- a/src/include/knl/knl_guc/knl_session_attr_common.h +++ b/src/include/knl/knl_guc/knl_session_attr_common.h @@ -209,6 +209,7 @@ typedef struct knl_session_attr_common { bool enable_wdr_snapshot; bool enable_set_variable_b_format; bool enable_asp; + bool show_fdw_remote_plan; int wdr_snapshot_interval; int wdr_snapshot_retention_days; int asp_sample_interval; diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h index e11b65357..903fa1666 100644 --- a/src/include/miscadmin.h +++ b/src/include/miscadmin.h @@ -117,6 +117,7 @@ extern const uint32 STANDBY_STMTHIST_VERSION_NUM; extern const uint32 PG_AUTHID_PASSWORDEXT_VERSION_NUM; extern const uint32 MAT_VIEW_RECURSIVE_VERSION_NUM; extern const uint32 SUPPORT_VIEW_AUTO_UPDATABLE; +extern const uint32 FDW_SUPPORT_JOIN_AGG_VERSION_NUM; extern void register_backend_version(uint32 backend_version); extern bool contain_backend_version(uint32 version_number); diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index 764642f4f..3fb67c4aa 100755 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -2007,6 +2007,7 @@ typedef struct WorkTableScanState { */ typedef struct ForeignScanState { ScanState ss; /* its first field is NodeTag */ + ExprState* fdw_recheck_quals; /* original quals not in ss.ps.qual */ /* use struct pointer to avoid including fdwapi.h here */ struct FdwRoutine* fdwroutine; void* fdw_state; /* foreign-data wrapper can keep state here */ diff --git a/src/include/nodes/pg_list.h b/src/include/nodes/pg_list.h index 1fd1779da..49cfefa5c 100644 --- a/src/include/nodes/pg_list.h +++ b/src/include/nodes/pg_list.h @@ -251,6 +251,7 @@ extern List* lcons_int(int datum, List* list); extern List* lcons_oid(Oid datum, List* list); extern List* list_concat(List* list1, List* list2); +extern List* list_concat2(List* list1, List* list2); extern List* list_truncate(List* list, int new_size); extern void* list_nth(const List* list, int n); diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h index 8db2b9313..1a4946ab1 100644 --- a/src/include/nodes/plannodes.h +++ b/src/include/nodes/plannodes.h @@ -921,11 +921,10 @@ typedef struct WorkTableScan { typedef struct ForeignScan { Scan scan; - Oid scan_relid; /* Oid of the scan relation */ + Oid scan_relid; /* Oid of the scan relation, InValidOid if this is a join\agg foreign scan. */ List* fdw_exprs; /* expressions that FDW may evaluate */ List* fdw_private; /* private data for FDW */ bool fsSystemCol; /* true if any "system column" is needed */ - bool needSaveError; ErrorCacheEntry* errCache; /* Error record cache */ @@ -945,6 +944,14 @@ typedef struct ForeignScan { */ bool in_compute_pool; bool not_use_bloomfilter; /* set true in ExecInitXXXX() of planrouter node */ + + // using for pg_fdw + CmdType operation; /* SELECT/INSERT/UPDATE/DELETE */ + Index resultRelation; /* direct modification target's RT index */ + Oid fs_server; /* OID of foreign server */ + Bitmapset *fs_relids; /* RTIs generated by this scan */ + List *fdw_scan_tlist; /* optional tlist describing scan tuple */ + List *fdw_recheck_quals; /* original quals not in scan.plan.qual */ } ForeignScan; /* ---------------- diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index c43a17bd2..2ad126770 100755 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -85,6 +85,23 @@ typedef struct AggClauseCosts { int aggWidth; /* total width of agg function */ } AggClauseCosts; +/* + * This enum identifies the different types of "upper" (post-scan/join) + * relations that we might deal with during planning. + */ +typedef enum UpperRelationKind { + UPPERREL_INIT, /* is a base rel */ + UPPERREL_SETOP, /* result of UNION/INTERSECT/EXCEPT, if any */ + UPPERREL_GROUP_AGG, /* result of grouping/aggregation, if any */ + UPPERREL_WINDOW, /* result of window functions, if any */ + UPPERREL_DISTINCT, /* result of "SELECT DISTINCT", if any */ + UPPERREL_ORDERED, /* result of ORDER BY, if any */ + UPPERREL_ROWMARKS, /* result of ROMARKS, if any */ + UPPERREL_LIMIT, /* result of limit offset, if any */ + UPPERREL_FINAL /* result of any remaining top-level actions */ + /* NB: UPPERREL_FINAL must be last enum entry; it's used to size arrays */ +} UpperRelationKind; + /* * For global path optimization, we should keep all paths with interesting distribute * keys. There are two kinds of such keys: super set (taking effect for intermediate @@ -465,9 +482,14 @@ typedef struct PlannerInfo { * * We also have "other rels", which are like base rels in that they refer to * single RT indexes; but they are not part of the join tree, and are given - * a different RelOptKind to identify them. Lastly, there is a RelOptKind - * for "dead" relations, which are base rels that we have proven we don't - * need to join after all. + * a different RelOptKind to identify them. + * There is also a RelOptKind for "upper" relations, which are RelOptInfos + * that describe post-scan/join processing steps, such as aggregation. + * Many of the fields in these RelOptInfos are meaningless, but their Path + * fields always hold Paths showing ways to do that processing step, currently + * this kind is only used for fdw to search path. + * Lastly, there is a RelOptKind for "dead" relations, which are base rels + * that we have proven we don't need to join after all. * * Currently the only kind of otherrels are those made for member relations * of an "append relation", that is an inheritance set or UNION ALL subquery. @@ -531,8 +553,6 @@ typedef struct PlannerInfo { * allvisfrac - fraction of disk pages that are marked all-visible * subplan - plan for subquery (NULL if it's not a subquery) * subroot - PlannerInfo for subquery (NULL if it's not a subquery) - * fdwroutine - function hooks for FDW, if foreign table (else NULL) - * fdw_private - private state for FDW, if foreign table (else NULL) * * Note: for a subquery, tuples, subplan, subroot are not set immediately * upon creation of the RelOptInfo object; they are filled in when @@ -541,7 +561,16 @@ typedef struct PlannerInfo { * * For otherrels that are appendrel members, these fields are filled * in just as for a baserel. + * If the relation is either a foreign table or a join of foreign tables that + * all belong to the same foreign server and are assigned to the same user to + * check access permissions as (cf checkAsUser), these fields will be set: * + * serverid - OID of foreign server, if foreign table (else InvalidOid) + * userid - OID of user to check access as (InvalidOid means current user) + * useridiscurrent - we've assumed that userid equals current user + * fdwroutine - function hooks for FDW, if foreign table (else NULL) + * fdw_private - private state for FDW, if foreign table (else NULL) + * * The presence of the remaining fields depends on the restrictions * and joins that the relation participates in: * @@ -575,7 +604,7 @@ typedef struct PlannerInfo { * and may need it multiple times to price index scans. * ---------- */ -typedef enum RelOptKind { RELOPT_BASEREL, RELOPT_JOINREL, RELOPT_OTHER_MEMBER_REL, RELOPT_DEADREL } RelOptKind; +typedef enum RelOptKind { RELOPT_BASEREL, RELOPT_JOINREL, RELOPT_OTHER_MEMBER_REL, RELOPT_UPPER_REL, RELOPT_DEADREL } RelOptKind; typedef enum PartitionFlag { PARTITION_NONE, PARTITION_REQURIED, PARTITION_ANCESOR } PartitionFlag; @@ -588,6 +617,9 @@ typedef enum PartitionFlag { PARTITION_NONE, PARTITION_REQURIED, PARTITION_ANCES /* Is the given relation a join relation? */ #define IS_JOIN_REL(rel) ((rel)->reloptkind == RELOPT_JOINREL) +/* Is the given relation an upper relation? */ +#define IS_UPPER_REL(rel) ((rel)->reloptkind == RELOPT_UPPER_REL) + typedef struct RelOptInfo { NodeTag type; @@ -653,6 +685,11 @@ typedef struct RelOptInfo { struct Plan* subplan; /* if subquery */ PlannerInfo* subroot; /* if subquery */ List *subplan_params; /* if subquery */ + + /* Information about foreign tables and foreign joins */ + Oid serverid; /* identifies server for the table or join */ + Oid userid; /* identifies user to check access as */ + bool useridiscurrent; /* join is only valid for current user */ /* use "struct FdwRoutine" to avoid including fdwapi.h here */ struct FdwRoutine* fdwroutine; /* if foreign table */ void* fdw_private; /* if foreign table */ diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h index ffc7991a5..77bd4faa6 100644 --- a/src/include/optimizer/pathnode.h +++ b/src/include/optimizer/pathnode.h @@ -84,7 +84,7 @@ extern Path* create_valuesscan_path(PlannerInfo* root, RelOptInfo* rel, Relids r extern Path* create_ctescan_path(PlannerInfo* root, RelOptInfo* rel); extern Path* create_worktablescan_path(PlannerInfo* root, RelOptInfo* rel); extern ForeignPath* create_foreignscan_path(PlannerInfo* root, RelOptInfo* rel, Cost startup_cost, Cost total_cost, - List* pathkeys, Relids required_outer, List* fdw_private, int dop = 1); + List* pathkeys, Relids required_outer, Path* fdw_outerpath, List* fdw_private, int dop = 1); extern Relids calc_nestloop_required_outer(Path* outer_path, Path* inner_path); extern Relids calc_non_nestloop_required_outer(Path* outer_path, Path* inner_path); diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h index ce1ec129e..2fec3b0ba 100644 --- a/src/include/optimizer/paths.h +++ b/src/include/optimizer/paths.h @@ -142,7 +142,7 @@ extern bool have_relevant_eclass_joinclause(PlannerInfo* root, RelOptInfo* rel1, extern bool has_relevant_eclass_joinclause(PlannerInfo* root, RelOptInfo* rel1); extern bool eclass_useful_for_merging(EquivalenceClass* eclass, RelOptInfo* rel); extern bool is_redundant_derived_clause(RestrictInfo* rinfo, List* clauselist); - +extern PathKey* make_canonical_pathkey(PlannerInfo* root, EquivalenceClass* eclass, Oid opfamily, int strategy, bool nulls_first); /* * pathkeys.c * utilities for matching and building path keys diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h index d5b775347..fe3ae7258 100755 --- a/src/include/optimizer/planmain.h +++ b/src/include/optimizer/planmain.h @@ -71,7 +71,7 @@ extern void disuse_physical_tlist(Plan* plan, Path* path); extern void copy_plan_costsize(Plan* dest, Plan* src); extern SubqueryScan* make_subqueryscan(List* qptlist, List* qpqual, Index scanrelid, Plan* subplan); extern ForeignScan* make_foreignscan(List* qptlist, List* qpqual, Index scanrelid, List* fdw_exprs, List* fdw_private, - RemoteQueryExecType type = EXEC_ON_ALL_NODES); + List *fdw_scan_tlist, List *fdw_recheck_quals, Plan *outer_plan, RemoteQueryExecType type = EXEC_ON_ALL_NODES); extern Append* make_append(List* appendplans, List* tlist); extern RecursiveUnion* make_recursive_union( List* tlist, Plan* lefttree, Plan* righttree, int wtParam, List* distinctList, long numGroups); diff --git a/src/test/regress/input/fdw_prepare.source b/src/test/regress/input/fdw_prepare.source new file mode 100644 index 000000000..6226e99b1 --- /dev/null +++ b/src/test/regress/input/fdw_prepare.source @@ -0,0 +1,24 @@ +-- 1. create usermapping.cihper +\! echo $OLDGAUSSHOME | xargs -I{} rm -f {}/bin/usermapping.key.cipher +\! echo $OLDGAUSSHOME | xargs -I{} rm -f {}/bin/usermapping.key.rand +\! echo $OLDGAUSSHOME | xargs -I{} @abs_bindir@/gs_guc generate -S 123456@pwd -D {}/bin -o usermapping > /dev/null 2>&1 ; echo $? + + +-- 2. copy the test case script from contrib/, touch it first in case copy failed, otherwise fastcheck will stop becase cannot find test case script; +\! sh -c "touch @abs_srcdir@/sql/postgres_fdw.sql" +\! sh -c "echo 'failed, please check the test case: fdw_prepare' > @abs_srcdir@/sql/postgres_fdw.sql" +\! sh -c "touch @abs_srcdir@/expected/postgres_fdw.out" +\! sh -c "cp @abs_srcdir@/../../../contrib/postgres_fdw/sql/postgres_fdw.sql @abs_srcdir@/sql/postgres_fdw.sql" +\! sh -c "cp @abs_srcdir@/../../../contrib/postgres_fdw/expected/postgres_fdw.out @abs_srcdir@/expected/postgres_fdw.out" + +\! sh -c "touch @abs_srcdir@/sql/postgres_fdw_cstore.sql" +\! sh -c "echo 'failed, please check the test case: fdw_prepare' > @abs_srcdir@/sql/postgres_fdw_cstore.sql" +\! sh -c "touch @abs_srcdir@/expected/postgres_fdw_cstore.out" +\! sh -c "cp @abs_srcdir@/../../../contrib/postgres_fdw/sql/postgres_fdw_cstore.sql @abs_srcdir@/sql/postgres_fdw_cstore.sql" +\! sh -c "cp @abs_srcdir@/../../../contrib/postgres_fdw/expected/postgres_fdw_cstore.out @abs_srcdir@/expected/postgres_fdw_cstore.out" + +\! sh -c "touch @abs_srcdir@/sql/postgres_fdw_partition.sql" +\! sh -c "echo 'failed, please check the test case: fdw_prepare' > @abs_srcdir@/sql/postgres_fdw_partition.sql" +\! sh -c "touch @abs_srcdir@/expected/postgres_fdw_partition.out" +\! sh -c "cp @abs_srcdir@/../../../contrib/postgres_fdw/sql/postgres_fdw_partition.sql @abs_srcdir@/sql/postgres_fdw_partition.sql" +\! sh -c "cp @abs_srcdir@/../../../contrib/postgres_fdw/expected/postgres_fdw_partition.out @abs_srcdir@/expected/postgres_fdw_partition.out" diff --git a/src/test/regress/output/fdw_prepare.source b/src/test/regress/output/fdw_prepare.source new file mode 100644 index 000000000..146edcdc5 --- /dev/null +++ b/src/test/regress/output/fdw_prepare.source @@ -0,0 +1,21 @@ +-- 1. create usermapping.cihper +\! echo $OLDGAUSSHOME | xargs -I{} rm -f {}/bin/usermapping.key.cipher +\! echo $OLDGAUSSHOME | xargs -I{} rm -f {}/bin/usermapping.key.rand +\! echo $OLDGAUSSHOME | xargs -I{} @abs_bindir@/gs_guc generate -S 123456@pwd -D {}/bin -o usermapping > /dev/null 2>&1 ; echo $? +0 +-- 2. copy the test case script from contrib/, touch it first in case copy failed, otherwise fastcheck will stop becase cannot find test case script; +\! sh -c "touch @abs_srcdir@/sql/postgres_fdw.sql" +\! sh -c "echo 'failed, please check the test case: fdw_prepare' > @abs_srcdir@/sql/postgres_fdw.sql" +\! sh -c "touch @abs_srcdir@/expected/postgres_fdw.out" +\! sh -c "cp @abs_srcdir@/../../../contrib/postgres_fdw/sql/postgres_fdw.sql @abs_srcdir@/sql/postgres_fdw.sql" +\! sh -c "cp @abs_srcdir@/../../../contrib/postgres_fdw/expected/postgres_fdw.out @abs_srcdir@/expected/postgres_fdw.out" +\! sh -c "touch @abs_srcdir@/sql/postgres_fdw_cstore.sql" +\! sh -c "echo 'failed, please check the test case: fdw_prepare' > @abs_srcdir@/sql/postgres_fdw_cstore.sql" +\! sh -c "touch @abs_srcdir@/expected/postgres_fdw_cstore.out" +\! sh -c "cp @abs_srcdir@/../../../contrib/postgres_fdw/sql/postgres_fdw_cstore.sql @abs_srcdir@/sql/postgres_fdw_cstore.sql" +\! sh -c "cp @abs_srcdir@/../../../contrib/postgres_fdw/expected/postgres_fdw_cstore.out @abs_srcdir@/expected/postgres_fdw_cstore.out" +\! sh -c "touch @abs_srcdir@/sql/postgres_fdw_partition.sql" +\! sh -c "echo 'failed, please check the test case: fdw_prepare' > @abs_srcdir@/sql/postgres_fdw_partition.sql" +\! sh -c "touch @abs_srcdir@/expected/postgres_fdw_partition.out" +\! sh -c "cp @abs_srcdir@/../../../contrib/postgres_fdw/sql/postgres_fdw_partition.sql @abs_srcdir@/sql/postgres_fdw_partition.sql" +\! sh -c "cp @abs_srcdir@/../../../contrib/postgres_fdw/expected/postgres_fdw_partition.out @abs_srcdir@/expected/postgres_fdw_partition.out" diff --git a/src/test/regress/output/recovery_2pc_tools.source b/src/test/regress/output/recovery_2pc_tools.source index 3d09a5cf6..7902ae1d9 100644 --- a/src/test/regress/output/recovery_2pc_tools.source +++ b/src/test/regress/output/recovery_2pc_tools.source @@ -584,6 +584,7 @@ select name,vartype,unit,min_val,max_val from pg_settings where name <> 'qunit_c shared_buffers | integer | 8kB | 16 | 1073741823 shared_preload_libraries | string | | | show_acce_estimate_detail | bool | | | + show_fdw_remote_plan | bool | | | skew_option | enum | | | sql_beta_feature | enum | | | sql_compatibility | enum | | | diff --git a/src/test/regress/parallel_schedule0 b/src/test/regress/parallel_schedule0 index ca0feeb15..9bf70a5e3 100644 --- a/src/test/regress/parallel_schedule0 +++ b/src/test/regress/parallel_schedule0 @@ -27,6 +27,12 @@ test: replace_func_with_two_args trunc_func_for_date nlssort_pinyin updatable_vi # test multiple statistics test: functional_dependency test: pg_proc_test + +# test fdw +# NOTICE: In the "fdw_prepare", we copy the fdw test to be used from contrib into regress sql set. +test: fdw_prepare +test: postgres_fdw postgres_fdw_cstore postgres_fdw_partition + # parse xlog and page #test: parse_page #test: parse_xlog @@ -880,7 +886,7 @@ test: llvm_vecsort llvm_vecsort2 test: udf_crem create_c_function # procedure, Function Test -#test: create_procedure postgres_fdw +#test: create_procedure test: create_function test: pg_compatibility