diff --git a/doc/src/sgml/ref/alter_view.sgml b/doc/src/sgml/ref/alter_view.sgml
new file mode 100644
index df527ae..62cce0f
*** a/doc/src/sgml/ref/alter_view.sgml
--- b/doc/src/sgml/ref/alter_view.sgml
*************** ALTER VIEW [ IF EXISTS ] name SET SCHEMA new_schema
ALTER VIEW [ IF EXISTS ] name SET ( view_option_name [= view_option_value] [, ... ] )
ALTER VIEW [ IF EXISTS ] name RESET ( view_option_name [, ... ] )
+
+ where view_option_name can be one of:
+
+ security_barrier [ boolean ]
+ check_option [ text (local or cascaded) ]
diff --git a/doc/src/sgml/ref/create_view.sgml b/doc/src/sgml/ref/create_view.sgml
new file mode 100644
index 2af6f6e..ff3111f
*** a/doc/src/sgml/ref/create_view.sgml
--- b/doc/src/sgml/ref/create_view.sgml
*************** PostgreSQL documentation
*** 24,29 ****
--- 24,35 ----
CREATE [ OR REPLACE ] [ TEMP | TEMPORARY ] [ RECURSIVE ] VIEW name [ ( column_name [, ...] ) ]
[ WITH ( view_option_name [= view_option_value] [, ... ] ) ]
AS query
+ [ WITH [ CASCADED | LOCAL ] CHECK OPTION ]
+
+ where view_option_name can be one of:
+
+ security_barrier [ boolean ]
+ check_option [ text (local or cascaded) ]
*************** 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.
--- 126,158 ----
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 parameter may be either local> or
! cascaded>, and is equivalent to specifying
! WITH [ CASCADED | LOCAL ] CHECK OPTION> (see below).
! This option can be changed on existing views using .
!
!
!
!
*************** CREATE VIEW name> AS WITH
*** 138,143 ****
--- 167,243 ----
+
+
+ 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.
--- 356,364 ----
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.
--- 416,425 ----
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/information_schema.sql b/src/backend/catalog/information_schema.sql
new file mode 100644
index e1f8e7f..95f267f
*** a/src/backend/catalog/information_schema.sql
--- b/src/backend/catalog/information_schema.sql
*************** CREATE VIEW views AS
*** 2494,2500 ****
ELSE null END
AS character_data) AS view_definition,
! CAST('NONE' AS character_data) AS check_option,
CAST(
-- (1 << CMD_UPDATE) + (1 << CMD_DELETE)
--- 2494,2506 ----
ELSE null END
AS character_data) AS view_definition,
! 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
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/tablecmds.c b/src/backend/commands/tablecmds.c
new file mode 100644
index 6708725..54d238c
*** a/src/backend/commands/tablecmds.c
--- b/src/backend/commands/tablecmds.c
*************** ATExecSetRelOptions(Relation rel, List *
*** 8773,8778 ****
--- 8773,8804 ----
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;
+ bool security_barrier;
+
+ 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 (check_option &&
+ view_query_is_auto_updatable(view_query, security_barrier) != NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("WITH CHECK OPTION is supported only on auto-updatable views")));
+ }
+
/*
* 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
new file mode 100644
index 6186a84..bb1ef86
*** a/src/backend/commands/view.c
--- b/src/backend/commands/view.c
***************
*** 27,32 ****
--- 27,33 ----
#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,43 ****
--- 39,62 ----
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
*** 374,379 ****
--- 393,401 ----
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
*************** DefineView(ViewStmt *stmt, const char *q
*** 411,416 ****
--- 433,471 ----
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.
+ */
+ 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 (check_option &&
+ view_query_is_auto_updatable(viewParse, security_barrier) != NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("WITH CHECK OPTION is supported only on auto-updatable views")));
+
+ /*
* 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
new file mode 100644
index 3b664d0..0ee7820
*** a/src/backend/executor/execMain.c
--- b/src/backend/executor/execMain.c
*************** ExecConstraints(ResultRelInfo *resultRel
*** 1622,1627 ****
--- 1622,1670 ----
}
/*
+ * 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 f67ef0c..e06e8b8
*** 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_
*** 7993,7998 ****
--- 7993,7999 ----
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_
*** 8005,8010 ****
--- 8006,8012 ----
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_
*** 8017,8022 ****
--- 8019,8025 ----
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_
*** 8029,8058 ****
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; }
;
/*****************************************************************************
--- 8032,8047 ----
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 d909de3..cad6482
*** 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"
*************** fireRules(Query *parsetree,
*** 1866,1872 ****
* Caller should have verified that the relation is a view, and therefore
* we should find an ON SELECT action.
*/
! static Query *
get_view_query(Relation view)
{
int i;
--- 1867,1873 ----
* Caller should have verified that the relation is a view, and therefore
* we should find an ON SELECT action.
*/
! Query *
get_view_query(Relation view)
{
int i;
*************** view_has_instead_trigger(Relation view,
*** 1940,1949 ****
* 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 *
! view_is_auto_updatable(Relation view)
{
- Query *viewquery = get_view_query(view);
RangeTblRef *rtr;
RangeTblEntry *base_rte;
Bitmapset *bms;
--- 1941,1949 ----
* 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.
*/
! const char *
! view_query_is_auto_updatable(Query *viewquery, bool security_barrier)
{
RangeTblRef *rtr;
RangeTblEntry *base_rte;
Bitmapset *bms;
*************** view_is_auto_updatable(Relation view)
*** 1997,2003 ****
* difficulty of keeping upper-level qual expressions away from
* lower-level data. This might get relaxed in future.
*/
! if (RelationIsSecurityView(view))
return gettext_noop("Security-barrier views are not automatically updatable.");
/*
--- 1997,2003 ----
* difficulty of keeping upper-level qual expressions away from
* lower-level data. This might get relaxed in future.
*/
! if (security_barrier)
return gettext_noop("Security-barrier views are not automatically updatable.");
/*
*************** view_is_auto_updatable(Relation view)
*** 2057,2062 ****
--- 2057,2071 ----
return NULL; /* the view is simply updatable */
}
+ static 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);
+ }
+
/*
* relation_is_updatable - determine which update events the specified
*************** rewriteTargetView(Query *parsetree, Rela
*** 2532,2539 ****
* 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)
--- 2541,2547 ----
* 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
*** 2544,2549 ****
--- 2552,2636 ----
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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
new file mode 100644
index f40961f..8987077
*** a/src/bin/pg_dump/pg_dump.c
--- b/src/bin/pg_dump/pg_dump.c
*************** dumpTableSchema(Archive *fout, TableInfo
*** 12813,12822 ****
--- 12813,12873 ----
if (tbinfo->relkind == RELKIND_VIEW)
{
PQExpBuffer result;
+ char *co;
+ size_t co_len;
+ char *new_co = NULL;
reltypename = "VIEW";
/*
+ * If the view's reloptions include "check_option", remove it and use
+ * the SQL standard syntax instead
+ */
+ co = strstr(tbinfo->reloptions, "check_option=local");
+ if (co != NULL)
+ {
+ co_len = 18;
+ if (co == tbinfo->reloptions)
+ {
+ while (co[co_len] == ',' || co[co_len] == ' ')
+ co_len++;
+ }
+ else
+ {
+ while (co > tbinfo->reloptions &&
+ (co[-1] == ',' || co[-1] == ' '))
+ {
+ co--;
+ co_len++;
+ }
+ }
+ memmove(co, co+co_len, 1+strlen(co+co_len));
+ new_co = "LOCAL CHECK OPTION";
+ }
+
+ co = strstr(tbinfo->reloptions, "check_option=cascaded");
+ if (co != NULL)
+ {
+ co_len = 21;
+ if (co == tbinfo->reloptions)
+ {
+ while (co[co_len] == ',' || co[co_len] == ' ')
+ co_len++;
+ }
+ else
+ {
+ while (co > tbinfo->reloptions &&
+ (co[-1] == ',' || co[-1] == ' '))
+ {
+ co--;
+ co_len++;
+ }
+ }
+ memmove(co, co+co_len, 1+strlen(co+co_len));
+ new_co = "CASCADED CHECK OPTION";
+ }
+
+ /*
* DROP must be fully qualified in case same name appears in
* pg_catalog
*/
*************** dumpTableSchema(Archive *fout, TableInfo
*** 12833,12841 ****
if (tbinfo->reloptions && strlen(tbinfo->reloptions) > 0)
appendPQExpBuffer(q, " WITH (%s)", tbinfo->reloptions);
result = createViewAsClause(fout, tbinfo);
! appendPQExpBuffer(q, " AS\n%s;\n", result->data);
destroyPQExpBuffer(result);
appendPQExpBuffer(labelq, "VIEW %s",
fmtId(tbinfo->dobj.name));
}
--- 12884,12896 ----
if (tbinfo->reloptions && strlen(tbinfo->reloptions) > 0)
appendPQExpBuffer(q, " WITH (%s)", tbinfo->reloptions);
result = createViewAsClause(fout, tbinfo);
! appendPQExpBuffer(q, " AS\n%s", result->data);
destroyPQExpBuffer(result);
+ if (new_co != NULL)
+ appendPQExpBuffer(q, "\n WITH %s", new_co);
+ appendPQExpBuffer(q, ";\n");
+
appendPQExpBuffer(labelq, "VIEW %s",
fmtId(tbinfo->dobj.name));
}
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 de22dff..07e86da
*** 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
*** 783,788 ****
--- 785,803 ----
} 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
*** 2332,2337 ****
--- 2347,2359 ----
* Create View Statement
* ----------------------
*/
+ typedef enum ViewCheckOption
+ {
+ NO_CHECK_OPTION,
+ LOCAL_CHECK_OPTION,
+ CASCADED_CHECK_OPTION
+ } ViewCheckOption;
+
typedef struct ViewStmt
{
NodeTag type;
*************** typedef struct ViewStmt
*** 2340,2345 ****
--- 2362,2368 ----
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/rewrite/rewriteHandler.h b/src/include/rewrite/rewriteHandler.h
new file mode 100644
index 1831de4..e043ac5
*** a/src/include/rewrite/rewriteHandler.h
--- b/src/include/rewrite/rewriteHandler.h
*************** extern List *QueryRewrite(Query *parsetr
*** 21,26 ****
--- 21,29 ----
extern void AcquireRewriteLocks(Query *parsetree, bool forUpdatePushedDown);
extern Node *build_column_default(Relation rel, int attrno);
+ extern Query *get_view_query(Relation view);
+ extern const char *view_query_is_auto_updatable(Query *viewquery,
+ bool security_barrier);
extern int relation_is_updatable(Oid reloid, bool include_triggers);
#endif /* REWRITEHANDLER_H */
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/create_view.out b/src/test/regress/expected/create_view.out
new file mode 100644
index 11ac795..4fa7749
*** a/src/test/regress/expected/create_view.out
--- b/src/test/regress/expected/create_view.out
*************** CREATE VIEW mysecview4 WITH (security_ba
*** 252,258 ****
AS SELECT * FROM tbl1 WHERE a <> 0;
CREATE VIEW mysecview5 WITH (security_barrier=100) -- Error
AS SELECT * FROM tbl1 WHERE a > 100;
! ERROR: invalid value for boolean option "security_barrier": 100
CREATE VIEW mysecview6 WITH (invalid_option) -- Error
AS SELECT * FROM tbl1 WHERE a < 100;
ERROR: unrecognized parameter "invalid_option"
--- 252,258 ----
AS SELECT * FROM tbl1 WHERE a <> 0;
CREATE VIEW mysecview5 WITH (security_barrier=100) -- Error
AS SELECT * FROM tbl1 WHERE a > 100;
! ERROR: security_barrier requires a Boolean value
CREATE VIEW mysecview6 WITH (invalid_option) -- Error
AS SELECT * FROM tbl1 WHERE a < 100;
ERROR: unrecognized parameter "invalid_option"
diff --git a/src/test/regress/expected/updatable_views.out b/src/test/regress/expected/updatable_views.out
new file mode 100644
index 1363103..bdab973
*** a/src/test/regress/expected/updatable_views.out
--- b/src/test/regress/expected/updatable_views.out
*************** DROP TABLE base_tbl_parent, base_tbl_chi
*** 1163,1165 ****
--- 1163,1528 ----
NOTICE: drop cascades to 2 other objects
DETAIL: drop cascades to view rw_view1
drop cascades to view rw_view2
+ -- 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
+
+ SELECT * FROM information_schema.views WHERE table_name = 'rw_view1';
+ table_catalog | table_schema | table_name | view_definition | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into
+ ---------------+--------------+------------+------------------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression | public | rw_view1 | SELECT base_tbl.a, +| LOCAL | YES | YES | NO | NO | NO
+ | | | base_tbl.b +| | | | | |
+ | | | FROM base_tbl +| | | | | |
+ | | | WHERE (base_tbl.a < base_tbl.b); | | | | | |
+ (1 row)
+
+ 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
+
+ SELECT * FROM information_schema.views WHERE table_name = 'rw_view2';
+ table_catalog | table_schema | table_name | view_definition | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into
+ ---------------+--------------+------------+----------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression | public | rw_view2 | SELECT rw_view1.a +| CASCADED | YES | YES | NO | NO | NO
+ | | | FROM rw_view1 +| | | | | |
+ | | | WHERE (rw_view1.a < 10); | | | | | |
+ (1 row)
+
+ 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
+
+ SELECT * FROM information_schema.views WHERE table_name = 'rw_view2';
+ table_catalog | table_schema | table_name | view_definition | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into
+ ---------------+--------------+------------+----------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression | public | rw_view2 | SELECT rw_view1.a +| LOCAL | YES | YES | NO | NO | NO
+ | | | FROM rw_view1 +| | | | | |
+ | | | WHERE (rw_view1.a < 10); | | | | | |
+ (1 row)
+
+ 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).
+ ALTER VIEW rw_view2 RESET (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;
+
+ SELECT * FROM information_schema.views WHERE table_name = 'rw_view2';
+ table_catalog | table_schema | table_name | view_definition | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into
+ ---------------+--------------+------------+----------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression | public | rw_view2 | SELECT rw_view1.a +| NONE | YES | YES | NO | NO | NO
+ | | | FROM rw_view1 +| | | | | |
+ | | | WHERE (rw_view1.a < 10); | | | | | |
+ (1 row)
+
+ INSERT INTO rw_view2 VALUES (30); -- ok, but not in view
+ SELECT * FROM base_tbl;
+ a
+ -----
+ 5
+ -10
+ 30
+ (3 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
+ -- 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;
+ SELECT * FROM information_schema.views WHERE table_name LIKE E'rw\_view_' ORDER BY table_name;
+ table_catalog | table_schema | table_name | view_definition | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into
+ ---------------+--------------+------------+---------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
+ regression | public | rw_view1 | SELECT base_tbl.a +| CASCADED | YES | YES | NO | NO | NO
+ | | | FROM base_tbl; | | | | | |
+ regression | public | rw_view2 | SELECT rw_view1.a +| NONE | YES | YES | NO | NO | NO
+ | | | FROM rw_view1 +| | | | | |
+ | | | WHERE (rw_view1.a > 0); | | | | | |
+ regression | public | rw_view3 | SELECT rw_view2.a +| CASCADED | YES | YES | NO | NO | NO
+ | | | FROM rw_view2; | | | | | |
+ (3 rows)
+
+ 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 c8a1c62..0481800
*** a/src/test/regress/sql/updatable_views.sql
--- b/src/test/regress/sql/updatable_views.sql
*************** SELECT * FROM ONLY base_tbl_parent ORDER
*** 541,543 ****
--- 541,742 ----
SELECT * FROM base_tbl_child ORDER BY a;
DROP TABLE base_tbl_parent, base_tbl_child 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
+ SELECT * FROM information_schema.views WHERE table_name = '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
+ SELECT * FROM information_schema.views WHERE table_name = '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
+ SELECT * FROM information_schema.views WHERE table_name = '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
+
+ ALTER VIEW rw_view2 RESET (check_option);
+ \d+ rw_view2
+ SELECT * FROM information_schema.views WHERE table_name = 'rw_view2';
+ INSERT INTO rw_view2 VALUES (30); -- ok, but not in view
+ SELECT * FROM base_tbl;
+
+ 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;
+ SELECT * FROM information_schema.views WHERE table_name LIKE E'rw\_view_' ORDER BY table_name;
+
+ 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;