diff --git a/doc/src/sgml/ref/create_view.sgml b/doc/src/sgml/ref/create_view.sgml
new file mode 100644
index ced3115..c991057
*** a/doc/src/sgml/ref/create_view.sgml
--- b/doc/src/sgml/ref/create_view.sgml
*************** PostgreSQL documentation
*** 24,29 ****
--- 24,30 ----
CREATE [ OR REPLACE ] [ TEMP | TEMPORARY ] [ RECURSIVE ] VIEW name [ ( column_name [, ...] ) ]
[ WITH ( view_option_name [= view_option_value] [, ... ] ) ]
AS query
+ [ WITH [ CASCADED | LOCAL ] CHECK OPTION ]
*************** CREATE VIEW name> AS WITH
*** 120,129 ****
WITH ( view_option_name [= view_option_value] [, ... ] )
! This clause specifies optional parameters for a view; currently, the
! only supported parameter name is security_barrier,
! which should be enabled when a view is intended to provide row-level
! security. See for full details.
--- 121,154 ----
WITH ( view_option_name [= view_option_value] [, ... ] )
! This clause specifies optional parameters for a view; the following
! parameters are supported:
!
!
!
! security_barrier(boolean)
!
!
! This should be used if the view is intended to provide row-level
! security. See for full details.
!
!
!
!
!
! check_option(text)
!
!
! This is parameter may be either local> or
! cascaded>, and is equivalent to specifying
! WITH [ CASCADED | LOCAL ] CHECK OPTION>. This allows an
! existing view's check option to be modified using rather than using CREATE OR REPLACE
! VIEW>.
!
!
!
!
*************** CREATE VIEW name> AS WITH
*** 138,143 ****
--- 163,239 ----
+
+
+ WITH [ CASCADED | LOCAL ] CHECK OPTION
+
+
+
+ CHECK OPTION
+
+
+ WITH CHECK OPTION
+
+ This option controls the behavior of automatically updatable views. When
+ this option is specified, INSERT> and UPDATE>
+ commands on the view will be checked to ensure that new rows satisfy the
+ view-defining condition (that is, the new rows are checked to ensure that
+ they are visible through the view). If they are not, the update will be
+ rejected. If the CHECK OPTION> is not specified,
+ INSERT> and UPDATE> commands on the view are
+ allowed to create rows that are not visible through the view. The
+ following check options are supported:
+
+
+
+ LOCAL
+
+
+ New rows are only checked against the conditions defined directly in
+ the view itself. Any conditions defined on underlying base views are
+ not checked (unless they also specify the CHECK OPTION>).
+
+
+
+
+
+ CASCADED
+
+
+ New rows are checked against the conditions of the view and all
+ underlying base views. If the CHECK OPTION> is specified,
+ and neither LOCAL> nor CASCADED> is specified,
+ then CASCADED> is assumed.
+
+
+
+
+
+
+
+ The CHECK OPTION> may not be used with RECURSIVE>
+ views.
+
+
+
+ Note that the CHECK OPTION> is only supported on views that
+ are automatically updatable, and do not have INSTEAD OF>
+ triggers or INSTEAD> rules. If an automatically updatable
+ view is defined on top of a base view that has INSTEAD OF>
+ triggers, then the LOCAL CHECK OPTION> may be used to check
+ the conditions on the automatically updatable view, but the conditions
+ on the base view with INSTEAD OF> triggers will not be
+ checked (a cascaded check option will not cascade down to a
+ trigger-updatable view, and any check options defined directly on a
+ trigger-updatable view will be ignored). If the view or any of its base
+ relations has an INSTEAD> rule that causes the
+ INSERT> or UPDATE> command to be rewritten, then
+ all check options will be ignored in the rewritten query, including any
+ checks from automatically updatable views defined on top of the relation
+ with the INSTEAD> rule.
+
+
+
*************** CREATE VIEW vista AS SELECT text 'Hello
*** 256,262 ****
condition, and thus is no longer visible through the view. Similarly,
an INSERT> command can potentially insert base-relation rows
that do not satisfy the WHERE> condition and thus are not
! visible through the view.
--- 352,360 ----
condition, and thus is no longer visible through the view. Similarly,
an INSERT> command can potentially insert base-relation rows
that do not satisfy the WHERE> condition and thus are not
! visible through the view. The CHECK OPTION> may be used to
! prevent INSERT> and UPDATE> commands from creating
! such rows that are not visible through the view.
*************** UNION ALL
*** 314,376 ****
Compatibility
- The SQL standard specifies some additional capabilities for the
- CREATE VIEW statement:
-
- CREATE VIEW name [ ( column_name [, ...] ) ]
- AS query
- [ WITH [ CASCADED | LOCAL ] CHECK OPTION ]
-
-
-
-
- The optional clauses for the full SQL command are:
-
-
-
- CHECK OPTION
-
-
- This option controls the behavior of automatically updatable views.
- When given, INSERT> and UPDATE> commands on
- the view will be checked to ensure new rows satisfy the
- view-defining condition (that is, the new rows would be visible
- through the view). If they do not, the update will be rejected.
- Without CHECK OPTION, INSERT> and
- UPDATE> commands on the view are allowed to create rows
- that are not visible through the view. (The latter behavior is the
- only one currently provided by PostgreSQL>.)
-
-
-
-
-
- LOCAL
-
-
- Check for integrity on this view.
-
-
-
-
-
- CASCADED
-
-
- Check for integrity on this view and on any dependent
- view. CASCADED> is assumed if neither
- CASCADED> nor LOCAL> is specified.
-
-
-
-
-
-
-
CREATE OR REPLACE VIEW is a
PostgreSQL language extension.
So is the concept of a temporary view.
! The WITH> clause is an extension as well.
--- 412,421 ----
Compatibility
CREATE OR REPLACE VIEW is a
PostgreSQL language extension.
So is the concept of a temporary view.
! The WITH ( ... )> clause is an extension as well.
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
new file mode 100644
index c439702..b5fd30a
*** a/src/backend/access/common/reloptions.c
--- b/src/backend/access/common/reloptions.c
***************
*** 24,29 ****
--- 24,30 ----
#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"
*************** static relopt_string stringRelOpts[] =
*** 248,253 ****
--- 249,265 ----
gistValidateBufferingOption,
"auto"
},
+ {
+ {
+ "check_option",
+ "View has WITH CHECK OPTION defined (local or cascaded).",
+ RELOPT_KIND_VIEW
+ },
+ 0,
+ true,
+ validateWithCheckOption,
+ NULL
+ },
/* list terminator */
{{NULL}}
};
*************** default_reloptions(Datum reloptions, boo
*** 1152,1157 ****
--- 1164,1171 ----
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/sql_features.txt b/src/backend/catalog/sql_features.txt
new file mode 100644
index 3a5e24e..71d2c17
*** a/src/backend/catalog/sql_features.txt
--- b/src/backend/catalog/sql_features.txt
*************** F311 Schema definition statement NO
*** 227,233 ****
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 05 GRANT statement YES
F312 MERGE statement NO
F313 Enhanced MERGE statement NO
--- 227,233 ----
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 YES
F311 Schema definition statement 05 GRANT statement YES
F312 MERGE statement NO
F313 Enhanced MERGE statement NO
*************** F711 ALTER domain YES
*** 301,307 ****
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
F761 Session management YES
F762 CURRENT_CATALOG YES
F763 CURRENT_SCHEMA YES
--- 301,307 ----
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 YES
F761 Session management YES
F762 CURRENT_CATALOG YES
F763 CURRENT_SCHEMA YES
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
new file mode 100644
index 6186a84..0c4d49b
*** a/src/backend/commands/view.c
--- b/src/backend/commands/view.c
***************
*** 38,43 ****
--- 38,61 ----
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 ||
+ (strcmp(value, "local") != 0 &&
+ strcmp(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,
*************** DefineView(ViewStmt *stmt, const char *q
*** 463,468 ****
--- 481,499 ----
}
/*
+ * 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")));
+
+ /*
* Create the view relation
*
* NOTE: if it already exists and replace is false, the xact will be
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
new file mode 100644
index 9b0cd8c..6d59c7f
*** a/src/backend/executor/execMain.c
--- b/src/backend/executor/execMain.c
*************** ExecConstraints(ResultRelInfo *resultRel
*** 1604,1609 ****
--- 1604,1652 ----
}
/*
+ * 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
new file mode 100644
index e934c7b..fe180ba
*** a/src/backend/executor/nodeModifyTable.c
--- b/src/backend/executor/nodeModifyTable.c
*************** ExecInsert(TupleTableSlot *slot,
*** 281,286 ****
--- 281,290 ----
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,
*************** lreplace:;
*** 777,782 ****
--- 781,790 ----
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,
*************** ExecInitModifyTable(ModifyTable *node, E
*** 1130,1135 ****
--- 1138,1168 ----
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
new file mode 100644
index b5b8d63..3c93881
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
*************** _copyModifyTable(const ModifyTable *from
*** 178,183 ****
--- 178,184 ----
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);
*************** _copyRangeTblEntry(const RangeTblEntry *
*** 2001,2006 ****
--- 2002,2019 ----
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)
{
*************** _copyQuery(const Query *from)
*** 2443,2448 ****
--- 2456,2462 ----
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);
*************** _copyViewStmt(const ViewStmt *from)
*** 3072,3077 ****
--- 3086,3092 ----
COPY_NODE_FIELD(query);
COPY_SCALAR_FIELD(replace);
COPY_NODE_FIELD(options);
+ COPY_SCALAR_FIELD(withCheckOption);
return newnode;
}
*************** copyObject(const void *from)
*** 4513,4518 ****
--- 4528,4536 ----
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
new file mode 100644
index 3f96595..345951a
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
*************** _equalQuery(const Query *a, const Query
*** 851,856 ****
--- 851,857 ----
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);
*************** _equalViewStmt(const ViewStmt *a, const
*** 1380,1385 ****
--- 1381,1387 ----
COMPARE_NODE_FIELD(query);
COMPARE_SCALAR_FIELD(replace);
COMPARE_NODE_FIELD(options);
+ COMPARE_SCALAR_FIELD(withCheckOption);
return true;
}
*************** _equalRangeTblEntry(const RangeTblEntry
*** 2250,2255 ****
--- 2252,2267 ----
}
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);
*************** equal(const void *a, const void *b)
*** 2983,2988 ****
--- 2995,3003 ----
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
new file mode 100644
index 42d6621..45e0c27
*** a/src/backend/nodes/nodeFuncs.c
--- b/src/backend/nodes/nodeFuncs.c
*************** expression_tree_walker(Node *node,
*** 1556,1561 ****
--- 1556,1563 ----
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;
*************** query_tree_walker(Query *query,
*** 1869,1874 ****
--- 1871,1878 ----
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))
*************** expression_tree_mutator(Node *node,
*** 2070,2075 ****
--- 2074,2088 ----
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;
*************** query_tree_mutator(Query *query,
*** 2583,2588 ****
--- 2596,2602 ----
}
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
new file mode 100644
index b2183f4..b6ead5e
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
*************** _outModifyTable(StringInfo str, const Mo
*** 332,337 ****
--- 332,338 ----
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);
*************** _outQuery(StringInfo str, const Query *n
*** 2244,2249 ****
--- 2245,2251 ----
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);
*************** _outQuery(StringInfo str, const Query *n
*** 2258,2263 ****
--- 2260,2275 ----
}
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");
*************** _outNode(StringInfo str, const void *obj
*** 3111,3116 ****
--- 3123,3131 ----
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
new file mode 100644
index 3a16e9d..44f0c15
*** a/src/backend/nodes/readfuncs.c
--- b/src/backend/nodes/readfuncs.c
*************** _readQuery(void)
*** 210,215 ****
--- 210,216 ----
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);
*************** _readDeclareCursorStmt(void)
*** 255,260 ****
--- 256,276 ----
}
/*
+ * _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 *
*************** parseNodeString(void)
*** 1258,1263 ****
--- 1274,1281 ----
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
new file mode 100644
index 52bab79..c17b460
*** a/src/backend/optimizer/plan/createplan.c
--- b/src/backend/optimizer/plan/createplan.c
*************** make_result(PlannerInfo *root,
*** 4699,4714 ****
* 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.
*/
ModifyTable *
make_modifytable(PlannerInfo *root,
CmdType operation, bool canSetTag,
! List *resultRelations,
! List *subplans, List *returningLists,
List *rowMarks, int epqParam)
{
ModifyTable *node = makeNode(ModifyTable);
--- 4699,4714 ----
* Build a ModifyTable plan node
*
* Currently, we don't charge anything extra for the actual table modification
! * 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 *withCheckOptionLists, List *returningLists,
List *rowMarks, int epqParam)
{
ModifyTable *node = makeNode(ModifyTable);
*************** make_modifytable(PlannerInfo *root,
*** 4720,4725 ****
--- 4720,4727 ----
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));
*************** make_modifytable(PlannerInfo *root,
*** 4756,4761 ****
--- 4758,4764 ----
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
new file mode 100644
index d80c264..01e2fa3
*** a/src/backend/optimizer/plan/planner.c
--- b/src/backend/optimizer/plan/planner.c
*************** subquery_planner(PlannerGlobal *glob, Qu
*** 294,299 ****
--- 294,300 ----
int num_old_subplans = list_length(glob->subplans);
PlannerInfo *root;
Plan *plan;
+ List *newWithCheckOptions;
List *newHaving;
bool hasOuterJoins;
ListCell *l;
*************** subquery_planner(PlannerGlobal *glob, Qu
*** 421,426 ****
--- 422,439 ----
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);
*************** subquery_planner(PlannerGlobal *glob, Qu
*** 559,570 ****
/* If it's not SELECT, we need a ModifyTable node */
if (parse->commandType != CMD_SELECT)
{
List *returningLists;
List *rowMarks;
/*
! * Set up the RETURNING list-of-lists, if needed.
*/
if (parse->returningList)
returningLists = list_make1(parse->returningList);
else
--- 572,590 ----
/* If it's not SELECT, we need a ModifyTable node */
if (parse->commandType != CMD_SELECT)
{
+ List *withCheckOptionLists;
List *returningLists;
List *rowMarks;
/*
! * 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
*************** subquery_planner(PlannerGlobal *glob, Qu
*** 585,590 ****
--- 605,611 ----
parse->canSetTag,
list_make1_int(parse->resultRelation),
list_make1(plan),
+ withCheckOptionLists,
returningLists,
rowMarks,
SS_assign_special_param(root));
*************** inheritance_planner(PlannerInfo *root)
*** 770,775 ****
--- 791,797 ----
RelOptInfo **save_rel_array = NULL;
List *subplans = NIL;
List *resultRelations = NIL;
+ List *withCheckOptionLists = NIL;
List *returningLists = NIL;
List *rowMarks;
ListCell *lc;
*************** inheritance_planner(PlannerInfo *root)
*** 930,936 ****
/* Build list of target-relation RT indexes */
resultRelations = lappend_int(resultRelations, appinfo->child_relid);
! /* Build list of per-relation RETURNING targetlists */
if (parse->returningList)
returningLists = lappend(returningLists,
subroot.parse->returningList);
--- 952,961 ----
/* Build list of target-relation RT indexes */
resultRelations = lappend_int(resultRelations, appinfo->child_relid);
! /* 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);
*************** inheritance_planner(PlannerInfo *root)
*** 979,984 ****
--- 1004,1010 ----
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
new file mode 100644
index 5094226..5377bca
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
*************** static Node *makeRecursiveViewSelect(cha
*** 470,476 ****
%type constraints_set_list
%type constraints_set_mode
%type OptTableSpace OptConsTableSpace OptTableSpaceOwner
! %type opt_check_option
%type opt_provider security_label
--- 470,476 ----
%type constraints_set_list
%type constraints_set_mode
%type OptTableSpace OptConsTableSpace OptTableSpaceOwner
! %type opt_check_option
%type opt_provider security_label
*************** ViewStmt: CREATE OptTemp VIEW qualified_
*** 7976,7981 ****
--- 7976,7982 ----
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
*************** ViewStmt: CREATE OptTemp VIEW qualified_
*** 7988,7993 ****
--- 7989,7995 ----
n->query = $10;
n->replace = true;
n->options = $8;
+ n->withCheckOption = $11;
$$ = (Node *) n;
}
| CREATE OptTemp RECURSIVE VIEW qualified_name '(' columnList ')' opt_reloptions
*************** ViewStmt: CREATE OptTemp VIEW qualified_
*** 8000,8005 ****
--- 8002,8008 ----
n->query = makeRecursiveViewSelect(n->view->relname, n->aliases, $11);
n->replace = false;
n->options = $9;
+ n->withCheckOption = NO_CHECK_OPTION;
$$ = (Node *) n;
}
| CREATE OR REPLACE OptTemp RECURSIVE VIEW qualified_name '(' columnList ')' opt_reloptions
*************** ViewStmt: CREATE OptTemp VIEW qualified_
*** 8012,8041 ****
n->query = makeRecursiveViewSelect(n->view->relname, n->aliases, $13);
n->replace = true;
n->options = $11;
$$ = (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; }
;
/*****************************************************************************
--- 8015,8030 ----
n->query = makeRecursiveViewSelect(n->view->relname, n->aliases, $13);
n->replace = true;
n->options = $11;
+ n->withCheckOption = NO_CHECK_OPTION;
$$ = (Node *) n;
}
;
opt_check_option:
! 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
new file mode 100644
index 01875fc..3e5da5f
*** a/src/backend/rewrite/rewriteHandler.c
--- b/src/backend/rewrite/rewriteHandler.c
***************
*** 19,24 ****
--- 19,25 ----
#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"
*************** rewriteTargetView(Query *parsetree, Rela
*** 2471,2478 ****
* 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.
*/
if (parsetree->commandType != CMD_INSERT &&
viewquery->jointree->quals != NULL)
--- 2472,2478 ----
* 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 in the main query.
*/
if (parsetree->commandType != CMD_INSERT &&
viewquery->jointree->quals != NULL)
*************** rewriteTargetView(Query *parsetree, Rela
*** 2483,2488 ****
--- 2483,2567 ----
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)
+ {
+ WithCheckOption *wco;
+
+ /**
+ * If the parent view has a cascaded check option, add any quals from
+ * this view to the parent's WithCheckOption, so that any constraint
+ * violation is reported against the parent view that defined the
+ * check.
+ *
+ * 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.
+ */
+ wco = NULL;
+ if (parsetree->withCheckOptions != NIL)
+ {
+ WithCheckOption *parent_wco =
+ (WithCheckOption *) linitial(parsetree->withCheckOptions);
+
+ if (parent_wco->cascaded)
+ wco = parent_wco;
+ }
+
+ /*
+ * Otherwise, if the WITH CHECK OPTION is defined on this view, make a
+ * new WithCheckOption structure that the quals can be added to.
+ *
+ * New WithCheckOptions are added 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 and we don't already have a
+ * WithCheckOption structure, we need to build one now even if this
+ * view has no quals, since there may be quals on child views.
+ */
+ if (wco == NULL &&
+ RelationHasCheckOption(view) &&
+ (viewquery->jointree->quals != NULL ||
+ RelationHasCascadedCheckOption(view)))
+ {
+ wco = makeNode(WithCheckOption);
+
+ wco->viewname = pstrdup(RelationGetRelationName(view));
+ wco->qual = NULL;
+ wco->cascaded = RelationHasCascadedCheckOption(view);
+
+ parsetree->withCheckOptions = lcons(wco,
+ parsetree->withCheckOptions);
+ }
+
+ /*
+ * Add any quals defined locally on this view to the WithCheckOption
+ * to be checked, if there is one.
+ */
+ if (wco != NULL && viewquery->jointree->quals != NULL)
+ {
+ Node *viewqual = (Node *) copyObject(viewquery->jointree->quals);
+
+ ChangeVarNodes(viewqual, base_rt_index, new_rt_index, 0);
+ wco->qual = make_and_qual(viewqual, wco->qual);
+
+ /*
+ * 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(viewqual);
+ }
+ }
+
return parsetree;
}
diff --git a/src/include/commands/view.h b/src/include/commands/view.h
new file mode 100644
index 431be94..e9b4b5d
*** a/src/include/commands/view.h
--- b/src/include/commands/view.h
***************
*** 16,21 ****
--- 16,23 ----
#include "nodes/parsenodes.h"
+ extern void validateWithCheckOption(char *value);
+
extern Oid DefineView(ViewStmt *stmt, const char *queryString);
extern void StoreViewQuery(Oid viewOid, Query *viewParse, bool replace);
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
new file mode 100644
index bc215d6..75841c8
*** a/src/include/executor/executor.h
--- b/src/include/executor/executor.h
*************** extern ResultRelInfo *ExecGetTriggerResu
*** 191,196 ****
--- 191,198 ----
extern bool ExecContextForcesOids(PlanState *planstate, bool *hasoids);
extern void ExecConstraints(ResultRelInfo *resultRelInfo,
TupleTableSlot *slot, EState *estate);
+ extern void ExecWithCheckOptions(ResultRelInfo *resultRelInfo,
+ TupleTableSlot *slot, EState *estate);
extern ExecRowMark *ExecFindRowMark(EState *estate, Index rti);
extern ExecAuxRowMark *ExecBuildAuxRowMark(ExecRowMark *erm, List *targetlist);
extern TupleTableSlot *EvalPlanQual(EState *estate, EPQState *epqstate,
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
new file mode 100644
index 4f77016..a129b68
*** a/src/include/nodes/execnodes.h
--- b/src/include/nodes/execnodes.h
*************** typedef struct JunkFilter
*** 303,308 ****
--- 303,310 ----
* TrigInstrument optional runtime measurements for triggers
* FdwRoutine FDW callback functions, if foreign table
* FdwState available to save private state of FDW
+ * WithCheckOptions list of WithCheckOption's for views
+ * WithCheckOptionExprs list of WithCheckOption expr states
* ConstraintExprs array of constraint-checking expr states
* junkFilter for removing junk attributes from tuples
* projectReturning for computing a RETURNING list
*************** typedef struct ResultRelInfo
*** 322,327 ****
--- 324,331 ----
Instrumentation *ri_TrigInstrument;
struct FdwRoutine *ri_FdwRoutine;
void *ri_FdwState;
+ List *ri_WithCheckOptions;
+ List *ri_WithCheckOptionExprs;
List **ri_ConstraintExprs;
JunkFilter *ri_junkFilter;
ProjectionInfo *ri_projectReturning;
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
new file mode 100644
index 0d5c007..78368c6
*** a/src/include/nodes/nodes.h
--- b/src/include/nodes/nodes.h
*************** typedef enum NodeTag
*** 388,393 ****
--- 388,394 ----
T_Constraint,
T_DefElem,
T_RangeTblEntry,
+ T_WithCheckOption,
T_SortGroupClause,
T_WindowClause,
T_PrivGrantee,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
new file mode 100644
index 6723647..f4a18fc
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
*************** typedef struct Query
*** 128,133 ****
--- 128,135 ----
List *targetList; /* target list (of TargetEntry) */
+ List *withCheckOptions; /* a list of WithCheckOption's */
+
List *returningList; /* return-values list (of TargetEntry) */
List *groupClause; /* a list of SortGroupClause's */
*************** typedef struct RangeTblEntry
*** 778,783 ****
--- 780,798 ----
} RangeTblEntry;
/*
+ * WithCheckOption -
+ * representation of WITH CHECK OPTION checks to be applied to new tuples
+ * when inserting/updating an auto-updatable view.
+ */
+ typedef struct WithCheckOption
+ {
+ NodeTag type;
+ char *viewname; /* name of view that specified the WCO */
+ Node *qual; /* constraint qual to check */
+ bool cascaded; /* true = WITH CASCADED CHECK OPTION */
+ } WithCheckOption;
+
+ /*
* SortGroupClause -
* representation of ORDER BY, GROUP BY, PARTITION BY,
* DISTINCT, DISTINCT ON items
*************** typedef struct AlterEnumStmt
*** 2326,2331 ****
--- 2341,2353 ----
* Create View Statement
* ----------------------
*/
+ typedef enum ViewCheckOption
+ {
+ NO_CHECK_OPTION,
+ LOCAL_CHECK_OPTION,
+ CASCADED_CHECK_OPTION
+ } ViewCheckOption;
+
typedef struct ViewStmt
{
NodeTag type;
*************** typedef struct ViewStmt
*** 2334,2339 ****
--- 2356,2362 ----
Node *query; /* the SELECT query */
bool replace; /* replace an existing view? */
List *options; /* options from WITH clause */
+ ViewCheckOption withCheckOption; /* WITH CHECK OPTION */
} ViewStmt;
/* ----------------------
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
new file mode 100644
index 841701e..aa4f12c
*** a/src/include/nodes/plannodes.h
--- b/src/include/nodes/plannodes.h
*************** typedef struct ModifyTable
*** 172,177 ****
--- 172,178 ----
List *resultRelations; /* integer list of RT indexes */
int resultRelIndex; /* index of first resultRel in plan's list */
List *plans; /* plan(s) producing source data */
+ List *withCheckOptionLists; /* per-target-table WCO lists */
List *returningLists; /* per-target-table RETURNING tlists */
List *fdwPrivLists; /* per-target-table FDW private data lists */
List *rowMarks; /* PlanRowMarks (non-locking only) */
diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h
new file mode 100644
index 33eaf32..bd6841f
*** a/src/include/optimizer/planmain.h
--- b/src/include/optimizer/planmain.h
*************** extern Result *make_result(PlannerInfo *
*** 85,91 ****
Node *resconstantqual, Plan *subplan);
extern ModifyTable *make_modifytable(PlannerInfo *root,
CmdType operation, bool canSetTag,
! List *resultRelations, List *subplans, List *returningLists,
List *rowMarks, int epqParam);
extern bool is_projection_capable_plan(Plan *plan);
--- 85,92 ----
Node *resconstantqual, Plan *subplan);
extern ModifyTable *make_modifytable(PlannerInfo *root,
CmdType operation, bool canSetTag,
! List *resultRelations, List *subplans,
! List *withCheckOptionLists, List *returningLists,
List *rowMarks, int epqParam);
extern bool is_projection_capable_plan(Plan *plan);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
new file mode 100644
index 58cc3f7..589c9a8
*** a/src/include/utils/rel.h
--- b/src/include/utils/rel.h
*************** typedef struct StdRdOptions
*** 208,213 ****
--- 208,214 ----
int fillfactor; /* page fill factor in percent (0..100) */
AutoVacOpts autovacuum; /* autovacuum-related options */
bool security_barrier; /* for views */
+ int check_option_offset; /* for views */
} StdRdOptions;
#define HEAP_MIN_FILLFACTOR 10
*************** typedef struct StdRdOptions
*** 244,249 ****
--- 245,283 ----
((StdRdOptions *) (relation)->rd_options)->security_barrier : false)
/*
+ * RelationHasCheckOption
+ * Returns true if the relation is a view defined with either the local
+ * or the cascaded check option.
+ */
+ #define RelationHasCheckOption(relation) \
+ ((relation)->rd_options && \
+ ((StdRdOptions *) (relation)->rd_options)->check_option_offset != 0)
+
+ /*
+ * RelationHasLocalCheckOption
+ * Returns true if the relation is a view defined with the local check
+ * option.
+ */
+ #define RelationHasLocalCheckOption(relation) \
+ ((relation)->rd_options && \
+ ((StdRdOptions *) (relation)->rd_options)->check_option_offset != 0 ? \
+ strcmp((char *) (relation)->rd_options + \
+ ((StdRdOptions *) (relation)->rd_options)->check_option_offset, \
+ "local") == 0 : false)
+
+ /*
+ * RelationHasCascadedCheckOption
+ * Returns true if the relation is a view defined with the cascaded check
+ * option.
+ */
+ #define RelationHasCascadedCheckOption(relation) \
+ ((relation)->rd_options && \
+ ((StdRdOptions *) (relation)->rd_options)->check_option_offset != 0 ? \
+ strcmp((char *) (relation)->rd_options + \
+ ((StdRdOptions *) (relation)->rd_options)->check_option_offset, \
+ "cascaded") == 0 : false)
+
+ /*
* RelationIsValid
* True iff relation descriptor is valid.
*/
diff --git a/src/test/regress/expected/updatable_views.out b/src/test/regress/expected/updatable_views.out
new file mode 100644
index ecb61e0..c7fa390
*** a/src/test/regress/expected/updatable_views.out
--- b/src/test/regress/expected/updatable_views.out
*************** SELECT * FROM rw_view1;
*** 1063,1065 ****
--- 1063,1363 ----
DROP TABLE base_tbl CASCADE;
NOTICE: drop cascades to view rw_view1
+ -- simple WITH CHECK OPTION
+ CREATE TABLE base_tbl (a int, b int DEFAULT 10);
+ INSERT INTO base_tbl VALUES (1,2), (2,3), (1,-1);
+ CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WHERE a < b
+ WITH LOCAL CHECK OPTION;
+ \d+ rw_view1
+ View "public.rw_view1"
+ Column | Type | Modifiers | Storage | Description
+ --------+---------+-----------+---------+-------------
+ a | integer | | plain |
+ b | integer | | plain |
+ View definition:
+ SELECT base_tbl.a,
+ base_tbl.b
+ FROM base_tbl
+ WHERE base_tbl.a < base_tbl.b;
+ Options: check_option=local
+
+ INSERT INTO rw_view1 VALUES(3,4); -- ok
+ INSERT INTO rw_view1 VALUES(4,3); -- should fail
+ ERROR: new row violates WITH CHECK OPTION for view "rw_view1"
+ DETAIL: Failing row contains (4, 3).
+ INSERT INTO rw_view1 VALUES(5,null); -- should fail
+ ERROR: new row violates WITH CHECK OPTION for view "rw_view1"
+ DETAIL: Failing row contains (5, null).
+ UPDATE rw_view1 SET b = 5 WHERE a = 3; -- ok
+ UPDATE rw_view1 SET b = -5 WHERE a = 3; -- should fail
+ ERROR: new row violates WITH CHECK OPTION for view "rw_view1"
+ DETAIL: Failing row contains (3, -5).
+ INSERT INTO rw_view1(a) VALUES (9); -- ok
+ INSERT INTO rw_view1(a) VALUES (10); -- should fail
+ ERROR: new row violates WITH CHECK OPTION for view "rw_view1"
+ DETAIL: Failing row contains (10, 10).
+ SELECT * FROM base_tbl;
+ a | b
+ ---+----
+ 1 | 2
+ 2 | 3
+ 1 | -1
+ 3 | 5
+ 9 | 10
+ (5 rows)
+
+ DROP TABLE base_tbl CASCADE;
+ NOTICE: drop cascades to view rw_view1
+ -- WITH LOCAL/CASCADED CHECK OPTION
+ CREATE TABLE base_tbl (a int);
+ CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WHERE a > 0;
+ CREATE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a < 10
+ WITH CHECK OPTION; -- implicitly cascaded
+ \d+ rw_view2
+ View "public.rw_view2"
+ Column | Type | Modifiers | Storage | Description
+ --------+---------+-----------+---------+-------------
+ a | integer | | plain |
+ View definition:
+ SELECT rw_view1.a
+ FROM rw_view1
+ WHERE rw_view1.a < 10;
+ Options: check_option=cascaded
+
+ INSERT INTO rw_view2 VALUES (-5); -- should fail
+ ERROR: new row violates WITH CHECK OPTION for view "rw_view2"
+ DETAIL: Failing row contains (-5).
+ INSERT INTO rw_view2 VALUES (5); -- ok
+ INSERT INTO rw_view2 VALUES (15); -- should fail
+ ERROR: new row violates WITH CHECK OPTION for view "rw_view2"
+ DETAIL: Failing row contains (15).
+ SELECT * FROM base_tbl;
+ a
+ ---
+ 5
+ (1 row)
+
+ UPDATE rw_view2 SET a = a - 10; -- should fail
+ ERROR: new row violates WITH CHECK OPTION for view "rw_view2"
+ DETAIL: Failing row contains (-5).
+ UPDATE rw_view2 SET a = a + 10; -- should fail
+ ERROR: new row violates WITH CHECK OPTION for view "rw_view2"
+ DETAIL: Failing row contains (15).
+ CREATE OR REPLACE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a < 10
+ WITH LOCAL CHECK OPTION;
+ \d+ rw_view2
+ View "public.rw_view2"
+ Column | Type | Modifiers | Storage | Description
+ --------+---------+-----------+---------+-------------
+ a | integer | | plain |
+ View definition:
+ SELECT rw_view1.a
+ FROM rw_view1
+ WHERE rw_view1.a < 10;
+ Options: check_option=local
+
+ INSERT INTO rw_view2 VALUES (-10); -- ok, but not in view
+ INSERT INTO rw_view2 VALUES (20); -- should fail
+ ERROR: new row violates WITH CHECK OPTION for view "rw_view2"
+ DETAIL: Failing row contains (20).
+ SELECT * FROM base_tbl;
+ a
+ -----
+ 5
+ -10
+ (2 rows)
+
+ ALTER VIEW rw_view1 SET (check_option=here); -- invalid
+ ERROR: invalid value for "check_option" option
+ DETAIL: Valid values are "local", and "cascaded".
+ ALTER VIEW rw_view1 SET (check_option=local);
+ INSERT INTO rw_view2 VALUES (-20); -- should fail
+ ERROR: new row violates WITH CHECK OPTION for view "rw_view1"
+ DETAIL: Failing row contains (-20).
+ INSERT INTO rw_view2 VALUES (30); -- should fail
+ ERROR: new row violates WITH CHECK OPTION for view "rw_view2"
+ DETAIL: Failing row contains (30).
+ DROP TABLE base_tbl CASCADE;
+ NOTICE: drop cascades to 2 other objects
+ DETAIL: drop cascades to view rw_view1
+ drop cascades to view rw_view2
+ -- WITH CHECK OPTION with no local view qual
+ CREATE TABLE base_tbl (a int);
+ CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WITH CHECK OPTION;
+ CREATE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a > 0;
+ CREATE VIEW rw_view3 AS SELECT * FROM rw_view2 WITH CHECK OPTION;
+ INSERT INTO rw_view1 VALUES (-1); -- ok
+ INSERT INTO rw_view1 VALUES (1); -- ok
+ INSERT INTO rw_view2 VALUES (-2); -- ok, but not in view
+ INSERT INTO rw_view2 VALUES (2); -- ok
+ INSERT INTO rw_view3 VALUES (-3); -- should fail
+ ERROR: new row violates WITH CHECK OPTION for view "rw_view3"
+ DETAIL: Failing row contains (-3).
+ INSERT INTO rw_view3 VALUES (3); -- ok
+ DROP TABLE base_tbl CASCADE;
+ NOTICE: drop cascades to 3 other objects
+ DETAIL: drop cascades to view rw_view1
+ drop cascades to view rw_view2
+ drop cascades to view rw_view3
+ -- WITH CHECK OPTION with subquery
+ CREATE TABLE base_tbl (a int);
+ CREATE TABLE ref_tbl (a int PRIMARY KEY);
+ INSERT INTO ref_tbl SELECT * FROM generate_series(1,10);
+ CREATE VIEW rw_view1 AS
+ SELECT * FROM base_tbl b
+ WHERE EXISTS(SELECT 1 FROM ref_tbl r WHERE r.a = b.a)
+ WITH CHECK OPTION;
+ INSERT INTO rw_view1 VALUES (5); -- ok
+ INSERT INTO rw_view1 VALUES (15); -- should fail
+ ERROR: new row violates WITH CHECK OPTION for view "rw_view1"
+ DETAIL: Failing row contains (15).
+ UPDATE rw_view1 SET a = a + 5; -- ok
+ UPDATE rw_view1 SET a = a + 5; -- should fail
+ ERROR: new row violates WITH CHECK OPTION for view "rw_view1"
+ DETAIL: Failing row contains (15).
+ EXPLAIN (costs off) INSERT INTO rw_view1 VALUES (5);
+ QUERY PLAN
+ ---------------------------------------------------------------
+ Insert on base_tbl b
+ -> Result
+ SubPlan 1
+ -> Index Only Scan using ref_tbl_pkey on ref_tbl r
+ Index Cond: (a = b.a)
+ SubPlan 2
+ -> Seq Scan on ref_tbl r_1
+ (7 rows)
+
+ EXPLAIN (costs off) UPDATE rw_view1 SET a = a + 5;
+ QUERY PLAN
+ -----------------------------------------------------------------
+ Update on base_tbl b
+ -> Hash Semi Join
+ Hash Cond: (b.a = r.a)
+ -> Seq Scan on base_tbl b
+ -> Hash
+ -> Seq Scan on ref_tbl r
+ SubPlan 1
+ -> Index Only Scan using ref_tbl_pkey on ref_tbl r_1
+ Index Cond: (a = b.a)
+ SubPlan 2
+ -> Seq Scan on ref_tbl r_2
+ (11 rows)
+
+ DROP TABLE base_tbl, ref_tbl CASCADE;
+ NOTICE: drop cascades to view rw_view1
+ -- WITH CHECK OPTION with BEFORE trigger on base table
+ CREATE TABLE base_tbl (a int, b int);
+ CREATE FUNCTION base_tbl_trig_fn()
+ RETURNS trigger AS
+ $$
+ BEGIN
+ NEW.b := 10;
+ RETURN NEW;
+ END;
+ $$
+ LANGUAGE plpgsql;
+ CREATE TRIGGER base_tbl_trig BEFORE INSERT OR UPDATE ON base_tbl
+ FOR EACH ROW EXECUTE PROCEDURE base_tbl_trig_fn();
+ CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WHERE a < b WITH CHECK OPTION;
+ INSERT INTO rw_view1 VALUES (5,0); -- ok
+ INSERT INTO rw_view1 VALUES (15, 20); -- should fail
+ ERROR: new row violates WITH CHECK OPTION for view "rw_view1"
+ DETAIL: Failing row contains (15, 10).
+ UPDATE rw_view1 SET a = 20, b = 30; -- should fail
+ ERROR: new row violates WITH CHECK OPTION for view "rw_view1"
+ DETAIL: Failing row contains (20, 10).
+ DROP TABLE base_tbl CASCADE;
+ NOTICE: drop cascades to view rw_view1
+ DROP FUNCTION base_tbl_trig_fn();
+ -- WITH LOCAL CHECK OPTION with INSTEAD OF trigger on base view
+ CREATE TABLE base_tbl (a int, b int);
+ CREATE VIEW rw_view1 AS SELECT a FROM base_tbl WHERE a < b;
+ CREATE FUNCTION rw_view1_trig_fn()
+ RETURNS trigger AS
+ $$
+ BEGIN
+ IF TG_OP = 'INSERT' THEN
+ INSERT INTO base_tbl VALUES (NEW.a, 10);
+ RETURN NEW;
+ ELSIF TG_OP = 'UPDATE' THEN
+ UPDATE base_tbl SET a=NEW.a WHERE a=OLD.a;
+ RETURN NEW;
+ ELSIF TG_OP = 'DELETE' THEN
+ DELETE FROM base_tbl WHERE a=OLD.a;
+ RETURN OLD;
+ END IF;
+ END;
+ $$
+ LANGUAGE plpgsql;
+ CREATE TRIGGER rw_view1_trig
+ INSTEAD OF INSERT OR UPDATE OR DELETE ON rw_view1
+ FOR EACH ROW EXECUTE PROCEDURE rw_view1_trig_fn();
+ CREATE VIEW rw_view2 AS
+ SELECT * FROM rw_view1 WHERE a > 0 WITH LOCAL CHECK OPTION;
+ INSERT INTO rw_view2 VALUES (-5); -- should fail
+ ERROR: new row violates WITH CHECK OPTION for view "rw_view2"
+ DETAIL: Failing row contains (-5).
+ INSERT INTO rw_view2 VALUES (5); -- ok
+ INSERT INTO rw_view2 VALUES (50); -- ok, but not in view
+ UPDATE rw_view2 SET a = a - 10; -- should fail
+ ERROR: new row violates WITH CHECK OPTION for view "rw_view2"
+ DETAIL: Failing row contains (-5).
+ SELECT * FROM base_tbl;
+ a | b
+ ----+----
+ 5 | 10
+ 50 | 10
+ (2 rows)
+
+ -- Check option won't cascade down to base view with INSTEAD OF triggers
+ ALTER VIEW rw_view2 SET (check_option=cascaded);
+ INSERT INTO rw_view2 VALUES (100); -- ok, but not in view (doesn't fail rw_view1's check)
+ UPDATE rw_view2 SET a = 200 WHERE a = 5; -- ok, but not in view (doesn't fail rw_view1's check)
+ SELECT * FROM base_tbl;
+ a | b
+ -----+----
+ 50 | 10
+ 100 | 10
+ 200 | 10
+ (3 rows)
+
+ -- Neither local nor cascaded check options work with INSTEAD rules
+ DROP TRIGGER rw_view1_trig ON rw_view1;
+ CREATE RULE rw_view1_ins_rule AS ON INSERT TO rw_view1
+ DO INSTEAD INSERT INTO base_tbl VALUES (NEW.a, 10);
+ CREATE RULE rw_view1_upd_rule AS ON UPDATE TO rw_view1
+ DO INSTEAD UPDATE base_tbl SET a=NEW.a WHERE a=OLD.a;
+ INSERT INTO rw_view2 VALUES (-10); -- ok, but not in view (doesn't fail rw_view2's check)
+ INSERT INTO rw_view2 VALUES (5); -- ok
+ INSERT INTO rw_view2 VALUES (20); -- ok, but not in view (doesn't fail rw_view1's check)
+ UPDATE rw_view2 SET a = 30 WHERE a = 5; -- ok, but not in view (doesn't fail rw_view1's check)
+ INSERT INTO rw_view2 VALUES (5); -- ok
+ UPDATE rw_view2 SET a = -5 WHERE a = 5; -- ok, but not in view (doesn't fail rw_view2's check)
+ SELECT * FROM base_tbl;
+ a | b
+ -----+----
+ 50 | 10
+ 100 | 10
+ 200 | 10
+ -10 | 10
+ 20 | 10
+ 30 | 10
+ -5 | 10
+ (7 rows)
+
+ DROP TABLE base_tbl CASCADE;
+ NOTICE: drop cascades to 2 other objects
+ DETAIL: drop cascades to view rw_view1
+ drop cascades to view rw_view2
+ DROP FUNCTION rw_view1_trig_fn();
+ CREATE TABLE base_tbl (a int);
+ CREATE VIEW rw_view1 AS SELECT a,10 AS b FROM base_tbl;
+ CREATE RULE rw_view1_ins_rule AS ON INSERT TO rw_view1
+ DO INSTEAD INSERT INTO base_tbl VALUES (NEW.a);
+ CREATE VIEW rw_view2 AS
+ SELECT * FROM rw_view1 WHERE a > b WITH LOCAL CHECK OPTION;
+ INSERT INTO rw_view2 VALUES (2,3); -- ok, but not in view (doesn't fail rw_view2's check)
+ DROP TABLE base_tbl CASCADE;
+ NOTICE: drop cascades to 2 other objects
+ DETAIL: drop cascades to view rw_view1
+ drop cascades to view rw_view2
diff --git a/src/test/regress/sql/updatable_views.sql b/src/test/regress/sql/updatable_views.sql
new file mode 100644
index 49dfedd..690dcea
*** a/src/test/regress/sql/updatable_views.sql
--- b/src/test/regress/sql/updatable_views.sql
*************** UPDATE rw_view1 SET arr[1] = 42, arr[2]
*** 509,511 ****
--- 509,700 ----
SELECT * FROM rw_view1;
DROP TABLE base_tbl CASCADE;
+
+ -- simple WITH CHECK OPTION
+
+ CREATE TABLE base_tbl (a int, b int DEFAULT 10);
+ INSERT INTO base_tbl VALUES (1,2), (2,3), (1,-1);
+
+ CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WHERE a < b
+ WITH LOCAL CHECK OPTION;
+ \d+ rw_view1
+
+ INSERT INTO rw_view1 VALUES(3,4); -- ok
+ INSERT INTO rw_view1 VALUES(4,3); -- should fail
+ INSERT INTO rw_view1 VALUES(5,null); -- should fail
+ UPDATE rw_view1 SET b = 5 WHERE a = 3; -- ok
+ UPDATE rw_view1 SET b = -5 WHERE a = 3; -- should fail
+ INSERT INTO rw_view1(a) VALUES (9); -- ok
+ INSERT INTO rw_view1(a) VALUES (10); -- should fail
+ SELECT * FROM base_tbl;
+
+ DROP TABLE base_tbl CASCADE;
+
+ -- WITH LOCAL/CASCADED CHECK OPTION
+
+ CREATE TABLE base_tbl (a int);
+
+ CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WHERE a > 0;
+ CREATE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a < 10
+ WITH CHECK OPTION; -- implicitly cascaded
+ \d+ rw_view2
+
+ INSERT INTO rw_view2 VALUES (-5); -- should fail
+ INSERT INTO rw_view2 VALUES (5); -- ok
+ INSERT INTO rw_view2 VALUES (15); -- should fail
+ SELECT * FROM base_tbl;
+
+ UPDATE rw_view2 SET a = a - 10; -- should fail
+ UPDATE rw_view2 SET a = a + 10; -- should fail
+
+ CREATE OR REPLACE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a < 10
+ WITH LOCAL CHECK OPTION;
+ \d+ rw_view2
+
+ INSERT INTO rw_view2 VALUES (-10); -- ok, but not in view
+ INSERT INTO rw_view2 VALUES (20); -- should fail
+ SELECT * FROM base_tbl;
+
+ ALTER VIEW rw_view1 SET (check_option=here); -- invalid
+ ALTER VIEW rw_view1 SET (check_option=local);
+
+ INSERT INTO rw_view2 VALUES (-20); -- should fail
+ INSERT INTO rw_view2 VALUES (30); -- should fail
+
+ DROP TABLE base_tbl CASCADE;
+
+ -- WITH CHECK OPTION with no local view qual
+
+ CREATE TABLE base_tbl (a int);
+
+ CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WITH CHECK OPTION;
+ CREATE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a > 0;
+ CREATE VIEW rw_view3 AS SELECT * FROM rw_view2 WITH CHECK OPTION;
+
+ INSERT INTO rw_view1 VALUES (-1); -- ok
+ INSERT INTO rw_view1 VALUES (1); -- ok
+ INSERT INTO rw_view2 VALUES (-2); -- ok, but not in view
+ INSERT INTO rw_view2 VALUES (2); -- ok
+ INSERT INTO rw_view3 VALUES (-3); -- should fail
+ INSERT INTO rw_view3 VALUES (3); -- ok
+
+ DROP TABLE base_tbl CASCADE;
+
+ -- WITH CHECK OPTION with subquery
+
+ CREATE TABLE base_tbl (a int);
+ CREATE TABLE ref_tbl (a int PRIMARY KEY);
+ INSERT INTO ref_tbl SELECT * FROM generate_series(1,10);
+
+ CREATE VIEW rw_view1 AS
+ SELECT * FROM base_tbl b
+ WHERE EXISTS(SELECT 1 FROM ref_tbl r WHERE r.a = b.a)
+ WITH CHECK OPTION;
+
+ INSERT INTO rw_view1 VALUES (5); -- ok
+ INSERT INTO rw_view1 VALUES (15); -- should fail
+
+ UPDATE rw_view1 SET a = a + 5; -- ok
+ UPDATE rw_view1 SET a = a + 5; -- should fail
+
+ EXPLAIN (costs off) INSERT INTO rw_view1 VALUES (5);
+ EXPLAIN (costs off) UPDATE rw_view1 SET a = a + 5;
+
+ DROP TABLE base_tbl, ref_tbl CASCADE;
+
+ -- WITH CHECK OPTION with BEFORE trigger on base table
+
+ CREATE TABLE base_tbl (a int, b int);
+
+ CREATE FUNCTION base_tbl_trig_fn()
+ RETURNS trigger AS
+ $$
+ BEGIN
+ NEW.b := 10;
+ RETURN NEW;
+ END;
+ $$
+ LANGUAGE plpgsql;
+
+ CREATE TRIGGER base_tbl_trig BEFORE INSERT OR UPDATE ON base_tbl
+ FOR EACH ROW EXECUTE PROCEDURE base_tbl_trig_fn();
+
+ CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WHERE a < b WITH CHECK OPTION;
+
+ INSERT INTO rw_view1 VALUES (5,0); -- ok
+ INSERT INTO rw_view1 VALUES (15, 20); -- should fail
+ UPDATE rw_view1 SET a = 20, b = 30; -- should fail
+
+ DROP TABLE base_tbl CASCADE;
+ DROP FUNCTION base_tbl_trig_fn();
+
+ -- WITH LOCAL CHECK OPTION with INSTEAD OF trigger on base view
+
+ CREATE TABLE base_tbl (a int, b int);
+
+ CREATE VIEW rw_view1 AS SELECT a FROM base_tbl WHERE a < b;
+
+ CREATE FUNCTION rw_view1_trig_fn()
+ RETURNS trigger AS
+ $$
+ BEGIN
+ IF TG_OP = 'INSERT' THEN
+ INSERT INTO base_tbl VALUES (NEW.a, 10);
+ RETURN NEW;
+ ELSIF TG_OP = 'UPDATE' THEN
+ UPDATE base_tbl SET a=NEW.a WHERE a=OLD.a;
+ RETURN NEW;
+ ELSIF TG_OP = 'DELETE' THEN
+ DELETE FROM base_tbl WHERE a=OLD.a;
+ RETURN OLD;
+ END IF;
+ END;
+ $$
+ LANGUAGE plpgsql;
+
+ CREATE TRIGGER rw_view1_trig
+ INSTEAD OF INSERT OR UPDATE OR DELETE ON rw_view1
+ FOR EACH ROW EXECUTE PROCEDURE rw_view1_trig_fn();
+
+ CREATE VIEW rw_view2 AS
+ SELECT * FROM rw_view1 WHERE a > 0 WITH LOCAL CHECK OPTION;
+
+ INSERT INTO rw_view2 VALUES (-5); -- should fail
+ INSERT INTO rw_view2 VALUES (5); -- ok
+ INSERT INTO rw_view2 VALUES (50); -- ok, but not in view
+ UPDATE rw_view2 SET a = a - 10; -- should fail
+ SELECT * FROM base_tbl;
+
+ -- Check option won't cascade down to base view with INSTEAD OF triggers
+
+ ALTER VIEW rw_view2 SET (check_option=cascaded);
+ INSERT INTO rw_view2 VALUES (100); -- ok, but not in view (doesn't fail rw_view1's check)
+ UPDATE rw_view2 SET a = 200 WHERE a = 5; -- ok, but not in view (doesn't fail rw_view1's check)
+ SELECT * FROM base_tbl;
+
+ -- Neither local nor cascaded check options work with INSTEAD rules
+
+ DROP TRIGGER rw_view1_trig ON rw_view1;
+ CREATE RULE rw_view1_ins_rule AS ON INSERT TO rw_view1
+ DO INSTEAD INSERT INTO base_tbl VALUES (NEW.a, 10);
+ CREATE RULE rw_view1_upd_rule AS ON UPDATE TO rw_view1
+ DO INSTEAD UPDATE base_tbl SET a=NEW.a WHERE a=OLD.a;
+ INSERT INTO rw_view2 VALUES (-10); -- ok, but not in view (doesn't fail rw_view2's check)
+ INSERT INTO rw_view2 VALUES (5); -- ok
+ INSERT INTO rw_view2 VALUES (20); -- ok, but not in view (doesn't fail rw_view1's check)
+ UPDATE rw_view2 SET a = 30 WHERE a = 5; -- ok, but not in view (doesn't fail rw_view1's check)
+ INSERT INTO rw_view2 VALUES (5); -- ok
+ UPDATE rw_view2 SET a = -5 WHERE a = 5; -- ok, but not in view (doesn't fail rw_view2's check)
+ SELECT * FROM base_tbl;
+
+ DROP TABLE base_tbl CASCADE;
+ DROP FUNCTION rw_view1_trig_fn();
+
+ CREATE TABLE base_tbl (a int);
+ CREATE VIEW rw_view1 AS SELECT a,10 AS b FROM base_tbl;
+ CREATE RULE rw_view1_ins_rule AS ON INSERT TO rw_view1
+ DO INSTEAD INSERT INTO base_tbl VALUES (NEW.a);
+ CREATE VIEW rw_view2 AS
+ SELECT * FROM rw_view1 WHERE a > b WITH LOCAL CHECK OPTION;
+ INSERT INTO rw_view2 VALUES (2,3); -- ok, but not in view (doesn't fail rw_view2's check)
+ DROP TABLE base_tbl CASCADE;