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 ] <replaceable cl
*** 28,33 ****
--- 28,38 ----
  ALTER VIEW [ IF EXISTS ] <replaceable class="parameter">name</replaceable> SET SCHEMA <replaceable class="parameter">new_schema</replaceable>
  ALTER VIEW [ IF EXISTS ] <replaceable class="parameter">name</replaceable> SET ( <replaceable class="parameter">view_option_name</replaceable> [= <replaceable class="parameter">view_option_value</replaceable>] [, ... ] )
  ALTER VIEW [ IF EXISTS ] <replaceable class="parameter">name</replaceable> RESET ( <replaceable class="parameter">view_option_name</replaceable> [, ... ] )
+ 
+ <phrase>where <replaceable class="parameter">view_option_name</replaceable> can be one of:</phrase>
+ 
+     security_barrier [ <replaceable class="parameter">boolean</replaceable> ]
+     check_option [ <replaceable class="parameter">text</replaceable> (<literal>local</literal> or <literal>cascaded</literal>) ]
  </synopsis>
   </refsynopsisdiv>
  
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 <replaceable class="PARAMETER">name</replaceable> [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ]
      [ WITH ( <replaceable class="PARAMETER">view_option_name</replaceable> [= <replaceable class="PARAMETER">view_option_value</replaceable>] [, ... ] ) ]
      AS <replaceable class="PARAMETER">query</replaceable>
+     [ WITH [ CASCADED | LOCAL ] CHECK OPTION ]
+ 
+ <phrase>where <replaceable class="parameter">view_option_name</replaceable> can be one of:</phrase>
+ 
+     security_barrier [ <replaceable class="parameter">boolean</replaceable> ]
+     check_option [ <replaceable class="parameter">text</replaceable> (<literal>local</literal> or <literal>cascaded</literal>) ]
  </synopsis>
   </refsynopsisdiv>
  
*************** CREATE VIEW <replaceable>name</> AS WITH
*** 120,129 ****
      <term><literal>WITH ( <replaceable class="PARAMETER">view_option_name</replaceable> [= <replaceable class="PARAMETER">view_option_value</replaceable>] [, ... ] )</literal></term>
      <listitem>
       <para>
!       This clause specifies optional parameters for a view; currently, the
!       only supported parameter name is <literal>security_barrier</literal>,
!       which should be enabled when a view is intended to provide row-level
!       security.  See <xref linkend="rules-privileges"> for full details.
       </para>
      </listitem>
     </varlistentry>
--- 126,158 ----
      <term><literal>WITH ( <replaceable class="PARAMETER">view_option_name</replaceable> [= <replaceable class="PARAMETER">view_option_value</replaceable>] [, ... ] )</literal></term>
      <listitem>
       <para>
!       This clause specifies optional parameters for a view; the following
!       parameters are supported:
! 
!       <variablelist>
!        <varlistentry>
!         <term><literal>security_barrier(boolean)</literal></term>
!         <listitem>
!          <para>
!           This should be used if the view is intended to provide row-level
!           security.  See <xref linkend="rules-privileges"> for full details.
!          </para>
!         </listitem>
!        </varlistentry>
! 
!        <varlistentry>
!         <term><literal>check_option(text)</literal></term>
!         <listitem>
!          <para>
!           This parameter may be either <literal>local</> or
!           <literal>cascaded</>, and is equivalent to specifying
!           <literal>WITH [ CASCADED | LOCAL ] CHECK OPTION</> (see below).
!           This option can be changed on existing views using <xref
!           linkend="sql-alterview">.
!          </para>
!         </listitem>
!        </varlistentry>
!       </variablelist>
       </para>
      </listitem>
     </varlistentry>
*************** CREATE VIEW <replaceable>name</> AS WITH
*** 138,143 ****
--- 167,243 ----
       </para>
      </listitem>
     </varlistentry>
+ 
+    <varlistentry>
+     <term><literal>WITH [ CASCADED | LOCAL ] CHECK OPTION</literal></term>
+     <listitem>
+      <para>
+       <indexterm zone="SQL-CREATEVIEW">
+        <primary>CHECK OPTION</primary>
+       </indexterm>
+       <indexterm zone="SQL-CREATEVIEW">
+        <primary>WITH CHECK OPTION</primary>
+       </indexterm>
+       This option controls the behavior of automatically updatable views.  When
+       this option is specified, <command>INSERT</> and <command>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 <literal>CHECK OPTION</> is not specified,
+       <command>INSERT</> and <command>UPDATE</> commands on the view are
+       allowed to create rows that are not visible through the view.  The
+       following check options are supported:
+ 
+       <variablelist>
+        <varlistentry>
+         <term><literal>LOCAL</literal></term>
+         <listitem>
+          <para>
+           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 <literal>CHECK OPTION</>).
+          </para>
+         </listitem>
+        </varlistentry>
+ 
+        <varlistentry>
+         <term><literal>CASCADED</literal></term>
+         <listitem>
+          <para>
+           New rows are checked against the conditions of the view and all
+           underlying base views.  If the <literal>CHECK OPTION</> is specified,
+           and neither <literal>LOCAL</> nor <literal>CASCADED</> is specified,
+           then <literal>CASCADED</> is assumed.
+          </para>
+         </listitem>
+        </varlistentry>
+       </variablelist>
+      </para>
+ 
+      <para>
+       The <literal>CHECK OPTION</> may not be used with <literal>RECURSIVE</>
+       views.
+      </para>
+ 
+      <para>
+       Note that the <literal>CHECK OPTION</> is only supported on views that
+       are automatically updatable, and do not have <literal>INSTEAD OF</>
+       triggers or <literal>INSTEAD</> rules.  If an automatically updatable
+       view is defined on top of a base view that has <literal>INSTEAD OF</>
+       triggers, then the <literal>LOCAL CHECK OPTION</> may be used to check
+       the conditions on the automatically updatable view, but the conditions
+       on the base view with <literal>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 <literal>INSTEAD</> rule that causes the
+       <command>INSERT</> or <command>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 <literal>INSTEAD</> rule.
+      </para>
+     </listitem>
+    </varlistentry>
    </variablelist>
   </refsect1>
  
*************** CREATE VIEW vista AS SELECT text 'Hello
*** 256,262 ****
      condition, and thus is no longer visible through the view.  Similarly,
      an <command>INSERT</> command can potentially insert base-relation rows
      that do not satisfy the <literal>WHERE</> condition and thus are not
!     visible through the view.
     </para>
  
     <para>
--- 356,364 ----
      condition, and thus is no longer visible through the view.  Similarly,
      an <command>INSERT</> command can potentially insert base-relation rows
      that do not satisfy the <literal>WHERE</> condition and thus are not
!     visible through the view.  The <literal>CHECK OPTION</> may be used to
!     prevent <command>INSERT</> and <command>UPDATE</> commands from creating
!     such rows that are not visible through the view.
     </para>
  
     <para>
*************** UNION ALL
*** 314,376 ****
    <title>Compatibility</title>
  
    <para>
-    The SQL standard specifies some additional capabilities for the
-    <command>CREATE VIEW</command> statement:
- <synopsis>
- CREATE VIEW <replaceable class="parameter">name</replaceable> [ ( <replaceable class="parameter">column_name</replaceable> [, ...] ) ]
-     AS <replaceable class="PARAMETER">query</replaceable>
-     [ WITH [ CASCADED | LOCAL ] CHECK OPTION ]
- </synopsis>
-   </para>
- 
-   <para>
-    The optional clauses for the full SQL command are:
- 
-    <variablelist>
-      <varlistentry>
-       <term><literal>CHECK OPTION</literal></term>
-       <listitem>
-        <para>
-         This option controls the behavior of automatically updatable views.
-         When given, <command>INSERT</> and <command>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 <literal>CHECK OPTION</literal>, <command>INSERT</> and
-         <command>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 <productname>PostgreSQL</>.)
-        </para>
-       </listitem>
-      </varlistentry>
- 
-      <varlistentry>
-       <term><literal>LOCAL</literal></term>
-       <listitem>
-        <para>
-         Check for integrity on this view.
-        </para>
-       </listitem>
-      </varlistentry>
- 
-      <varlistentry>
-       <term><literal>CASCADED</literal></term>
-       <listitem>
-        <para>
-         Check for integrity on this view and on any dependent
-         view. <literal>CASCADED</> is assumed if neither
-         <literal>CASCADED</> nor <literal>LOCAL</> is specified.
-        </para>
-       </listitem>
-      </varlistentry>
-    </variablelist>
-   </para>
- 
-   <para>
     <command>CREATE OR REPLACE VIEW</command> is a
     <productname>PostgreSQL</productname> language extension.
     So is the concept of a temporary view.
!    The <literal>WITH</> clause is an extension as well.
    </para>
   </refsect1>
  
--- 416,425 ----
    <title>Compatibility</title>
  
    <para>
     <command>CREATE OR REPLACE VIEW</command> is a
     <productname>PostgreSQL</productname> language extension.
     So is the concept of a temporary view.
!    The <literal>WITH ( ... )</> clause is an extension as well.
    </para>
   </refsect1>
  
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 <list>	constraints_set_list
  %type <boolean> constraints_set_mode
  %type <str>		OptTableSpace OptConsTableSpace OptTableSpaceOwner
! %type <list>	opt_check_option
  
  %type <str>		opt_provider security_label
  
--- 470,476 ----
  %type <list>	constraints_set_list
  %type <boolean> constraints_set_mode
  %type <str>		OptTableSpace OptConsTableSpace OptTableSpaceOwner
! %type <ival>	opt_check_option
  
  %type <str>		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;
