Impressum deparse.c
Sprache: unbekannt
/*-------------------------------------------------------------------------
*
* deparse . c
* Query deparser for postgres_fdw
*
* This file includes functions that examine query WHERE clauses to see
* whether they ' re safe to send to the remote server for execution , as
* well as functions to construct the query text to be sent . The latter
* functionality is annoyingly duplicative of ruleutils . c , but there are
* enough special considerations that it seems best to keep this separate .
* One saving grace is that we only need deparse logic for node types that
* we consider safe to send .
*
* We assume that the remote session ' s search_path is exactly " pg_catalog " ,
* and thus we need schema - qualify all and only names outside pg_catalog .
*
* We do not consider that it is ever safe to send COLLATE expressions to
* the remote server : it might not have the same collation names we do .
* ( Later we might consider it safe to send COLLATE " C " , but even that would
* fail on old remote servers . ) An expression is considered safe to send
* only if all operator / function input collations used in it are traceable to
* Var ( s ) of the foreign table . That implies that if the remote server gets
* a different answer than we do , the foreign table ' s columns are not marked
* with collations that match the remote table ' s columns , which we can
* consider to be user error .
*
* Portions Copyright ( c ) 2012 - 2025 , PostgreSQL Global Development Group
*
* IDENTIFICATION
* contrib / postgres_fdw / deparse . c
*
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/
#include "postgres.h"
#include "access/htup_details.h"
#include "access/sysattr.h"
#include "access/table.h"
#include "catalog/pg_aggregate.h"
#include "catalog/pg_authid.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_opfamily.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_ts_config.h"
#include "catalog/pg_ts_dict.h"
#include "catalog/pg_type.h"
#include "commands/defrem.h"
#include "nodes/nodeFuncs.h"
#include "nodes/plannodes.h"
#include "optimizer/optimizer.h"
#include "optimizer/prep.h"
#include "optimizer/tlist.h"
#include "parser/parsetree.h"
#include "postgres_fdw.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/syscache.h"
#include "utils/typcache.h"
/*
* Global context for foreign_expr_walker ' s search of an expression tree .
*/
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;
/*
* Local ( per - tree - level ) context for foreign_expr_walker ' s search .
* This is concerned with identifying collations used in the expression .
*/
typedef enum
{
FDW_COLLATE_NONE, /* expression is of a noncollatable type, or
* it has default collation that is not
* traceable to a foreign Var */
FDW_COLLATE_SAFE, /* collation derives from a foreign Var */
FDW_COLLATE_UNSAFE, /* collation is non-default and derives from
* something other than a foreign Var */
} FDWCollateState;
typedef struct foreign_loc_cxt
{
Oid collation; /* OID of current collation, if any */
FDWCollateState state; /* state of current collation choice */
} foreign_loc_cxt;
/*
* Context for deparseExpr
*/
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,
foreign_loc_cxt *case_arg_cxt);
static char *deparse_type_name(Oid type_oid, int32 typemod);
/*
* 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,
bool qualify_col,
List **retrieved_attrs);
static void deparseExplicitTargetList(List *tlist,
bool is_returning,
List **retrieved_attrs,
deparse_expr_cxt *context);
static void deparseSubqueryTargetList(deparse_expr_cxt *context);
static void deparseReturningList(StringInfo buf, RangeTblEntry *rte,
Index rtindex, Relation rel,
bool trig_after_row,
List *withCheckOptionList,
List *returningList,
List **retrieved_attrs);
static void deparseColumnRef(StringInfo buf, int varno, int varattno,
RangeTblEntry *rte, bool qualify_col);
static void deparseRelation(StringInfo buf, Relation rel);
static void deparseExpr(Expr *node, deparse_expr_cxt *context);
static void deparseVar(Var *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 deparseSubscriptingRef(SubscriptingRef *node, deparse_expr_cxt *context);
static void deparseFuncExpr(FuncExpr *node, deparse_expr_cxt *context);
static void deparseOpExpr(OpExpr *node, deparse_expr_cxt *context);
static bool isPlainForeignVar(Expr *node, deparse_expr_cxt *context);
static void deparseOperatorName(StringInfo buf, Form_pg_operator opform);
static void deparseDistinctExpr(DistinctExpr *node, deparse_expr_cxt *context);
static void deparseScalarArrayOpExpr(ScalarArrayOpExpr *node,
deparse_expr_cxt *context);
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 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 appendOrderByClause(List *pathkeys, bool has_final_sort,
deparse_expr_cxt *context);
static void appendLimitClause(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 **additional_conds,
List **params_list);
static void appendWhereClause(List *exprs, List *additional_conds,
deparse_expr_cxt *context);
static void deparseFromExpr(List *quals, deparse_expr_cxt *context);
static void deparseRangeTblRef(StringInfo buf, PlannerInfo *root,
RelOptInfo *foreignrel, bool make_subquery,
Index ignore_rel, List **ignore_conds,
List **additional_conds, List **params_list);
static void deparseAggref(Aggref *node, deparse_expr_cxt *context);
static void appendGroupByClause(List *tlist, deparse_expr_cxt *context);
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 appendFunctionName(Oid funcid, deparse_expr_cxt *context);
static Node *deparseSortGroupClause(Index ref, List *tlist, bool force_colno,
deparse_expr_cxt *context);
/*
* Helper functions
*/
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 :
* - remote_conds contains expressions that can be evaluated remotely
* - local_conds contains expressions that can ' t be evaluated remotely
*/
void
classifyConditions(PlannerInfo *root,
RelOptInfo *baserel,
List *input_conds,
List **remote_conds,
List **local_conds)
{
ListCell *lc;
*remote_conds = NIL;
*local_conds = NIL;
foreach(lc, input_conds)
{
RestrictInfo *ri = lfirst_node(RestrictInfo, lc);
if (is_foreign_expr(root, baserel, ri->clause))
*remote_conds = lappend(*remote_conds, ri);
else
*local_conds = lappend(*local_conds, ri);
}
}
/*
* Returns true if given expr is safe to evaluate on the foreign server .
*/
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
* remotely .
*/
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, NULL))
return false ;
/*
* 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
* because its result is not stable . For example , sending now ( ) remote
* side could cause confusion from clock offsets . Future versions might
* be able to make this choice with more granularity . ( We check this last
* because it requires a lot of expensive catalog lookups . )
*/
if (contain_mutable_functions((Node *) expr))
return false ;
/* OK to evaluate on the remote server */
return true ;
}
/*
* Check if expression is safe to execute remotely , and return true if so .
*
* In addition , * outer_cxt is updated with collation information .
*
* case_arg_cxt is NULL if this subexpression is not inside a CASE - with - arg .
* Otherwise , it points to the collation info derived from the arg expression ,
* which must be consulted by any CaseTestExpr .
*
* We must check that the expression contains only node types we can deparse ,
* that all types / functions / operators are safe to send ( they are " shippable " ) ,
* and that all collations used in the expression derive from Vars of the
* foreign table . Because of the latter , the logic is pretty close to
* assign_collations_walker ( ) in parse_collate . c , though we can assume here
* that the given expression is valid . Note function mutability is not
* currently considered here .
*/
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;
foreign_loc_cxt inner_cxt;
Oid collation;
FDWCollateState state;
/* 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:
{
Var *var = (Var *) node;
/*
* If the Var is from the foreign table , we consider its
* collation ( if any ) safe to use . If it is from another
* table , we treat its collation the same way as we would a
* Param ' s collation , ie it ' s not safe for it to have a
* non - default collation .
*/
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
* that local and remote values match ( tableoid , in
* particular , almost certainly doesn ' t match ) .
*/
if (var->varattno < 0 &&
var->varattno != SelfItemPointerAttributeNumber)
return false ;
/* Else check the collation */
collation = var->varcollid;
state = OidIsValid(collation) ? FDW_COLLATE_SAFE : FDW_COLLATE_NONE;
}
else
{
/* Var belongs to some other table */
collation = var->varcollid;
if (collation == InvalidOid ||
collation == DEFAULT_COLLATION_OID)
{
/*
* It ' s noncollatable , or it ' s safe to combine with a
* collatable foreign Var , so set state to NONE .
*/
state = FDW_COLLATE_NONE;
}
else
{
/*
* Do not fail right away , since the Var might appear
* in a collation - insensitive context .
*/
state = FDW_COLLATE_UNSAFE;
}
}
}
break ;
case T_Const:
{
Const *c = (Const *) node;
/*
* Constants of regproc and related types can ' t be shipped
* unless the referenced object is shippable . But NULL ' s ok .
* ( See also the related code in dependency . c . )
*/
if (!c->constisnull)
{
switch (c->consttype)
{
case REGPROCOID:
case REGPROCEDUREOID:
if (!is_shippable(DatumGetObjectId(c->constvalue),
ProcedureRelationId, fpinfo))
return false ;
break ;
case REGOPEROID:
case REGOPERATOROID:
if (!is_shippable(DatumGetObjectId(c->constvalue),
OperatorRelationId, fpinfo))
return false ;
break ;
case REGCLASSOID:
if (!is_shippable(DatumGetObjectId(c->constvalue),
RelationRelationId, fpinfo))
return false ;
break ;
case REGTYPEOID:
if (!is_shippable(DatumGetObjectId(c->constvalue),
TypeRelationId, fpinfo))
return false ;
break ;
case REGCOLLATIONOID:
if (!is_shippable(DatumGetObjectId(c->constvalue),
CollationRelationId, fpinfo))
return false ;
break ;
case REGCONFIGOID:
/*
* For text search objects only , we weaken the
* normal shippability criterion to allow all OIDs
* below FirstNormalObjectId . Without this , none
* of the initdb - installed TS configurations would
* be shippable , which would be quite annoying .
*/
if (DatumGetObjectId(c->constvalue) >= FirstNormalObjectId &&
!is_shippable(DatumGetObjectId(c->constvalue),
TSConfigRelationId, fpinfo))
return false ;
break ;
case REGDICTIONARYOID:
if (DatumGetObjectId(c->constvalue) >= FirstNormalObjectId &&
!is_shippable(DatumGetObjectId(c->constvalue),
TSDictionaryRelationId, fpinfo))
return false ;
break ;
case REGNAMESPACEOID:
if (!is_shippable(DatumGetObjectId(c->constvalue),
NamespaceRelationId, fpinfo))
return false ;
break ;
case REGROLEOID:
if (!is_shippable(DatumGetObjectId(c->constvalue),
AuthIdRelationId, fpinfo))
return false ;
break ;
}
}
/*
* If the constant has nondefault collation , either it ' s of a
* non - builtin type , or it reflects folding of a CollateExpr .
* It ' s unsafe to send to the remote unless it ' s used in a
* non - collation - sensitive context .
*/
collation = c->constcollid;
if (collation == InvalidOid ||
collation == DEFAULT_COLLATION_OID)
state = FDW_COLLATE_NONE;
else
state = FDW_COLLATE_UNSAFE;
}
break ;
case T_Param:
{
Param *p = (Param *) node;
/*
* If it ' s a MULTIEXPR Param , punt . We can ' t tell from here
* whether the referenced sublink / subplan contains any remote
* Vars ; if it does , handling that is too complicated to
* consider supporting at present . Fortunately , MULTIEXPR
* Params are not reduced to plain PARAM_EXEC until the end of
* planning , so we can easily detect this case . ( Normal
* PARAM_EXEC Params are safe to ship because their values
* come from somewhere else in the plan tree ; but a MULTIEXPR
* references a sub - select elsewhere in the same targetlist ,
* so we ' d be on the hook to evaluate it somehow if we wanted
* to handle such cases as direct foreign updates . )
*/
if (p->paramkind == PARAM_MULTIEXPR)
return false ;
/*
* Collation rule is same as for Consts and non - foreign Vars .
*/
collation = p->paramcollid;
if (collation == InvalidOid ||
collation == DEFAULT_COLLATION_OID)
state = FDW_COLLATE_NONE;
else
state = FDW_COLLATE_UNSAFE;
}
break ;
case T_SubscriptingRef:
{
SubscriptingRef *sr = (SubscriptingRef *) node;
/* Assignment should not be in restrictions. */
if (sr->refassgnexpr != NULL)
return false ;
/*
* Recurse into the remaining subexpressions . The container
* subscripts will not affect collation of the SubscriptingRef
* result , so do those first and reset inner_cxt afterwards .
*/
if (!foreign_expr_walker((Node *) sr->refupperindexpr,
glob_cxt, &inner_cxt, case_arg_cxt))
return false ;
inner_cxt.collation = InvalidOid;
inner_cxt.state = FDW_COLLATE_NONE;
if (!foreign_expr_walker((Node *) sr->reflowerindexpr,
glob_cxt, &inner_cxt, case_arg_cxt))
return false ;
inner_cxt.collation = InvalidOid;
inner_cxt.state = FDW_COLLATE_NONE;
if (!foreign_expr_walker((Node *) sr->refexpr,
glob_cxt, &inner_cxt, case_arg_cxt))
return false ;
/*
* Container subscripting typically yields same collation as
* refexpr ' s , but in case it doesn ' t , use same logic as for
* function nodes .
*/
collation = sr->refcollid;
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;
}
break ;
case T_FuncExpr:
{
FuncExpr *fe = (FuncExpr *) node;
/*
* If function used by the expression is not shippable , it
* can ' t be sent to remote because it might have incompatible
* semantics on remote side .
*/
if (!is_shippable(fe->funcid, ProcedureRelationId, fpinfo))
return false ;
/*
* Recurse to input subexpressions .
*/
if (!foreign_expr_walker((Node *) fe->args,
glob_cxt, &inner_cxt, case_arg_cxt))
return false ;
/*
* If function ' s input collation is not derived from a foreign
* Var , it can ' t be sent to remote .
*/
if (fe->inputcollid == InvalidOid)
/* OK, inputs are all noncollatable */ ;
else if (inner_cxt.state != FDW_COLLATE_SAFE ||
fe->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 = fe->funccollid;
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;
}
break ;
case T_OpExpr:
case T_DistinctExpr: /* struct-equivalent to OpExpr */
{
OpExpr *oe = (OpExpr *) node;
/*
* Similarly , only shippable operators can be sent to remote .
* ( If the operator is shippable , we assume its underlying
* function is too . )
*/
if (!is_shippable(oe->opno, OperatorRelationId, fpinfo))
return false ;
/*
* Recurse to input subexpressions .
*/
if (!foreign_expr_walker((Node *) oe->args,
glob_cxt, &inner_cxt, case_arg_cxt))
return false ;
/*
* If operator ' s input collation is not derived from a foreign
* Var , it can ' t be sent to remote .
*/
if (oe->inputcollid == InvalidOid)
/* OK, inputs are all noncollatable */ ;
else if (inner_cxt.state != FDW_COLLATE_SAFE ||
oe->inputcollid != inner_cxt.collation)
return false ;
/* Result-collation handling is same as for functions */
collation = oe->opcollid;
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;
}
break ;
case T_ScalarArrayOpExpr:
{
ScalarArrayOpExpr *oe = (ScalarArrayOpExpr *) node;
/*
* Again , only shippable operators can be sent to remote .
*/
if (!is_shippable(oe->opno, OperatorRelationId, fpinfo))
return false ;
/*
* Recurse to input subexpressions .
*/
if (!foreign_expr_walker((Node *) oe->args,
glob_cxt, &inner_cxt, case_arg_cxt))
return false ;
/*
* If operator ' s input collation is not derived from a foreign
* Var , it can ' t be sent to remote .
*/
if (oe->inputcollid == InvalidOid)
/* OK, inputs are all noncollatable */ ;
else if (inner_cxt.state != FDW_COLLATE_SAFE ||
oe->inputcollid != inner_cxt.collation)
return false ;
/* Output is always boolean and so noncollatable. */
collation = InvalidOid;
state = FDW_COLLATE_NONE;
}
break ;
case T_RelabelType:
{
RelabelType *r = (RelabelType *) node;
/*
* Recurse to input subexpression .
*/
if (!foreign_expr_walker((Node *) r->arg,
glob_cxt, &inner_cxt, case_arg_cxt))
return false ;
/*
* RelabelType must not introduce a collation not derived from
* an input foreign Var ( same logic as for a real function ) .
*/
collation = r->resultcollid;
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;
}
break ;
case T_BoolExpr:
{
BoolExpr *b = (BoolExpr *) node;
/*
* Recurse to input subexpressions .
*/
if (!foreign_expr_walker((Node *) b->args,
glob_cxt, &inner_cxt, case_arg_cxt))
return false ;
/* Output is always boolean and so noncollatable. */
collation = InvalidOid;
state = FDW_COLLATE_NONE;
}
break ;
case T_NullTest:
{
NullTest *nt = (NullTest *) node;
/*
* Recurse to input subexpressions .
*/
if (!foreign_expr_walker((Node *) nt->arg,
glob_cxt, &inner_cxt, case_arg_cxt))
return false ;
/* Output is always boolean and so noncollatable. */
collation = InvalidOid;
state = FDW_COLLATE_NONE;
}
break ;
case T_CaseExpr:
{
CaseExpr *ce = (CaseExpr *) node;
foreign_loc_cxt arg_cxt;
foreign_loc_cxt tmp_cxt;
ListCell *lc;
/*
* 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;
if (!IsA(whenExpr, OpExpr))
return false ;
opArgs = ((OpExpr *) whenExpr)->args;
if (list_length(opArgs) != 2 ||
!IsA(strip_implicit_coercions(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;
}
break ;
case T_CaseTestExpr:
{
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;
}
break ;
case T_ArrayExpr:
{
ArrayExpr *a = (ArrayExpr *) node;
/*
* Recurse to input subexpressions .
*/
if (!foreign_expr_walker((Node *) a->elements,
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 = a->array_collid;
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;
}
break ;
case T_List:
{
List *l = (List *) node;
ListCell *lc;
/*
* Recurse to component subexpressions .
*/
foreach(lc, l)
{
if (!foreign_expr_walker((Node *) lfirst(lc),
glob_cxt, &inner_cxt, case_arg_cxt))
return false ;
}
/*
* When processing a list , collation state just bubbles up
* from the list elements .
*/
collation = inner_cxt.collation;
state = inner_cxt.state;
/* Don't apply exprType() to the list. */
check_type = false ;
}
break ;
case T_Aggref:
{
Aggref *agg = (Aggref *) node;
ListCell *lc;
/* Not safe to pushdown when not in grouping context */
if (!IS_UPPER_REL(glob_cxt->foreignrel))
return false ;
/* Only non-split aggregates are pushable. */
if (agg->aggsplit != AGGSPLIT_SIMPLE)
return false ;
/* As usual, it must be shippable. */
if (!is_shippable(agg->aggfnoid, ProcedureRelationId, fpinfo))
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)
{
foreach(lc, agg->aggorder)
{
SortGroupClause *srt = (SortGroupClause *) lfirst(lc);
Oid sortcoltype;
TypeCacheEntry *typentry;
TargetEntry *tle;
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_shippable(srt->sortop, OperatorRelationId,
fpinfo))
return false ;
}
}
/* Check aggregate filter */
if (!foreign_expr_walker((Node *) agg->aggfilter,
glob_cxt, &inner_cxt, case_arg_cxt))
return false ;
/*
* 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;
}
break ;
default :
/*
* If it ' s anything else , assume it ' s unsafe . This list can be
* expanded later , but don ' t forget to add deparse support below .
*/
return false ;
}
/*
* If result type of given expression is not shippable , it can ' t be sent
* to remote because it might have incompatible semantics on remote side .
*/
if (check_type && !is_shippable(exprType(node), TypeRelationId, fpinfo))
return false ;
/*
* Now , merge my collation information into my parent ' s state .
*/
if (state > outer_cxt->state)
{
/* Override previous parent state */
outer_cxt->collation = collation;
outer_cxt->state = state;
}
else if (state == outer_cxt->state)
{
/* Merge, or detect error if there's a collation conflict */
switch (state)
{
case FDW_COLLATE_NONE:
/* Nothing + nothing is still nothing */
break ;
case FDW_COLLATE_SAFE:
if (collation != outer_cxt->collation)
{
/*
* Non - default collation always beats default .
*/
if (outer_cxt->collation == DEFAULT_COLLATION_OID)
{
/* Override previous parent state */
outer_cxt->collation = collation;
}
else if (collation != DEFAULT_COLLATION_OID)
{
/*
* Conflict ; show state as indeterminate . We don ' t
* want to " return false " right away , since parent
* node might not care about collation .
*/
outer_cxt->state = FDW_COLLATE_UNSAFE;
}
}
break ;
case FDW_COLLATE_UNSAFE:
/* We're still conflicted ... */
break ;
}
}
/* It looks OK */
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;
PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) baserel->fdw_private;
/*
* is_foreign_expr would detect volatile expressions as well , but checking
* ec_has_volatile here saves some cycles .
*/
if (pathkey_ec->ec_has_volatile)
return false ;
/* can't push down the sort if the pathkey's opfamily is not shippable */
if (!is_shippable(pathkey->pk_opfamily, OperatorFamilyRelationId, fpinfo))
return false ;
/* can push if a suitable EC member exists */
return (find_em_for_rel(root, pathkey_ec, baserel) != NULL);
}
/*
* 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)
{
bits16 flags = FORMAT_TYPE_TYPEMOD_GIVEN;
if (!is_builtin(type_oid))
flags |= FORMAT_TYPE_FORCE_QUALIFY;
return format_type_extended(type_oid, typemod, flags);
}
/*
* 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;
/*
* 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->reltarget->exprs,
PVC_RECURSE_PLACEHOLDERS));
foreach(lc, fpinfo->local_conds)
{
RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc);
tlist = add_to_flat_tlist(tlist,
pull_var_clause((Node *) rinfo->clause,
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;
/*
* 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 . . . " .
*
* We also create an integer List of the columns being retrieved , which is
* 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 .
*/
static void
deparseSelectSql(List *tlist, bool is_subquery, List **retrieved_attrs,
deparse_expr_cxt *context)
{
StringInfo buf = context->buf;
RelOptInfo *foreignrel = context->foreignrel;
PlannerInfo *root = context->root;
PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) foreignrel->fdw_private;
/*
* Construct SELECT list
*/
appendStringInfoString(buf, "SELECT " );
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 = table_open(rte->relid, NoLock);
deparseTargetList(buf, rte, foreignrel->relid, rel, false ,
fpinfo->attrs_used, false , retrieved_attrs);
table_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;
List *additional_conds = NIL;
/* 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 " );
deparseFromExprForRel(buf, context->root, scanrel,
(bms_membership(scanrel->relids) == BMS_MULTIPLE),
(Index) 0 , NULL, &additional_conds,
context->params_list);
appendWhereClause(quals, additional_conds, context);
if (additional_conds != NIL)
list_free_deep(additional_conds);
}
/*
* Emit a target list that retrieves the columns specified in attrs_used .
* This is used for both SELECT and RETURNING targetlists ; the is_returning
* parameter is true only for a RETURNING targetlist .
*
* 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,
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. */
have_wholerow = bms_is_member(0 - FirstLowInvalidHeapAttributeNumber,
attrs_used);
first = true ;
for (i = 1 ; i <= tupdesc->natts; i++)
{
Form_pg_attribute attr = TupleDescAttr(tupdesc, i - 1 );
/* Ignore dropped attributes. */
if (attr->attisdropped)
continue ;
if (have_wholerow ||
bms_is_member(i - FirstLowInvalidHeapAttributeNumber,
attrs_used))
{
if (!first)
appendStringInfoString(buf, ", " );
else if (is_returning)
appendStringInfoString(buf, " RETURNING " );
first = false ;
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
* system columns .
*/
if (bms_is_member(SelfItemPointerAttributeNumber - 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, "ctid" );
*retrieved_attrs = lappend_int(*retrieved_attrs,
SelfItemPointerAttributeNumber);
}
/* Don't generate bad syntax if no undropped columns */
if (first && !is_returning)
appendStringInfoString(buf, "NULL" );
}
/*
* 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 (bms_is_member(relid, root->all_result_relids) &&
(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
{
PlanRowMark *rc = get_plan_rowmark(root->rowMarks, relid);
if (rc)
{
/*
* Relation is specified as a FOR UPDATE / SHARE target , so
* handle that . ( But we could also see LCS_NONE , meaning this
* isn ' t a target relation after all . )
*
* 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_NONE:
/* No locking needed */
break ;
case LCS_FORKEYSHARE:
case LCS_FORSHARE:
appendStringInfoString(buf, " FOR SHARE" );
break ;
case LCS_FORNOKEYUPDATE:
case LCS_FORUPDATE:
appendStringInfoString(buf, " FOR UPDATE" );
break ;
}
/* Add the relation alias if we are here for a join relation */
if (bms_membership(rel->relids) == BMS_MULTIPLE &&
rc->strength != LCS_NONE)
appendStringInfo(buf, " OF %s%d" , REL_ALIAS_PREFIX, relid);
}
}
}
}
/*
* 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;
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)
{
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, " AND " );
appendStringInfoChar(buf, '(' );
deparseExpr(expr, context);
appendStringInfoChar(buf, ')' );
is_first = false ;
}
reset_transmission_modes(nestlevel);
}
/*
* Append WHERE clause , containing conditions from exprs and additional_conds ,
* to context - > buf .
*/
static void
appendWhereClause(List *exprs, List *additional_conds, deparse_expr_cxt *context)
{
StringInfo buf = context->buf;
bool need_and = false ;
ListCell *lc;
if (exprs != NIL || additional_conds != NIL)
appendStringInfoString(buf, " WHERE " );
/*
* If there are some filters , append them .
*/
if (exprs != NIL)
{
appendConditions(exprs, context);
need_and = true ;
}
/*
* If there are some EXISTS conditions , coming from SEMI - JOINS , append
* them .
*/
foreach(lc, additional_conds)
{
if (need_and)
appendStringInfoString(buf, " AND " );
appendStringInfoString(buf, (char *) lfirst(lc));
need_and = true ;
}
}
/* 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" ;
case JOIN_SEMI:
return "SEMI" ;
default :
/* Shouldn't come here, but protect from buggy code. */
elog(ERROR, "unsupported join type %d" , jointype);
}
/* Keep compiler happy */
return NULL;
}
/*
* 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;
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++;
}
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;
/* Should only be called in these cases. */
Assert(IS_SIMPLE_REL(foreignrel) || IS_JOIN_REL(foreignrel));
first = true ;
foreach(lc, foreignrel->reltarget->exprs)
{
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 .
*
* ' additional_conds ' is a pointer to a list of strings to be appended to
* the WHERE clause , coming from lower - level SEMI - JOINs .
*/
static void
deparseFromExprForRel(StringInfo buf, PlannerInfo *root, RelOptInfo *foreignrel,
bool use_alias, Index ignore_rel, List **ignore_conds,
List **additional_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 ;
List *additional_conds_i = NIL;
List *additional_conds_o = NIL;
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, &additional_conds_o,
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);
/* Pass EXISTS conditions to upper level */
if (additional_conds_o != NIL)
{
Assert(*additional_conds == NIL);
*additional_conds = additional_conds_o;
}
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, &additional_conds_i,
params_list);
/*
* SEMI - JOIN is deparsed as the EXISTS subquery . It references
* outer and inner relations , so it should be evaluated as the
* condition in the upper - level WHERE clause . We deparse the
* condition and pass it to upper level callers as an
* additional_conds list . Upper level callers are responsible for
* inserting conditions from the list where appropriate .
*/
if (fpinfo->jointype == JOIN_SEMI)
{
deparse_expr_cxt context;
StringInfoData str;
/* Construct deparsed condition from this SEMI-JOIN */
initStringInfo(&str);
appendStringInfo(&str, "EXISTS (SELECT NULL FROM %s" ,
join_sql_i.data);
context.buf = &str;
context.foreignrel = foreignrel;
context.scanrel = foreignrel;
context.root = root;
context.params_list = params_list;
/*
* Append SEMI - JOIN clauses and EXISTS conditions from lower
* levels to the current EXISTS subquery
*/
appendWhereClause(fpinfo->joinclauses, additional_conds_i, &context);
/*
* EXISTS conditions , coming from lower join levels , have just
* been processed .
*/
if (additional_conds_i != NIL)
{
list_free_deep(additional_conds_i);
additional_conds_i = NIL;
}
/* Close parentheses for EXISTS subquery */
appendStringInfoChar(&str, ')' );
*additional_conds = lappend(*additional_conds, str.data);
}
/*
* 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);
/* Pass EXISTS conditions to the upper call */
if (additional_conds_i != NIL)
{
Assert(*additional_conds == NIL);
*additional_conds = additional_conds_i;
}
return ;
}
}
/* Neither of the relations is the target relation. */
Assert(!outerrel_is_target && !innerrel_is_target);
/*
* For semijoin FROM clause is deparsed as an outer relation . An inner
* relation and join clauses are converted to EXISTS condition and
* passed to the upper level .
*/
if (fpinfo->jointype == JOIN_SEMI)
{
appendBinaryStringInfo(buf, join_sql_o.data, join_sql_o.len);
}
else
{
/*
* For a join relation FROM clause , entry is deparsed as
*
* ( ( outer relation ) < join type > ( 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, ')' );
}
/*
* Construct additional_conds to be passed to the upper caller from
* current level additional_conds and additional_conds , coming from
* inner and outer rels .
*/
if (additional_conds_o != NIL)
{
*additional_conds = list_concat(*additional_conds,
additional_conds_o);
list_free(additional_conds_o);
}
if (additional_conds_i != NIL)
{
*additional_conds = list_concat(*additional_conds,
additional_conds_i);
list_free(additional_conds_i);
}
}
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 = table_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);
table_close(rel, NoLock);
}
}
/*
* Append FROM clause entry for the given relation into buf .
* Conditions from lower - level SEMI - JOINs are appended to additional_conds
* and should be added to upper level WHERE clause .
*/
static void
deparseRangeTblRef(StringInfo buf, PlannerInfo *root, RelOptInfo *foreignrel,
bool make_subquery, Index ignore_rel, List **ignore_conds,
List **additional_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;
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->reltarget->exprs);
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, additional_conds,
params_list);
}
/*
* deparse remote INSERT statement
*
* The statement text is appended to buf , and we also create an integer List
* of the columns being retrieved by WITH CHECK OPTION or RETURNING ( if any ) ,
* which is returned to * retrieved_attrs .
*
* This also stores end position of the VALUES clause , so that we can rebuild
* an INSERT for a batch of rows later .
*/
void
deparseInsertSql(StringInfo buf, RangeTblEntry *rte,
Index rtindex, Relation rel,
List *targetAttrs, bool doNothing,
List *withCheckOptionList, List *returningList,
List **retrieved_attrs, int *values_end_len)
{
TupleDesc tupdesc = RelationGetDescr(rel);
AttrNumber pindex;
bool first;
ListCell *lc;
appendStringInfoString(buf, "INSERT INTO " );
deparseRelation(buf, rel);
if (targetAttrs)
{
appendStringInfoChar(buf, '(' );
first = true ;
foreach(lc, targetAttrs)
{
int attnum = lfirst_int(lc);
if (!first)
appendStringInfoString(buf, ", " );
first = false ;
deparseColumnRef(buf, rtindex, attnum, rte, false );
}
appendStringInfoString(buf, ") VALUES (" );
pindex = 1 ;
first = true ;
foreach(lc, targetAttrs)
{
int attnum = lfirst_int(lc);
Form_pg_attribute attr = TupleDescAttr(tupdesc, attnum - 1 );
if (!first)
appendStringInfoString(buf, ", " );
first = false ;
if (attr->attgenerated)
appendStringInfoString(buf, "DEFAULT" );
else
{
appendStringInfo(buf, "$%d" , pindex);
pindex++;
}
}
appendStringInfoChar(buf, ')' );
}
else
appendStringInfoString(buf, " DEFAULT VALUES" );
*values_end_len = buf->len;
if (doNothing)
appendStringInfoString(buf, " ON CONFLICT DO NOTHING" );
deparseReturningList(buf, rte, rtindex, rel,
rel->trigdesc && rel->trigdesc->trig_insert_after_row,
withCheckOptionList, returningList, retrieved_attrs);
}
/*
* rebuild remote INSERT statement
*
* Provided a number of rows in a batch , builds INSERT statement with the
* right number of parameters .
*/
void
rebuildInsertSql(StringInfo buf, Relation rel,
char *orig_query, List *target_attrs,
int values_end_len, int num_params,
int num_rows)
{
TupleDesc tupdesc = RelationGetDescr(rel);
int i;
int pindex;
bool first;
ListCell *lc;
/* Make sure the values_end_len is sensible */
Assert((values_end_len > 0 ) && (values_end_len <= strlen(orig_query)));
/* Copy up to the end of the first record from the original query */
appendBinaryStringInfo(buf, orig_query, values_end_len);
/*
* Add records to VALUES clause ( we already have parameters for the first
* row , so start at the right offset ) .
*/
pindex = num_params + 1 ;
for (i = 0 ; i < num_rows; i++)
{
appendStringInfoString(buf, ", (" );
first = true ;
foreach(lc, target_attrs)
{
int attnum = lfirst_int(lc);
Form_pg_attribute attr = TupleDescAttr(tupdesc, attnum - 1 );
if (!first)
appendStringInfoString(buf, ", " );
first = false ;
if (attr->attgenerated)
appendStringInfoString(buf, "DEFAULT" );
else
{
appendStringInfo(buf, "$%d" , pindex);
pindex++;
}
}
appendStringInfoChar(buf, ')' );
}
/* Copy stuff after VALUES clause from the original query */
appendStringInfoString(buf, orig_query + values_end_len);
}
/*
* deparse remote UPDATE statement
*
* The statement text is appended to buf , and we also create an integer List
* of the columns being retrieved by WITH CHECK OPTION or RETURNING ( if any ) ,
* which is returned to * retrieved_attrs .
*/
void
deparseUpdateSql(StringInfo buf, RangeTblEntry *rte,
Index rtindex, Relation rel,
List *targetAttrs,
List *withCheckOptionList, List *returningList,
List **retrieved_attrs)
{
TupleDesc tupdesc = RelationGetDescr(rel);
AttrNumber pindex;
bool first;
ListCell *lc;
appendStringInfoString(buf, "UPDATE " );
deparseRelation(buf, rel);
appendStringInfoString(buf, " SET " );
pindex = 2 ; /* ctid is always the first param */
first = true ;
foreach(lc, targetAttrs)
{
int attnum = lfirst_int(lc);
Form_pg_attribute attr = TupleDescAttr(tupdesc, attnum - 1 );
if (!first)
appendStringInfoString(buf, ", " );
first = false ;
deparseColumnRef(buf, rtindex, attnum, rte, false );
if (attr->attgenerated)
appendStringInfoString(buf, " = DEFAULT" );
else
{
appendStringInfo(buf, " = $%d" , pindex);
pindex++;
}
}
appendStringInfoString(buf, " WHERE ctid = $1" );
deparseReturningList(buf, rte, rtindex, rel,
rel->trigdesc && rel->trigdesc->trig_update_after_row,
withCheckOptionList, returningList, retrieved_attrs);
}
/*
* deparse remote UPDATE statement
*
* ' buf ' is the output buffer to append the statement to
* ' rtindex ' is the RT index of the associated target relation
* ' rel ' is the relation descriptor for the target relation
* ' foreignrel ' is the RelOptInfo for the target relation or the join relation
* containing all base relations in the query
* ' targetlist ' is the tlist of the underlying foreign - scan plan node
* ( note that this only contains new - value expressions and junk attrs )
* ' targetAttrs ' is the target columns of the UPDATE
* ' remote_conds ' is the qual clauses that must be evaluated remotely
* ' * params_list ' is an output list of exprs that will become remote Params
* ' returningList ' is the RETURNING targetlist
* ' * retrieved_attrs ' is an output list of integers of columns being retrieved
* by RETURNING ( if any )
*/
void
deparseDirectUpdateSql(StringInfo buf, PlannerInfo *root,
Index rtindex, Relation rel,
RelOptInfo *foreignrel,
List *targetlist,
List *targetAttrs,
List *remote_conds,
List **params_list,
List *returningList,
List **retrieved_attrs)
{
deparse_expr_cxt context;
int nestlevel;
bool first;
RangeTblEntry *rte = planner_rt_fetch(rtindex, root);
ListCell *lc,
*lc2;
List *additional_conds = NIL;
/* Set up context struct for recursion */
context.root = root;
context.foreignrel = foreignrel;
context.scanrel = foreignrel;
context.buf = buf;
context.params_list = params_list;
appendStringInfoString(buf, "UPDATE " );
deparseRelation(buf, rel);
if (foreignrel->reloptkind == RELOPT_JOINREL)
appendStringInfo(buf, " %s%d" , REL_ALIAS_PREFIX, rtindex);
appendStringInfoString(buf, " SET " );
/* Make sure any constants in the exprs are printed portably */
nestlevel = set_transmission_modes();
first = true ;
forboth(lc, targetlist, lc2, targetAttrs)
{
TargetEntry *tle = lfirst_node(TargetEntry, lc);
int attnum = lfirst_int(lc2);
/* update's new-value expressions shouldn't be resjunk */
Assert(!tle->resjunk);
if (!first)
appendStringInfoString(buf, ", " );
first = false ;
deparseColumnRef(buf, rtindex, attnum, rte, false );
appendStringInfoString(buf, " = " );
deparseExpr((Expr *) tle->expr, &context);
}
reset_transmission_modes(nestlevel);
if (foreignrel->reloptkind == RELOPT_JOINREL)
{
List *ignore_conds = NIL;
appendStringInfoString(buf, " FROM " );
deparseFromExprForRel(buf, root, foreignrel, true , rtindex,
&ignore_conds, &additional_conds, params_list);
remote_conds = list_concat(remote_conds, ignore_conds);
}
appendWhereClause(remote_conds, additional_conds, &context);
if (additional_conds != NIL)
list_free_deep(additional_conds);
if (foreignrel->reloptkind == RELOPT_JOINREL)
deparseExplicitTargetList(returningList, true , retrieved_attrs,
&context);
else
deparseReturningList(buf, rte, rtindex, rel, false ,
NIL, returningList, retrieved_attrs);
}
/*
* deparse remote DELETE statement
*
* The statement text is appended to buf , and we also create an integer List
* of the columns being retrieved by RETURNING ( if any ) , which is returned
* to * retrieved_attrs .
*/
void
deparseDeleteSql(StringInfo buf, RangeTblEntry *rte,
Index rtindex, Relation rel,
List *returningList,
List **retrieved_attrs)
{
appendStringInfoString(buf, "DELETE FROM " );
deparseRelation(buf, rel);
appendStringInfoString(buf, " WHERE ctid = $1" );
deparseReturningList(buf, rte, rtindex, rel,
rel->trigdesc && rel->trigdesc->trig_delete_after_row,
NIL, returningList, retrieved_attrs);
}
/*
* deparse remote DELETE statement
*
* ' buf ' is the output buffer to append the statement to
* ' rtindex ' is the RT index of the associated target relation
* ' rel ' is the relation descriptor for the target relation
* ' foreignrel ' is the RelOptInfo for the target relation or the join relation
* containing all base relations in the query
* ' remote_conds ' is the qual clauses that must be evaluated remotely
* ' * params_list ' is an output list of exprs that will become remote Params
* ' returningList ' is the RETURNING targetlist
* ' * retrieved_attrs ' is an output list of integers of columns being retrieved
* by RETURNING ( if any )
*/
void
deparseDirectDeleteSql(StringInfo buf, PlannerInfo *root,
Index rtindex, Relation rel,
RelOptInfo *foreignrel,
List *remote_conds,
List **params_list,
List *returningList,
List **retrieved_attrs)
{
deparse_expr_cxt context;
List *additional_conds = NIL;
/* Set up context struct for recursion */
context.root = root;
context.foreignrel = foreignrel;
context.scanrel = foreignrel;
context.buf = buf;
context.params_list = params_list;
appendStringInfoString(buf, "DELETE FROM " );
deparseRelation(buf, rel);
if (foreignrel->reloptkind == RELOPT_JOINREL)
appendStringInfo(buf, " %s%d" , REL_ALIAS_PREFIX, rtindex);
if (foreignrel->reloptkind == RELOPT_JOINREL)
{
List *ignore_conds = NIL;
appendStringInfoString(buf, " USING " );
deparseFromExprForRel(buf, root, foreignrel, true , rtindex,
&ignore_conds, &additional_conds, params_list);
remote_conds = list_concat(remote_conds, ignore_conds);
}
appendWhereClause(remote_conds, additional_conds, &context);
if (additional_conds != NIL)
list_free_deep(additional_conds);
if (foreignrel->reloptkind == RELOPT_JOINREL)
deparseExplicitTargetList(returningList, true , retrieved_attrs,
&context);
else
deparseReturningList(buf, planner_rt_fetch(rtindex, root),
rtindex, rel, false ,
NIL, returningList, retrieved_attrs);
}
/*
* Add a RETURNING clause , if needed , to an INSERT / UPDATE / DELETE .
*/
static void
deparseReturningList(StringInfo buf, RangeTblEntry *rte,
Index rtindex, Relation rel,
bool trig_after_row,
List *withCheckOptionList,
List *returningList,
List **retrieved_attrs)
{
Bitmapset *attrs_used = NULL;
if (trig_after_row)
{
/* whole-row reference acquires all non-system columns */
attrs_used =
bms_make_singleton(0 - FirstLowInvalidHeapAttributeNumber);
}
if (withCheckOptionList != NIL)
{
/*
* We need the attrs , non - system and system , mentioned in the local
* query ' s WITH CHECK OPTION list .
*
* Note : we do this to ensure that WCO constraints will be evaluated
* on the data actually inserted / updated on the remote side , which
* might differ from the data supplied by the core code , for example
* as a result of remote triggers .
*/
pull_varattnos((Node *) withCheckOptionList, rtindex,
&attrs_used);
}
if (returningList != NIL)
{
/*
* We need the attrs , non - system and system , mentioned in the local
* query ' s RETURNING list .
*/
pull_varattnos((Node *) returningList, rtindex,
&attrs_used);
}
if (attrs_used != NULL)
deparseTargetList(buf, rte, rtindex, rel, true , attrs_used, false ,
retrieved_attrs);
else
*retrieved_attrs = NIL;
}
/*
* Construct SELECT statement to acquire size in blocks of given relation .
*
* Note : we use local definition of block size , not remote definition .
* This is perhaps debatable .
*
* Note : pg_relation_size ( ) exists in 8 . 1 and later .
*/
void
deparseAnalyzeSizeSql(StringInfo buf, Relation rel)
{
StringInfoData relname;
/* We'll need the remote relation name as a literal. */
initStringInfo(&relname);
deparseRelation(&relname, rel);
appendStringInfoString(buf, "SELECT pg_catalog.pg_relation_size(" );
deparseStringLiteral(buf, relname.data);
appendStringInfo(buf, "::pg_catalog.regclass) / %d" , BLCKSZ);
}
/*
* Construct SELECT statement to acquire the number of rows and the relkind of
* a relation .
*
* Note : we just return the remote server ' s reltuples value , which might
* be off a good deal , but it doesn ' t seem worth working harder . See
* comments in postgresAcquireSampleRowsFunc .
*/
void
deparseAnalyzeInfoSql(StringInfo buf, Relation rel)
{
StringInfoData relname;
/* We'll need the remote relation name as a literal. */
initStringInfo(&relname);
deparseRelation(&relname, rel);
appendStringInfoString(buf, "SELECT reltuples, relkind FROM pg_catalog.pg_class WHERE oid = " );
deparseStringLiteral(buf, relname.data);
appendStringInfoString(buf, "::pg_catalog.regclass" );
}
/*
* Construct SELECT statement to acquire sample rows of given relation .
*
* SELECT command is appended to buf , and list of columns retrieved
* is returned to * retrieved_attrs .
*
* We only support sampling methods we can decide based on server version .
* Allowing custom TSM modules ( like tsm_system_rows ) might be useful , but it
* would require detecting which extensions are installed , to allow automatic
* fall - back . Moreover , the methods may use different parameters like number
* of rows ( and not sampling rate ) . So we leave this for future improvements .
*
* Using random ( ) to sample rows on the remote server has the advantage that
* this works on all PostgreSQL versions ( unlike TABLESAMPLE ) , and that it
* does the sampling on the remote side ( without transferring everything and
* then discarding most rows ) .
*
* The disadvantage is that we still have to read all rows and evaluate the
* random ( ) , while TABLESAMPLE ( at least with the " system " method ) may skip .
* It ' s not that different from the " bernoulli " method , though .
*
* We could also do " ORDER BY random ( ) LIMIT x " , which would always pick
* the expected number of rows , but it requires sorting so it may be much
* more expensive ( particularly on large tables , which is what the
* remote sampling is meant to improve ) .
*/
void
deparseAnalyzeSql(StringInfo buf, Relation rel,
PgFdwSamplingMethod sample_method, double sample_frac,
List **retrieved_attrs)
{
Oid relid = RelationGetRelid(rel);
TupleDesc tupdesc = RelationGetDescr(rel);
int i;
char *colname;
List *options;
ListCell *lc;
bool first = true ;
*retrieved_attrs = NIL;
appendStringInfoString(buf, "SELECT " );
for (i = 0 ; i < tupdesc->natts; i++)
{
/* Ignore dropped columns. */
if (TupleDescAttr(tupdesc, i)->attisdropped)
continue ;
if (!first)
appendStringInfoString(buf, ", " );
first = false ;
/* Use attribute name or column_name option. */
colname = NameStr(TupleDescAttr(tupdesc, i)->attname);
options = GetForeignColumnOptions(relid, i + 1 );
foreach(lc, options)
{
DefElem *def = (DefElem *) lfirst(lc);
if (strcmp(def->defname, "column_name" ) == 0 )
{
colname = defGetString(def);
break ;
}
}
appendStringInfoString(buf, quote_identifier(colname));
*retrieved_attrs = lappend_int(*retrieved_attrs, i + 1 );
}
/* Don't generate bad syntax for zero-column relation. */
if (first)
appendStringInfoString(buf, "NULL" );
/*
* Construct FROM clause , and perhaps WHERE clause too , depending on the
* selected sampling method .
*/
appendStringInfoString(buf, " FROM " );
deparseRelation(buf, rel);
switch (sample_method)
{
case ANALYZE_SAMPLE_OFF:
/* nothing to do here */
break ;
case ANALYZE_SAMPLE_RANDOM:
appendStringInfo(buf, " WHERE pg_catalog.random() < %f" , sample_frac);
break ;
case ANALYZE_SAMPLE_SYSTEM:
appendStringInfo(buf, " TABLESAMPLE SYSTEM(%f)" , (100 .0 * sample_frac));
break ;
case ANALYZE_SAMPLE_BERNOULLI:
appendStringInfo(buf, " TABLESAMPLE BERNOULLI(%f)" , (100 .0 * sample_frac));
break ;
case ANALYZE_SAMPLE_AUTO:
/* should have been resolved into actual method */
elog(ERROR, "unexpected sampling method" );
break ;
}
}
/*
* Construct a simple " TRUNCATE rel " statement
*/
void
deparseTruncateSql(StringInfo buf,
List *rels,
DropBehavior behavior,
bool restart_seqs)
{
ListCell *cell;
appendStringInfoString(buf, "TRUNCATE " );
foreach(cell, rels)
{
Relation rel = lfirst(cell);
if (cell != list_head(rels))
appendStringInfoString(buf, ", " );
deparseRelation(buf, rel);
}
appendStringInfo(buf, " %s IDENTITY" ,
restart_seqs ? "RESTART" : "CONTINUE" );
if (behavior == DROP_RESTRICT)
appendStringInfoString(buf, " RESTRICT" );
else if (behavior == DROP_CASCADE)
appendStringInfoString(buf, " CASCADE" );
}
/*
* 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, int varno, int varattno, RangeTblEntry *rte,
bool qualify_col)
{
/* 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 (varattno == TableOidAttributeNumber)
fetchval = rte->relid;
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 = table_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" );
table_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, false );
if (qualify_col)
ADD_REL_QUALIFIER(buf, varno);
appendStringInfoString(buf, quote_identifier(colname));
}
}
/*
* Append remote name of specified foreign table to buf .
* Use value of table_name FDW option ( if any ) instead of relation ' s name .
* Similarly , schema_name FDW option overrides schema name .
*/
static void
deparseRelation(StringInfo buf, Relation rel)
{
ForeignTable *table;
const char *nspname = NULL;
const char *relname = NULL;
ListCell *lc;
/* obtain additional catalog information. */
table = GetForeignTable(RelationGetRelid(rel));
/*
* Use value of FDW options if any , instead of the name of object itself .
*/
foreach(lc, table->options)
{
DefElem *def = (DefElem *) lfirst(lc);
if (strcmp(def->defname, "schema_name" ) == 0 )
nspname = defGetString(def);
else if (strcmp(def->defname, "table_name" ) == 0 )
relname = defGetString(def);
}
/*
* Note : we could skip printing the schema name if it ' s pg_catalog , but
* that doesn ' t seem worth the trouble .
*/
if (nspname == NULL)
nspname = get_namespace_name(RelationGetNamespace(rel));
if (relname == NULL)
relname = RelationGetRelationName(rel);
appendStringInfo(buf, "%s.%s" ,
quote_identifier(nspname), quote_identifier(relname));
}
/*
* Append a SQL string literal representing " val " to buf .
*/
void
deparseStringLiteral(StringInfo buf, const char *val)
{
const char *valptr;
/*
* Rather than making assumptions about the remote server ' s value of
* standard_conforming_strings , always use E ' foo ' syntax if there are any
* backslashes . This will fail on remote servers before 8 . 1 , but those
* are long out of support .
*/
if (strchr(val, '\\' ) != NULL)
appendStringInfoChar(buf, ESCAPE_STRING_SYNTAX);
appendStringInfoChar(buf, '\' ');
for (valptr = val; *valptr; valptr++)
{
char ch = *valptr;
if (SQL_STR_DOUBLE(ch, true ))
appendStringInfoChar(buf, ch);
appendStringInfoChar(buf, ch);
}
appendStringInfoChar(buf, '\' ');
}
/*
* Deparse given expression into context - > buf .
*
* This function must support all the same node types that foreign_expr_walker
* accepts .
*
* Note : unlike ruleutils . c , we just use a simple hard - wired parenthesization
* scheme : anything more complex than a Var , Const , function call or cast
* should be self - parenthesized .
*/
static void
deparseExpr(Expr *node, deparse_expr_cxt *context)
{
if (node == NULL)
return ;
switch (nodeTag(node))
{
case T_Var:
deparseVar((Var *) node, context);
break ;
case T_Const:
deparseConst((Const *) node, context, 0 );
break ;
case T_Param:
deparseParam((Param *) node, context);
break ;
case T_SubscriptingRef:
deparseSubscriptingRef((SubscriptingRef *) node, context);
break ;
case T_FuncExpr:
deparseFuncExpr((FuncExpr *) node, context);
break ;
case T_OpExpr:
deparseOpExpr((OpExpr *) node, context);
break ;
case T_DistinctExpr:
deparseDistinctExpr((DistinctExpr *) node, context);
break ;
case T_ScalarArrayOpExpr:
deparseScalarArrayOpExpr((ScalarArrayOpExpr *) node, context);
break ;
case T_RelabelType:
deparseRelabelType((RelabelType *) node, context);
break ;
case T_BoolExpr:
deparseBoolExpr((BoolExpr *) node, context);
break ;
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 ;
}
}
/*
* Deparse given Var node into context - > buf .
*
* If the Var belongs to the foreign relation , just print its remote name .
* Otherwise , it ' s effectively a Param ( and will in fact be a Param at
* run time ) . Handle it the same way we handle plain Params - - - see
* deparseParam for comments .
*/
static void
deparseVar(Var *node, deparse_expr_cxt *context)
{
Relids relids = context->scanrel->relids;
int relno;
int colno;
/* 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;
/* find its index in params_list */
foreach(lc, *context->params_list)
{
pindex++;
if (equal(node, (Node *) lfirst(lc)))
break ;
}
if (lc == NULL)
{
/* not in list, so add it */
pindex++;
*context->params_list = lappend(*context->params_list, node);
}
printRemoteParam(pindex, node->vartype, node->vartypmod, context);
}
else
{
printRemotePlaceholder(node->vartype, node->vartypmod, 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, int showtype)
{
StringInfo buf = context->buf;
Oid typoutput;
bool typIsVarlena;
char *extval;
bool isfloat = false ;
bool isstring = false ;
bool needlabel;
if (node->constisnull)
{
appendStringInfoString(buf, "NULL" );
if (showtype >= 0 )
appendStringInfo(buf, "::%s" ,
deparse_type_name(node->consttype,
node->consttypmod));
return ;
}
getTypeOutputInfo(node->consttype,
&typoutput, &typIsVarlena);
extval = OidOutputFunctionCall(typoutput, node->constvalue);
switch (node->consttype)
{
case INT2OID:
case INT4OID:
case INT8OID:
case OIDOID:
case FLOAT4OID:
case FLOAT8OID:
case NUMERICOID:
{
/*
* No need to quote unless it ' s a special value such as ' NaN ' .
* See comments in get_const_expr ( ) .
*/
if (strspn(extval, "0123456789+-eE." ) == strlen(extval))
{
if (extval[0 ] == '+' || extval[0 ] == '-' )
appendStringInfo(buf, "(%s)" , extval);
else
appendStringInfoString(buf, extval);
if (strcspn(extval, "eE." ) != strlen(extval))
isfloat = true ; /* it looks like a float */
}
else
appendStringInfo(buf, "'%s'" , extval);
}
break ;
case BITOID:
case VARBITOID:
appendStringInfo(buf, "B'%s'" , extval);
break ;
case BOOLOID:
if (strcmp(extval, "t" ) == 0 )
appendStringInfoString(buf, "true" );
else
appendStringInfoString(buf, "false" );
break ;
default :
deparseStringLiteral(buf, extval);
isstring = true ;
break ;
}
pfree(extval);
if (showtype == -1 )
return ; /* never print type label */
/*
* 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 .
*/
switch (node->consttype)
{
case BOOLOID:
case INT4OID:
case UNKNOWNOID:
needlabel = false ;
break ;
case NUMERICOID:
needlabel = !isfloat || (node->consttypmod >= 0 );
break ;
default :
if (showtype == -2 )
{
/* label unless we printed it as an untyped string */
needlabel = !isstring;
}
else
needlabel = true ;
break ;
}
if (needlabel || showtype > 0 )
appendStringInfo(buf, "::%s" ,
deparse_type_name(node->consttype,
node->consttypmod));
}
/*
* Deparse given Param node .
*
* If we ' re generating the query " for real " , add the Param to
* context - > params_list if it ' s not already present , and then use its index
* in that list as the remote parameter number . During EXPLAIN , there ' s
* no need to identify a parameter number .
*/
static void
deparseParam(Param *node, deparse_expr_cxt *context)
{
if (context->params_list)
{
int pindex = 0 ;
ListCell *lc;
/* find its index in params_list */
foreach(lc, *context->params_list)
{
pindex++;
if (equal(node, (Node *) lfirst(lc)))
break ;
}
if (lc == NULL)
{
/* not in list, so add it */
pindex++;
*context->params_list = lappend(*context->params_list, node);
}
printRemoteParam(pindex, node->paramtype, node->paramtypmod, context);
}
else
{
printRemotePlaceholder(node->paramtype, node->paramtypmod, context);
}
}
/*
* Deparse a container subscript expression .
*/
static void
deparseSubscriptingRef(SubscriptingRef *node, deparse_expr_cxt *context)
{
StringInfo buf = context->buf;
ListCell *lowlist_item;
ListCell *uplist_item;
/* Always parenthesize the expression. */
appendStringInfoChar(buf, '(' );
/*
* Deparse referenced array expression first . If that expression includes
* a cast , we have to parenthesize to prevent the array subscript from
* being taken as typename decoration . We can avoid that in the typical
* case of subscripting a Var , but otherwise do it .
*/
if (IsA(node->refexpr, Var))
deparseExpr(node->refexpr, context);
else
{
appendStringInfoChar(buf, '(' );
deparseExpr(node->refexpr, context);
appendStringInfoChar(buf, ')' );
}
/* Deparse subscript expressions. */
lowlist_item = list_head(node->reflowerindexpr); /* could be NULL */
foreach(uplist_item, node->refupperindexpr)
{
appendStringInfoChar(buf, '[' );
if (lowlist_item)
{
deparseExpr(lfirst(lowlist_item), context);
appendStringInfoChar(buf, ':' );
lowlist_item = lnext(node->reflowerindexpr, lowlist_item);
}
deparseExpr(lfirst(uplist_item), context);
appendStringInfoChar(buf, ']' );
}
appendStringInfoChar(buf, ')' );
}
/*
* Deparse a function call .
*/
static void
deparseFuncExpr(FuncExpr *node, deparse_expr_cxt *context)
{
StringInfo buf = context->buf;
bool use_variadic;
bool first;
ListCell *arg;
/*
* If the function call came from an implicit coercion , then just show the
* first argument .
*/
if (node->funcformat == COERCE_IMPLICIT_CAST)
{
deparseExpr((Expr *) linitial(node->args), context);
return ;
}
/*
* If the function call came from a cast , then show the first argument
* plus an explicit cast operation .
*/
if (node->funcformat == COERCE_EXPLICIT_CAST)
{
Oid rettype = node->funcresulttype;
int32 coercedTypmod;
/* Get the typmod if this is a length-coercion function */
(void ) exprIsLengthCoercion((Node *) node, &coercedTypmod);
deparseExpr((Expr *) linitial(node->args), context);
appendStringInfo(buf, "::%s" ,
deparse_type_name(rettype, coercedTypmod));
return ;
}
/* Check if need to print VARIADIC (cf. ruleutils.c) */
use_variadic = node->funcvariadic;
/*
* Normal function : display as proname ( args ) .
*/
appendFunctionName(node->funcid, context);
appendStringInfoChar(buf, '(' );
/* ... and all the arguments */
first = true ;
foreach(arg, node->args)
{
if (!first)
appendStringInfoString(buf, ", " );
if (use_variadic && lnext(node->args, arg) == NULL)
appendStringInfoString(buf, "VARIADIC " );
deparseExpr((Expr *) lfirst(arg), context);
first = false ;
}
appendStringInfoChar(buf, ')' );
}
/*
* Deparse given operator expression . To avoid problems around
* priority of operations , we always parenthesize the arguments .
*/
static void
deparseOpExpr(OpExpr *node, deparse_expr_cxt *context)
{
StringInfo buf = context->buf;
HeapTuple tuple;
Form_pg_operator form;
Expr *right;
bool canSuppressRightConstCast = false ;
char oprkind;
/* Retrieve information about the operator from system catalog. */
tuple = SearchSysCache1(OPEROID, ObjectIdGetDatum(node->opno));
if (!HeapTupleIsValid(tuple))
elog(ERROR, "cache lookup failed for operator %u" , node->opno);
form = (Form_pg_operator) GETSTRUCT(tuple);
oprkind = form->oprkind;
/* Sanity check. */
Assert((oprkind == 'l' && list_length(node->args) == 1 ) ||
(oprkind == 'b' && list_length(node->args) == 2 ));
right = llast(node->args);
/* Always parenthesize the expression. */
appendStringInfoChar(buf, '(' );
/* Deparse left operand, if any. */
if (oprkind == 'b' )
{
Expr *left = linitial(node->args);
Oid leftType = exprType((Node *) left);
Oid rightType = exprType((Node *) right);
bool canSuppressLeftConstCast = false ;
/*
* When considering a binary operator , if one operand is a Const that
* can be printed as a bare string literal or NULL ( i . e . , it will look
* like type UNKNOWN to the remote parser ) , the Const normally
* receives an explicit cast to the operator ' s input type . However ,
* in Const - to - Var comparisons where both operands are of the same
* type , we prefer to suppress the explicit cast , leaving the Const ' s
* type resolution up to the remote parser . The remote ' s resolution
* heuristic will assume that an unknown input type being compared to
* a known input type is of that known type as well .
*
* This hack allows some cases to succeed where a remote column is
* declared with a different type in the local ( foreign ) table . By
* emitting " foreigncol = ' foo ' " not " foreigncol = ' foo ' : : text " or the
* like , we allow the remote parser to pick an " = " operator that ' s
* compatible with whatever type the remote column really is , such as
* an enum .
*
* We allow cast suppression to happen only when the other operand is
* a plain foreign Var . Although the remote ' s unknown - type heuristic
* would apply to other cases just as well , we would be taking a
* bigger risk that the inferred type is something unexpected . With
* this restriction , if anything goes wrong it ' s the user ' s fault for
* not declaring the local column with the same type as the remote
* column .
*/
if (leftType == rightType)
{
if (IsA(left, Const ))
canSuppressLeftConstCast = isPlainForeignVar(right, context);
else if (IsA(right, Const ))
canSuppressRightConstCast = isPlainForeignVar(left, context);
}
if (canSuppressLeftConstCast)
deparseConst((Const *) left, context, -2 );
else
deparseExpr(left, context);
appendStringInfoChar(buf, ' ' );
}
/* Deparse operator name. */
deparseOperatorName(buf, form);
/* Deparse right operand. */
appendStringInfoChar(buf, ' ' );
if (canSuppressRightConstCast)
deparseConst((Const *) right, context, -2 );
else
deparseExpr(right, context);
appendStringInfoChar(buf, ')' );
ReleaseSysCache(tuple);
}
/*
* Will " node " deparse as a plain foreign Var ?
*/
static bool
isPlainForeignVar(Expr *node, deparse_expr_cxt *context)
{
/*
* We allow the foreign Var to have an implicit RelabelType , mainly so
* that this ' ll work with varchar columns . Note that deparseRelabelType
* will not print such a cast , so we ' re not breaking the restriction that
* the expression print as a plain Var . We won ' t risk it for an implicit
* cast that requires a function , nor for non - implicit RelabelType ; such
* cases seem too likely to involve semantics changes compared to what
* would happen on the remote side .
*/
if (IsA(node, RelabelType) &&
((RelabelType *) node)->relabelformat == COERCE_IMPLICIT_CAST)
node = ((RelabelType *) node)->arg;
if (IsA(node, Var))
{
/*
* The Var must be one that ' ll deparse as a foreign column reference
* ( cf . deparseVar ) .
*/
Var *var = (Var *) node;
Relids relids = context->scanrel->relids;
if (bms_is_member(var->varno, relids) && var->varlevelsup == 0 )
return true ;
}
return false ;
}
/*
* Print the name of an operator .
*/
static void
deparseOperatorName(StringInfo buf, Form_pg_operator opform)
{
char *opname;
/* opname is not a SQL identifier, so we should not quote it. */
opname = NameStr(opform->oprname);
/* Print schema name only if it's not pg_catalog */
if (opform->oprnamespace != PG_CATALOG_NAMESPACE)
{
const char *opnspname;
opnspname = get_namespace_name(opform->oprnamespace);
/* Print fully qualified operator name. */
appendStringInfo(buf, "OPERATOR(%s.%s)" ,
quote_identifier(opnspname), opname);
}
else
{
/* Just print operator name. */
appendStringInfoString(buf, opname);
}
}
/*
* Deparse IS DISTINCT FROM .
*/
static void
deparseDistinctExpr(DistinctExpr *node, deparse_expr_cxt *context)
{
StringInfo buf = context->buf;
Assert(list_length(node->args) == 2 );
appendStringInfoChar(buf, '(' );
deparseExpr(linitial(node->args), context);
appendStringInfoString(buf, " IS DISTINCT FROM " );
deparseExpr(lsecond(node->args), context);
appendStringInfoChar(buf, ')' );
}
/*
* Deparse given ScalarArrayOpExpr expression . To avoid problems
* around priority of operations , we always parenthesize the arguments .
*/
static void
deparseScalarArrayOpExpr(ScalarArrayOpExpr *node, deparse_expr_cxt *context)
{
StringInfo buf = context->buf;
HeapTuple tuple;
Form_pg_operator form;
Expr *arg1;
Expr *arg2;
/* Retrieve information about the operator from system catalog. */
tuple = SearchSysCache1(OPEROID, ObjectIdGetDatum(node->opno));
if (!HeapTupleIsValid(tuple))
elog(ERROR, "cache lookup failed for operator %u" , node->opno);
form = (Form_pg_operator) GETSTRUCT(tuple);
/* Sanity check. */
Assert(list_length(node->args) == 2 );
/* Always parenthesize the expression. */
appendStringInfoChar(buf, '(' );
/* Deparse left operand. */
arg1 = linitial(node->args);
deparseExpr(arg1, context);
appendStringInfoChar(buf, ' ' );
/* Deparse operator name plus decoration. */
deparseOperatorName(buf, form);
appendStringInfo(buf, " %s (" , node->useOr ? "ANY" : "ALL" );
/* Deparse right operand. */
arg2 = lsecond(node->args);
deparseExpr(arg2, context);
appendStringInfoChar(buf, ')' );
/* Always parenthesize the expression. */
appendStringInfoChar(buf, ')' );
ReleaseSysCache(tuple);
}
/*
* Deparse a RelabelType ( binary - compatible cast ) node .
*/
static void
deparseRelabelType(RelabelType *node, deparse_expr_cxt *context)
{
deparseExpr(node->arg, context);
if (node->relabelformat != COERCE_IMPLICIT_CAST)
appendStringInfo(context->buf, "::%s" ,
deparse_type_name(node->resulttype,
node->resulttypmod));
}
/*
* Deparse a BoolExpr node .
*/
static void
deparseBoolExpr(BoolExpr *node, deparse_expr_cxt *context)
{
StringInfo buf = context->buf;
const char *op = NULL; /* keep compiler quiet */
bool first;
ListCell *lc;
switch (node->boolop)
{
case AND_EXPR:
op = "AND" ;
break ;
case OR_EXPR:
op = "OR" ;
break ;
case NOT_EXPR:
appendStringInfoString(buf, "(NOT " );
deparseExpr(linitial(node->args), context);
appendStringInfoChar(buf, ')' );
return ;
}
appendStringInfoChar(buf, '(' );
first = true ;
foreach(lc, node->args)
{
if (!first)
appendStringInfo(buf, " %s " , op);
deparseExpr((Expr *) lfirst(lc), context);
first = false ;
}
appendStringInfoChar(buf, ')' );
}
/*
* Deparse IS [ NOT ] NULL expression .
*/
static void
deparseNullTest(NullTest *node, deparse_expr_cxt *context)
{
StringInfo buf = context->buf;
appendStringInfoChar(buf, '(' );
deparseExpr(node->arg, context);
/*
* For scalar inputs , we prefer to print as IS [ NOT ] NULL , which is
* shorter and traditional . If it ' s a rowtype input but we ' re applying a
* scalar test , must print IS [ NOT ] DISTINCT FROM NULL to be semantically
* correct .
*/
if (node->argisrow || !type_is_rowtype(exprType((Node *) node->arg)))
{
if (node->nulltesttype == IS_NULL)
appendStringInfoString(buf, " IS NULL)" );
else
appendStringInfoString(buf, " IS NOT NULL)" );
}
else
{
if (node->nulltesttype == IS_NULL)
appendStringInfoString(buf, " IS NOT DISTINCT FROM NULL)" );
else
appendStringInfoString(buf, " IS DISTINCT FROM NULL)" );
}
}
/*
* Deparse CASE expression
*/
static void
deparseCaseExpr(CaseExpr *node, deparse_expr_cxt *context)
{
StringInfo buf = context->buf;
ListCell *lc;
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(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 .
*/
static void
deparseArrayExpr(ArrayExpr *node, deparse_expr_cxt *context)
{
StringInfo buf = context->buf;
bool first = true ;
ListCell *lc;
appendStringInfoString(buf, "ARRAY[" );
foreach(lc, node->elements)
{
if (!first)
appendStringInfoString(buf, ", " );
deparseExpr(lfirst(lc), context);
first = false ;
}
appendStringInfoChar(buf, ']' );
/* If the array is empty, we need an explicit cast to the array type. */
if (node->elements == NIL)
appendStringInfo(buf, "::%s" ,
deparse_type_name(node->array_typeid, -1 ));
}
/*
* Deparse an Aggref node .
*/
static void
deparseAggref(Aggref *node, deparse_expr_cxt *context)
{
StringInfo buf = context->buf;
bool use_variadic;
/* Only basic, non-split aggregation accepted. */
Assert(node->aggsplit == AGGSPLIT_SIMPLE);
/* 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;
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(node->args, 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);
}
}
/* Add FILTER (WHERE ..) */
if (node->aggfilter != NULL)
{
appendStringInfoString(buf, ") FILTER (WHERE " );
deparseExpr((Expr *) node->aggfilter, 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;
bool first = true ;
foreach(lc, orderList)
{
SortGroupClause *srt = (SortGroupClause *) lfirst(lc);
Node *sortexpr;
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 < OPERATOR > 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;
/* 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 .
*
* Note : we always label the Param ' s type explicitly rather than relying on
* transmitting a numeric type OID in PQsendQueryParams ( ) . This allows us to
* avoid assuming that types have the same OIDs on the remote side as they
* do locally - - - they need only have the same names .
*/
static void
printRemoteParam(int paramindex, Oid paramtype, int32 paramtypmod,
deparse_expr_cxt *context)
{
StringInfo buf = context->buf;
char *ptypename = deparse_type_name(paramtype, paramtypmod);
appendStringInfo(buf, "$%d::%s" , paramindex, ptypename);
}
/*
* Print the representation of a placeholder for a parameter that will be
* sent to the remote side at execution time .
*
* This is used when we ' re just trying to EXPLAIN the remote query .
* We don ' t have the actual value of the runtime parameter yet , and we don ' t
* want the remote planner to generate a plan that depends on such a value
* anyway . Thus , we can ' t do something simple like " $ 1 : : paramtype " .
* Instead , we emit " ( ( SELECT null : : paramtype ) : : paramtype ) " .
* In all extant versions of Postgres , the planner will see that as an unknown
* constant value , which is what we want . This might need adjustment if we
* ever make the planner flatten scalar subqueries . Note : the reason for the
* apparently useless outer cast is to ensure that the representation as a
* whole will be parsed as an a_expr and not a select_with_parens ; the latter
* would do the wrong thing in the context " x = ANY ( . . . ) " .
*/
static void
printRemotePlaceholder(Oid paramtype, int32 paramtypmod,
deparse_expr_cxt *context)
{
StringInfo buf = context->buf;
char *ptypename = deparse_type_name(paramtype, paramtypmod);
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;
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);
/*
* We intentionally print query - > groupClause not processed_groupClause ,
* leaving it to the remote planner to get rid of any redundant GROUP BY
* items again . This is necessary in case processed_groupClause reduced
* to empty , and in any case the redundancy situation on the remote might
* be different than what we think here .
*/
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;
int nestlevel;
StringInfo buf = context->buf;
bool gotone = false ;
/* Make sure any constants in the exprs are printed portably */
nestlevel = set_transmission_modes();
foreach(lcell, pathkeys)
{
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;
/*
* If the member is a Const expression then we needn ' t add it to the
* ORDER BY clause . This can happen in UNION ALL queries where the
* union child targetlist has a Const . Adding these would be
* wasteful , but also , for INT columns , an integer literal would be
* seen as an ordinal column position rather than a value to sort by .
* deparseConst ( ) does have code to handle this , but it seems less
* effort on all accounts just to skip these for ORDER BY clauses .
*/
if (IsA(em_expr, Const ))
continue ;
if (!gotone)
{
appendStringInfoString(buf, " ORDER BY " );
gotone = true ;
}
else
appendStringInfoString(buf, ", " );
/*
* Lookup the operator corresponding to the compare type 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_for_cmptype(pathkey->pk_opfamily,
em->em_datatype,
em->em_datatype,
pathkey->pk_cmptype);
if (!OidIsValid(oprid))
elog(ERROR, "missing operator %d(%u,%u) in opfamily %u" ,
pathkey->pk_cmptype, em->em_datatype, em->em_datatype,
pathkey->pk_opfamily);
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);
}
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;
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;
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;
Expr *expr;
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;
/* Get the relation alias ID */
*relno = fpinfo->relation_index;
/* Get the column alias ID */
i = 1 ;
foreach(lc, foreignrel->reltarget->exprs)
{
Var *tlvar = (Var *) lfirst(lc);
/*
* Match reltarget entries only on varno / varattno . Ideally there
* would be some cross - check on varnullingrels , but it ' s unclear what
* to do exactly ; we don ' t have enough context to know what that value
* should be .
*/
if (IsA(tlvar, Var) &&
tlvar->varno == node->varno &&
tlvar->varattno == node->varattno)
{
*colno = i;
return ;
}
i++;
}
/* Shouldn't get here */
elog(ERROR, "unexpected expression in subquery output" );
}
Messung V0.5 in Prozent C=94 H=94 G=93
[Verzeichnis aufwärts0.251unsichere VerbindungÜbersetzung europäischer Sprachen durch Browser2026-08-08]
2026-08-25
Neuigkeiten
Aktuelles
Motto des Tages
letze Version des Elbe Quellennavigators
Jenseits des Üblichen ....
Besucher
Statistik
#Sources=141584
#Domains=752002