diff options
Diffstat (limited to 'src/backend')
-rw-r--r-- | src/backend/access/common/reloptions.c | 14 | ||||
-rw-r--r-- | src/backend/catalog/information_schema.sql | 8 | ||||
-rw-r--r-- | src/backend/catalog/sql_features.txt | 4 | ||||
-rw-r--r-- | src/backend/commands/tablecmds.c | 36 | ||||
-rw-r--r-- | src/backend/commands/view.c | 68 | ||||
-rw-r--r-- | src/backend/executor/execMain.c | 43 | ||||
-rw-r--r-- | src/backend/executor/nodeModifyTable.c | 33 | ||||
-rw-r--r-- | src/backend/nodes/copyfuncs.c | 18 | ||||
-rw-r--r-- | src/backend/nodes/equalfuncs.c | 15 | ||||
-rw-r--r-- | src/backend/nodes/nodeFuncs.c | 14 | ||||
-rw-r--r-- | src/backend/nodes/outfuncs.c | 15 | ||||
-rw-r--r-- | src/backend/nodes/readfuncs.c | 18 | ||||
-rw-r--r-- | src/backend/optimizer/plan/createplan.c | 15 | ||||
-rw-r--r-- | src/backend/optimizer/plan/planner.c | 30 | ||||
-rw-r--r-- | src/backend/parser/gram.y | 43 | ||||
-rw-r--r-- | src/backend/rewrite/rewriteHandler.c | 117 |
16 files changed, 448 insertions, 43 deletions
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index c439702a011..b5fd30a4f95 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -24,6 +24,7 @@ #include "catalog/pg_type.h" #include "commands/defrem.h" #include "commands/tablespace.h" +#include "commands/view.h" #include "nodes/makefuncs.h" #include "utils/array.h" #include "utils/attoptcache.h" @@ -248,6 +249,17 @@ static relopt_string stringRelOpts[] = gistValidateBufferingOption, "auto" }, + { + { + "check_option", + "View has WITH CHECK OPTION defined (local or cascaded).", + RELOPT_KIND_VIEW + }, + 0, + true, + validateWithCheckOption, + NULL + }, /* list terminator */ {{NULL}} }; @@ -1152,6 +1164,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, autovacuum) +offsetof(AutoVacOpts, analyze_scale_factor)}, {"security_barrier", RELOPT_TYPE_BOOL, offsetof(StdRdOptions, security_barrier)}, + {"check_option", RELOPT_TYPE_STRING, + offsetof(StdRdOptions, check_option_offset)}, }; options = parseRelOptions(reloptions, validate, kind, &numoptions); diff --git a/src/backend/catalog/information_schema.sql b/src/backend/catalog/information_schema.sql index e1f8e7f4b1c..95f267f224e 100644 --- a/src/backend/catalog/information_schema.sql +++ b/src/backend/catalog/information_schema.sql @@ -2494,7 +2494,13 @@ CREATE VIEW views AS ELSE null END AS character_data) AS view_definition, - CAST('NONE' AS character_data) AS check_option, + CAST( + CASE WHEN 'check_option=cascaded' = ANY (c.reloptions) + THEN 'CASCADED' + WHEN 'check_option=local' = ANY (c.reloptions) + THEN 'LOCAL' + ELSE 'NONE' END + AS character_data) AS check_option, CAST( -- (1 << CMD_UPDATE) + (1 << CMD_DELETE) diff --git a/src/backend/catalog/sql_features.txt b/src/backend/catalog/sql_features.txt index 3a5e24e0454..71d2c178689 100644 --- a/src/backend/catalog/sql_features.txt +++ b/src/backend/catalog/sql_features.txt @@ -227,7 +227,7 @@ F311 Schema definition statement NO F311 Schema definition statement 01 CREATE SCHEMA YES F311 Schema definition statement 02 CREATE TABLE for persistent base tables YES F311 Schema definition statement 03 CREATE VIEW YES -F311 Schema definition statement 04 CREATE VIEW: WITH CHECK OPTION NO +F311 Schema definition statement 04 CREATE VIEW: WITH CHECK OPTION YES F311 Schema definition statement 05 GRANT statement YES F312 MERGE statement NO F313 Enhanced MERGE statement NO @@ -301,7 +301,7 @@ F711 ALTER domain YES F721 Deferrable constraints NO foreign and unique keys only F731 INSERT column privileges YES F741 Referential MATCH types NO no partial match yet -F751 View CHECK enhancements NO +F751 View CHECK enhancements YES F761 Session management YES F762 CURRENT_CATALOG YES F763 CURRENT_SCHEMA YES diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index bd0a21987c8..cb87d906ef8 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -8774,6 +8774,42 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation, break; } + /* Special-case validation of view options */ + if (rel->rd_rel->relkind == RELKIND_VIEW) + { + Query *view_query = get_view_query(rel); + List *view_options = untransformRelOptions(newOptions); + ListCell *cell; + bool check_option = false; + bool security_barrier = false; + + foreach(cell, view_options) + { + DefElem *defel = (DefElem *) lfirst(cell); + + if (pg_strcasecmp(defel->defname, "check_option") == 0) + check_option = true; + if (pg_strcasecmp(defel->defname, "security_barrier") == 0) + security_barrier = defGetBoolean(defel); + } + + /* + * If the check option is specified, look to see if the view is + * actually auto-updatable or not. + */ + if (check_option) + { + const char *view_updatable_error = + view_query_is_auto_updatable(view_query, security_barrier); + + if (view_updatable_error) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("WITH CHECK OPTION is supported only on auto-updatable views"), + errhint("%s", view_updatable_error))); + } + } + /* * All we need do here is update the pg_class row; the new options will be * propagated into relcaches during post-commit cache inval. diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c index 6186a841556..832de439789 100644 --- a/src/backend/commands/view.c +++ b/src/backend/commands/view.c @@ -27,6 +27,7 @@ #include "parser/parse_relation.h" #include "rewrite/rewriteDefine.h" #include "rewrite/rewriteManip.h" +#include "rewrite/rewriteHandler.h" #include "rewrite/rewriteSupport.h" #include "utils/acl.h" #include "utils/builtins.h" @@ -38,6 +39,24 @@ static void checkViewTupleDesc(TupleDesc newdesc, TupleDesc olddesc); /*--------------------------------------------------------------------- + * Validator for "check_option" reloption on views. The allowed values + * are "local" and "cascaded". + */ +void +validateWithCheckOption(char *value) +{ + if (value == NULL || + (pg_strcasecmp(value, "local") != 0 && + pg_strcasecmp(value, "cascaded") != 0)) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for \"check_option\" option"), + errdetail("Valid values are \"local\", and \"cascaded\"."))); + } +} + +/*--------------------------------------------------------------------- * DefineVirtualRelation * * Create the "view" relation. `DefineRelation' does all the work, @@ -374,6 +393,9 @@ DefineView(ViewStmt *stmt, const char *queryString) Query *viewParse; Oid viewOid; RangeVar *view; + ListCell *cell; + bool check_option; + bool security_barrier; /* * Run parse analysis to convert the raw parse tree to a Query. Note this @@ -411,6 +433,52 @@ DefineView(ViewStmt *stmt, const char *queryString) errmsg("views must not contain data-modifying statements in WITH"))); /* + * If the user specified the WITH CHECK OPTION, add it to the list of + * reloptions. + */ + if (stmt->withCheckOption == LOCAL_CHECK_OPTION) + stmt->options = lappend(stmt->options, + makeDefElem("check_option", + (Node *) makeString("local"))); + else if (stmt->withCheckOption == CASCADED_CHECK_OPTION) + stmt->options = lappend(stmt->options, + makeDefElem("check_option", + (Node *) makeString("cascaded"))); + + /* + * Check that the view is auto-updatable if WITH CHECK OPTION was + * specified. + */ + check_option = false; + security_barrier = false; + + foreach(cell, stmt->options) + { + DefElem *defel = (DefElem *) lfirst(cell); + + if (pg_strcasecmp(defel->defname, "check_option") == 0) + check_option = true; + if (pg_strcasecmp(defel->defname, "security_barrier") == 0) + security_barrier = defGetBoolean(defel); + } + + /* + * If the check option is specified, look to see if the view is + * actually auto-updatable or not. + */ + if (check_option) + { + const char *view_updatable_error = + view_query_is_auto_updatable(viewParse, security_barrier); + + if (view_updatable_error) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("WITH CHECK OPTION is supported only on auto-updatable views"), + errhint("%s", view_updatable_error))); + } + + /* * If a list of column names was given, run through and insert these into * the actual query tree. - thomas 2000-03-08 */ diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index 4d7345da577..038f064931e 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -1624,6 +1624,49 @@ ExecConstraints(ResultRelInfo *resultRelInfo, } /* + * ExecWithCheckOptions -- check that tuple satisfies any WITH CHECK OPTIONs + */ +void +ExecWithCheckOptions(ResultRelInfo *resultRelInfo, + TupleTableSlot *slot, EState *estate) +{ + ExprContext *econtext; + ListCell *l1, *l2; + + /* + * We will use the EState's per-tuple context for evaluating constraint + * expressions (creating it if it's not already there). + */ + econtext = GetPerTupleExprContext(estate); + + /* Arrange for econtext's scan tuple to be the tuple under test */ + econtext->ecxt_scantuple = slot; + + /* Check each of the constraints */ + forboth(l1, resultRelInfo->ri_WithCheckOptions, + l2, resultRelInfo->ri_WithCheckOptionExprs) + { + WithCheckOption *wco = (WithCheckOption *) lfirst(l1); + ExprState *wcoExpr = (ExprState *) lfirst(l2); + + /* + * WITH CHECK OPTION checks are intended to ensure that the new tuple + * is visible in the view. If the view's qual evaluates to NULL, then + * the new tuple won't be included in the view. Therefore we need to + * tell ExecQual to return FALSE for NULL (the opposite of what we do + * above for CHECK constraints). + */ + if (!ExecQual((List *) wcoExpr, econtext, false)) + ereport(ERROR, + (errcode(ERRCODE_WITH_CHECK_OPTION_VIOLATION), + errmsg("new row violates WITH CHECK OPTION for view \"%s\"", + wco->viewname), + errdetail("Failing row contains %s.", + ExecBuildSlotValueDescription(slot, 64)))); + } +} + +/* * ExecBuildSlotValueDescription -- construct a string representing a tuple * * This is intentionally very similar to BuildIndexValueDescription, but diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 8fe5f1d427a..15f5dccb82a 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -281,6 +281,10 @@ ExecInsert(TupleTableSlot *slot, list_free(recheckIndexes); + /* Check any WITH CHECK OPTION constraints */ + if (resultRelInfo->ri_WithCheckOptions != NIL) + ExecWithCheckOptions(resultRelInfo, slot, estate); + /* Process RETURNING if present */ if (resultRelInfo->ri_projectReturning) return ExecProcessReturning(resultRelInfo->ri_projectReturning, @@ -777,6 +781,10 @@ lreplace:; list_free(recheckIndexes); + /* Check any WITH CHECK OPTION constraints */ + if (resultRelInfo->ri_WithCheckOptions != NIL) + ExecWithCheckOptions(resultRelInfo, slot, estate); + /* Process RETURNING if present */ if (resultRelInfo->ri_projectReturning) return ExecProcessReturning(resultRelInfo->ri_projectReturning, @@ -1130,6 +1138,31 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) estate->es_result_relation_info = saved_resultRelInfo; /* + * Initialize any WITH CHECK OPTION constraints if needed. + */ + resultRelInfo = mtstate->resultRelInfo; + i = 0; + foreach(l, node->withCheckOptionLists) + { + List *wcoList = (List *) lfirst(l); + List *wcoExprs = NIL; + ListCell *ll; + + foreach(ll, wcoList) + { + WithCheckOption *wco = (WithCheckOption *) lfirst(ll); + ExprState *wcoExpr = ExecInitExpr((Expr *) wco->qual, + mtstate->mt_plans[i]); + wcoExprs = lappend(wcoExprs, wcoExpr); + } + + resultRelInfo->ri_WithCheckOptions = wcoList; + resultRelInfo->ri_WithCheckOptionExprs = wcoExprs; + resultRelInfo++; + i++; + } + + /* * Initialize RETURNING projections if needed. */ if (node->returningLists) diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index f524a7a9943..bcc6496a952 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -178,6 +178,7 @@ _copyModifyTable(const ModifyTable *from) COPY_NODE_FIELD(resultRelations); COPY_SCALAR_FIELD(resultRelIndex); COPY_NODE_FIELD(plans); + COPY_NODE_FIELD(withCheckOptionLists); COPY_NODE_FIELD(returningLists); COPY_NODE_FIELD(fdwPrivLists); COPY_NODE_FIELD(rowMarks); @@ -2003,6 +2004,18 @@ _copyRangeTblEntry(const RangeTblEntry *from) return newnode; } +static WithCheckOption * +_copyWithCheckOption(const WithCheckOption *from) +{ + WithCheckOption *newnode = makeNode(WithCheckOption); + + COPY_STRING_FIELD(viewname); + COPY_NODE_FIELD(qual); + COPY_SCALAR_FIELD(cascaded); + + return newnode; +} + static SortGroupClause * _copySortGroupClause(const SortGroupClause *from) { @@ -2446,6 +2459,7 @@ _copyQuery(const Query *from) COPY_NODE_FIELD(rtable); COPY_NODE_FIELD(jointree); COPY_NODE_FIELD(targetList); + COPY_NODE_FIELD(withCheckOptions); COPY_NODE_FIELD(returningList); COPY_NODE_FIELD(groupClause); COPY_NODE_FIELD(havingQual); @@ -3075,6 +3089,7 @@ _copyViewStmt(const ViewStmt *from) COPY_NODE_FIELD(query); COPY_SCALAR_FIELD(replace); COPY_NODE_FIELD(options); + COPY_SCALAR_FIELD(withCheckOption); return newnode; } @@ -4517,6 +4532,9 @@ copyObject(const void *from) case T_RangeTblEntry: retval = _copyRangeTblEntry(from); break; + case T_WithCheckOption: + retval = _copyWithCheckOption(from); + break; case T_SortGroupClause: retval = _copySortGroupClause(from); break; diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index 904cf374d81..7f9737ee8e8 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -853,6 +853,7 @@ _equalQuery(const Query *a, const Query *b) COMPARE_NODE_FIELD(rtable); COMPARE_NODE_FIELD(jointree); COMPARE_NODE_FIELD(targetList); + COMPARE_NODE_FIELD(withCheckOptions); COMPARE_NODE_FIELD(returningList); COMPARE_NODE_FIELD(groupClause); COMPARE_NODE_FIELD(havingQual); @@ -1382,6 +1383,7 @@ _equalViewStmt(const ViewStmt *a, const ViewStmt *b) COMPARE_NODE_FIELD(query); COMPARE_SCALAR_FIELD(replace); COMPARE_NODE_FIELD(options); + COMPARE_SCALAR_FIELD(withCheckOption); return true; } @@ -2254,6 +2256,16 @@ _equalRangeTblEntry(const RangeTblEntry *a, const RangeTblEntry *b) } static bool +_equalWithCheckOption(const WithCheckOption *a, const WithCheckOption *b) +{ + COMPARE_STRING_FIELD(viewname); + COMPARE_NODE_FIELD(qual); + COMPARE_SCALAR_FIELD(cascaded); + + return true; +} + +static bool _equalSortGroupClause(const SortGroupClause *a, const SortGroupClause *b) { COMPARE_SCALAR_FIELD(tleSortGroupRef); @@ -2987,6 +2999,9 @@ equal(const void *a, const void *b) case T_RangeTblEntry: retval = _equalRangeTblEntry(a, b); break; + case T_WithCheckOption: + retval = _equalWithCheckOption(a, b); + break; case T_SortGroupClause: retval = _equalSortGroupClause(a, b); break; diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c index 310400eedab..a896d763b8f 100644 --- a/src/backend/nodes/nodeFuncs.c +++ b/src/backend/nodes/nodeFuncs.c @@ -1556,6 +1556,8 @@ expression_tree_walker(Node *node, case T_SortGroupClause: /* primitive node types with no expression subnodes */ break; + case T_WithCheckOption: + return walker(((WithCheckOption *) node)->qual, context); case T_Aggref: { Aggref *expr = (Aggref *) node; @@ -1873,6 +1875,8 @@ query_tree_walker(Query *query, if (walker((Node *) query->targetList, context)) return true; + if (walker((Node *) query->withCheckOptions, context)) + return true; if (walker((Node *) query->returningList, context)) return true; if (walker((Node *) query->jointree, context)) @@ -2074,6 +2078,15 @@ expression_tree_mutator(Node *node, case T_RangeTblRef: case T_SortGroupClause: return (Node *) copyObject(node); + case T_WithCheckOption: + { + WithCheckOption *wco = (WithCheckOption *) node; + WithCheckOption *newnode; + + FLATCOPY(newnode, wco, WithCheckOption); + MUTATE(newnode->qual, wco->qual, Node *); + return (Node *) newnode; + } case T_Aggref: { Aggref *aggref = (Aggref *) node; @@ -2589,6 +2602,7 @@ query_tree_mutator(Query *query, } MUTATE(query->targetList, query->targetList, List *); + MUTATE(query->withCheckOptions, query->withCheckOptions, List *); MUTATE(query->returningList, query->returningList, List *); MUTATE(query->jointree, query->jointree, FromExpr *); MUTATE(query->setOperations, query->setOperations, Node *); diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 2475f8d520e..48cd9dcb8d4 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -332,6 +332,7 @@ _outModifyTable(StringInfo str, const ModifyTable *node) WRITE_NODE_FIELD(resultRelations); WRITE_INT_FIELD(resultRelIndex); WRITE_NODE_FIELD(plans); + WRITE_NODE_FIELD(withCheckOptionLists); WRITE_NODE_FIELD(returningLists); WRITE_NODE_FIELD(fdwPrivLists); WRITE_NODE_FIELD(rowMarks); @@ -2247,6 +2248,7 @@ _outQuery(StringInfo str, const Query *node) WRITE_NODE_FIELD(rtable); WRITE_NODE_FIELD(jointree); WRITE_NODE_FIELD(targetList); + WRITE_NODE_FIELD(withCheckOptions); WRITE_NODE_FIELD(returningList); WRITE_NODE_FIELD(groupClause); WRITE_NODE_FIELD(havingQual); @@ -2261,6 +2263,16 @@ _outQuery(StringInfo str, const Query *node) } static void +_outWithCheckOption(StringInfo str, const WithCheckOption *node) +{ + WRITE_NODE_TYPE("WITHCHECKOPTION"); + + WRITE_STRING_FIELD(viewname); + WRITE_NODE_FIELD(qual); + WRITE_BOOL_FIELD(cascaded); +} + +static void _outSortGroupClause(StringInfo str, const SortGroupClause *node) { WRITE_NODE_TYPE("SORTGROUPCLAUSE"); @@ -3114,6 +3126,9 @@ _outNode(StringInfo str, const void *obj) case T_Query: _outQuery(str, obj); break; + case T_WithCheckOption: + _outWithCheckOption(str, obj); + break; case T_SortGroupClause: _outSortGroupClause(str, obj); break; diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c index 30c51504c3f..dc9cb3ebd2c 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -210,6 +210,7 @@ _readQuery(void) READ_NODE_FIELD(rtable); READ_NODE_FIELD(jointree); READ_NODE_FIELD(targetList); + READ_NODE_FIELD(withCheckOptions); READ_NODE_FIELD(returningList); READ_NODE_FIELD(groupClause); READ_NODE_FIELD(havingQual); @@ -255,6 +256,21 @@ _readDeclareCursorStmt(void) } /* + * _readWithCheckOption + */ +static WithCheckOption * +_readWithCheckOption(void) +{ + READ_LOCALS(WithCheckOption); + + READ_STRING_FIELD(viewname); + READ_NODE_FIELD(qual); + READ_BOOL_FIELD(cascaded); + + READ_DONE(); +} + +/* * _readSortGroupClause */ static SortGroupClause * @@ -1260,6 +1276,8 @@ parseNodeString(void) if (MATCH("QUERY", 5)) return_value = _readQuery(); + else if (MATCH("WITHCHECKOPTION", 15)) + return_value = _readWithCheckOption(); else if (MATCH("SORTGROUPCLAUSE", 15)) return_value = _readSortGroupClause(); else if (MATCH("WINDOWCLAUSE", 12)) diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index 2560e9cbc1e..7fed5e97945 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -4702,16 +4702,16 @@ make_result(PlannerInfo *root, * Build a ModifyTable plan node * * Currently, we don't charge anything extra for the actual table modification - * work, nor for the RETURNING expressions if any. It would only be window - * dressing, since these are always top-level nodes and there is no way for - * the costs to change any higher-level planning choices. But we might want - * to make it look better sometime. + * work, nor for the WITH CHECK OPTIONS or RETURNING expressions if any. It + * would only be window dressing, since these are always top-level nodes and + * there is no way for the costs to change any higher-level planning choices. + * But we might want to make it look better sometime. */ ModifyTable * make_modifytable(PlannerInfo *root, CmdType operation, bool canSetTag, - List *resultRelations, - List *subplans, List *returningLists, + List *resultRelations, List *subplans, + List *withCheckOptionLists, List *returningLists, List *rowMarks, int epqParam) { ModifyTable *node = makeNode(ModifyTable); @@ -4723,6 +4723,8 @@ make_modifytable(PlannerInfo *root, int i; Assert(list_length(resultRelations) == list_length(subplans)); + Assert(withCheckOptionLists == NIL || + list_length(resultRelations) == list_length(withCheckOptionLists)); Assert(returningLists == NIL || list_length(resultRelations) == list_length(returningLists)); @@ -4759,6 +4761,7 @@ make_modifytable(PlannerInfo *root, node->resultRelations = resultRelations; node->resultRelIndex = -1; /* will be set correctly in setrefs.c */ node->plans = subplans; + node->withCheckOptionLists = withCheckOptionLists; node->returningLists = returningLists; node->rowMarks = rowMarks; node->epqParam = epqParam; diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index d80c26420fa..01e2fa32a3c 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -294,6 +294,7 @@ subquery_planner(PlannerGlobal *glob, Query *parse, int num_old_subplans = list_length(glob->subplans); PlannerInfo *root; Plan *plan; + List *newWithCheckOptions; List *newHaving; bool hasOuterJoins; ListCell *l; @@ -421,6 +422,18 @@ subquery_planner(PlannerGlobal *glob, Query *parse, preprocess_expression(root, (Node *) parse->targetList, EXPRKIND_TARGET); + newWithCheckOptions = NIL; + foreach(l, parse->withCheckOptions) + { + WithCheckOption *wco = (WithCheckOption *) lfirst(l); + + wco->qual = preprocess_expression(root, wco->qual, + EXPRKIND_QUAL); + if (wco->qual != NULL) + newWithCheckOptions = lappend(newWithCheckOptions, wco); + } + parse->withCheckOptions = newWithCheckOptions; + parse->returningList = (List *) preprocess_expression(root, (Node *) parse->returningList, EXPRKIND_TARGET); @@ -559,12 +572,19 @@ subquery_planner(PlannerGlobal *glob, Query *parse, /* If it's not SELECT, we need a ModifyTable node */ if (parse->commandType != CMD_SELECT) { + List *withCheckOptionLists; List *returningLists; List *rowMarks; /* - * Set up the RETURNING list-of-lists, if needed. + * Set up the WITH CHECK OPTION and RETURNING lists-of-lists, if + * needed. */ + if (parse->withCheckOptions) + withCheckOptionLists = list_make1(parse->withCheckOptions); + else + withCheckOptionLists = NIL; + if (parse->returningList) returningLists = list_make1(parse->returningList); else @@ -585,6 +605,7 @@ subquery_planner(PlannerGlobal *glob, Query *parse, parse->canSetTag, list_make1_int(parse->resultRelation), list_make1(plan), + withCheckOptionLists, returningLists, rowMarks, SS_assign_special_param(root)); @@ -770,6 +791,7 @@ inheritance_planner(PlannerInfo *root) RelOptInfo **save_rel_array = NULL; List *subplans = NIL; List *resultRelations = NIL; + List *withCheckOptionLists = NIL; List *returningLists = NIL; List *rowMarks; ListCell *lc; @@ -930,7 +952,10 @@ inheritance_planner(PlannerInfo *root) /* Build list of target-relation RT indexes */ resultRelations = lappend_int(resultRelations, appinfo->child_relid); - /* Build list of per-relation RETURNING targetlists */ + /* Build lists of per-relation WCO and RETURNING targetlists */ + if (parse->withCheckOptions) + withCheckOptionLists = lappend(withCheckOptionLists, + subroot.parse->withCheckOptions); if (parse->returningList) returningLists = lappend(returningLists, subroot.parse->returningList); @@ -979,6 +1004,7 @@ inheritance_planner(PlannerInfo *root) parse->canSetTag, resultRelations, subplans, + withCheckOptionLists, returningLists, rowMarks, SS_assign_special_param(root)); diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 57f49d6d517..d8d2bdf09a4 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -470,7 +470,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); %type <list> constraints_set_list %type <boolean> constraints_set_mode %type <str> OptTableSpace OptConsTableSpace OptTableSpaceOwner -%type <list> opt_check_option +%type <ival> opt_check_option %type <str> opt_provider security_label @@ -7995,6 +7995,7 @@ ViewStmt: CREATE OptTemp VIEW qualified_name opt_column_list opt_reloptions n->query = $8; n->replace = false; n->options = $6; + n->withCheckOption = $9; $$ = (Node *) n; } | CREATE OR REPLACE OptTemp VIEW qualified_name opt_column_list opt_reloptions @@ -8007,10 +8008,11 @@ ViewStmt: CREATE OptTemp VIEW qualified_name opt_column_list opt_reloptions n->query = $10; n->replace = true; n->options = $8; + n->withCheckOption = $11; $$ = (Node *) n; } | CREATE OptTemp RECURSIVE VIEW qualified_name '(' columnList ')' opt_reloptions - AS SelectStmt + AS SelectStmt opt_check_option { ViewStmt *n = makeNode(ViewStmt); n->view = $5; @@ -8019,10 +8021,16 @@ ViewStmt: CREATE OptTemp VIEW qualified_name opt_column_list opt_reloptions n->query = makeRecursiveViewSelect(n->view->relname, n->aliases, $11); n->replace = false; n->options = $9; + n->withCheckOption = $12; + if (n->withCheckOption != NO_CHECK_OPTION) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("WITH CHECK OPTION not supported on recursive views"), + parser_errposition(@12))); $$ = (Node *) n; } | CREATE OR REPLACE OptTemp RECURSIVE VIEW qualified_name '(' columnList ')' opt_reloptions - AS SelectStmt + AS SelectStmt opt_check_option { ViewStmt *n = makeNode(ViewStmt); n->view = $7; @@ -8031,30 +8039,21 @@ ViewStmt: CREATE OptTemp VIEW qualified_name opt_column_list opt_reloptions n->query = makeRecursiveViewSelect(n->view->relname, n->aliases, $13); n->replace = true; n->options = $11; + n->withCheckOption = $14; + if (n->withCheckOption != NO_CHECK_OPTION) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("WITH CHECK OPTION not supported on recursive views"), + parser_errposition(@14))); $$ = (Node *) n; } ; opt_check_option: - WITH CHECK OPTION - { - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("WITH CHECK OPTION is not implemented"))); - } - | WITH CASCADED CHECK OPTION - { - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("WITH CHECK OPTION is not implemented"))); - } - | WITH LOCAL CHECK OPTION - { - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("WITH CHECK OPTION is not implemented"))); - } - | /* EMPTY */ { $$ = NIL; } + WITH CHECK OPTION { $$ = CASCADED_CHECK_OPTION; } + | WITH CASCADED CHECK OPTION { $$ = CASCADED_CHECK_OPTION; } + | WITH LOCAL CHECK OPTION { $$ = LOCAL_CHECK_OPTION; } + | /* EMPTY */ { $$ = NO_CHECK_OPTION; } ; /***************************************************************************** diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c index d909de3a539..7f527bd74a2 100644 --- a/src/backend/rewrite/rewriteHandler.c +++ b/src/backend/rewrite/rewriteHandler.c @@ -19,6 +19,7 @@ #include "foreign/fdwapi.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" +#include "optimizer/clauses.h" #include "parser/analyze.h" #include "parser/parse_coerce.h" #include "parser/parsetree.h" @@ -1866,7 +1867,7 @@ fireRules(Query *parsetree, * Caller should have verified that the relation is a view, and therefore * we should find an ON SELECT action. */ -static Query * +Query * get_view_query(Relation view) { int i; @@ -1927,11 +1928,16 @@ view_has_instead_trigger(Relation view, CmdType event) /* * view_is_auto_updatable - - * Test if the specified view can be automatically updated. This will - * either return NULL (if the view can be updated) or a message string - * giving the reason that it cannot be. + * Retrive the view definition and options and then determine if the view + * can be auto-updated by calling view_query_is_auto_updatable(). Returns + * NULL or a message string giving the reason the view is not auto + * updateable. See view_query_is_auto_updatable() for details. + * + * The only view option which affects if a view can be auto-updated, today, + * is the security_barrier option. If other options are added later, they + * will also need to be handled here. * - * Caller must have verified that relation is a view! + * Caller must have verified that the relation is a view! * * Note that the checks performed here are local to this view. We do not * check whether the view's underlying base relation is updatable; that @@ -1940,10 +1946,32 @@ view_has_instead_trigger(Relation view, CmdType event) * Also note that we don't check for INSTEAD triggers or rules here; those * also prevent auto-update, but they must be checked for by the caller. */ -static const char * +const char * view_is_auto_updatable(Relation view) { Query *viewquery = get_view_query(view); + bool security_barrier = RelationIsSecurityView(view); + + return view_query_is_auto_updatable(viewquery, security_barrier); +} + + +/* + * view_query_is_auto_updatable - + * Test if the specified view definition can be automatically updated, given + * the view's options (currently only security_barrier affects a view's + * auto-updatable status). + * + * This will either return NULL (if the view can be updated) or a message + * string giving the reason that it cannot be. + * + * Note that the checks performed here are only based on the view + * definition. We do not check whether any base relations referred to by + * the view are updatable. + */ +const char * +view_query_is_auto_updatable(Query *viewquery, bool security_barrier) +{ RangeTblRef *rtr; RangeTblEntry *base_rte; Bitmapset *bms; @@ -1995,9 +2023,9 @@ view_is_auto_updatable(Relation view) /* * For now, we also don't support security-barrier views, because of the * difficulty of keeping upper-level qual expressions away from - * lower-level data. This might get relaxed in future. + * lower-level data. This might get relaxed in the future. */ - if (RelationIsSecurityView(view)) + if (security_barrier) return gettext_noop("Security-barrier views are not automatically updatable."); /* @@ -2532,8 +2560,7 @@ rewriteTargetView(Query *parsetree, Relation view) * only adjust their varnos to reference the new target (just the same as * we did with the view targetlist). * - * For INSERT, the view's quals can be ignored for now. When we implement - * WITH CHECK OPTION, this might be a good place to collect them. + * For INSERT, the view's quals can be ignored in the main query. */ if (parsetree->commandType != CMD_INSERT && viewquery->jointree->quals != NULL) @@ -2544,6 +2571,76 @@ rewriteTargetView(Query *parsetree, Relation view) AddQual(parsetree, (Node *) viewqual); } + /* + * For INSERT/UPDATE, if the view has the WITH CHECK OPTION, or any parent + * view specified WITH CASCADED CHECK OPTION, add the quals from the view + * to the query's withCheckOptions list. + */ + if (parsetree->commandType != CMD_DELETE) + { + bool has_wco = RelationHasCheckOption(view); + bool cascaded = RelationHasCascadedCheckOption(view); + + /* + * If the parent view has a cascaded check option, treat this view as + * if it also had a cascaded check option. + * + * New WithCheckOptions are added to the start of the list, so if there + * is a cascaded check option, it will be the first item in the list. + */ + if (parsetree->withCheckOptions != NIL) + { + WithCheckOption *parent_wco = + (WithCheckOption *) linitial(parsetree->withCheckOptions); + + if (parent_wco->cascaded) + { + has_wco = true; + cascaded = true; + } + } + + /* + * Add the new WithCheckOption to the start of the list, so that + * checks on inner views are run before checks on outer views, as + * required by the SQL standard. + * + * If the new check is CASCADED, we need to add it even if this view + * has no quals, since there may be quals on child views. A LOCAL + * check can be omitted if this view has no quals. + */ + if (has_wco && (cascaded || viewquery->jointree->quals != NULL)) + { + WithCheckOption *wco; + + wco = makeNode(WithCheckOption); + wco->viewname = pstrdup(RelationGetRelationName(view)); + wco->qual = NULL; + wco->cascaded = cascaded; + + parsetree->withCheckOptions = lcons(wco, + parsetree->withCheckOptions); + + if (viewquery->jointree->quals != NULL) + { + wco->qual = (Node *) copyObject(viewquery->jointree->quals); + ChangeVarNodes(wco->qual, base_rt_index, new_rt_index, 0); + + /* + * Make sure that the query is marked correctly if the added + * qual has sublinks. We can skip this check if the query is + * already marked, or if the command is an UPDATE, in which + * case the same qual will have already been added to the + * query's WHERE clause, and AddQual will have already done + * this check. + */ + if (!parsetree->hasSubLinks && + parsetree->commandType != CMD_UPDATE) + parsetree->hasSubLinks = checkExprHasSubLink(wco->qual); + } + } + } + return parsetree; } |