Introduce less-bogus handling of collations in contrib/postgres_fdw.
Treat expressions as being remotely executable only if all collations used in them are determined by Vars of the foreign table. This means that, if the foreign server gets different answers than we do, it's the user's fault for not having marked the foreign table columns with collations equivalent to the remote table's. This rule allows most simple expressions such as "var < 'constant'" to be sent to the remote side, because the constant isn't determining the collation (the Var's collation would win). There's still room for improvement, but it's hard to see how to do it without a lot more knowledge and/or assumptions about what the remote side will do.
This commit is contained in:
parent
209f675f0f
commit
ed3ddf918b
3 changed files with 474 additions and 60 deletions
|
@ -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 @@
|
|||
|
||||
|
||||
/*
|
||||
* Context for foreign_expr_walker's search of an expression tree.
|
||||
* Global context 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 = context.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 true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -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;
|
||||
|
||||
/* Recurse to examine sub-nodes */
|
||||
return expression_tree_walker(node, foreign_expr_walker, context);
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
@ -477,7 +477,7 @@ EXECUTE st1(101, 101);
|
|||
(1 row)
|
||||
|
||||
-- subquery using stable function (can't be sent to remote)
|
||||
PREPARE st2(int) AS SELECT * FROM ft1 t1 WHERE t1.c1 < $2 AND t1.c3 IN (SELECT c3 FROM ft2 t2 WHERE c1 > $1 AND EXTRACT(dow FROM c4) = 6) ORDER BY c1;
|
||||
PREPARE st2(int) AS SELECT * FROM ft1 t1 WHERE t1.c1 < $2 AND t1.c3 IN (SELECT c3 FROM ft2 t2 WHERE c1 > $1 AND date(c4) = '1970-01-17'::date) ORDER BY c1;
|
||||
EXPLAIN (VERBOSE, COSTS false) EXECUTE st2(10, 20);
|
||||
QUERY PLAN
|
||||
-------------------------------------------------------------------------------------------------------------------------
|
||||
|
@ -494,7 +494,7 @@ EXPLAIN (VERBOSE, COSTS false) EXECUTE st2(10, 20);
|
|||
Output: t2.c3
|
||||
-> Foreign Scan on public.ft2 t2
|
||||
Output: t2.c3
|
||||
Filter: (date_part('dow'::text, t2.c4) = 6::double precision)
|
||||
Filter: (date(t2.c4) = '01-17-1970'::date)
|
||||
Remote SQL: SELECT NULL, NULL, c3, c4, NULL, NULL, NULL, NULL FROM "S 1"."T 1" WHERE (("C 1" > 10))
|
||||
(15 rows)
|
||||
|
||||
|
@ -504,17 +504,17 @@ EXECUTE st2(10, 20);
|
|||
16 | 6 | 00016 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo
|
||||
(1 row)
|
||||
|
||||
EXECUTE st1(101, 101);
|
||||
c3 | c3
|
||||
-------+-------
|
||||
00101 | 00101
|
||||
EXECUTE st2(101, 121);
|
||||
c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8
|
||||
-----+----+-------+------------------------------+--------------------------+----+------------+-----
|
||||
116 | 6 | 00116 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo
|
||||
(1 row)
|
||||
|
||||
-- subquery using immutable function (can be sent to remote)
|
||||
PREPARE st3(int) AS SELECT * FROM ft1 t1 WHERE t1.c1 < $2 AND t1.c3 IN (SELECT c3 FROM ft2 t2 WHERE c1 > $1 AND EXTRACT(dow FROM c5) = 6) ORDER BY c1;
|
||||
PREPARE st3(int) AS SELECT * FROM ft1 t1 WHERE t1.c1 < $2 AND t1.c3 IN (SELECT c3 FROM ft2 t2 WHERE c1 > $1 AND date(c5) = '1970-01-17'::date) ORDER BY c1;
|
||||
EXPLAIN (VERBOSE, COSTS false) EXECUTE st3(10, 20);
|
||||
QUERY PLAN
|
||||
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
QUERY PLAN
|
||||
-----------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
Sort
|
||||
Output: t1.c1, t1.c2, t1.c3, t1.c4, t1.c5, t1.c6, t1.c7, t1.c8
|
||||
Sort Key: t1.c1
|
||||
|
@ -528,7 +528,7 @@ EXPLAIN (VERBOSE, COSTS false) EXECUTE st3(10, 20);
|
|||
Output: t2.c3
|
||||
-> Foreign Scan on public.ft2 t2
|
||||
Output: t2.c3
|
||||
Remote SQL: SELECT NULL, NULL, c3, NULL, NULL, NULL, NULL, NULL FROM "S 1"."T 1" WHERE (("C 1" > 10)) AND ((date_part('dow'::text, c5) = 6::double precision))
|
||||
Remote SQL: SELECT NULL, NULL, c3, NULL, NULL, NULL, NULL, NULL FROM "S 1"."T 1" WHERE (("C 1" > 10)) AND ((date(c5) = '1970-01-17'::date))
|
||||
(14 rows)
|
||||
|
||||
EXECUTE st3(10, 20);
|
||||
|
@ -538,10 +538,9 @@ EXECUTE st3(10, 20);
|
|||
(1 row)
|
||||
|
||||
EXECUTE st3(20, 30);
|
||||
c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8
|
||||
----+----+-------+------------------------------+--------------------------+----+------------+-----
|
||||
23 | 3 | 00023 | Sat Jan 24 00:00:00 1970 PST | Sat Jan 24 00:00:00 1970 | 3 | 3 | foo
|
||||
(1 row)
|
||||
c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8
|
||||
----+----+----+----+----+----+----+----
|
||||
(0 rows)
|
||||
|
||||
-- custom plan should be chosen initially
|
||||
PREPARE st4(int) AS SELECT * FROM ft1 t1 WHERE t1.c1 = $1;
|
||||
|
@ -731,6 +730,74 @@ SELECT * FROM ft1 ORDER BY c1 LIMIT 1;
|
|||
(1 row)
|
||||
|
||||
COMMIT;
|
||||
-- ===================================================================
|
||||
-- test handling of collations
|
||||
-- ===================================================================
|
||||
create table loct3 (f1 text collate "C", f2 text);
|
||||
create foreign table ft3 (f1 text collate "C", f2 text)
|
||||
server loopback options (table_name 'loct3');
|
||||
-- can be sent to remote
|
||||
explain (verbose, costs off) select * from ft3 where f1 = 'foo';
|
||||
QUERY PLAN
|
||||
--------------------------------------------------------------------------
|
||||
Foreign Scan on public.ft3
|
||||
Output: f1, f2
|
||||
Remote SQL: SELECT f1, f2 FROM public.loct3 WHERE ((f1 = 'foo'::text))
|
||||
(3 rows)
|
||||
|
||||
explain (verbose, costs off) select * from ft3 where f1 COLLATE "C" = 'foo';
|
||||
QUERY PLAN
|
||||
--------------------------------------------------------------------------
|
||||
Foreign Scan on public.ft3
|
||||
Output: f1, f2
|
||||
Remote SQL: SELECT f1, f2 FROM public.loct3 WHERE ((f1 = 'foo'::text))
|
||||
(3 rows)
|
||||
|
||||
explain (verbose, costs off) select * from ft3 where f2 = 'foo';
|
||||
QUERY PLAN
|
||||
--------------------------------------------------------------------------
|
||||
Foreign Scan on public.ft3
|
||||
Output: f1, f2
|
||||
Remote SQL: SELECT f1, f2 FROM public.loct3 WHERE ((f2 = 'foo'::text))
|
||||
(3 rows)
|
||||
|
||||
-- can't be sent to remote
|
||||
explain (verbose, costs off) select * from ft3 where f1 COLLATE "POSIX" = 'foo';
|
||||
QUERY PLAN
|
||||
-----------------------------------------------
|
||||
Foreign Scan on public.ft3
|
||||
Output: f1, f2
|
||||
Filter: ((ft3.f1)::text = 'foo'::text)
|
||||
Remote SQL: SELECT f1, f2 FROM public.loct3
|
||||
(4 rows)
|
||||
|
||||
explain (verbose, costs off) select * from ft3 where f1 = 'foo' COLLATE "C";
|
||||
QUERY PLAN
|
||||
-----------------------------------------------
|
||||
Foreign Scan on public.ft3
|
||||
Output: f1, f2
|
||||
Filter: (ft3.f1 = 'foo'::text COLLATE "C")
|
||||
Remote SQL: SELECT f1, f2 FROM public.loct3
|
||||
(4 rows)
|
||||
|
||||
explain (verbose, costs off) select * from ft3 where f2 COLLATE "C" = 'foo';
|
||||
QUERY PLAN
|
||||
-----------------------------------------------
|
||||
Foreign Scan on public.ft3
|
||||
Output: f1, f2
|
||||
Filter: ((ft3.f2)::text = 'foo'::text)
|
||||
Remote SQL: SELECT f1, f2 FROM public.loct3
|
||||
(4 rows)
|
||||
|
||||
explain (verbose, costs off) select * from ft3 where f2 = 'foo' COLLATE "C";
|
||||
QUERY PLAN
|
||||
-----------------------------------------------
|
||||
Foreign Scan on public.ft3
|
||||
Output: f1, f2
|
||||
Filter: (ft3.f2 = 'foo'::text COLLATE "C")
|
||||
Remote SQL: SELECT f1, f2 FROM public.loct3
|
||||
(4 rows)
|
||||
|
||||
-- ===================================================================
|
||||
-- test writable foreign table stuff
|
||||
-- ===================================================================
|
||||
|
|
|
@ -199,12 +199,12 @@ EXPLAIN (VERBOSE, COSTS false) EXECUTE st1(1, 2);
|
|||
EXECUTE st1(1, 1);
|
||||
EXECUTE st1(101, 101);
|
||||
-- subquery using stable function (can't be sent to remote)
|
||||
PREPARE st2(int) AS SELECT * FROM ft1 t1 WHERE t1.c1 < $2 AND t1.c3 IN (SELECT c3 FROM ft2 t2 WHERE c1 > $1 AND EXTRACT(dow FROM c4) = 6) ORDER BY c1;
|
||||
PREPARE st2(int) AS SELECT * FROM ft1 t1 WHERE t1.c1 < $2 AND t1.c3 IN (SELECT c3 FROM ft2 t2 WHERE c1 > $1 AND date(c4) = '1970-01-17'::date) ORDER BY c1;
|
||||
EXPLAIN (VERBOSE, COSTS false) EXECUTE st2(10, 20);
|
||||
EXECUTE st2(10, 20);
|
||||
EXECUTE st1(101, 101);
|
||||
EXECUTE st2(101, 121);
|
||||
-- subquery using immutable function (can be sent to remote)
|
||||
PREPARE st3(int) AS SELECT * FROM ft1 t1 WHERE t1.c1 < $2 AND t1.c3 IN (SELECT c3 FROM ft2 t2 WHERE c1 > $1 AND EXTRACT(dow FROM c5) = 6) ORDER BY c1;
|
||||
PREPARE st3(int) AS SELECT * FROM ft1 t1 WHERE t1.c1 < $2 AND t1.c3 IN (SELECT c3 FROM ft2 t2 WHERE c1 > $1 AND date(c5) = '1970-01-17'::date) ORDER BY c1;
|
||||
EXPLAIN (VERBOSE, COSTS false) EXECUTE st3(10, 20);
|
||||
EXECUTE st3(10, 20);
|
||||
EXECUTE st3(20, 30);
|
||||
|
@ -274,6 +274,23 @@ FETCH c;
|
|||
SELECT * FROM ft1 ORDER BY c1 LIMIT 1;
|
||||
COMMIT;
|
||||
|
||||
-- ===================================================================
|
||||
-- test handling of collations
|
||||
-- ===================================================================
|
||||
create table loct3 (f1 text collate "C", f2 text);
|
||||
create foreign table ft3 (f1 text collate "C", f2 text)
|
||||
server loopback options (table_name 'loct3');
|
||||
|
||||
-- can be sent to remote
|
||||
explain (verbose, costs off) select * from ft3 where f1 = 'foo';
|
||||
explain (verbose, costs off) select * from ft3 where f1 COLLATE "C" = 'foo';
|
||||
explain (verbose, costs off) select * from ft3 where f2 = 'foo';
|
||||
-- can't be sent to remote
|
||||
explain (verbose, costs off) select * from ft3 where f1 COLLATE "POSIX" = 'foo';
|
||||
explain (verbose, costs off) select * from ft3 where f1 = 'foo' COLLATE "C";
|
||||
explain (verbose, costs off) select * from ft3 where f2 COLLATE "C" = 'foo';
|
||||
explain (verbose, costs off) select * from ft3 where f2 = 'foo' COLLATE "C";
|
||||
|
||||
-- ===================================================================
|
||||
-- test writable foreign table stuff
|
||||
-- ===================================================================
|
||||
|
|
Loading…
Reference in a new issue