diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c new file mode 100644 index 9cbbcfb..34ddf41 *** a/src/backend/optimizer/plan/planner.c --- b/src/backend/optimizer/plan/planner.c *************** inheritance_planner(PlannerInfo *root) *** 790,795 **** --- 790,796 ---- { Query *parse = root->parse; int parentRTindex = parse->resultRelation; + Bitmapset *resultRTindexes = NULL; List *final_rtable = NIL; int save_rel_array_size = 0; RelOptInfo **save_rel_array = NULL; *************** inheritance_planner(PlannerInfo *root) *** 814,820 **** --- 815,835 ---- * (1) would result in a rangetable of length O(N^2) for N targets, with * at least O(N^3) work expended here; and (2) would greatly complicate * management of the rowMarks list. + * + * Note that any RTEs with security barrier quals will be turned into + * subqueries during planning, and so we must create copies of them too, + * except where they are target relations, which will each only be used + * in a single plan. */ + resultRTindexes = bms_add_member(resultRTindexes, parentRTindex); + foreach(lc, root->append_rel_list) + { + AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(lc); + if (appinfo->parent_relid == parentRTindex) + resultRTindexes = bms_add_member(resultRTindexes, + appinfo->child_relid); + } + foreach(lc, root->append_rel_list) { AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(lc); *************** inheritance_planner(PlannerInfo *root) *** 885,905 **** { RangeTblEntry *rte = (RangeTblEntry *) lfirst(lr); ! if (rte->rtekind == RTE_SUBQUERY) { Index newrti; /* * The RTE can't contain any references to its own RT ! * index, so we can save a few cycles by applying ! * ChangeVarNodes before we append the RTE to the ! * rangetable. */ newrti = list_length(subroot.parse->rtable) + 1; ChangeVarNodes((Node *) subroot.parse, rti, newrti, 0); ChangeVarNodes((Node *) subroot.rowMarks, rti, newrti, 0); ChangeVarNodes((Node *) subroot.append_rel_list, rti, newrti, 0); rte = copyObject(rte); subroot.parse->rtable = lappend(subroot.parse->rtable, rte); } --- 900,928 ---- { RangeTblEntry *rte = (RangeTblEntry *) lfirst(lr); ! /* ! * Copy subquery RTEs and RTEs with security barrier quals ! * that will be turned into subqueries, except those that are ! * target relations. ! */ ! if (rte->rtekind == RTE_SUBQUERY || ! (rte->securityQuals != NIL && ! !bms_is_member(rti, resultRTindexes))) { Index newrti; /* * The RTE can't contain any references to its own RT ! * index, except in the security barrier quals, so we can ! * save a few cycles by applying ChangeVarNodes before we ! * append the RTE to the rangetable. */ newrti = list_length(subroot.parse->rtable) + 1; ChangeVarNodes((Node *) subroot.parse, rti, newrti, 0); ChangeVarNodes((Node *) subroot.rowMarks, rti, newrti, 0); ChangeVarNodes((Node *) subroot.append_rel_list, rti, newrti, 0); rte = copyObject(rte); + ChangeVarNodes((Node *) rte->securityQuals, rti, newrti, 0); subroot.parse->rtable = lappend(subroot.parse->rtable, rte); } *************** preprocess_rowmarks(PlannerInfo *root) *** 2254,2261 **** newrc = makeNode(PlanRowMark); newrc->rti = newrc->prti = i; newrc->rowmarkId = ++(root->glob->lastRowMarkId); ! /* real tables support REFERENCE, anything else needs COPY */ if (rte->rtekind == RTE_RELATION && rte->relkind != RELKIND_FOREIGN_TABLE) newrc->markType = ROW_MARK_REFERENCE; else --- 2277,2289 ---- newrc = makeNode(PlanRowMark); newrc->rti = newrc->prti = i; newrc->rowmarkId = ++(root->glob->lastRowMarkId); ! /* ! * Real tables support REFERENCE, anything else needs COPY. Since ! * RTEs with security barrier quals will become subqueries, they also ! * need COPY. ! */ if (rte->rtekind == RTE_RELATION && + rte->securityQuals == NIL && rte->relkind != RELKIND_FOREIGN_TABLE) newrc->markType = ROW_MARK_REFERENCE; else diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c new file mode 100644 index b8e6e7a..002f6dd *** a/src/backend/rewrite/rewriteHandler.c --- b/src/backend/rewrite/rewriteHandler.c *************** fireRIRrules(Query *parsetree, List *act *** 1756,1761 **** --- 1756,1763 ---- expression_tree_walker( (Node*) quals, fireRIRonSubLink, (void*)activeRIRs); + + activeRIRs = list_delete_first(activeRIRs); } } diff --git a/src/backend/rewrite/rowsecurity.c b/src/backend/rewrite/rowsecurity.c new file mode 100644 index 35790a9..4eb8d10 *** a/src/backend/rewrite/rowsecurity.c --- b/src/backend/rewrite/rowsecurity.c *************** *** 57,63 **** static List *pull_row_security_policies(CmdType cmd, Relation relation, Oid user_id); ! static void process_policies(List *policies, int rt_index, Expr **final_qual, Expr **final_with_check_qual, bool *hassublinks); --- 57,63 ---- static List *pull_row_security_policies(CmdType cmd, Relation relation, Oid user_id); ! static void process_policies(Query* root, List *policies, int rt_index, Expr **final_qual, Expr **final_with_check_qual, bool *hassublinks); *************** prepend_row_security_policies(Query* roo *** 95,101 **** Oid user_id; int sec_context; int rls_status; ! bool defaultDeny = true; bool hassublinks = false; /* This is just to get the security context */ --- 95,101 ---- Oid user_id; int sec_context; int rls_status; ! bool defaultDeny = false; bool hassublinks = false; /* This is just to get the security context */ *************** prepend_row_security_policies(Query* roo *** 166,172 **** defaultDeny = true; /* Now that we have our policies, build the expressions from them. */ ! process_policies(rowsec_policies, rt_index, &rowsec_expr, &rowsec_with_check_expr, &hassublinks); /* --- 166,172 ---- defaultDeny = true; /* Now that we have our policies, build the expressions from them. */ ! process_policies(root, rowsec_policies, rt_index, &rowsec_expr, &rowsec_with_check_expr, &hassublinks); /* *************** prepend_row_security_policies(Query* roo *** 196,202 **** hook_policies = (*row_security_policy_hook)(root->commandType, rel); /* Build the expression from any policies returned. */ ! process_policies(hook_policies, rt_index, &hook_expr, &hook_with_check_expr, &hassublinks); } --- 196,202 ---- hook_policies = (*row_security_policy_hook)(root->commandType, rel); /* Build the expression from any policies returned. */ ! process_policies(root, hook_policies, rt_index, &hook_expr, &hook_with_check_expr, &hassublinks); } *************** pull_row_security_policies(CmdType cmd, *** 383,389 **** * qual_eval, with_check_eval, and hassublinks are output variables */ static void ! process_policies(List *policies, int rt_index, Expr **qual_eval, Expr **with_check_eval, bool *hassublinks) { ListCell *item; --- 383,389 ---- * qual_eval, with_check_eval, and hassublinks are output variables */ static void ! process_policies(Query* root, List *policies, int rt_index, Expr **qual_eval, Expr **with_check_eval, bool *hassublinks) { ListCell *item; *************** process_policies(List *policies, int rt_ *** 392,398 **** /* * Extract the USING and WITH CHECK quals from each of the policies ! * and add them to our lists. */ foreach(item, policies) { --- 392,399 ---- /* * Extract the USING and WITH CHECK quals from each of the policies ! * and add them to our lists. We only want WITH CHECK quals if this ! * RTE is the query's result relation. */ foreach(item, policies) { *************** process_policies(List *policies, int rt_ *** 401,407 **** if (policy->qual != NULL) quals = lcons(copyObject(policy->qual), quals); ! if (policy->with_check_qual != NULL) with_check_quals = lcons(copyObject(policy->with_check_qual), with_check_quals); --- 402,409 ---- if (policy->qual != NULL) quals = lcons(copyObject(policy->qual), quals); ! if (policy->with_check_qual != NULL && ! rt_index == root->resultRelation) with_check_quals = lcons(copyObject(policy->with_check_qual), with_check_quals); *************** process_policies(List *policies, int rt_ *** 421,427 **** * If we end up with only USING quals, then use those as * WITH CHECK quals also. */ ! if (with_check_quals == NIL) with_check_quals = copyObject(quals); /* --- 423,429 ---- * If we end up with only USING quals, then use those as * WITH CHECK quals also. */ ! if (with_check_quals == NIL && rt_index == root->resultRelation) with_check_quals = copyObject(quals); /* *************** process_policies(List *policies, int rt_ *** 450,457 **** */ if (list_length(with_check_quals) > 1) *with_check_eval = makeBoolExpr(OR_EXPR, with_check_quals, -1); ! else *with_check_eval = (Expr*) linitial(with_check_quals); return; } --- 452,461 ---- */ if (list_length(with_check_quals) > 1) *with_check_eval = makeBoolExpr(OR_EXPR, with_check_quals, -1); ! else if (with_check_quals != NIL) *with_check_eval = (Expr*) linitial(with_check_quals); + else + *with_check_eval = NULL; return; } diff --git a/src/test/regress/expected/rowsecurity.out b/src/test/regress/expected/rowsecurity.out new file mode 100644 index 430da55..19287fd *** a/src/test/regress/expected/rowsecurity.out --- b/src/test/regress/expected/rowsecurity.out *************** NOTICE: f_leak => yyyyyy *** 1111,1116 **** --- 1111,1185 ---- 302 | 2 | yyyyyy | (2,yyyyyy) (5 rows) + -- update with from clause self join + EXPLAIN (COSTS OFF) UPDATE t1 t1_1 SET b = t1_2.b FROM t1 t1_2 + WHERE t1_1.a = 4 AND t1_2.a = t1_1.a AND t1_2.b = t1_1.b + AND f_leak(t1_1.b) AND f_leak(t1_2.b) RETURNING *, t1_1, t1_2; + QUERY PLAN + --------------------------------------------------------------- + Update on t1 t1_1_3 + -> Nested Loop + Join Filter: (t1_1.b = t1_2.b) + -> Subquery Scan on t1_1 + Filter: f_leak(t1_1.b) + -> Seq Scan on t1 t1_1_4 + Filter: ((a = 4) AND ((a % 2) = 0)) + -> Subquery Scan on t1_2 + Filter: f_leak(t1_2.b) + -> Append + -> Seq Scan on t1 t1_2_3 + Filter: ((a = 4) AND ((a % 2) = 0)) + -> Seq Scan on t2 t1_2_4 + Filter: ((a = 4) AND ((a % 2) = 0)) + -> Seq Scan on t3 t1_2_5 + Filter: ((a = 4) AND ((a % 2) = 0)) + -> Nested Loop + Join Filter: (t1_1_1.b = t1_2_1.b) + -> Subquery Scan on t1_1_1 + Filter: f_leak(t1_1_1.b) + -> Seq Scan on t2 t1_1_5 + Filter: ((a = 4) AND ((a % 2) = 0)) + -> Subquery Scan on t1_2_1 + Filter: f_leak(t1_2_1.b) + -> Append + -> Seq Scan on t1 t1_2_6 + Filter: ((a = 4) AND ((a % 2) = 0)) + -> Seq Scan on t2 t1_2_7 + Filter: ((a = 4) AND ((a % 2) = 0)) + -> Seq Scan on t3 t1_2_8 + Filter: ((a = 4) AND ((a % 2) = 0)) + -> Nested Loop + Join Filter: (t1_1_2.b = t1_2_2.b) + -> Subquery Scan on t1_1_2 + Filter: f_leak(t1_1_2.b) + -> Seq Scan on t3 t1_1_6 + Filter: ((a = 4) AND ((a % 2) = 0)) + -> Subquery Scan on t1_2_2 + Filter: f_leak(t1_2_2.b) + -> Append + -> Seq Scan on t1 t1_2_9 + Filter: ((a = 4) AND ((a % 2) = 0)) + -> Seq Scan on t2 t1_2_10 + Filter: ((a = 4) AND ((a % 2) = 0)) + -> Seq Scan on t3 t1_2_11 + Filter: ((a = 4) AND ((a % 2) = 0)) + (46 rows) + + UPDATE t1 t1_1 SET b = t1_2.b FROM t1 t1_2 + WHERE t1_1.a = 4 AND t1_2.a = t1_1.a AND t1_2.b = t1_1.b + AND f_leak(t1_1.b) AND f_leak(t1_2.b) RETURNING *, t1_1, t1_2; + NOTICE: f_leak => dddddd_updt + NOTICE: f_leak => dddddd_updt + NOTICE: f_leak => defdef + NOTICE: f_leak => defdef + NOTICE: f_leak => dddddd_updt + NOTICE: f_leak => defdef + a | b | a | b | t1_1 | t1_2 + ---+-------------+---+-------------+-----------------+----------------- + 4 | dddddd_updt | 4 | dddddd_updt | (4,dddddd_updt) | (4,dddddd_updt) + 4 | defdef | 4 | defdef | (4,defdef) | (4,defdef) + (2 rows) + RESET SESSION AUTHORIZATION; SET row_security TO OFF; SELECT * FROM t1; diff --git a/src/test/regress/sql/rowsecurity.sql b/src/test/regress/sql/rowsecurity.sql new file mode 100644 index ee28a2c..274a3c4 *** a/src/test/regress/sql/rowsecurity.sql --- b/src/test/regress/sql/rowsecurity.sql *************** UPDATE only t1 SET b = b WHERE f_leak(b) *** 423,428 **** --- 423,437 ---- UPDATE t1 SET b = b WHERE f_leak(b) RETURNING *; UPDATE t1 SET b = b WHERE f_leak(b) RETURNING oid, *, t1; + -- update with from clause self join + EXPLAIN (COSTS OFF) UPDATE t1 t1_1 SET b = t1_2.b FROM t1 t1_2 + WHERE t1_1.a = 4 AND t1_2.a = t1_1.a AND t1_2.b = t1_1.b + AND f_leak(t1_1.b) AND f_leak(t1_2.b) RETURNING *, t1_1, t1_2; + + UPDATE t1 t1_1 SET b = t1_2.b FROM t1 t1_2 + WHERE t1_1.a = 4 AND t1_2.a = t1_1.a AND t1_2.b = t1_1.b + AND f_leak(t1_1.b) AND f_leak(t1_2.b) RETURNING *, t1_1, t1_2; + RESET SESSION AUTHORIZATION; SET row_security TO OFF; SELECT * FROM t1;