diff --git a/contrib/postgres_fdw/deparse.c b/contrib/postgres_fdw/deparse.c index 3a04358df3ec38b6d7b78918029bb2d2c6287212..04e6c8cfdda9584095527f2bfae95a773e88bb33 100644 --- a/contrib/postgres_fdw/deparse.c +++ b/contrib/postgres_fdw/deparse.c @@ -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; + + /* + * 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; } /* diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out index d2913a9ae6ef8c86e0f546b8a79b636fd38b477f..706a37c685a48e768f881d01af0517364757b932 100644 --- a/contrib/postgres_fdw/expected/postgres_fdw.out +++ b/contrib/postgres_fdw/expected/postgres_fdw.out @@ -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 -- =================================================================== diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql index 70c1e85f32503f2b00743071e24a46a2cf6e3135..6dc50e4a2a7b32522c7551b430f4f3a0470223dd 100644 --- a/contrib/postgres_fdw/sql/postgres_fdw.sql +++ b/contrib/postgres_fdw/sql/postgres_fdw.sql @@ -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 -- ===================================================================