@ -14,6 +14,15 @@
* 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 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 - 2013 , PostgreSQL Global Development Group
*
* IDENTIFICATION
@ -29,6 +38,7 @@
# include "access/htup_details.h"
# include "access/sysattr.h"
# include "access/transam.h"
# include "catalog/pg_collation.h"
# include "catalog/pg_namespace.h"
# include "catalog/pg_operator.h"
# include "catalog/pg_proc.h"
@ -44,16 +54,33 @@
/*
* C ontext for foreign_expr_walker ' s search of an expression tree .
* Global c ontext for foreign_expr_walker ' s search of an expression tree .
*/
typedef struct foreign_expr _cxt
typedef struct foreign_glob _cxt
{
/* Input values */
PlannerInfo * root ;
RelOptInfo * foreignrel ;
/* Result values */
List * param_numbers ; /* Param IDs of PARAM_EXTERN Params */
} foreign_expr_cxt ;
} 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 */
FDW_COLLATE_SAFE , /* collation derives from a foreign Var */
FDW_COLLATE_UNSAFE /* collation derives from something else */
} FDWCollateState ;
typedef struct foreign_loc_cxt
{
Oid collation ; /* OID of current collation, if any */
FDWCollateState state ; /* state of current collation choice */
} foreign_loc_cxt ;
/*
* Functions to determine whether an expression can be evaluated safely on
@ -61,7 +88,9 @@ typedef struct foreign_expr_cxt
*/
static bool is_foreign_expr ( PlannerInfo * root , RelOptInfo * baserel ,
Expr * expr , List * * param_numbers ) ;
static bool foreign_expr_walker ( Node * node , foreign_expr_cxt * context ) ;
static bool foreign_expr_walker ( Node * node ,
foreign_glob_cxt * glob_cxt ,
foreign_loc_cxt * outer_cxt ) ;
static bool is_builtin ( Oid procid ) ;
/*
@ -166,7 +195,8 @@ is_foreign_expr(PlannerInfo *root,
Expr * expr ,
List * * param_numbers )
{
foreign_expr_cxt context ;
foreign_glob_cxt glob_cxt ;
foreign_loc_cxt loc_cxt ;
* param_numbers = NIL ; /* default result */
@ -174,12 +204,18 @@ is_foreign_expr(PlannerInfo *root,
* Check that the expression consists of nodes that are safe to execute
* remotely .
*/
context . root = root ;
context . foreignrel = baserel ;
context . param_numbers = NIL ;
if ( foreign_expr_walker ( ( Node * ) expr , & context ) )
glob_cxt . root = root ;
glob_cxt . foreignrel = baserel ;
glob_cxt . param_numbers = NIL ;
loc_cxt . collation = InvalidOid ;
loc_cxt . state = FDW_COLLATE_NONE ;
if ( ! foreign_expr_walker ( ( Node * ) expr , & glob_cxt , & loc_cxt ) )
return false ;
/* Expressions examined here should be boolean, ie noncollatable */
Assert ( loc_cxt . collation = = InvalidOid ) ;
Assert ( loc_cxt . state = = FDW_COLLATE_NONE ) ;
/*
* An expression which includes any mutable functions can ' t be sent over
* because its result is not stable . For example , sending now ( ) remote
@ -193,42 +229,80 @@ is_foreign_expr(PlannerInfo *root,
/*
* OK , so return list of param IDs too .
*/
* param_numbers = conte xt . param_numbers ;
* param_numbers = glob_ cxt. param_numbers ;
return true ;
}
/*
* Return true if expression includes any node that is not safe to execute
* remotely . ( We use this convention because expression_tree_walker is
* designed to abort the tree walk as soon as a TRUE result is detected . )
* Check if expression is safe to execute remotely , and return true if so .
*
* In addition , glob_cxt - > param_numbers and * outer_cxt are updated .
*
* We must check that the expression contains only node types we can deparse ,
* that all types / functions / operators are safe to send ( which we approximate
* as being built - in ) , 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 .
*/
static bool
foreign_expr_walker ( Node * node , foreign_expr_cxt * context )
foreign_expr_walker ( Node * node ,
foreign_glob_cxt * glob_cxt ,
foreign_loc_cxt * outer_cxt )
{
bool check_type = true ;
foreign_loc_cxt inner_cxt ;
Oid collation ;
FDWCollateState state ;
/* Need do nothing for empty subexpressions */
if ( node = = NULL )
return false ;
return true ;
/* 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 ;
/*
* Var can be used if it is in the foreign table ( we shouldn ' t
* really see anything else in baserestrict clauses , but let ' s
* check anyway ) .
*/
Var * var = ( Var * ) node ;
if ( var - > varno ! = context - > foreignrel - > relid | |
if ( var - > varno ! = glob_cxt - > foreignrel - > relid | |
var - > varlevelsup ! = 0 )
return true ;
return false ;
/*
* If Var has a collation , consider that safe to use .
*/
collation = var - > varcollid ;
state = OidIsValid ( collation ) ? FDW_COLLATE_SAFE : FDW_COLLATE_NONE ;
}
break ;
case T_Const :
/* OK */
{
Const * c = ( Const * ) node ;
/*
* If the constant has nondefault collation , either it ' s of a
* non - builtin type , or it reflects folding of a CollateExpr ;
* either way , it ' s unsafe to send to the remote .
*/
if ( c - > constcollid ! = InvalidOid & &
c - > constcollid ! = DEFAULT_COLLATION_OID )
return false ;
/* Otherwise, we can consider that it doesn't set collation */
collation = InvalidOid ;
state = FDW_COLLATE_NONE ;
}
break ;
case T_Param :
{
@ -240,15 +314,24 @@ foreign_expr_walker(Node *node, foreign_expr_cxt *context)
* runs , we should only see PARAM_EXTERN Params anyway . )
*/
if ( p - > paramkind ! = PARAM_EXTERN )
return true ;
return false ;
/*
* Collation handling is same as for Consts .
*/
if ( p - > paramcollid ! = InvalidOid & &
p - > paramcollid ! = DEFAULT_COLLATION_OID )
return false ;
collation = InvalidOid ;
state = FDW_COLLATE_NONE ;
/*
* Report IDs of PARAM_EXTERN Params . We don ' t bother to
* eliminate duplicate list elements here ; classifyConditions
* will do that .
*/
context - > param_numbers = lappend_int ( context - > param_numbers ,
p - > paramid ) ;
glob_ cxt- > param_numbers = lappend_int ( glob_ cxt- > param_numbers ,
p - > paramid ) ;
}
break ;
case T_ArrayRef :
@ -257,60 +340,262 @@ foreign_expr_walker(Node *node, foreign_expr_cxt *context)
/* Assignment should not be in restrictions. */
if ( ar - > refassgnexpr ! = NULL )
return true ;
return false ;
/*
* Recurse to remaining subexpressions . Since the array
* subscripts must yield ( noncollatable ) integers , they won ' t
* affect the inner_cxt state .
*/
if ( ! foreign_expr_walker ( ( Node * ) ar - > refupperindexpr ,
glob_cxt , & inner_cxt ) )
return false ;
if ( ! foreign_expr_walker ( ( Node * ) ar - > reflowerindexpr ,
glob_cxt , & inner_cxt ) )
return false ;
if ( ! foreign_expr_walker ( ( Node * ) ar - > refexpr ,
glob_cxt , & inner_cxt ) )
return false ;
/*
* Array subscripting should yield same collation as input ,
* but for safety use same logic as for function nodes .
*/
collation = ar - > 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
state = FDW_COLLATE_UNSAFE ;
}
break ;
case T_FuncExpr :
{
FuncExpr * fe = ( FuncExpr * ) node ;
/*
* If function used by the expression is not built - in , it
* can ' t be sent to remote because it might have incompatible
* semantics on remote side .
*/
FuncExpr * fe = ( FuncExpr * ) node ;
if ( ! is_builtin ( fe - > funcid ) )
return true ;
return false ;
/*
* Recurse to input subexpressions .
*/
if ( ! foreign_expr_walker ( ( Node * ) fe - > args ,
glob_cxt , & inner_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
state = FDW_COLLATE_UNSAFE ;
}
break ;
case T_OpExpr :
case T_DistinctExpr : /* struct-equivalent to OpExpr */
{
OpExpr * oe = ( OpExpr * ) node ;
/*
* Similarly , only built - in operators can be sent to remote .
* ( If the operator is , surely its underlying function is
* too . )
*/
OpExpr * oe = ( OpExpr * ) node ;
if ( ! is_builtin ( oe - > opno ) )
return true ;
return false ;
/*
* Recurse to input subexpressions .
*/
if ( ! foreign_expr_walker ( ( Node * ) oe - > args ,
glob_cxt , & inner_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
state = FDW_COLLATE_UNSAFE ;
}
break ;
case T_ScalarArrayOpExpr :
{
ScalarArrayOpExpr * oe = ( ScalarArrayOpExpr * ) node ;
/*
* Again , only built - in operators can be sent to remote .
*/
ScalarArrayOpExpr * oe = ( ScalarArrayOpExpr * ) node ;
if ( ! is_builtin ( oe - > opno ) )
return true ;
return false ;
/*
* Recurse to input subexpressions .
*/
if ( ! foreign_expr_walker ( ( Node * ) oe - > args ,
glob_cxt , & inner_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 ) )
return false ;
/*
* RelabelType must not introduce a collation not derived from
* an input foreign Var .
*/
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
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 ) )
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 ) )
return false ;
/* Output is always boolean and so noncollatable. */
collation = InvalidOid ;
state = FDW_COLLATE_NONE ;
}
break ;
case T_ArrayExpr :
/* OK */
{
ArrayExpr * a = ( ArrayExpr * ) node ;
/*
* Recurse to input subexpressions .
*/
if ( ! foreign_expr_walker ( ( Node * ) a - > elements ,
glob_cxt , & inner_cxt ) )
return false ;
/*
* ArrayExpr must not introduce a collation not derived from
* an input foreign Var .
*/
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
state = FDW_COLLATE_UNSAFE ;
}
break ;
case T_List :
{
List * l = ( List * ) node ;
ListCell * lc ;
/*
* We need only fall through to let expression_tree_walker scan
* the list elements - - - but don ' t apply exprType ( ) to the list .
*/
check_type = false ;
/*
* Recurse to component subexpressions .
*/
foreach ( lc , l )
{
if ( ! foreign_expr_walker ( ( Node * ) lfirst ( lc ) ,
glob_cxt , & inner_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 ;
default :
@ -318,7 +603,7 @@ foreign_expr_walker(Node *node, foreign_expr_cxt *context)
* 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 tru e;
return fals e;
}
/*
@ -326,10 +611,55 @@ foreign_expr_walker(Node *node, foreign_expr_cxt *context)
* remote because it might have incompatible semantics on remote side .
*/
if ( check_type & & ! is_builtin ( exprType ( node ) ) )
return true ;
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 ;
}
}
/* Recurse to examine sub-nodes */
return expression_tree_walker ( node , foreign_expr_walker , context ) ;
/* It looks OK */
return true ;
}
/*