WITH CHECK OPTION for auto-updatable views
Here's a rebased version of the patch implementing WITH CHECK OPTION
for auto-updatable views.
It now includes documentation, and a clearer description of the
patch's limitations --- WITH CHECK OPTION is only supported on
auto-updatable views, not trigger-updatable or rule-updatable views. I
believe that's compatible with the following features from the SQL
standard:
F311-04 Schema definition statement CREATE VIEW: WITH CHECK OPTION
F751 View CHECK enhancements
Regards,
Dean
Attachments:
with-check-option.patchapplication/octet-stream; name=with-check-option.patchDownload
diff --git a/doc/src/sgml/ref/create_view.sgml b/doc/src/sgml/ref/create_view.sgml
new file mode 100644
index ced3115..c991057
*** a/doc/src/sgml/ref/create_view.sgml
--- b/doc/src/sgml/ref/create_view.sgml
*************** PostgreSQL documentation
*** 24,29 ****
--- 24,30 ----
CREATE [ OR REPLACE ] [ TEMP | TEMPORARY ] [ RECURSIVE ] VIEW <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 ]
</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>
--- 121,154 ----
<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 is parameter may be either <literal>local</> or
! <literal>cascaded</>, and is equivalent to specifying
! <literal>WITH [ CASCADED | LOCAL ] CHECK OPTION</>. This allows an
! existing view's check option to be modified using <xref
! linkend="sql-alterview"> rather than using <command>CREATE OR REPLACE
! VIEW</>.
! </para>
! </listitem>
! </varlistentry>
! </variablelist>
</para>
</listitem>
</varlistentry>
*************** CREATE VIEW <replaceable>name</> AS WITH
*** 138,143 ****
--- 163,239 ----
</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>
--- 352,360 ----
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>
--- 412,421 ----
<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/sql_features.txt b/src/backend/catalog/sql_features.txt
new file mode 100644
index 3a5e24e..71d2c17
*** a/src/backend/catalog/sql_features.txt
--- b/src/backend/catalog/sql_features.txt
*************** F311 Schema definition statement NO
*** 227,233 ****
F311 Schema definition statement 01 CREATE SCHEMA YES
F311 Schema definition statement 02 CREATE TABLE for persistent base tables YES
F311 Schema definition statement 03 CREATE VIEW YES
! F311 Schema definition statement 04 CREATE VIEW: WITH CHECK OPTION NO
F311 Schema definition statement 05 GRANT statement YES
F312 MERGE statement NO
F313 Enhanced MERGE statement NO
--- 227,233 ----
F311 Schema definition statement 01 CREATE SCHEMA YES
F311 Schema definition statement 02 CREATE TABLE for persistent base tables YES
F311 Schema definition statement 03 CREATE VIEW YES
! F311 Schema definition statement 04 CREATE VIEW: WITH CHECK OPTION YES
F311 Schema definition statement 05 GRANT statement YES
F312 MERGE statement NO
F313 Enhanced MERGE statement NO
*************** F711 ALTER domain YES
*** 301,307 ****
F721 Deferrable constraints NO foreign and unique keys only
F731 INSERT column privileges YES
F741 Referential MATCH types NO no partial match yet
! F751 View CHECK enhancements NO
F761 Session management YES
F762 CURRENT_CATALOG YES
F763 CURRENT_SCHEMA YES
--- 301,307 ----
F721 Deferrable constraints NO foreign and unique keys only
F731 INSERT column privileges YES
F741 Referential MATCH types NO no partial match yet
! F751 View CHECK enhancements YES
F761 Session management YES
F762 CURRENT_CATALOG YES
F763 CURRENT_SCHEMA YES
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
new file mode 100644
index 6186a84..0c4d49b
*** a/src/backend/commands/view.c
--- b/src/backend/commands/view.c
***************
*** 38,43 ****
--- 38,61 ----
static void checkViewTupleDesc(TupleDesc newdesc, TupleDesc olddesc);
/*---------------------------------------------------------------------
+ * Validator for "check_option" reloption on views. The allowed values
+ * are "local" and "cascaded".
+ */
+ void
+ validateWithCheckOption(char *value)
+ {
+ if (value == NULL ||
+ (strcmp(value, "local") != 0 &&
+ strcmp(value, "cascaded") != 0))
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for \"check_option\" option"),
+ errdetail("Valid values are \"local\", and \"cascaded\".")));
+ }
+ }
+
+ /*---------------------------------------------------------------------
* DefineVirtualRelation
*
* Create the "view" relation. `DefineRelation' does all the work,
*************** DefineView(ViewStmt *stmt, const char *q
*** 463,468 ****
--- 481,499 ----
}
/*
+ * If the user specified the WITH CHECK OPTION, add it to the list of
+ * reloptions.
+ */
+ if (stmt->withCheckOption == LOCAL_CHECK_OPTION)
+ stmt->options = lappend(stmt->options,
+ makeDefElem("check_option",
+ (Node *) makeString("local")));
+ else if (stmt->withCheckOption == CASCADED_CHECK_OPTION)
+ stmt->options = lappend(stmt->options,
+ makeDefElem("check_option",
+ (Node *) makeString("cascaded")));
+
+ /*
* Create the view relation
*
* NOTE: if it already exists and replace is false, the xact will be
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
new file mode 100644
index 9b0cd8c..6d59c7f
*** a/src/backend/executor/execMain.c
--- b/src/backend/executor/execMain.c
*************** ExecConstraints(ResultRelInfo *resultRel
*** 1604,1609 ****
--- 1604,1652 ----
}
/*
+ * ExecWithCheckOptions -- check that tuple satisfies any WITH CHECK OPTIONs
+ */
+ void
+ ExecWithCheckOptions(ResultRelInfo *resultRelInfo,
+ TupleTableSlot *slot, EState *estate)
+ {
+ ExprContext *econtext;
+ ListCell *l1, *l2;
+
+ /*
+ * We will use the EState's per-tuple context for evaluating constraint
+ * expressions (creating it if it's not already there).
+ */
+ econtext = GetPerTupleExprContext(estate);
+
+ /* Arrange for econtext's scan tuple to be the tuple under test */
+ econtext->ecxt_scantuple = slot;
+
+ /* Check each of the constraints */
+ forboth(l1, resultRelInfo->ri_WithCheckOptions,
+ l2, resultRelInfo->ri_WithCheckOptionExprs)
+ {
+ WithCheckOption *wco = (WithCheckOption *) lfirst(l1);
+ ExprState *wcoExpr = (ExprState *) lfirst(l2);
+
+ /*
+ * WITH CHECK OPTION checks are intended to ensure that the new tuple
+ * is visible in the view. If the view's qual evaluates to NULL, then
+ * the new tuple won't be included in the view. Therefore we need to
+ * tell ExecQual to return FALSE for NULL (the opposite of what we do
+ * above for CHECK constraints).
+ */
+ if (!ExecQual((List *) wcoExpr, econtext, false))
+ ereport(ERROR,
+ (errcode(ERRCODE_WITH_CHECK_OPTION_VIOLATION),
+ errmsg("new row violates WITH CHECK OPTION for view \"%s\"",
+ wco->viewname),
+ errdetail("Failing row contains %s.",
+ ExecBuildSlotValueDescription(slot, 64))));
+ }
+ }
+
+ /*
* ExecBuildSlotValueDescription -- construct a string representing a tuple
*
* This is intentionally very similar to BuildIndexValueDescription, but
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
new file mode 100644
index e934c7b..fe180ba
*** a/src/backend/executor/nodeModifyTable.c
--- b/src/backend/executor/nodeModifyTable.c
*************** ExecInsert(TupleTableSlot *slot,
*** 281,286 ****
--- 281,290 ----
list_free(recheckIndexes);
+ /* Check any WITH CHECK OPTION constraints */
+ if (resultRelInfo->ri_WithCheckOptions != NIL)
+ ExecWithCheckOptions(resultRelInfo, slot, estate);
+
/* Process RETURNING if present */
if (resultRelInfo->ri_projectReturning)
return ExecProcessReturning(resultRelInfo->ri_projectReturning,
*************** lreplace:;
*** 777,782 ****
--- 781,790 ----
list_free(recheckIndexes);
+ /* Check any WITH CHECK OPTION constraints */
+ if (resultRelInfo->ri_WithCheckOptions != NIL)
+ ExecWithCheckOptions(resultRelInfo, slot, estate);
+
/* Process RETURNING if present */
if (resultRelInfo->ri_projectReturning)
return ExecProcessReturning(resultRelInfo->ri_projectReturning,
*************** ExecInitModifyTable(ModifyTable *node, E
*** 1130,1135 ****
--- 1138,1168 ----
estate->es_result_relation_info = saved_resultRelInfo;
/*
+ * Initialize any WITH CHECK OPTION constraints if needed.
+ */
+ resultRelInfo = mtstate->resultRelInfo;
+ i = 0;
+ foreach(l, node->withCheckOptionLists)
+ {
+ List *wcoList = (List *) lfirst(l);
+ List *wcoExprs = NIL;
+ ListCell *ll;
+
+ foreach(ll, wcoList)
+ {
+ WithCheckOption *wco = (WithCheckOption *) lfirst(ll);
+ ExprState *wcoExpr = ExecInitExpr((Expr *) wco->qual,
+ mtstate->mt_plans[i]);
+ wcoExprs = lappend(wcoExprs, wcoExpr);
+ }
+
+ resultRelInfo->ri_WithCheckOptions = wcoList;
+ resultRelInfo->ri_WithCheckOptionExprs = wcoExprs;
+ resultRelInfo++;
+ i++;
+ }
+
+ /*
* Initialize RETURNING projections if needed.
*/
if (node->returningLists)
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
new file mode 100644
index b5b8d63..3c93881
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
*************** _copyModifyTable(const ModifyTable *from
*** 178,183 ****
--- 178,184 ----
COPY_NODE_FIELD(resultRelations);
COPY_SCALAR_FIELD(resultRelIndex);
COPY_NODE_FIELD(plans);
+ COPY_NODE_FIELD(withCheckOptionLists);
COPY_NODE_FIELD(returningLists);
COPY_NODE_FIELD(fdwPrivLists);
COPY_NODE_FIELD(rowMarks);
*************** _copyRangeTblEntry(const RangeTblEntry *
*** 2001,2006 ****
--- 2002,2019 ----
return newnode;
}
+ static WithCheckOption *
+ _copyWithCheckOption(const WithCheckOption *from)
+ {
+ WithCheckOption *newnode = makeNode(WithCheckOption);
+
+ COPY_STRING_FIELD(viewname);
+ COPY_NODE_FIELD(qual);
+ COPY_SCALAR_FIELD(cascaded);
+
+ return newnode;
+ }
+
static SortGroupClause *
_copySortGroupClause(const SortGroupClause *from)
{
*************** _copyQuery(const Query *from)
*** 2443,2448 ****
--- 2456,2462 ----
COPY_NODE_FIELD(rtable);
COPY_NODE_FIELD(jointree);
COPY_NODE_FIELD(targetList);
+ COPY_NODE_FIELD(withCheckOptions);
COPY_NODE_FIELD(returningList);
COPY_NODE_FIELD(groupClause);
COPY_NODE_FIELD(havingQual);
*************** _copyViewStmt(const ViewStmt *from)
*** 3072,3077 ****
--- 3086,3092 ----
COPY_NODE_FIELD(query);
COPY_SCALAR_FIELD(replace);
COPY_NODE_FIELD(options);
+ COPY_SCALAR_FIELD(withCheckOption);
return newnode;
}
*************** copyObject(const void *from)
*** 4513,4518 ****
--- 4528,4536 ----
case T_RangeTblEntry:
retval = _copyRangeTblEntry(from);
break;
+ case T_WithCheckOption:
+ retval = _copyWithCheckOption(from);
+ break;
case T_SortGroupClause:
retval = _copySortGroupClause(from);
break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
new file mode 100644
index 3f96595..345951a
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
*************** _equalQuery(const Query *a, const Query
*** 851,856 ****
--- 851,857 ----
COMPARE_NODE_FIELD(rtable);
COMPARE_NODE_FIELD(jointree);
COMPARE_NODE_FIELD(targetList);
+ COMPARE_NODE_FIELD(withCheckOptions);
COMPARE_NODE_FIELD(returningList);
COMPARE_NODE_FIELD(groupClause);
COMPARE_NODE_FIELD(havingQual);
*************** _equalViewStmt(const ViewStmt *a, const
*** 1380,1385 ****
--- 1381,1387 ----
COMPARE_NODE_FIELD(query);
COMPARE_SCALAR_FIELD(replace);
COMPARE_NODE_FIELD(options);
+ COMPARE_SCALAR_FIELD(withCheckOption);
return true;
}
*************** _equalRangeTblEntry(const RangeTblEntry
*** 2250,2255 ****
--- 2252,2267 ----
}
static bool
+ _equalWithCheckOption(const WithCheckOption *a, const WithCheckOption *b)
+ {
+ COMPARE_STRING_FIELD(viewname);
+ COMPARE_NODE_FIELD(qual);
+ COMPARE_SCALAR_FIELD(cascaded);
+
+ return true;
+ }
+
+ static bool
_equalSortGroupClause(const SortGroupClause *a, const SortGroupClause *b)
{
COMPARE_SCALAR_FIELD(tleSortGroupRef);
*************** equal(const void *a, const void *b)
*** 2983,2988 ****
--- 2995,3003 ----
case T_RangeTblEntry:
retval = _equalRangeTblEntry(a, b);
break;
+ case T_WithCheckOption:
+ retval = _equalWithCheckOption(a, b);
+ break;
case T_SortGroupClause:
retval = _equalSortGroupClause(a, b);
break;
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
new file mode 100644
index 42d6621..45e0c27
*** a/src/backend/nodes/nodeFuncs.c
--- b/src/backend/nodes/nodeFuncs.c
*************** expression_tree_walker(Node *node,
*** 1556,1561 ****
--- 1556,1563 ----
case T_SortGroupClause:
/* primitive node types with no expression subnodes */
break;
+ case T_WithCheckOption:
+ return walker(((WithCheckOption *) node)->qual, context);
case T_Aggref:
{
Aggref *expr = (Aggref *) node;
*************** query_tree_walker(Query *query,
*** 1869,1874 ****
--- 1871,1878 ----
if (walker((Node *) query->targetList, context))
return true;
+ if (walker((Node *) query->withCheckOptions, context))
+ return true;
if (walker((Node *) query->returningList, context))
return true;
if (walker((Node *) query->jointree, context))
*************** expression_tree_mutator(Node *node,
*** 2070,2075 ****
--- 2074,2088 ----
case T_RangeTblRef:
case T_SortGroupClause:
return (Node *) copyObject(node);
+ case T_WithCheckOption:
+ {
+ WithCheckOption *wco = (WithCheckOption *) node;
+ WithCheckOption *newnode;
+
+ FLATCOPY(newnode, wco, WithCheckOption);
+ MUTATE(newnode->qual, wco->qual, Node *);
+ return (Node *) newnode;
+ }
case T_Aggref:
{
Aggref *aggref = (Aggref *) node;
*************** query_tree_mutator(Query *query,
*** 2583,2588 ****
--- 2596,2602 ----
}
MUTATE(query->targetList, query->targetList, List *);
+ MUTATE(query->withCheckOptions, query->withCheckOptions, List *);
MUTATE(query->returningList, query->returningList, List *);
MUTATE(query->jointree, query->jointree, FromExpr *);
MUTATE(query->setOperations, query->setOperations, Node *);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
new file mode 100644
index b2183f4..b6ead5e
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
*************** _outModifyTable(StringInfo str, const Mo
*** 332,337 ****
--- 332,338 ----
WRITE_NODE_FIELD(resultRelations);
WRITE_INT_FIELD(resultRelIndex);
WRITE_NODE_FIELD(plans);
+ WRITE_NODE_FIELD(withCheckOptionLists);
WRITE_NODE_FIELD(returningLists);
WRITE_NODE_FIELD(fdwPrivLists);
WRITE_NODE_FIELD(rowMarks);
*************** _outQuery(StringInfo str, const Query *n
*** 2244,2249 ****
--- 2245,2251 ----
WRITE_NODE_FIELD(rtable);
WRITE_NODE_FIELD(jointree);
WRITE_NODE_FIELD(targetList);
+ WRITE_NODE_FIELD(withCheckOptions);
WRITE_NODE_FIELD(returningList);
WRITE_NODE_FIELD(groupClause);
WRITE_NODE_FIELD(havingQual);
*************** _outQuery(StringInfo str, const Query *n
*** 2258,2263 ****
--- 2260,2275 ----
}
static void
+ _outWithCheckOption(StringInfo str, const WithCheckOption *node)
+ {
+ WRITE_NODE_TYPE("WITHCHECKOPTION");
+
+ WRITE_STRING_FIELD(viewname);
+ WRITE_NODE_FIELD(qual);
+ WRITE_BOOL_FIELD(cascaded);
+ }
+
+ static void
_outSortGroupClause(StringInfo str, const SortGroupClause *node)
{
WRITE_NODE_TYPE("SORTGROUPCLAUSE");
*************** _outNode(StringInfo str, const void *obj
*** 3111,3116 ****
--- 3123,3131 ----
case T_Query:
_outQuery(str, obj);
break;
+ case T_WithCheckOption:
+ _outWithCheckOption(str, obj);
+ break;
case T_SortGroupClause:
_outSortGroupClause(str, obj);
break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
new file mode 100644
index 3a16e9d..44f0c15
*** a/src/backend/nodes/readfuncs.c
--- b/src/backend/nodes/readfuncs.c
*************** _readQuery(void)
*** 210,215 ****
--- 210,216 ----
READ_NODE_FIELD(rtable);
READ_NODE_FIELD(jointree);
READ_NODE_FIELD(targetList);
+ READ_NODE_FIELD(withCheckOptions);
READ_NODE_FIELD(returningList);
READ_NODE_FIELD(groupClause);
READ_NODE_FIELD(havingQual);
*************** _readDeclareCursorStmt(void)
*** 255,260 ****
--- 256,276 ----
}
/*
+ * _readWithCheckOption
+ */
+ static WithCheckOption *
+ _readWithCheckOption(void)
+ {
+ READ_LOCALS(WithCheckOption);
+
+ READ_STRING_FIELD(viewname);
+ READ_NODE_FIELD(qual);
+ READ_BOOL_FIELD(cascaded);
+
+ READ_DONE();
+ }
+
+ /*
* _readSortGroupClause
*/
static SortGroupClause *
*************** parseNodeString(void)
*** 1258,1263 ****
--- 1274,1281 ----
if (MATCH("QUERY", 5))
return_value = _readQuery();
+ else if (MATCH("WITHCHECKOPTION", 15))
+ return_value = _readWithCheckOption();
else if (MATCH("SORTGROUPCLAUSE", 15))
return_value = _readSortGroupClause();
else if (MATCH("WINDOWCLAUSE", 12))
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
new file mode 100644
index 52bab79..c17b460
*** a/src/backend/optimizer/plan/createplan.c
--- b/src/backend/optimizer/plan/createplan.c
*************** make_result(PlannerInfo *root,
*** 4699,4714 ****
* Build a ModifyTable plan node
*
* Currently, we don't charge anything extra for the actual table modification
! * work, nor for the RETURNING expressions if any. It would only be window
! * dressing, since these are always top-level nodes and there is no way for
! * the costs to change any higher-level planning choices. But we might want
! * to make it look better sometime.
*/
ModifyTable *
make_modifytable(PlannerInfo *root,
CmdType operation, bool canSetTag,
! List *resultRelations,
! List *subplans, List *returningLists,
List *rowMarks, int epqParam)
{
ModifyTable *node = makeNode(ModifyTable);
--- 4699,4714 ----
* Build a ModifyTable plan node
*
* Currently, we don't charge anything extra for the actual table modification
! * work, nor for the WITH CHECK OPTIONS or RETURNING expressions if any. It
! * would only be window dressing, since these are always top-level nodes and
! * there is no way for the costs to change any higher-level planning choices.
! * But we might want to make it look better sometime.
*/
ModifyTable *
make_modifytable(PlannerInfo *root,
CmdType operation, bool canSetTag,
! List *resultRelations, List *subplans,
! List *withCheckOptionLists, List *returningLists,
List *rowMarks, int epqParam)
{
ModifyTable *node = makeNode(ModifyTable);
*************** make_modifytable(PlannerInfo *root,
*** 4720,4725 ****
--- 4720,4727 ----
int i;
Assert(list_length(resultRelations) == list_length(subplans));
+ Assert(withCheckOptionLists == NIL ||
+ list_length(resultRelations) == list_length(withCheckOptionLists));
Assert(returningLists == NIL ||
list_length(resultRelations) == list_length(returningLists));
*************** make_modifytable(PlannerInfo *root,
*** 4756,4761 ****
--- 4758,4764 ----
node->resultRelations = resultRelations;
node->resultRelIndex = -1; /* will be set correctly in setrefs.c */
node->plans = subplans;
+ node->withCheckOptionLists = withCheckOptionLists;
node->returningLists = returningLists;
node->rowMarks = rowMarks;
node->epqParam = epqParam;
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
new file mode 100644
index d80c264..01e2fa3
*** a/src/backend/optimizer/plan/planner.c
--- b/src/backend/optimizer/plan/planner.c
*************** subquery_planner(PlannerGlobal *glob, Qu
*** 294,299 ****
--- 294,300 ----
int num_old_subplans = list_length(glob->subplans);
PlannerInfo *root;
Plan *plan;
+ List *newWithCheckOptions;
List *newHaving;
bool hasOuterJoins;
ListCell *l;
*************** subquery_planner(PlannerGlobal *glob, Qu
*** 421,426 ****
--- 422,439 ----
preprocess_expression(root, (Node *) parse->targetList,
EXPRKIND_TARGET);
+ newWithCheckOptions = NIL;
+ foreach(l, parse->withCheckOptions)
+ {
+ WithCheckOption *wco = (WithCheckOption *) lfirst(l);
+
+ wco->qual = preprocess_expression(root, wco->qual,
+ EXPRKIND_QUAL);
+ if (wco->qual != NULL)
+ newWithCheckOptions = lappend(newWithCheckOptions, wco);
+ }
+ parse->withCheckOptions = newWithCheckOptions;
+
parse->returningList = (List *)
preprocess_expression(root, (Node *) parse->returningList,
EXPRKIND_TARGET);
*************** subquery_planner(PlannerGlobal *glob, Qu
*** 559,570 ****
/* If it's not SELECT, we need a ModifyTable node */
if (parse->commandType != CMD_SELECT)
{
List *returningLists;
List *rowMarks;
/*
! * Set up the RETURNING list-of-lists, if needed.
*/
if (parse->returningList)
returningLists = list_make1(parse->returningList);
else
--- 572,590 ----
/* If it's not SELECT, we need a ModifyTable node */
if (parse->commandType != CMD_SELECT)
{
+ List *withCheckOptionLists;
List *returningLists;
List *rowMarks;
/*
! * Set up the WITH CHECK OPTION and RETURNING lists-of-lists, if
! * needed.
*/
+ if (parse->withCheckOptions)
+ withCheckOptionLists = list_make1(parse->withCheckOptions);
+ else
+ withCheckOptionLists = NIL;
+
if (parse->returningList)
returningLists = list_make1(parse->returningList);
else
*************** subquery_planner(PlannerGlobal *glob, Qu
*** 585,590 ****
--- 605,611 ----
parse->canSetTag,
list_make1_int(parse->resultRelation),
list_make1(plan),
+ withCheckOptionLists,
returningLists,
rowMarks,
SS_assign_special_param(root));
*************** inheritance_planner(PlannerInfo *root)
*** 770,775 ****
--- 791,797 ----
RelOptInfo **save_rel_array = NULL;
List *subplans = NIL;
List *resultRelations = NIL;
+ List *withCheckOptionLists = NIL;
List *returningLists = NIL;
List *rowMarks;
ListCell *lc;
*************** inheritance_planner(PlannerInfo *root)
*** 930,936 ****
/* Build list of target-relation RT indexes */
resultRelations = lappend_int(resultRelations, appinfo->child_relid);
! /* Build list of per-relation RETURNING targetlists */
if (parse->returningList)
returningLists = lappend(returningLists,
subroot.parse->returningList);
--- 952,961 ----
/* Build list of target-relation RT indexes */
resultRelations = lappend_int(resultRelations, appinfo->child_relid);
! /* Build lists of per-relation WCO and RETURNING targetlists */
! if (parse->withCheckOptions)
! withCheckOptionLists = lappend(withCheckOptionLists,
! subroot.parse->withCheckOptions);
if (parse->returningList)
returningLists = lappend(returningLists,
subroot.parse->returningList);
*************** inheritance_planner(PlannerInfo *root)
*** 979,984 ****
--- 1004,1010 ----
parse->canSetTag,
resultRelations,
subplans,
+ withCheckOptionLists,
returningLists,
rowMarks,
SS_assign_special_param(root));
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
new file mode 100644
index 5094226..5377bca
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
*************** static Node *makeRecursiveViewSelect(cha
*** 470,476 ****
%type <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_
*** 7976,7981 ****
--- 7976,7982 ----
n->query = $8;
n->replace = false;
n->options = $6;
+ n->withCheckOption = $9;
$$ = (Node *) n;
}
| CREATE OR REPLACE OptTemp VIEW qualified_name opt_column_list opt_reloptions
*************** ViewStmt: CREATE OptTemp VIEW qualified_
*** 7988,7993 ****
--- 7989,7995 ----
n->query = $10;
n->replace = true;
n->options = $8;
+ n->withCheckOption = $11;
$$ = (Node *) n;
}
| CREATE OptTemp RECURSIVE VIEW qualified_name '(' columnList ')' opt_reloptions
*************** ViewStmt: CREATE OptTemp VIEW qualified_
*** 8000,8005 ****
--- 8002,8008 ----
n->query = makeRecursiveViewSelect(n->view->relname, n->aliases, $11);
n->replace = false;
n->options = $9;
+ n->withCheckOption = NO_CHECK_OPTION;
$$ = (Node *) n;
}
| CREATE OR REPLACE OptTemp RECURSIVE VIEW qualified_name '(' columnList ')' opt_reloptions
*************** ViewStmt: CREATE OptTemp VIEW qualified_
*** 8012,8041 ****
n->query = makeRecursiveViewSelect(n->view->relname, n->aliases, $13);
n->replace = true;
n->options = $11;
$$ = (Node *) n;
}
;
opt_check_option:
! WITH CHECK OPTION
! {
! ereport(ERROR,
! (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! errmsg("WITH CHECK OPTION is not implemented")));
! }
! | WITH CASCADED CHECK OPTION
! {
! ereport(ERROR,
! (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! errmsg("WITH CHECK OPTION is not implemented")));
! }
! | WITH LOCAL CHECK OPTION
! {
! ereport(ERROR,
! (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! errmsg("WITH CHECK OPTION is not implemented")));
! }
! | /* EMPTY */ { $$ = NIL; }
;
/*****************************************************************************
--- 8015,8030 ----
n->query = makeRecursiveViewSelect(n->view->relname, n->aliases, $13);
n->replace = true;
n->options = $11;
+ n->withCheckOption = NO_CHECK_OPTION;
$$ = (Node *) n;
}
;
opt_check_option:
! WITH CHECK OPTION { $$ = CASCADED_CHECK_OPTION; }
! | WITH CASCADED CHECK OPTION { $$ = CASCADED_CHECK_OPTION; }
! | WITH LOCAL CHECK OPTION { $$ = LOCAL_CHECK_OPTION; }
! | /* EMPTY */ { $$ = NO_CHECK_OPTION; }
;
/*****************************************************************************
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
new file mode 100644
index 01875fc..3e5da5f
*** a/src/backend/rewrite/rewriteHandler.c
--- b/src/backend/rewrite/rewriteHandler.c
***************
*** 19,24 ****
--- 19,25 ----
#include "foreign/fdwapi.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
+ #include "optimizer/clauses.h"
#include "parser/analyze.h"
#include "parser/parse_coerce.h"
#include "parser/parsetree.h"
*************** rewriteTargetView(Query *parsetree, Rela
*** 2471,2478 ****
* only adjust their varnos to reference the new target (just the same as
* we did with the view targetlist).
*
! * For INSERT, the view's quals can be ignored for now. When we implement
! * WITH CHECK OPTION, this might be a good place to collect them.
*/
if (parsetree->commandType != CMD_INSERT &&
viewquery->jointree->quals != NULL)
--- 2472,2478 ----
* only adjust their varnos to reference the new target (just the same as
* we did with the view targetlist).
*
! * For INSERT, the view's quals can be ignored in the main query.
*/
if (parsetree->commandType != CMD_INSERT &&
viewquery->jointree->quals != NULL)
*************** rewriteTargetView(Query *parsetree, Rela
*** 2483,2488 ****
--- 2483,2567 ----
AddQual(parsetree, (Node *) viewqual);
}
+ /*
+ * For INSERT/UPDATE, if the view has the WITH CHECK OPTION, or any parent
+ * view specified WITH CASCADED CHECK OPTION, add the quals from the view
+ * to the query's withCheckOptions list.
+ */
+ if (parsetree->commandType != CMD_DELETE)
+ {
+ WithCheckOption *wco;
+
+ /**
+ * If the parent view has a cascaded check option, add any quals from
+ * this view to the parent's WithCheckOption, so that any constraint
+ * violation is reported against the parent view that defined the
+ * check.
+ *
+ * New WithCheckOptions are added to the start of the list, so if there
+ * is a cascaded check option, it will be the first item in the list.
+ */
+ wco = NULL;
+ if (parsetree->withCheckOptions != NIL)
+ {
+ WithCheckOption *parent_wco =
+ (WithCheckOption *) linitial(parsetree->withCheckOptions);
+
+ if (parent_wco->cascaded)
+ wco = parent_wco;
+ }
+
+ /*
+ * Otherwise, if the WITH CHECK OPTION is defined on this view, make a
+ * new WithCheckOption structure that the quals can be added to.
+ *
+ * New WithCheckOptions are added to the start of the list so that
+ * checks on inner views are run before checks on outer views, as
+ * required by the SQL standard.
+ *
+ * If the new check is CASCADED and we don't already have a
+ * WithCheckOption structure, we need to build one now even if this
+ * view has no quals, since there may be quals on child views.
+ */
+ if (wco == NULL &&
+ RelationHasCheckOption(view) &&
+ (viewquery->jointree->quals != NULL ||
+ RelationHasCascadedCheckOption(view)))
+ {
+ wco = makeNode(WithCheckOption);
+
+ wco->viewname = pstrdup(RelationGetRelationName(view));
+ wco->qual = NULL;
+ wco->cascaded = RelationHasCascadedCheckOption(view);
+
+ parsetree->withCheckOptions = lcons(wco,
+ parsetree->withCheckOptions);
+ }
+
+ /*
+ * Add any quals defined locally on this view to the WithCheckOption
+ * to be checked, if there is one.
+ */
+ if (wco != NULL && viewquery->jointree->quals != NULL)
+ {
+ Node *viewqual = (Node *) copyObject(viewquery->jointree->quals);
+
+ ChangeVarNodes(viewqual, base_rt_index, new_rt_index, 0);
+ wco->qual = make_and_qual(viewqual, wco->qual);
+
+ /*
+ * Make sure that the query is marked correctly if the added qual
+ * has sublinks. We can skip this check if the query is already
+ * marked, or if the command is an UPDATE, in which case the same
+ * qual will have already been added to the query's WHERE clause,
+ * and AddQual will have already done this check.
+ */
+ if (!parsetree->hasSubLinks &&
+ parsetree->commandType != CMD_UPDATE)
+ parsetree->hasSubLinks = checkExprHasSubLink(viewqual);
+ }
+ }
+
return parsetree;
}
diff --git a/src/include/commands/view.h b/src/include/commands/view.h
new file mode 100644
index 431be94..e9b4b5d
*** a/src/include/commands/view.h
--- b/src/include/commands/view.h
***************
*** 16,21 ****
--- 16,23 ----
#include "nodes/parsenodes.h"
+ extern void validateWithCheckOption(char *value);
+
extern Oid DefineView(ViewStmt *stmt, const char *queryString);
extern void StoreViewQuery(Oid viewOid, Query *viewParse, bool replace);
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
new file mode 100644
index bc215d6..75841c8
*** a/src/include/executor/executor.h
--- b/src/include/executor/executor.h
*************** extern ResultRelInfo *ExecGetTriggerResu
*** 191,196 ****
--- 191,198 ----
extern bool ExecContextForcesOids(PlanState *planstate, bool *hasoids);
extern void ExecConstraints(ResultRelInfo *resultRelInfo,
TupleTableSlot *slot, EState *estate);
+ extern void ExecWithCheckOptions(ResultRelInfo *resultRelInfo,
+ TupleTableSlot *slot, EState *estate);
extern ExecRowMark *ExecFindRowMark(EState *estate, Index rti);
extern ExecAuxRowMark *ExecBuildAuxRowMark(ExecRowMark *erm, List *targetlist);
extern TupleTableSlot *EvalPlanQual(EState *estate, EPQState *epqstate,
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
new file mode 100644
index 4f77016..a129b68
*** a/src/include/nodes/execnodes.h
--- b/src/include/nodes/execnodes.h
*************** typedef struct JunkFilter
*** 303,308 ****
--- 303,310 ----
* TrigInstrument optional runtime measurements for triggers
* FdwRoutine FDW callback functions, if foreign table
* FdwState available to save private state of FDW
+ * WithCheckOptions list of WithCheckOption's for views
+ * WithCheckOptionExprs list of WithCheckOption expr states
* ConstraintExprs array of constraint-checking expr states
* junkFilter for removing junk attributes from tuples
* projectReturning for computing a RETURNING list
*************** typedef struct ResultRelInfo
*** 322,327 ****
--- 324,331 ----
Instrumentation *ri_TrigInstrument;
struct FdwRoutine *ri_FdwRoutine;
void *ri_FdwState;
+ List *ri_WithCheckOptions;
+ List *ri_WithCheckOptionExprs;
List **ri_ConstraintExprs;
JunkFilter *ri_junkFilter;
ProjectionInfo *ri_projectReturning;
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
new file mode 100644
index 0d5c007..78368c6
*** a/src/include/nodes/nodes.h
--- b/src/include/nodes/nodes.h
*************** typedef enum NodeTag
*** 388,393 ****
--- 388,394 ----
T_Constraint,
T_DefElem,
T_RangeTblEntry,
+ T_WithCheckOption,
T_SortGroupClause,
T_WindowClause,
T_PrivGrantee,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
new file mode 100644
index 6723647..f4a18fc
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
*************** typedef struct Query
*** 128,133 ****
--- 128,135 ----
List *targetList; /* target list (of TargetEntry) */
+ List *withCheckOptions; /* a list of WithCheckOption's */
+
List *returningList; /* return-values list (of TargetEntry) */
List *groupClause; /* a list of SortGroupClause's */
*************** typedef struct RangeTblEntry
*** 778,783 ****
--- 780,798 ----
} RangeTblEntry;
/*
+ * WithCheckOption -
+ * representation of WITH CHECK OPTION checks to be applied to new tuples
+ * when inserting/updating an auto-updatable view.
+ */
+ typedef struct WithCheckOption
+ {
+ NodeTag type;
+ char *viewname; /* name of view that specified the WCO */
+ Node *qual; /* constraint qual to check */
+ bool cascaded; /* true = WITH CASCADED CHECK OPTION */
+ } WithCheckOption;
+
+ /*
* SortGroupClause -
* representation of ORDER BY, GROUP BY, PARTITION BY,
* DISTINCT, DISTINCT ON items
*************** typedef struct AlterEnumStmt
*** 2326,2331 ****
--- 2341,2353 ----
* Create View Statement
* ----------------------
*/
+ typedef enum ViewCheckOption
+ {
+ NO_CHECK_OPTION,
+ LOCAL_CHECK_OPTION,
+ CASCADED_CHECK_OPTION
+ } ViewCheckOption;
+
typedef struct ViewStmt
{
NodeTag type;
*************** typedef struct ViewStmt
*** 2334,2339 ****
--- 2356,2362 ----
Node *query; /* the SELECT query */
bool replace; /* replace an existing view? */
List *options; /* options from WITH clause */
+ ViewCheckOption withCheckOption; /* WITH CHECK OPTION */
} ViewStmt;
/* ----------------------
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
new file mode 100644
index 841701e..aa4f12c
*** a/src/include/nodes/plannodes.h
--- b/src/include/nodes/plannodes.h
*************** typedef struct ModifyTable
*** 172,177 ****
--- 172,178 ----
List *resultRelations; /* integer list of RT indexes */
int resultRelIndex; /* index of first resultRel in plan's list */
List *plans; /* plan(s) producing source data */
+ List *withCheckOptionLists; /* per-target-table WCO lists */
List *returningLists; /* per-target-table RETURNING tlists */
List *fdwPrivLists; /* per-target-table FDW private data lists */
List *rowMarks; /* PlanRowMarks (non-locking only) */
diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h
new file mode 100644
index 33eaf32..bd6841f
*** a/src/include/optimizer/planmain.h
--- b/src/include/optimizer/planmain.h
*************** extern Result *make_result(PlannerInfo *
*** 85,91 ****
Node *resconstantqual, Plan *subplan);
extern ModifyTable *make_modifytable(PlannerInfo *root,
CmdType operation, bool canSetTag,
! List *resultRelations, List *subplans, List *returningLists,
List *rowMarks, int epqParam);
extern bool is_projection_capable_plan(Plan *plan);
--- 85,92 ----
Node *resconstantqual, Plan *subplan);
extern ModifyTable *make_modifytable(PlannerInfo *root,
CmdType operation, bool canSetTag,
! List *resultRelations, List *subplans,
! List *withCheckOptionLists, List *returningLists,
List *rowMarks, int epqParam);
extern bool is_projection_capable_plan(Plan *plan);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
new file mode 100644
index 58cc3f7..589c9a8
*** a/src/include/utils/rel.h
--- b/src/include/utils/rel.h
*************** typedef struct StdRdOptions
*** 208,213 ****
--- 208,214 ----
int fillfactor; /* page fill factor in percent (0..100) */
AutoVacOpts autovacuum; /* autovacuum-related options */
bool security_barrier; /* for views */
+ int check_option_offset; /* for views */
} StdRdOptions;
#define HEAP_MIN_FILLFACTOR 10
*************** typedef struct StdRdOptions
*** 244,249 ****
--- 245,283 ----
((StdRdOptions *) (relation)->rd_options)->security_barrier : false)
/*
+ * RelationHasCheckOption
+ * Returns true if the relation is a view defined with either the local
+ * or the cascaded check option.
+ */
+ #define RelationHasCheckOption(relation) \
+ ((relation)->rd_options && \
+ ((StdRdOptions *) (relation)->rd_options)->check_option_offset != 0)
+
+ /*
+ * RelationHasLocalCheckOption
+ * Returns true if the relation is a view defined with the local check
+ * option.
+ */
+ #define RelationHasLocalCheckOption(relation) \
+ ((relation)->rd_options && \
+ ((StdRdOptions *) (relation)->rd_options)->check_option_offset != 0 ? \
+ strcmp((char *) (relation)->rd_options + \
+ ((StdRdOptions *) (relation)->rd_options)->check_option_offset, \
+ "local") == 0 : false)
+
+ /*
+ * RelationHasCascadedCheckOption
+ * Returns true if the relation is a view defined with the cascaded check
+ * option.
+ */
+ #define RelationHasCascadedCheckOption(relation) \
+ ((relation)->rd_options && \
+ ((StdRdOptions *) (relation)->rd_options)->check_option_offset != 0 ? \
+ strcmp((char *) (relation)->rd_options + \
+ ((StdRdOptions *) (relation)->rd_options)->check_option_offset, \
+ "cascaded") == 0 : false)
+
+ /*
* RelationIsValid
* True iff relation descriptor is valid.
*/
diff --git a/src/test/regress/expected/updatable_views.out b/src/test/regress/expected/updatable_views.out
new file mode 100644
index ecb61e0..c7fa390
*** a/src/test/regress/expected/updatable_views.out
--- b/src/test/regress/expected/updatable_views.out
*************** SELECT * FROM rw_view1;
*** 1063,1065 ****
--- 1063,1363 ----
DROP TABLE base_tbl CASCADE;
NOTICE: drop cascades to view rw_view1
+ -- simple WITH CHECK OPTION
+ CREATE TABLE base_tbl (a int, b int DEFAULT 10);
+ INSERT INTO base_tbl VALUES (1,2), (2,3), (1,-1);
+ CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WHERE a < b
+ WITH LOCAL CHECK OPTION;
+ \d+ rw_view1
+ View "public.rw_view1"
+ Column | Type | Modifiers | Storage | Description
+ --------+---------+-----------+---------+-------------
+ a | integer | | plain |
+ b | integer | | plain |
+ View definition:
+ SELECT base_tbl.a,
+ base_tbl.b
+ FROM base_tbl
+ WHERE base_tbl.a < base_tbl.b;
+ Options: check_option=local
+
+ INSERT INTO rw_view1 VALUES(3,4); -- ok
+ INSERT INTO rw_view1 VALUES(4,3); -- should fail
+ ERROR: new row violates WITH CHECK OPTION for view "rw_view1"
+ DETAIL: Failing row contains (4, 3).
+ INSERT INTO rw_view1 VALUES(5,null); -- should fail
+ ERROR: new row violates WITH CHECK OPTION for view "rw_view1"
+ DETAIL: Failing row contains (5, null).
+ UPDATE rw_view1 SET b = 5 WHERE a = 3; -- ok
+ UPDATE rw_view1 SET b = -5 WHERE a = 3; -- should fail
+ ERROR: new row violates WITH CHECK OPTION for view "rw_view1"
+ DETAIL: Failing row contains (3, -5).
+ INSERT INTO rw_view1(a) VALUES (9); -- ok
+ INSERT INTO rw_view1(a) VALUES (10); -- should fail
+ ERROR: new row violates WITH CHECK OPTION for view "rw_view1"
+ DETAIL: Failing row contains (10, 10).
+ SELECT * FROM base_tbl;
+ a | b
+ ---+----
+ 1 | 2
+ 2 | 3
+ 1 | -1
+ 3 | 5
+ 9 | 10
+ (5 rows)
+
+ DROP TABLE base_tbl CASCADE;
+ NOTICE: drop cascades to view rw_view1
+ -- WITH LOCAL/CASCADED CHECK OPTION
+ CREATE TABLE base_tbl (a int);
+ CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WHERE a > 0;
+ CREATE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a < 10
+ WITH CHECK OPTION; -- implicitly cascaded
+ \d+ rw_view2
+ View "public.rw_view2"
+ Column | Type | Modifiers | Storage | Description
+ --------+---------+-----------+---------+-------------
+ a | integer | | plain |
+ View definition:
+ SELECT rw_view1.a
+ FROM rw_view1
+ WHERE rw_view1.a < 10;
+ Options: check_option=cascaded
+
+ INSERT INTO rw_view2 VALUES (-5); -- should fail
+ ERROR: new row violates WITH CHECK OPTION for view "rw_view2"
+ DETAIL: Failing row contains (-5).
+ INSERT INTO rw_view2 VALUES (5); -- ok
+ INSERT INTO rw_view2 VALUES (15); -- should fail
+ ERROR: new row violates WITH CHECK OPTION for view "rw_view2"
+ DETAIL: Failing row contains (15).
+ SELECT * FROM base_tbl;
+ a
+ ---
+ 5
+ (1 row)
+
+ UPDATE rw_view2 SET a = a - 10; -- should fail
+ ERROR: new row violates WITH CHECK OPTION for view "rw_view2"
+ DETAIL: Failing row contains (-5).
+ UPDATE rw_view2 SET a = a + 10; -- should fail
+ ERROR: new row violates WITH CHECK OPTION for view "rw_view2"
+ DETAIL: Failing row contains (15).
+ CREATE OR REPLACE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a < 10
+ WITH LOCAL CHECK OPTION;
+ \d+ rw_view2
+ View "public.rw_view2"
+ Column | Type | Modifiers | Storage | Description
+ --------+---------+-----------+---------+-------------
+ a | integer | | plain |
+ View definition:
+ SELECT rw_view1.a
+ FROM rw_view1
+ WHERE rw_view1.a < 10;
+ Options: check_option=local
+
+ INSERT INTO rw_view2 VALUES (-10); -- ok, but not in view
+ INSERT INTO rw_view2 VALUES (20); -- should fail
+ ERROR: new row violates WITH CHECK OPTION for view "rw_view2"
+ DETAIL: Failing row contains (20).
+ SELECT * FROM base_tbl;
+ a
+ -----
+ 5
+ -10
+ (2 rows)
+
+ ALTER VIEW rw_view1 SET (check_option=here); -- invalid
+ ERROR: invalid value for "check_option" option
+ DETAIL: Valid values are "local", and "cascaded".
+ ALTER VIEW rw_view1 SET (check_option=local);
+ INSERT INTO rw_view2 VALUES (-20); -- should fail
+ ERROR: new row violates WITH CHECK OPTION for view "rw_view1"
+ DETAIL: Failing row contains (-20).
+ INSERT INTO rw_view2 VALUES (30); -- should fail
+ ERROR: new row violates WITH CHECK OPTION for view "rw_view2"
+ DETAIL: Failing row contains (30).
+ DROP TABLE base_tbl CASCADE;
+ NOTICE: drop cascades to 2 other objects
+ DETAIL: drop cascades to view rw_view1
+ drop cascades to view rw_view2
+ -- WITH CHECK OPTION with no local view qual
+ CREATE TABLE base_tbl (a int);
+ CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WITH CHECK OPTION;
+ CREATE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a > 0;
+ CREATE VIEW rw_view3 AS SELECT * FROM rw_view2 WITH CHECK OPTION;
+ INSERT INTO rw_view1 VALUES (-1); -- ok
+ INSERT INTO rw_view1 VALUES (1); -- ok
+ INSERT INTO rw_view2 VALUES (-2); -- ok, but not in view
+ INSERT INTO rw_view2 VALUES (2); -- ok
+ INSERT INTO rw_view3 VALUES (-3); -- should fail
+ ERROR: new row violates WITH CHECK OPTION for view "rw_view3"
+ DETAIL: Failing row contains (-3).
+ INSERT INTO rw_view3 VALUES (3); -- ok
+ DROP TABLE base_tbl CASCADE;
+ NOTICE: drop cascades to 3 other objects
+ DETAIL: drop cascades to view rw_view1
+ drop cascades to view rw_view2
+ drop cascades to view rw_view3
+ -- WITH CHECK OPTION with subquery
+ CREATE TABLE base_tbl (a int);
+ CREATE TABLE ref_tbl (a int PRIMARY KEY);
+ INSERT INTO ref_tbl SELECT * FROM generate_series(1,10);
+ CREATE VIEW rw_view1 AS
+ SELECT * FROM base_tbl b
+ WHERE EXISTS(SELECT 1 FROM ref_tbl r WHERE r.a = b.a)
+ WITH CHECK OPTION;
+ INSERT INTO rw_view1 VALUES (5); -- ok
+ INSERT INTO rw_view1 VALUES (15); -- should fail
+ ERROR: new row violates WITH CHECK OPTION for view "rw_view1"
+ DETAIL: Failing row contains (15).
+ UPDATE rw_view1 SET a = a + 5; -- ok
+ UPDATE rw_view1 SET a = a + 5; -- should fail
+ ERROR: new row violates WITH CHECK OPTION for view "rw_view1"
+ DETAIL: Failing row contains (15).
+ EXPLAIN (costs off) INSERT INTO rw_view1 VALUES (5);
+ QUERY PLAN
+ ---------------------------------------------------------------
+ Insert on base_tbl b
+ -> Result
+ SubPlan 1
+ -> Index Only Scan using ref_tbl_pkey on ref_tbl r
+ Index Cond: (a = b.a)
+ SubPlan 2
+ -> Seq Scan on ref_tbl r_1
+ (7 rows)
+
+ EXPLAIN (costs off) UPDATE rw_view1 SET a = a + 5;
+ QUERY PLAN
+ -----------------------------------------------------------------
+ Update on base_tbl b
+ -> Hash Semi Join
+ Hash Cond: (b.a = r.a)
+ -> Seq Scan on base_tbl b
+ -> Hash
+ -> Seq Scan on ref_tbl r
+ SubPlan 1
+ -> Index Only Scan using ref_tbl_pkey on ref_tbl r_1
+ Index Cond: (a = b.a)
+ SubPlan 2
+ -> Seq Scan on ref_tbl r_2
+ (11 rows)
+
+ DROP TABLE base_tbl, ref_tbl CASCADE;
+ NOTICE: drop cascades to view rw_view1
+ -- WITH CHECK OPTION with BEFORE trigger on base table
+ CREATE TABLE base_tbl (a int, b int);
+ CREATE FUNCTION base_tbl_trig_fn()
+ RETURNS trigger AS
+ $$
+ BEGIN
+ NEW.b := 10;
+ RETURN NEW;
+ END;
+ $$
+ LANGUAGE plpgsql;
+ CREATE TRIGGER base_tbl_trig BEFORE INSERT OR UPDATE ON base_tbl
+ FOR EACH ROW EXECUTE PROCEDURE base_tbl_trig_fn();
+ CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WHERE a < b WITH CHECK OPTION;
+ INSERT INTO rw_view1 VALUES (5,0); -- ok
+ INSERT INTO rw_view1 VALUES (15, 20); -- should fail
+ ERROR: new row violates WITH CHECK OPTION for view "rw_view1"
+ DETAIL: Failing row contains (15, 10).
+ UPDATE rw_view1 SET a = 20, b = 30; -- should fail
+ ERROR: new row violates WITH CHECK OPTION for view "rw_view1"
+ DETAIL: Failing row contains (20, 10).
+ DROP TABLE base_tbl CASCADE;
+ NOTICE: drop cascades to view rw_view1
+ DROP FUNCTION base_tbl_trig_fn();
+ -- WITH LOCAL CHECK OPTION with INSTEAD OF trigger on base view
+ CREATE TABLE base_tbl (a int, b int);
+ CREATE VIEW rw_view1 AS SELECT a FROM base_tbl WHERE a < b;
+ CREATE FUNCTION rw_view1_trig_fn()
+ RETURNS trigger AS
+ $$
+ BEGIN
+ IF TG_OP = 'INSERT' THEN
+ INSERT INTO base_tbl VALUES (NEW.a, 10);
+ RETURN NEW;
+ ELSIF TG_OP = 'UPDATE' THEN
+ UPDATE base_tbl SET a=NEW.a WHERE a=OLD.a;
+ RETURN NEW;
+ ELSIF TG_OP = 'DELETE' THEN
+ DELETE FROM base_tbl WHERE a=OLD.a;
+ RETURN OLD;
+ END IF;
+ END;
+ $$
+ LANGUAGE plpgsql;
+ CREATE TRIGGER rw_view1_trig
+ INSTEAD OF INSERT OR UPDATE OR DELETE ON rw_view1
+ FOR EACH ROW EXECUTE PROCEDURE rw_view1_trig_fn();
+ CREATE VIEW rw_view2 AS
+ SELECT * FROM rw_view1 WHERE a > 0 WITH LOCAL CHECK OPTION;
+ INSERT INTO rw_view2 VALUES (-5); -- should fail
+ ERROR: new row violates WITH CHECK OPTION for view "rw_view2"
+ DETAIL: Failing row contains (-5).
+ INSERT INTO rw_view2 VALUES (5); -- ok
+ INSERT INTO rw_view2 VALUES (50); -- ok, but not in view
+ UPDATE rw_view2 SET a = a - 10; -- should fail
+ ERROR: new row violates WITH CHECK OPTION for view "rw_view2"
+ DETAIL: Failing row contains (-5).
+ SELECT * FROM base_tbl;
+ a | b
+ ----+----
+ 5 | 10
+ 50 | 10
+ (2 rows)
+
+ -- Check option won't cascade down to base view with INSTEAD OF triggers
+ ALTER VIEW rw_view2 SET (check_option=cascaded);
+ INSERT INTO rw_view2 VALUES (100); -- ok, but not in view (doesn't fail rw_view1's check)
+ UPDATE rw_view2 SET a = 200 WHERE a = 5; -- ok, but not in view (doesn't fail rw_view1's check)
+ SELECT * FROM base_tbl;
+ a | b
+ -----+----
+ 50 | 10
+ 100 | 10
+ 200 | 10
+ (3 rows)
+
+ -- Neither local nor cascaded check options work with INSTEAD rules
+ DROP TRIGGER rw_view1_trig ON rw_view1;
+ CREATE RULE rw_view1_ins_rule AS ON INSERT TO rw_view1
+ DO INSTEAD INSERT INTO base_tbl VALUES (NEW.a, 10);
+ CREATE RULE rw_view1_upd_rule AS ON UPDATE TO rw_view1
+ DO INSTEAD UPDATE base_tbl SET a=NEW.a WHERE a=OLD.a;
+ INSERT INTO rw_view2 VALUES (-10); -- ok, but not in view (doesn't fail rw_view2's check)
+ INSERT INTO rw_view2 VALUES (5); -- ok
+ INSERT INTO rw_view2 VALUES (20); -- ok, but not in view (doesn't fail rw_view1's check)
+ UPDATE rw_view2 SET a = 30 WHERE a = 5; -- ok, but not in view (doesn't fail rw_view1's check)
+ INSERT INTO rw_view2 VALUES (5); -- ok
+ UPDATE rw_view2 SET a = -5 WHERE a = 5; -- ok, but not in view (doesn't fail rw_view2's check)
+ SELECT * FROM base_tbl;
+ a | b
+ -----+----
+ 50 | 10
+ 100 | 10
+ 200 | 10
+ -10 | 10
+ 20 | 10
+ 30 | 10
+ -5 | 10
+ (7 rows)
+
+ DROP TABLE base_tbl CASCADE;
+ NOTICE: drop cascades to 2 other objects
+ DETAIL: drop cascades to view rw_view1
+ drop cascades to view rw_view2
+ DROP FUNCTION rw_view1_trig_fn();
+ CREATE TABLE base_tbl (a int);
+ CREATE VIEW rw_view1 AS SELECT a,10 AS b FROM base_tbl;
+ CREATE RULE rw_view1_ins_rule AS ON INSERT TO rw_view1
+ DO INSTEAD INSERT INTO base_tbl VALUES (NEW.a);
+ CREATE VIEW rw_view2 AS
+ SELECT * FROM rw_view1 WHERE a > b WITH LOCAL CHECK OPTION;
+ INSERT INTO rw_view2 VALUES (2,3); -- ok, but not in view (doesn't fail rw_view2's check)
+ DROP TABLE base_tbl CASCADE;
+ NOTICE: drop cascades to 2 other objects
+ DETAIL: drop cascades to view rw_view1
+ drop cascades to view rw_view2
diff --git a/src/test/regress/sql/updatable_views.sql b/src/test/regress/sql/updatable_views.sql
new file mode 100644
index 49dfedd..690dcea
*** a/src/test/regress/sql/updatable_views.sql
--- b/src/test/regress/sql/updatable_views.sql
*************** UPDATE rw_view1 SET arr[1] = 42, arr[2]
*** 509,511 ****
--- 509,700 ----
SELECT * FROM rw_view1;
DROP TABLE base_tbl CASCADE;
+
+ -- simple WITH CHECK OPTION
+
+ CREATE TABLE base_tbl (a int, b int DEFAULT 10);
+ INSERT INTO base_tbl VALUES (1,2), (2,3), (1,-1);
+
+ CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WHERE a < b
+ WITH LOCAL CHECK OPTION;
+ \d+ rw_view1
+
+ INSERT INTO rw_view1 VALUES(3,4); -- ok
+ INSERT INTO rw_view1 VALUES(4,3); -- should fail
+ INSERT INTO rw_view1 VALUES(5,null); -- should fail
+ UPDATE rw_view1 SET b = 5 WHERE a = 3; -- ok
+ UPDATE rw_view1 SET b = -5 WHERE a = 3; -- should fail
+ INSERT INTO rw_view1(a) VALUES (9); -- ok
+ INSERT INTO rw_view1(a) VALUES (10); -- should fail
+ SELECT * FROM base_tbl;
+
+ DROP TABLE base_tbl CASCADE;
+
+ -- WITH LOCAL/CASCADED CHECK OPTION
+
+ CREATE TABLE base_tbl (a int);
+
+ CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WHERE a > 0;
+ CREATE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a < 10
+ WITH CHECK OPTION; -- implicitly cascaded
+ \d+ rw_view2
+
+ INSERT INTO rw_view2 VALUES (-5); -- should fail
+ INSERT INTO rw_view2 VALUES (5); -- ok
+ INSERT INTO rw_view2 VALUES (15); -- should fail
+ SELECT * FROM base_tbl;
+
+ UPDATE rw_view2 SET a = a - 10; -- should fail
+ UPDATE rw_view2 SET a = a + 10; -- should fail
+
+ CREATE OR REPLACE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a < 10
+ WITH LOCAL CHECK OPTION;
+ \d+ rw_view2
+
+ INSERT INTO rw_view2 VALUES (-10); -- ok, but not in view
+ INSERT INTO rw_view2 VALUES (20); -- should fail
+ SELECT * FROM base_tbl;
+
+ ALTER VIEW rw_view1 SET (check_option=here); -- invalid
+ ALTER VIEW rw_view1 SET (check_option=local);
+
+ INSERT INTO rw_view2 VALUES (-20); -- should fail
+ INSERT INTO rw_view2 VALUES (30); -- should fail
+
+ DROP TABLE base_tbl CASCADE;
+
+ -- WITH CHECK OPTION with no local view qual
+
+ CREATE TABLE base_tbl (a int);
+
+ CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WITH CHECK OPTION;
+ CREATE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a > 0;
+ CREATE VIEW rw_view3 AS SELECT * FROM rw_view2 WITH CHECK OPTION;
+
+ INSERT INTO rw_view1 VALUES (-1); -- ok
+ INSERT INTO rw_view1 VALUES (1); -- ok
+ INSERT INTO rw_view2 VALUES (-2); -- ok, but not in view
+ INSERT INTO rw_view2 VALUES (2); -- ok
+ INSERT INTO rw_view3 VALUES (-3); -- should fail
+ INSERT INTO rw_view3 VALUES (3); -- ok
+
+ DROP TABLE base_tbl CASCADE;
+
+ -- WITH CHECK OPTION with subquery
+
+ CREATE TABLE base_tbl (a int);
+ CREATE TABLE ref_tbl (a int PRIMARY KEY);
+ INSERT INTO ref_tbl SELECT * FROM generate_series(1,10);
+
+ CREATE VIEW rw_view1 AS
+ SELECT * FROM base_tbl b
+ WHERE EXISTS(SELECT 1 FROM ref_tbl r WHERE r.a = b.a)
+ WITH CHECK OPTION;
+
+ INSERT INTO rw_view1 VALUES (5); -- ok
+ INSERT INTO rw_view1 VALUES (15); -- should fail
+
+ UPDATE rw_view1 SET a = a + 5; -- ok
+ UPDATE rw_view1 SET a = a + 5; -- should fail
+
+ EXPLAIN (costs off) INSERT INTO rw_view1 VALUES (5);
+ EXPLAIN (costs off) UPDATE rw_view1 SET a = a + 5;
+
+ DROP TABLE base_tbl, ref_tbl CASCADE;
+
+ -- WITH CHECK OPTION with BEFORE trigger on base table
+
+ CREATE TABLE base_tbl (a int, b int);
+
+ CREATE FUNCTION base_tbl_trig_fn()
+ RETURNS trigger AS
+ $$
+ BEGIN
+ NEW.b := 10;
+ RETURN NEW;
+ END;
+ $$
+ LANGUAGE plpgsql;
+
+ CREATE TRIGGER base_tbl_trig BEFORE INSERT OR UPDATE ON base_tbl
+ FOR EACH ROW EXECUTE PROCEDURE base_tbl_trig_fn();
+
+ CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WHERE a < b WITH CHECK OPTION;
+
+ INSERT INTO rw_view1 VALUES (5,0); -- ok
+ INSERT INTO rw_view1 VALUES (15, 20); -- should fail
+ UPDATE rw_view1 SET a = 20, b = 30; -- should fail
+
+ DROP TABLE base_tbl CASCADE;
+ DROP FUNCTION base_tbl_trig_fn();
+
+ -- WITH LOCAL CHECK OPTION with INSTEAD OF trigger on base view
+
+ CREATE TABLE base_tbl (a int, b int);
+
+ CREATE VIEW rw_view1 AS SELECT a FROM base_tbl WHERE a < b;
+
+ CREATE FUNCTION rw_view1_trig_fn()
+ RETURNS trigger AS
+ $$
+ BEGIN
+ IF TG_OP = 'INSERT' THEN
+ INSERT INTO base_tbl VALUES (NEW.a, 10);
+ RETURN NEW;
+ ELSIF TG_OP = 'UPDATE' THEN
+ UPDATE base_tbl SET a=NEW.a WHERE a=OLD.a;
+ RETURN NEW;
+ ELSIF TG_OP = 'DELETE' THEN
+ DELETE FROM base_tbl WHERE a=OLD.a;
+ RETURN OLD;
+ END IF;
+ END;
+ $$
+ LANGUAGE plpgsql;
+
+ CREATE TRIGGER rw_view1_trig
+ INSTEAD OF INSERT OR UPDATE OR DELETE ON rw_view1
+ FOR EACH ROW EXECUTE PROCEDURE rw_view1_trig_fn();
+
+ CREATE VIEW rw_view2 AS
+ SELECT * FROM rw_view1 WHERE a > 0 WITH LOCAL CHECK OPTION;
+
+ INSERT INTO rw_view2 VALUES (-5); -- should fail
+ INSERT INTO rw_view2 VALUES (5); -- ok
+ INSERT INTO rw_view2 VALUES (50); -- ok, but not in view
+ UPDATE rw_view2 SET a = a - 10; -- should fail
+ SELECT * FROM base_tbl;
+
+ -- Check option won't cascade down to base view with INSTEAD OF triggers
+
+ ALTER VIEW rw_view2 SET (check_option=cascaded);
+ INSERT INTO rw_view2 VALUES (100); -- ok, but not in view (doesn't fail rw_view1's check)
+ UPDATE rw_view2 SET a = 200 WHERE a = 5; -- ok, but not in view (doesn't fail rw_view1's check)
+ SELECT * FROM base_tbl;
+
+ -- Neither local nor cascaded check options work with INSTEAD rules
+
+ DROP TRIGGER rw_view1_trig ON rw_view1;
+ CREATE RULE rw_view1_ins_rule AS ON INSERT TO rw_view1
+ DO INSTEAD INSERT INTO base_tbl VALUES (NEW.a, 10);
+ CREATE RULE rw_view1_upd_rule AS ON UPDATE TO rw_view1
+ DO INSTEAD UPDATE base_tbl SET a=NEW.a WHERE a=OLD.a;
+ INSERT INTO rw_view2 VALUES (-10); -- ok, but not in view (doesn't fail rw_view2's check)
+ INSERT INTO rw_view2 VALUES (5); -- ok
+ INSERT INTO rw_view2 VALUES (20); -- ok, but not in view (doesn't fail rw_view1's check)
+ UPDATE rw_view2 SET a = 30 WHERE a = 5; -- ok, but not in view (doesn't fail rw_view1's check)
+ INSERT INTO rw_view2 VALUES (5); -- ok
+ UPDATE rw_view2 SET a = -5 WHERE a = 5; -- ok, but not in view (doesn't fail rw_view2's check)
+ SELECT * FROM base_tbl;
+
+ DROP TABLE base_tbl CASCADE;
+ DROP FUNCTION rw_view1_trig_fn();
+
+ CREATE TABLE base_tbl (a int);
+ CREATE VIEW rw_view1 AS SELECT a,10 AS b FROM base_tbl;
+ CREATE RULE rw_view1_ins_rule AS ON INSERT TO rw_view1
+ DO INSTEAD INSERT INTO base_tbl VALUES (NEW.a);
+ CREATE VIEW rw_view2 AS
+ SELECT * FROM rw_view1 WHERE a > b WITH LOCAL CHECK OPTION;
+ INSERT INTO rw_view2 VALUES (2,3); -- ok, but not in view (doesn't fail rw_view2's check)
+ DROP TABLE base_tbl CASCADE;
On 9 June 2013 11:14, Dean Rasheed <dean.a.rasheed@gmail.com> wrote:
Here's a rebased version of the patch implementing WITH CHECK OPTION
for auto-updatable views.It now includes documentation, and a clearer description of the
patch's limitations --- WITH CHECK OPTION is only supported on
auto-updatable views, not trigger-updatable or rule-updatable views. I
believe that's compatible with the following features from the SQL
standard:F311-04 Schema definition statement CREATE VIEW: WITH CHECK OPTION
F751 View CHECK enhancements
Here's an updated version --- I missed the necessary update to the
check_option column of information_schema.views.
Regards,
Dean
Attachments:
with-check-option.patch.gzapplication/x-gzip; name=with-check-option.patch.gzDownload
����Q with-check-option.patch �<ks�F���_1�����_�-���h[�2�P�S����!�5� (�I����g<HQ�7w[VB����w�<��sV�,�����g��_,����5��f�'�6�b#�>>�����v8[z3��z��~f�3��Y|�j4:��u|��wz��Y�Y�R�k�����xA�����W&���
���\Z��6���|xl����3��G��x�~a�#6�\���#<�oo����z��L��������{��r�;���1-nN��c�i��?������k.�IMs@J���<g�t'y�
V�V?�2����������0�ro�$�|� ���NA�(qc:r����~^s����P��/��{��\ 3����W������?�����zH�������;8���\���{l�H�d|���ig�=�@�h����!�����������s��
'5��I�pU�;vBO��NV�o�=�N>��� �^�+n�s�L,j:/9@��������Z�>h��1Xx�#`��lX�^�<?��x2��3X(�`��n&S��m�����;��c���vfl�wq�3h���a�n���X������`�|����{�D���U�n9g'_@j�c��`�i�_;<��|�L���3��|�8l�C�v�jD����zJQ��vo����7gd��������[��b���=��"��
6}��
:���?b�A�#��4���X�z��M7���qc�9b!���=��>M�������������&��il�q����R�������_li���l�
�c{�x�������A3�L��1���*���AK`o�!�]<�sa �*�6Q����������I�s�$��TDcJ!*�� �$`D|v*��!0�$��w��&[�r �=�.2���s"��D|�vMoSP�s��9O�:2�V-6�-��:�
�S:d"�9I7�h��"������&4�PaN�9Na�z.?-B�\B���0���& �\X�� k�?�m��,F�#�������5��;��{�����%�����W33$��j���!D1� ���>kf��q9���(>h��w7�Gh�`����&��zK�,�<w�584P��aj��% �`���JD ���.j+lxf�%�j�� �K�/�m�oh���H��������2�r�s�P�Bt�j;@���[���i�MLY>$/@�H�z_R����@�]���
L�g���6����$a�3���y� Fb��39&��6�������P� ���`��v��6IT�H��{f��Oo� �q������& 0f
��;�,����%����B��v!z ��9A�pwJT���B-M�\���b���'e��=�������! � X���O5s�?����������*� ���m�)�z ����Uk�*eu�����(�*� ������+�^~���C<��T�
�'�-a�Q���E��<�xQm50��A��]��w��b�Y��|9q�LDD�[VM������[��eXP�����r�����X���NPw� �w���j����m%�&�����r�����2�&S�K2Y�&�n`��f�V���������8X�1XQ�c/�R�Y\�n,�B�k�*D-�pL�0r�t�J�
+A"x� *P`��2���$�`E���E�"������E �<
h���m2l6����}o�;b���� P�d)!�)J��\k���yV���+�B���+\������jp>fX`��p��D���5���^t��G�cx�D ��I����K��-l �'+
��t�<��M���������L��2
p�^jQ�Vx3I�R{���Dx�&�%��}&��N�hu�z����d{����K`+��c��$ ���b�E�`m�=%�����B��!H���T��h�^7V�Pa���{��4���J�Q�V����Xx����L��ko Nn&�`��3W&�N,*���$l���'5����� �_��������R���QM�IGk�����h��J�}��U%qzU�8_a�1���l.
��EQ���$�����B+���N&��Le6;KO���c���L��O�j4hX�a���*h������ebO,�<� i �d�/��"�e^�%�����A��/��O������zKb��U^J(��9��I��O�����R� }7Xa�fk+D�?�o
`����j-)�z��%}����G[*9OR�s�6Q���h�G?��@�>��m�y|�M�&���+<s���U���,Z�b�7)Q���z����Y5�Lr��0���,nY
[$_o�rks��� L��An=�kh�,�
E;[y��o��Q�������g�6�jt`{�0��L'�$��M��l��"��H�A����?O��~��SMM� k�e� ��{.�qd�Z��t��;/�[��z�Z�v��V�����,n��98
?z���D��Y�������bnV�zWLvK�Vw����n�K��8�0o1;5��������������5������r��2!D��� #l�b�T� �1F��^��/��������tzC��Q+`����������#�kb�A��!���X�-��_��P�����l|�6K8�3���Q��y,��RV����W0z�������Z������k.~�K�1"?�p���;�� s��v��3�`�k���A
;~�
~�
�����P6�N8�E�ta��%�6�DU����z�s����^#&�7E�y�6��f�
R�����e�Fc������������W>�9|27-�EYl��[1}��h0I���7����W�.����������z;]_��[���'b�D�MV��
l�x�#�B���:��
� ����w�K�Y~��$*s`��,�R��K�|vRi��]��7�G�7�V�;�f�7�1�� ���G&���a%�+-�1X�N�����������f���/[w�� �'���������HY��-�����������lQ=$1?���`''����D�+�0Z���
r���x��n���_��d5�OoC�\������A����Y���R_NC�������y������>y���� �I#�Rc�z�?j@m&s�Z!����p�����R�����6�V{�Y�j�v(~�v��M+��V�Q�%c�bY�+p�K��B��=��j)�tb�Q������~���ma��M5q���j@I����H��)�����*hVML�n�im}��l,��o�N���?kM1������� Es��*�;r��E�I�FZ��Y����o2��\�W������i�0���z��^�^�f�p�!��Em�k���0����^����k^s��o��>�o5
�P�DM��w|����^�Q�2�L������0Lx�:����A�`��
�#�W�N�@A� :���1�z]�=�fHq�u ������D������?�_]�N���^)�����$����gS,����d��Z"Mg���].��8��G�j�n�g��i�KA��\�y |���/��c7��df{���f�|�^9��V)��UvH�-��M�3���L(j�o����&d�Z�'��8
���T�������E aM�H1X�D��"�"�C:zH��?��m �)v@@�0�q{�J��NO�����)+�Y���4��e��)�����1,5&BI+����o����Hf��(����������}���b��M�����&0������&�����P�gIE5��\�fC��>�,�?��}(����V>��rDk��U���V2���T�xo���tF��v��sqR�u�"]��nsT���j�s�0�^�������#�(��m��A ����L�gR�v�e��G����F�8��^�*������Wv����85e�������w&p Y���U�DKMI,"[9{H�7J0�Zb"� ,� q��1WxXPJ�+)����N�/K��*�)
� ����RZI)���`�v�*���<Va�<N����H�/~,��
����]�8��m��*��<�������/��E����,��[k����-������3��v��Y\���f=�u�A�d�W����
��<�NJ#��4���{��W���m6
��������*%<�O�� �_q
M\E�'��v�e�2H��<�[���H���c`v�xhe<��b��8E��/|Y�@ ��u�-��4�
����t bN����&��Z�$���`EJ,�<�L�"6.A���h��6�2JP�X�J���3I�s�b��m<6�e�B)�A[_����Dmw%��x�����L����:%��
qg��.o�@����q�K�u^W�<eHvm1q��MR��@-j�� ������ �V�|{��q���`$D���nv�`y�s)�^f����p��$��\@��y��s�}V3"����A��j�a��������A�U��HZ\X�]���v�X����>���Q�]%P �����ty\��R��(�����&n�)H�5��?���������W}����Q�W
���l��n��+H����)�����~%����Sk�J����#�oCx &n �1"2,�''�/����1c��Ey�H�9D�,�qK��>�|(�nP����x����B�W����}���AU��N �k���|��'��-"sa��rn�)�����C��HMy����3V�N,�5,�+�$�����a����@��D��E/�_�W���{�k<�|�/�n�l����;�L�[m�7�V��qT��'� ���g���K��Z��p�r
�r�����b�q��1^��w2�9/�����"��u������|��k����WeiEs�{��3!�����7����l4�
/���GJ��g���|/����x��tQZ'�HB��#��82�|A�������,�Kz���d��;�4���f�~#�0��������M���*$��'��&*1���'�?�=�M�����X"6�W�c����w���K��U��D,�0�t�_�8��c0�x&=D��GK������(��C)�b$aqgR;Q`�������?�)t�h-�)y��n2X��a��JZ����p������NU������_���P�SE����P8��s��F[�P�D��&��&K�"�4dB�f9��
J �����#�[m����w���r����h�mU�-��ut��q�YxY_��6#��m�(]%, �t!�HL����7PC�����o~�1�|u9���
��n��A�����(=�|�6JE"*d.�����@R��3�=����.(��[������a
:�:��* �hc�&]���]�m��&45���� +����^q�J��]�E�lMxd�#s��A�W.�V������6QQ2(�JW�$���?�kh����@DW��4dXX�B������sq�������T�T`@�\���o����[� 6��-���K~�n]����t��/_���9B��������Q���}4!�"&P� |]�]�(��K7U�����1�Z�^"�
]�~�������7��[�U�q����
�CF�Ex���������W�v�M�j��h���t�G����W-�0�$��A,�M�1���2/��)��_��RJ�B��$����5�~)��0�|98>�N9Z�]�T��D=?�v�;�P������� ��Q�2�O�9&�TC�Lu�QGs ���I����h��td���#3 k@2C���,.yf$3*��d��6&D���DV)�#�uZGz�����v�5aSD�.�����,91.x�:����?Vo���
���OT4��������o�a�@��5D����F�DD�]1A�� ���"A����o����
D[��LE��M@B�GSF|�#��F�9�O{������g�W��\L�#�3�g|��"Q�v���xS�[�!9�S��G�����Cw�>DYvb�K&1xM��/t7�`��/$�f�K��.Q4�U�������9|������%��-Y�su�\�+��q����q�J0���T�T�� p����e1
���
�x����!��g�8�7D��\���5_�g��F7��=g�k���y��(y�d�����}����7����%�.Kt�'��q/�)WW��R����_�
��s)*�}�
iy
��Sr�k����k����Q����2� �J�c7�/N��O��'�R^'&���zjv�?�7��R~?R���X�����gY������:?*�+�����e��(����&�#�����g*����a��:hVKMM��O��m�#��y&��$���E�������I�yL���oBB�@���Eko����f�L.����*��j��U��x�X�O����Q$���������|�:�F7���4�y����c� ����<^�o�Y���Q��K�.y�S�8�����R*�kq4���Q�.�jU,!�?�
��J�g ��L�I?P�_^�6,H(���I���I�N��9�H�'�i=�6E:&d�"�J�1��'��AJz��b��[�
��AE��
*U�%��j���0��Z�MEt4��dFK�j�+��A�� }K�������nLx��(�h��6��Sf��� @��N�����)��j�hh���AH�K7K+b��_���/h)_��B��^�������/��\��e,!���6��B���}���,3���(���%O��,���
��{�j�.K��%&h�=���g��
.�g�nKo�<��=���kqs����ey�W3���_W��X�i=`��������")$V�N��(�����T�|j9����Y��$��bQ8�B>�0��q4����t��6
d�*m����z-���30��t��;�r���
_��3���NU3A�5|�E�T���Y���9�]��7v>y�wVe.�����*��Z�k��M�A�7�V�*>���������)=�'�U��a����&���_M������#\4;5���4k~&�rw������6�1��9�s������3���
X���k95�(\5�G�z�T��~��r�����uy�M��Z��S|��L��p0�J��J���*u���<�
z)�(� c��@�I�#B�Vp��x�+t�Y�O��_�Y���E���b�t����B�c���d��]Cw$A~��K���H�������>N?PgC�>���;@wo�*�9�C�;���]/��]��2��"w'������"/0U1:��o���o"� Q^�w�� S1�D��k^��&�������{>wL����%E/RN�7.*B�w��ke�����a���EJ#&H�����x�����'�I*��O��>*=����.jP�P�#��w���_�@~H%�Y>j�o���oq< UX��>/,���!�N.�2v������*D51xK���,�"p�D�tm������I�W�)v=(��hr�,JN6G�<�g�st�Fo�q<�Z��,!9�?��>���0��D��2�6�i+!�.�L��6�|4��B����~+u�L�5�(q�����yq�0J�;����W������L@:�9����]!P )���B�5�
������e���KGqj^�u`����$PG|���M����9��!���R��T�<�l�(����qp��e�����YR�D/� d4U$z�O���<���{��%)\�5��[���j�l�����7{r�Y�����jK�bXH�J� j�pAy�a��b��k�.��u��.����9����Iu�+m����S |�h��Jx���B��v������"����5���� ��#����&��t^��*t��'\sE�[:~����#x~�� i[��fx�k/������+�����!�3�e��.���8v�U� ���c�!�������j�q��VHZ�6�j]%|���-��.f�T�aZ�E!B������("����[�B���� ��S�HK_������-��1t1 .����1 Ni��BA�
h3N��-��T��n'�J?n�j=��I��'���2�������5�,����^��yj���<A�[X��a�|��*�/A3?��n#7��k����U���G��JLJ���Zq�=�����B��gt��)������j�>���z*�����qbz5�a�h������9�h�����3��M2f��e��;���F��2����=��3�H�a�^F|)-�h2"�����%������<�k��oJ��
b���5�Z���-D�|a$�$��t�lD���������R����g
��q`FC����,���<���69��k���]H9h��U���c�j�5k~�m|> �[0|{�6H���l1G>�E�et��~<L �&t�'W�����Q���l�>��r���~���+(�eB5^@�4���j��l7�^I�3�m|���+^��������z��\��J��^�\a��lm:��b�t�F=0�d���~�D\���o����:���o�z�3^�0��<��2/M����_2��5�E5��e��W�2@�z�Az�tI����c!�Y��Lo==� ��A���te�� G�<�� �^W ����p��.^�D����
���o(7h��>�GrG�^�rF��%*
R�~�OwPq*�M��V*�o���+��$��75���V�2�MS����g���,'�����Z#�����!t�".bi7z�������pj�:�M(�f��/�a�\�m�5V���7��x)uK�t���E�R�-<�{�S�5�����h"o~��F��h��2� <�0w��>Z4������9�6��~�N%�=���b� ���H���|��t_���������f����o)����tg��:������n�����v��:��Z��P�YB�7?��c�]���x����N^��%M����3�1�-Z?��������"h�,�S����#h�_�;,,���m��vN�6�����JaAV��M:�1U�o�wM��8#1�m��;��TR���X���k�c��`V�<�%����F��.�zG������ZLfU�Ua���
�*i*��Jd`v9���6���G4n�J�0���f{�����2��\��g��*�-��WK ��L�,��U+����@�A��RX��]i��/�9f���o�l2�S�FL;O5�bEY��A����3p
�+�1�ab}� �`����������r�W7k���c�d
I'��U>�"��7G�g���|qxW��N��H��L�@;a�k3�����Cd���[��0m#d����Y ��5�$�_�E�O�C���`�D���!] t�k(���`8�<�Z�m�����h��Z��t��^<�S �{�2�����K���U�_l�$,��e��z&h���rT��Hh��}<7
fs���Y��O�R�u/C?��*���@K�� 'RW��$�S��������)�yU�!��_��F����6_�Q:�2A{*��C<���
��.������@��W�����ge�o`����P���=�w�B�?cT$�1��LO�� ��JYN�l����]�>U��N/�I���������9!�2��z���^#J(�7^��!g&z���'����&I
#�^�`�
��0�L�bs:�������lF������R��23�H�\�!����=�#������������I�)8Q%��e����Rj .k�CO��lhN\�e��#�"Sp���e��w�o3�i��YOv��l��Y�g��z�y���L ����x��Z���p�$/j���p�Q����E
�@�u4O������Rq5�N���(���y/���Wf�3E���P.o�r�W�:`9�5�X����*!
�������~2�<����� �����ff��d������x ����[�+;�� �f_l�x�)b(�Q��7��u$muQ��LSJ����d��U�!:�����yQv���h�������V�Uv� �������;IO��6�X��@*�!���h��W��KD�h����=_�9�x� oc$����ZEAs.�R�z�Q��P�wV+q���F�w�@�(��A� �%&Z?��(E�����u���c{�W�����@��w+�f���Cm[p �/�� E�
����t���������UqQ����+��2y��O����B�5n�z'T������B�7+�R��W���fs��-15d�pZ���'�?C-^�� ,�+��Rp=:��I�s3�p����aa��� )�QfH��>_pQ �S�b{D�����(|���@�$��2W4u�|�V�Q#7i� ���Z�T�6*���XYW����j�����q�d���;]]�3x(�M��7k��
���!'���@\I�s�9�R�[A��a��Ac�'|+�U"_}�������!#�Z��<7p��o��^���A����((��y�B:f������_�N�(),�F�g�2XI��h=������m��Zo~�%7�RY��/�la0�z�K�.�����G��~��=����a�1��pIJ�����������
G�����DX����'��9�+A�U���r�Uzc�q�EI1w���������X8�4;>z�@�K1[���R����GIc���������9�H�c,���������tp3�<���[����|=�9F8��'�Z����&o�X0��OS���/j���av�S0a��E��.�!�`�ll��@p.y�����W�L�x���w��A��� ��m��l�K�Z�� ~`��'V�FN���!���H����H����>�J����d�`?��P��J�/(�}uE�M%�����<���:������2I�*2������5:�u�+B�F����T� � �j��@\)(~�s"#�2���FQ�#�SE�.��,�<{51��<'H�
�w����V�kpY���� s
Rf-D������R�������PP���dG$_��WE �+r2E�y\�=:���?�S��eX��R3Hyvb�'����T������}�^�Y(C�XE��|��z���w>�e��Sy_�m�y0r�W��n�"�0��@�����xD����Mr�p�6�{�8<�-o�t� W�#.1�c�M���44����4p�_(@u������,Ux���������m�C��4R���-�����`!�Q)�X8�:pe7�x����|-a�� k�d4R����9ju��?{���7�� ���G'���3����������e����'��"�!�2kU���UC]HW�����yz�}��3� �;�w�@�� ��B��N�\�o�����U�@��S� ta@�G���)����P����"�`:��7a���QBB�L�J��hBw������h������V�-E7D������n �]@ZkS�?
�e��*��y�1y���������9Q��r��_&_����-���c=�|��
*H�N��������\�Q�����P�t�\'�oB�z�X,�uFg��- l������4\ �����#u�2��0���{� 9�����5���`gz�N���h�Ry�q�E��!$@�����t�?��~(dbu:Gx>X������/���0(�����Kn;����Y������b?��p����{�<�WGF��c:��*lP7�����c��[$����0���k�z��^���q����!�T�m��[ 9s�r�9-�����a5���kc�Fs��\�5�'{?�OW0����3l'��,(7���S�bA���S_F��tF��&�[��P�J�A�!�v�\*��O����?E>������ryM�$�)�<!���1*���f�X�AY��3�T���M//�1�k�� z�����!.{�:x�����w|rzz|p�mwx�~ys�B��T�f���H.�`����`�2�to��l(�d�I��P`�����:���@�^d���:"�(U�*z��>$���/�q4������J&+sy %J�i�_�zWu�X����o�S{�� ����=�d�<��
�R���e���}���E��qp}S��Y=���
���'�y� }�������ZO��-�4��������\�?��J�f.-���d�r�3 \��d���v�Ts:��-�����M� +���_7\:����U�G�\��B�����^�>����s��Y��+��~��h�~�� K�v�i��f��O���Z��?�]mXT �;x�:�7�{��i������y�=kwO[�16�Mo��A�!�09$_e��=�m*�����|\
E���u|pq�����g�
~r�m'-~:8�h����{+^�����m�0�(���
@���'b������b{����ok~�:����%Y�@�!�Y�������7���P0��������H�=l�'}�����J����>*��Gm�A�� |���
*�����b�x{Y �R�1UD�M�����O��:�F tA��c:�y!�/��� dvM���y���DR$
D�I�?>����|���W�m�7u�~bS
>����#8}� �y/1��O2�c��S*��f]Y<������J�'������2���w�b��
)��1��������������b��~�d�e���gmk�����c�O��� G�0����-�)�b�i�j}>�YU(f����+���Y}H7#d%��)�&�W�
�����+jV8[���w��2���i��A��C� ��m�����#����'���c�!G���Bb��{�s�z��x|��z����k2����-T�L��� ������?�;��C&�������+pG�������W������I��B����;������P����BY�5�7���
��������n&'B�3w�|i�6r�KV�h��U����a�z"2�p?��7_�{i�8XS.� �8G��p+E�\�R
�I=�l�<�As'"m��H�g�0���"�La5,.L)�B
?�5MU�����M��}�������k��l�"�Y5_�H*���
~u�(A��W�EI�|�| �Ei��$��zz?�p�S��N�Xi��*nl'n��|�U�XG��e���L�U�X��R����X��<x�5&�m�s��:�'Xi/ :,w6-��
�V���n�c(��^�)� ����� Nj�)J�� o�Ho�H�\���S���r�Src�������������_�u�<�b�r��2�43��i,8�}�G����W�������q�>ki��U��Ka���-[g,D�����):UN1��\�#+�r+��A�{���d*\������������A��C��:p�`[6qz��kqV����X��i�v�?�(��K�Y����mW�k�v;�;��_c�R��K������2"_�&�����,-k�8���e=7.F�{��lo��#L���OQ��� ��(�Y����R^x�R^n�0W��in �Q�Y|�=go:'?t~fk�l��D���uO N�KE�l���'<�3dL��rf_���~r�=/�����b
3i>(��V��%GB����wV�N�wx:�����4�O��U������e����9=89c����W��[�(+Y�����Q�wz�S�{7��NP��������L�B�s=_�1��������
��!�r�#�����1���p����d�lX
���{���WW��,�u��Z�|��]��k�E�u�R��^���;����t4���O`�HTfhM��'��/&�kp7��s���h��W�P<��V��6���u���t_�."�ZE:�:�8;��eUT�{��"���8W�!/��������3��Y�m���{!l�L4�r��:;z.�����U���o����u��9y���IOC�� ������|qH^��_�N�-����^�M�}�:��"i�w�]L�6\�+��w=v?GP0�p4��A�C�p�5��/H�w��������e�����m����-��������(']{Z����>9f�W=��1��c�}�:$27�A��`Ef�Z��� �j�����^`���_�O�J��C�,�!��
Og��L�}v��mK-�9A��u���\�����c�v�&.�u����K��{���C��w2-T$�aU&�@X�,��=A��j)2���D�W6�~�`�DI���#X\������f%�A���ub��4��Ta�?����cP.k�L�[����N_}M�J@���$��6>�3���N�&�����d�Wo�>�D�%��$���[:5��J��T�<{��.^_��&������p�����>�X��7�0[����"��b74*��w��k�d�LR���$����w� �j���M[ >�#��h�+Vl}�yn�g�^�OEYn/���/�
���&?
���.����%�j���e<�J��K�o�Y h���w����n��q*�������T�x�#�'��ZnzU_�1����6���H1 '��z�.�����`��3�Jz�o��}���� |����������I�i��%_�7m���P�+�������E�������S��'[�����.���#�u�|q�������^��������(����wD��������jm��[���L��R{��D�������K�*\��WQ�����[{�l���C�3@.B�8�_O�3�������q��4�-7�.��:�_M�9�6������B�-�����~���=����2���nz~����O�s5��g�R����:�}�������wS�g��z����j�?n=U4�$ Dean,
* Dean Rasheed (dean.a.rasheed@gmail.com) wrote:
Here's an updated version --- I missed the necessary update to the
check_option column of information_schema.views.
Thanks! This is really looking quite good, but it's a bit late and I'm
going on vacation tomorrow, so I didn't quite want to commit it yet. :)
Instead, here are a few things that I'd like to see fixed up:
I could word-smith the docs all day, most likely, but at least the
following would be nice to have cleaned up:
- 'This is parameter may be either'
- I don't like "This allows an existing view's ...". The option can be
used on CREATE VIEW as well as ALTER VIEW. I'd say something like:
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">.
- wrt what shows up in '\h create view' and '\h alter view', I think we
should go ahead and add in with the options are, ala EXPLAIN. That
avoids having to guess at it (I was trying 'with_check_option'
initially :).
- Supposedly, this option isn't available for RECURSIVE views, but it's
happily accepted:
=*# create recursive view qq (a) with (check_option = local) as select z from q;
CREATE VIEW
(same is true of ALTER VIEW on a RECURSIVE view)
- pg_dump support is there, but it outputs the definition using the PG
syntax instead of the SQL syntax; is there any particular reason for
this..? imv, we should be dumping SQL spec where we can trivially
do so.
- Why check_option_offset instead of simply check_option..? We don't
have security_barrier_offset and it seems like we should be
consistent there.
The rest looks pretty good to me. If you can fix the above, I'll review
again and would be happy to commit it. :)
Thanks!
Stephen
On 22 June 2013 07:24, Stephen Frost <sfrost@snowman.net> wrote:
Dean,
* Dean Rasheed (dean.a.rasheed@gmail.com) wrote:
Here's an updated version --- I missed the necessary update to the
check_option column of information_schema.views.Thanks! This is really looking quite good, but it's a bit late and I'm
going on vacation tomorrow, so I didn't quite want to commit it yet. :)
Thanks for looking at this!
Instead, here are a few things that I'd like to see fixed up:
I could word-smith the docs all day, most likely, but at least the
following would be nice to have cleaned up:- 'This is parameter may be either'
Fixed.
- I don't like "This allows an existing view's ...". The option can be
used on CREATE VIEW as well as ALTER VIEW. I'd say something like: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">.
Yes, that sounds clearer. Done.
- wrt what shows up in '\h create view' and '\h alter view', I think we
should go ahead and add in with the options are, ala EXPLAIN. That
avoids having to guess at it (I was trying 'with_check_option'
initially :).
Done.
- Supposedly, this option isn't available for RECURSIVE views, but it's
happily accepted:=*# create recursive view qq (a) with (check_option = local) as select z from q;
CREATE VIEW(same is true of ALTER VIEW on a RECURSIVE view)
Recursive views are just a special case of non-auto-updatable views
--- they don't support DML without triggers or rules, so they don't
support the check option. I've added checks to CREATE/ALTER VIEW to
prevent the check_option from being added to non-auto-updatable views,
which covers the recursive view case above.
- pg_dump support is there, but it outputs the definition using the PG
syntax instead of the SQL syntax; is there any particular reason for
this..? imv, we should be dumping SQL spec where we can trivially
do so.
The code's not pretty, but done.
- Why check_option_offset instead of simply check_option..? We don't
have security_barrier_offset and it seems like we should be
consistent there.
It's because it's a string-valued option, with space allocated
separately, so it's the offset to the actual option text. This is
consistent with bufferingModeOffset in GiSTOptions.
The rest looks pretty good to me. If you can fix the above, I'll review
again and would be happy to commit it. :)Thanks!
Stephen
Thanks.
Regards,
Dean
Attachments:
with-check-option.patchapplication/octet-stream; name=with-check-option.patchDownload
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 ced3115..8dbb8cc
*** 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 8294b29..cf97c17
*** a/src/backend/commands/tablecmds.c
--- b/src/backend/commands/tablecmds.c
*************** ATExecSetRelOptions(Relation rel, List *
*** 8623,8628 ****
--- 8623,8654 ----
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 5094226..5377bca
*** 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_
*** 7976,7981 ****
--- 7976,7982 ----
n->query = $8;
n->replace = false;
n->options = $6;
+ n->withCheckOption = $9;
$$ = (Node *) n;
}
| CREATE OR REPLACE OptTemp VIEW qualified_name opt_column_list opt_reloptions
*************** ViewStmt: CREATE OptTemp VIEW qualified_
*** 7988,7993 ****
--- 7989,7995 ----
n->query = $10;
n->replace = true;
n->options = $8;
+ n->withCheckOption = $11;
$$ = (Node *) n;
}
| CREATE OptTemp RECURSIVE VIEW qualified_name '(' columnList ')' opt_reloptions
*************** ViewStmt: CREATE OptTemp VIEW qualified_
*** 8000,8005 ****
--- 8002,8008 ----
n->query = makeRecursiveViewSelect(n->view->relname, n->aliases, $11);
n->replace = false;
n->options = $9;
+ n->withCheckOption = NO_CHECK_OPTION;
$$ = (Node *) n;
}
| CREATE OR REPLACE OptTemp RECURSIVE VIEW qualified_name '(' columnList ')' opt_reloptions
*************** ViewStmt: CREATE OptTemp VIEW qualified_
*** 8012,8041 ****
n->query = makeRecursiveViewSelect(n->view->relname, n->aliases, $13);
n->replace = true;
n->options = $11;
$$ = (Node *) n;
}
;
opt_check_option:
! WITH CHECK OPTION
! {
! ereport(ERROR,
! (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! errmsg("WITH CHECK OPTION is not implemented")));
! }
! | WITH CASCADED CHECK OPTION
! {
! ereport(ERROR,
! (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! errmsg("WITH CHECK OPTION is not implemented")));
! }
! | WITH LOCAL CHECK OPTION
! {
! ereport(ERROR,
! (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! errmsg("WITH CHECK OPTION is not implemented")));
! }
! | /* EMPTY */ { $$ = NIL; }
;
/*****************************************************************************
--- 8015,8030 ----
n->query = makeRecursiveViewSelect(n->view->relname, n->aliases, $13);
n->replace = true;
n->options = $11;
+ n->withCheckOption = NO_CHECK_OPTION;
$$ = (Node *) n;
}
;
opt_check_option:
! WITH CHECK OPTION { $$ = CASCADED_CHECK_OPTION; }
! | WITH CASCADED CHECK OPTION { $$ = CASCADED_CHECK_OPTION; }
! | WITH LOCAL CHECK OPTION { $$ = LOCAL_CHECK_OPTION; }
! | /* EMPTY */ { $$ = NO_CHECK_OPTION; }
;
/*****************************************************************************
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
new file mode 100644
index a467588..656a095
*** 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
*** 2525,2532 ****
* 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)
--- 2534,2540 ----
* 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
*** 2537,2542 ****
--- 2545,2629 ----
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 ec956ad..cf268de
*** 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 6723647..f4a18fc
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
*************** typedef struct Query
*** 128,133 ****
--- 128,135 ----
List *targetList; /* target list (of TargetEntry) */
+ List *withCheckOptions; /* a list of WithCheckOption's */
+
List *returningList; /* return-values list (of TargetEntry) */
List *groupClause; /* a list of SortGroupClause's */
*************** typedef struct RangeTblEntry
*** 778,783 ****
--- 780,798 ----
} RangeTblEntry;
/*
+ * WithCheckOption -
+ * representation of WITH CHECK OPTION checks to be applied to new tuples
+ * when inserting/updating an auto-updatable view.
+ */
+ typedef struct WithCheckOption
+ {
+ NodeTag type;
+ char *viewname; /* name of view that specified the WCO */
+ Node *qual; /* constraint qual to check */
+ bool cascaded; /* true = WITH CASCADED CHECK OPTION */
+ } WithCheckOption;
+
+ /*
* SortGroupClause -
* representation of ORDER BY, GROUP BY, PARTITION BY,
* DISTINCT, DISTINCT ON items
*************** typedef struct AlterEnumStmt
*** 2326,2331 ****
--- 2341,2353 ----
* Create View Statement
* ----------------------
*/
+ typedef enum ViewCheckOption
+ {
+ NO_CHECK_OPTION,
+ LOCAL_CHECK_OPTION,
+ CASCADED_CHECK_OPTION
+ } ViewCheckOption;
+
typedef struct ViewStmt
{
NodeTag type;
*************** typedef struct ViewStmt
*** 2334,2339 ****
--- 2356,2362 ----
Node *query; /* the SELECT query */
bool replace; /* replace an existing view? */
List *options; /* options from WITH clause */
+ ViewCheckOption withCheckOption; /* WITH CHECK OPTION */
} ViewStmt;
/* ----------------------
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
new file mode 100644
index 841701e..aa4f12c
*** a/src/include/nodes/plannodes.h
--- b/src/include/nodes/plannodes.h
*************** typedef struct ModifyTable
*** 172,177 ****
--- 172,178 ----
List *resultRelations; /* integer list of RT indexes */
int resultRelIndex; /* index of first resultRel in plan's list */
List *plans; /* plan(s) producing source data */
+ List *withCheckOptionLists; /* per-target-table WCO lists */
List *returningLists; /* per-target-table RETURNING tlists */
List *fdwPrivLists; /* per-target-table FDW private data lists */
List *rowMarks; /* PlanRowMarks (non-locking only) */
diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h
new file mode 100644
index 33eaf32..bd6841f
*** a/src/include/optimizer/planmain.h
--- b/src/include/optimizer/planmain.h
*************** extern Result *make_result(PlannerInfo *
*** 85,91 ****
Node *resconstantqual, Plan *subplan);
extern ModifyTable *make_modifytable(PlannerInfo *root,
CmdType operation, bool canSetTag,
! List *resultRelations, List *subplans, List *returningLists,
List *rowMarks, int epqParam);
extern bool is_projection_capable_plan(Plan *plan);
--- 85,92 ----
Node *resconstantqual, Plan *subplan);
extern ModifyTable *make_modifytable(PlannerInfo *root,
CmdType operation, bool canSetTag,
! List *resultRelations, List *subplans,
! List *withCheckOptionLists, List *returningLists,
List *rowMarks, int epqParam);
extern bool is_projection_capable_plan(Plan *plan);
diff --git a/src/include/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 ecb61e0..71f4b04
*** a/src/test/regress/expected/updatable_views.out
--- b/src/test/regress/expected/updatable_views.out
*************** SELECT * FROM rw_view1;
*** 1063,1065 ****
--- 1063,1428 ----
DROP TABLE base_tbl CASCADE;
NOTICE: drop cascades to view rw_view1
+ -- simple WITH CHECK OPTION
+ CREATE TABLE base_tbl (a int, b int DEFAULT 10);
+ INSERT INTO base_tbl VALUES (1,2), (2,3), (1,-1);
+ CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WHERE a < b
+ WITH LOCAL CHECK OPTION;
+ \d+ rw_view1
+ View "public.rw_view1"
+ Column | Type | Modifiers | Storage | Description
+ --------+---------+-----------+---------+-------------
+ a | integer | | plain |
+ b | integer | | plain |
+ View definition:
+ SELECT base_tbl.a,
+ base_tbl.b
+ FROM base_tbl
+ WHERE base_tbl.a < base_tbl.b;
+ Options: check_option=local
+
+ 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 49dfedd..6ff1183
*** a/src/test/regress/sql/updatable_views.sql
--- b/src/test/regress/sql/updatable_views.sql
*************** UPDATE rw_view1 SET arr[1] = 42, arr[2]
*** 509,511 ****
--- 509,710 ----
SELECT * FROM rw_view1;
DROP TABLE base_tbl CASCADE;
+
+ -- simple WITH CHECK OPTION
+
+ CREATE TABLE base_tbl (a int, b int DEFAULT 10);
+ INSERT INTO base_tbl VALUES (1,2), (2,3), (1,-1);
+
+ CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WHERE a < b
+ WITH LOCAL CHECK OPTION;
+ \d+ rw_view1
+ 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;
On 24 June 2013 14:39, Dean Rasheed <dean.a.rasheed@gmail.com> wrote:
On 22 June 2013 07:24, Stephen Frost <sfrost@snowman.net> wrote:
Dean,
* Dean Rasheed (dean.a.rasheed@gmail.com) wrote:
Here's an updated version --- I missed the necessary update to the
check_option column of information_schema.views.Thanks! This is really looking quite good, but it's a bit late and I'm
going on vacation tomorrow, so I didn't quite want to commit it yet. :)Thanks for looking at this!
Instead, here are a few things that I'd like to see fixed up:
I could word-smith the docs all day, most likely, but at least the
following would be nice to have cleaned up:- 'This is parameter may be either'
Fixed.
- I don't like "This allows an existing view's ...". The option can be
used on CREATE VIEW as well as ALTER VIEW. I'd say something like: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">.Yes, that sounds clearer. Done.
- wrt what shows up in '\h create view' and '\h alter view', I think we
should go ahead and add in with the options are, ala EXPLAIN. That
avoids having to guess at it (I was trying 'with_check_option'
initially :).Done.
- Supposedly, this option isn't available for RECURSIVE views, but it's
happily accepted:=*# create recursive view qq (a) with (check_option = local) as select z from q;
CREATE VIEW(same is true of ALTER VIEW on a RECURSIVE view)
Recursive views are just a special case of non-auto-updatable views --- they don't support DML without triggers or rules, so they don't support the check option. I've added checks to CREATE/ALTER VIEW to prevent the check_option from being added to non-auto-updatable views, which covers the recursive view case above.- pg_dump support is there, but it outputs the definition using the PG
syntax instead of the SQL syntax; is there any particular reason for
this..? imv, we should be dumping SQL spec where we can trivially
do so.The code's not pretty, but done.
- Why check_option_offset instead of simply check_option..? We don't
have security_barrier_offset and it seems like we should be
consistent there.It's because it's a string-valued option, with space allocated
separately, so it's the offset to the actual option text. This is
consistent with bufferingModeOffset in GiSTOptions.The rest looks pretty good to me. If you can fix the above, I'll review
again and would be happy to commit it. :)
Here's a rebased version that applies cleanly to git master.
Regards,
Dean
Attachments:
with-check-option.patchapplication/octet-stream; name=with-check-option.patchDownload
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;
Hello
I try to check this patch
I have a problem with initdb after patching
error
initializing dependencies ... ok
creating system views ... FATAL: WITH CHECK OPTION is supported only
on auto-updatable views
STATEMENT: /*
I found missing initialization (strange, gcc doesn't raise warnings :( )
+ bool check_option;
+ bool security_barrier;
Regards
Pavel
2013/7/3 Dean Rasheed <dean.a.rasheed@gmail.com>:
Show quoted text
On 24 June 2013 14:39, Dean Rasheed <dean.a.rasheed@gmail.com> wrote:
On 22 June 2013 07:24, Stephen Frost <sfrost@snowman.net> wrote:
Dean,
* Dean Rasheed (dean.a.rasheed@gmail.com) wrote:
Here's an updated version --- I missed the necessary update to the
check_option column of information_schema.views.Thanks! This is really looking quite good, but it's a bit late and I'm
going on vacation tomorrow, so I didn't quite want to commit it yet. :)Thanks for looking at this!
Instead, here are a few things that I'd like to see fixed up:
I could word-smith the docs all day, most likely, but at least the
following would be nice to have cleaned up:- 'This is parameter may be either'
Fixed.
- I don't like "This allows an existing view's ...". The option can be
used on CREATE VIEW as well as ALTER VIEW. I'd say something like: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">.Yes, that sounds clearer. Done.
- wrt what shows up in '\h create view' and '\h alter view', I think we
should go ahead and add in with the options are, ala EXPLAIN. That
avoids having to guess at it (I was trying 'with_check_option'
initially :).Done.
- Supposedly, this option isn't available for RECURSIVE views, but it's
happily accepted:=*# create recursive view qq (a) with (check_option = local) as select z from q;
CREATE VIEW(same is true of ALTER VIEW on a RECURSIVE view)
Recursive views are just a special case of non-auto-updatable views --- they don't support DML without triggers or rules, so they don't support the check option. I've added checks to CREATE/ALTER VIEW to prevent the check_option from being added to non-auto-updatable views, which covers the recursive view case above.- pg_dump support is there, but it outputs the definition using the PG
syntax instead of the SQL syntax; is there any particular reason for
this..? imv, we should be dumping SQL spec where we can trivially
do so.The code's not pretty, but done.
- Why check_option_offset instead of simply check_option..? We don't
have security_barrier_offset and it seems like we should be
consistent there.It's because it's a string-valued option, with space allocated
separately, so it's the offset to the actual option text. This is
consistent with bufferingModeOffset in GiSTOptions.The rest looks pretty good to me. If you can fix the above, I'll review
again and would be happy to commit it. :)Here's a rebased version that applies cleanly to git master.
Regards,
Dean--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Attachments:
with-check-option-fixed.patchapplication/octet-stream; name=with-check-option-fixed.patchDownload
*** a/doc/src/sgml/ref/alter_view.sgml
--- b/doc/src/sgml/ref/alter_view.sgml
***************
*** 28,33 **** ALTER VIEW [ IF EXISTS ] <replaceable class="parameter">name</replaceable> RENAM
--- 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>
*** a/doc/src/sgml/ref/create_view.sgml
--- b/doc/src/sgml/ref/create_view.sgml
***************
*** 24,29 **** PostgreSQL documentation
--- 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>
***************
*** 120,129 **** CREATE VIEW <replaceable>name</> AS WITH RECURSIVE <replaceable>name</> (<replac
<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>
***************
*** 138,143 **** CREATE VIEW <replaceable>name</> AS WITH RECURSIVE <replaceable>name</> (<replac
--- 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>
***************
*** 256,262 **** CREATE VIEW vista AS SELECT text 'Hello World' AS hello;
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>
***************
*** 314,376 **** UNION ALL
<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>
*** 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"
***************
*** 248,253 **** static relopt_string stringRelOpts[] =
--- 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}}
};
***************
*** 1152,1157 **** default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
--- 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);
*** a/src/backend/catalog/information_schema.sql
--- b/src/backend/catalog/information_schema.sql
***************
*** 2494,2500 **** CREATE VIEW views AS
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)
*** a/src/backend/catalog/sql_features.txt
--- b/src/backend/catalog/sql_features.txt
***************
*** 227,233 **** F311 Schema definition statement NO
F311 Schema definition statement 01 CREATE SCHEMA YES
F311 Schema definition statement 02 CREATE TABLE for persistent base tables YES
F311 Schema definition statement 03 CREATE VIEW YES
! F311 Schema definition statement 04 CREATE VIEW: WITH CHECK OPTION NO
F311 Schema definition statement 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
***************
*** 301,307 **** F711 ALTER domain YES
F721 Deferrable constraints NO foreign and unique keys only
F731 INSERT column privileges YES
F741 Referential MATCH types NO no partial match yet
! F751 View CHECK enhancements NO
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
*** a/src/backend/commands/tablecmds.c
--- b/src/backend/commands/tablecmds.c
***************
*** 8773,8778 **** ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
--- 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 = false;
+ bool security_barrier = false;
+
+ foreach(cell, view_options)
+ {
+ DefElem *defel = (DefElem *) lfirst(cell);
+
+ if (pg_strcasecmp(defel->defname, "check_option") == 0)
+ check_option = true;
+ if (pg_strcasecmp(defel->defname, "security_barrier") == 0)
+ security_barrier = defGetBoolean(defel);
+ }
+
+ if (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.
*** 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,
***************
*** 374,379 **** DefineView(ViewStmt *stmt, const char *queryString)
--- 393,401 ----
Query *viewParse;
Oid viewOid;
RangeVar *view;
+ ListCell *cell;
+ bool check_option = false;
+ bool security_barrier = false;
/*
* Run parse analysis to convert the raw parse tree to a Query. Note this
***************
*** 411,416 **** DefineView(ViewStmt *stmt, const char *queryString)
--- 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
*/
*** a/src/backend/executor/execMain.c
--- b/src/backend/executor/execMain.c
***************
*** 1622,1627 **** ExecConstraints(ResultRelInfo *resultRelInfo,
--- 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
*** a/src/backend/executor/nodeModifyTable.c
--- b/src/backend/executor/nodeModifyTable.c
***************
*** 281,286 **** ExecInsert(TupleTableSlot *slot,
--- 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,
***************
*** 777,782 **** lreplace:;
--- 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,
***************
*** 1130,1135 **** ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
--- 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)
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
***************
*** 178,183 **** _copyModifyTable(const ModifyTable *from)
--- 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);
***************
*** 2001,2006 **** _copyRangeTblEntry(const RangeTblEntry *from)
--- 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)
{
***************
*** 2443,2448 **** _copyQuery(const Query *from)
--- 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);
***************
*** 3072,3077 **** _copyViewStmt(const ViewStmt *from)
--- 3086,3092 ----
COPY_NODE_FIELD(query);
COPY_SCALAR_FIELD(replace);
COPY_NODE_FIELD(options);
+ COPY_SCALAR_FIELD(withCheckOption);
return newnode;
}
***************
*** 4513,4518 **** copyObject(const void *from)
--- 4528,4536 ----
case T_RangeTblEntry:
retval = _copyRangeTblEntry(from);
break;
+ case T_WithCheckOption:
+ retval = _copyWithCheckOption(from);
+ break;
case T_SortGroupClause:
retval = _copySortGroupClause(from);
break;
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
***************
*** 851,856 **** _equalQuery(const Query *a, const Query *b)
--- 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);
***************
*** 1380,1385 **** _equalViewStmt(const ViewStmt *a, const ViewStmt *b)
--- 1381,1387 ----
COMPARE_NODE_FIELD(query);
COMPARE_SCALAR_FIELD(replace);
COMPARE_NODE_FIELD(options);
+ COMPARE_SCALAR_FIELD(withCheckOption);
return true;
}
***************
*** 2250,2255 **** _equalRangeTblEntry(const RangeTblEntry *a, const RangeTblEntry *b)
--- 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);
***************
*** 2983,2988 **** equal(const void *a, const void *b)
--- 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;
*** a/src/backend/nodes/nodeFuncs.c
--- b/src/backend/nodes/nodeFuncs.c
***************
*** 1556,1561 **** expression_tree_walker(Node *node,
--- 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;
***************
*** 1869,1874 **** query_tree_walker(Query *query,
--- 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))
***************
*** 2070,2075 **** expression_tree_mutator(Node *node,
--- 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;
***************
*** 2583,2588 **** query_tree_mutator(Query *query,
--- 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 *);
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
***************
*** 332,337 **** _outModifyTable(StringInfo str, const ModifyTable *node)
--- 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);
***************
*** 2244,2249 **** _outQuery(StringInfo str, const Query *node)
--- 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);
***************
*** 2258,2263 **** _outQuery(StringInfo str, const Query *node)
--- 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");
***************
*** 3111,3116 **** _outNode(StringInfo str, const void *obj)
--- 3123,3131 ----
case T_Query:
_outQuery(str, obj);
break;
+ case T_WithCheckOption:
+ _outWithCheckOption(str, obj);
+ break;
case T_SortGroupClause:
_outSortGroupClause(str, obj);
break;
*** a/src/backend/nodes/readfuncs.c
--- b/src/backend/nodes/readfuncs.c
***************
*** 210,215 **** _readQuery(void)
--- 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);
***************
*** 255,260 **** _readDeclareCursorStmt(void)
--- 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 *
***************
*** 1258,1263 **** parseNodeString(void)
--- 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))
*** a/src/backend/optimizer/plan/createplan.c
--- b/src/backend/optimizer/plan/createplan.c
***************
*** 4699,4714 **** make_result(PlannerInfo *root,
* Build a ModifyTable plan node
*
* Currently, we don't charge anything extra for the actual table modification
! * work, nor for the RETURNING expressions if any. It would only be window
! * dressing, since these are always top-level nodes and there is no way for
! * the costs to change any higher-level planning choices. But we might want
! * to make it look better sometime.
*/
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);
***************
*** 4720,4725 **** make_modifytable(PlannerInfo *root,
--- 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));
***************
*** 4756,4761 **** make_modifytable(PlannerInfo *root,
--- 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;
*** a/src/backend/optimizer/plan/planner.c
--- b/src/backend/optimizer/plan/planner.c
***************
*** 294,299 **** subquery_planner(PlannerGlobal *glob, Query *parse,
--- 294,300 ----
int num_old_subplans = list_length(glob->subplans);
PlannerInfo *root;
Plan *plan;
+ List *newWithCheckOptions;
List *newHaving;
bool hasOuterJoins;
ListCell *l;
***************
*** 421,426 **** subquery_planner(PlannerGlobal *glob, Query *parse,
--- 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);
***************
*** 559,570 **** subquery_planner(PlannerGlobal *glob, Query *parse,
/* 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
***************
*** 585,590 **** subquery_planner(PlannerGlobal *glob, Query *parse,
--- 605,611 ----
parse->canSetTag,
list_make1_int(parse->resultRelation),
list_make1(plan),
+ withCheckOptionLists,
returningLists,
rowMarks,
SS_assign_special_param(root));
***************
*** 770,775 **** inheritance_planner(PlannerInfo *root)
--- 791,797 ----
RelOptInfo **save_rel_array = NULL;
List *subplans = NIL;
List *resultRelations = NIL;
+ List *withCheckOptionLists = NIL;
List *returningLists = NIL;
List *rowMarks;
ListCell *lc;
***************
*** 930,936 **** inheritance_planner(PlannerInfo *root)
/* Build list of target-relation RT indexes */
resultRelations = lappend_int(resultRelations, appinfo->child_relid);
! /* Build list of per-relation RETURNING targetlists */
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);
***************
*** 979,984 **** inheritance_planner(PlannerInfo *root)
--- 1004,1010 ----
parse->canSetTag,
resultRelations,
subplans,
+ withCheckOptionLists,
returningLists,
rowMarks,
SS_assign_special_param(root));
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 470,476 **** static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <list> constraints_set_list
%type <boolean> constraints_set_mode
%type <str> OptTableSpace OptConsTableSpace OptTableSpaceOwner
! %type <list> opt_check_option
%type <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
***************
*** 7993,7998 **** ViewStmt: CREATE OptTemp VIEW qualified_name opt_column_list opt_reloptions
--- 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
***************
*** 8005,8010 **** ViewStmt: CREATE OptTemp VIEW qualified_name opt_column_list opt_reloptions
--- 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
***************
*** 8017,8022 **** ViewStmt: CREATE OptTemp VIEW qualified_name opt_column_list opt_reloptions
--- 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
***************
*** 8029,8058 **** ViewStmt: CREATE OptTemp VIEW qualified_name opt_column_list opt_reloptions
n->query = makeRecursiveViewSelect(n->view->relname, n->aliases, $13);
n->replace = true;
n->options = $11;
$$ = (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; }
;
/*****************************************************************************
*** 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"
***************
*** 1866,1872 **** fireRules(Query *parsetree,
* 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;
***************
*** 1940,1949 **** view_has_instead_trigger(Relation view, CmdType event)
* Also note that we don't check for INSTEAD triggers or rules here; those
* also prevent auto-update, but they must be checked for by the caller.
*/
! static const char *
! 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;
***************
*** 1997,2003 **** view_is_auto_updatable(Relation view)
* 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.");
/*
***************
*** 2057,2062 **** view_is_auto_updatable(Relation view)
--- 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
***************
*** 2532,2539 **** rewriteTargetView(Query *parsetree, Relation view)
* only adjust their varnos to reference the new target (just the same as
* we did with the view targetlist).
*
! * For INSERT, the view's quals can be ignored for now. When we implement
! * WITH CHECK OPTION, this might be a good place to collect them.
*/
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)
***************
*** 2544,2549 **** rewriteTargetView(Query *parsetree, Relation view)
--- 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;
}
*** a/src/bin/pg_dump/pg_dump.c
--- b/src/bin/pg_dump/pg_dump.c
***************
*** 12813,12822 **** dumpTableSchema(Archive *fout, TableInfo *tbinfo)
--- 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
*/
***************
*** 12833,12841 **** dumpTableSchema(Archive *fout, TableInfo *tbinfo)
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));
}
*** 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);
*** a/src/include/executor/executor.h
--- b/src/include/executor/executor.h
***************
*** 191,196 **** extern ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid);
--- 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,
*** a/src/include/nodes/execnodes.h
--- b/src/include/nodes/execnodes.h
***************
*** 303,308 **** typedef struct JunkFilter
--- 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
***************
*** 322,327 **** typedef struct ResultRelInfo
--- 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;
*** a/src/include/nodes/nodes.h
--- b/src/include/nodes/nodes.h
***************
*** 388,393 **** typedef enum NodeTag
--- 388,394 ----
T_Constraint,
T_DefElem,
T_RangeTblEntry,
+ T_WithCheckOption,
T_SortGroupClause,
T_WindowClause,
T_PrivGrantee,
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
***************
*** 128,133 **** typedef struct Query
--- 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 */
***************
*** 783,788 **** typedef struct RangeTblEntry
--- 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
***************
*** 2332,2337 **** typedef struct AlterEnumStmt
--- 2347,2359 ----
* Create View Statement
* ----------------------
*/
+ typedef enum ViewCheckOption
+ {
+ NO_CHECK_OPTION,
+ LOCAL_CHECK_OPTION,
+ CASCADED_CHECK_OPTION
+ } ViewCheckOption;
+
typedef struct ViewStmt
{
NodeTag type;
***************
*** 2340,2345 **** typedef struct ViewStmt
--- 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;
/* ----------------------
*** a/src/include/nodes/plannodes.h
--- b/src/include/nodes/plannodes.h
***************
*** 172,177 **** typedef struct ModifyTable
--- 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) */
*** a/src/include/optimizer/planmain.h
--- b/src/include/optimizer/planmain.h
***************
*** 85,91 **** extern Result *make_result(PlannerInfo *root, List *tlist,
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);
*** a/src/include/rewrite/rewriteHandler.h
--- b/src/include/rewrite/rewriteHandler.h
***************
*** 21,26 **** extern List *QueryRewrite(Query *parsetree);
--- 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 */
*** a/src/include/utils/rel.h
--- b/src/include/utils/rel.h
***************
*** 208,213 **** typedef struct StdRdOptions
--- 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
***************
*** 244,249 **** typedef struct StdRdOptions
--- 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.
*/
*** a/src/test/regress/expected/create_view.out
--- b/src/test/regress/expected/create_view.out
***************
*** 252,258 **** CREATE VIEW mysecview4 WITH (security_barrier)
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"
*** a/src/test/regress/expected/updatable_views.out
--- b/src/test/regress/expected/updatable_views.out
***************
*** 1163,1165 **** DROP TABLE base_tbl_parent, base_tbl_child CASCADE;
--- 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
*** a/src/test/regress/sql/updatable_views.sql
--- b/src/test/regress/sql/updatable_views.sql
***************
*** 541,543 **** SELECT * FROM ONLY base_tbl_parent ORDER BY a;
--- 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;
Hello
just some notes:
* autocomplete for INSERT, UPDATE, DELETE should to show updatable views too
* can you explain better in doc differences between WITH CASCADED or
WITH LOCAL OPTION - assign some simple example to doc, please
* is possible to better identify (describe) failed constraints?
postgres=# create view v1 as select * from bubu where a > 0;
CREATE VIEW
postgres=# create view v2 as select * from v1 where a < 10 with check option;
CREATE VIEW
postgres=# insert into v1 values(-10);
INSERT 0 1
postgres=# insert into v2 values(-10);
ERROR: new row violates WITH CHECK OPTION for view "v2" --- but this
constraint is related to v1
DETAIL: Failing row contains (-10).
* I found a difference against MySQL - LOCAL option ignore all other constraints
postgres=# CREATE TABLE t1 (a INT);
CREATE TABLE
postgres=# CREATE VIEW v1 AS SELECT * FROM t1 WHERE a < 2 WITH CHECK OPTION;
CREATE VIEW
postgres=# CREATE VIEW v2 AS SELECT * FROM v1 WHERE a > 0 WITH LOCAL
CHECK OPTION;
CREATE VIEW
postgres=# INSERT INTO v2 VALUES (2);
ERROR: new row violates WITH CHECK OPTION for view "v1" -- it will be
ok on MySQL
DETAIL: Failing row contains (2).
Probably MySQL is wrong (due differet behave than in DB2) -- but who
know http://bugs.mysql.com/bug.php?id=6404
What is a correct behave?
Regards
Pavel
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 5 July 2013 07:02, Pavel Stehule <pavel.stehule@gmail.com> wrote:
Hello
I try to check this patch
I have a problem with initdb after patching
error
initializing dependencies ... ok
creating system views ... FATAL: WITH CHECK OPTION is supported only
on auto-updatable views
STATEMENT: /*I found missing initialization (strange, gcc doesn't raise warnings :( )
+ bool check_option; + bool security_barrier;
Ah, good catch. I was being careless there.
It turns out that although I compile with -Wall which implies
-Wuninitialized and -Wmaybe-uninitialized, those warnings are only
supported in optimised builds, which is why I didn't see it. So I've
learned something new today: always do an optimised build as well
during development.
Will fix. Thanks.
Regards,
Dean
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 5 July 2013 08:22, Pavel Stehule <pavel.stehule@gmail.com> wrote:
Hello
just some notes:
* autocomplete for INSERT, UPDATE, DELETE should to show updatable views too
I think that is the subject for a separate patch. It was discussed
previously and Tom suggested that tab-completion should just complete
with all views regardless of whether they are updatable or not,
because the cost of calling pg_relation_is_updatable() for all views
in a database would be too high, because it would require opening them
all to do the check.
* can you explain better in doc differences between WITH CASCADED or
WITH LOCAL OPTION - assign some simple example to doc, please
OK, I've added another couple of examples to illustrate the difference.
* is possible to better identify (describe) failed constraints?
postgres=# create view v1 as select * from bubu where a > 0;
CREATE VIEW
postgres=# create view v2 as select * from v1 where a < 10 with check option;
CREATE VIEW
postgres=# insert into v1 values(-10);
INSERT 0 1
postgres=# insert into v2 values(-10);
ERROR: new row violates WITH CHECK OPTION for view "v2" --- but this
constraint is related to v1
DETAIL: Failing row contains (-10).
Hmm, I was originally checking this as a single constraint "a > 0 AND
a < 10" attached to v2, which seemed logical to me, since that's where
the constraint is specified. On closer reading of the SQL spec,
however, it appears that all constraints from inner views are meant to
be checked before the constraints from outer views, which does indeed
then allow for reporting the error corresponding to whichever view's
conditions were violated.
So I've updated the patch to do as you suggest (which makes the code
is little simpler too), and the above test now reports that v1's qual
was violated.
* I found a difference against MySQL - LOCAL option ignore all other constraints
postgres=# CREATE TABLE t1 (a INT);
CREATE TABLE
postgres=# CREATE VIEW v1 AS SELECT * FROM t1 WHERE a < 2 WITH CHECK OPTION;
CREATE VIEW
postgres=# CREATE VIEW v2 AS SELECT * FROM v1 WHERE a > 0 WITH LOCAL
CHECK OPTION;
CREATE VIEW
postgres=# INSERT INTO v2 VALUES (2);
ERROR: new row violates WITH CHECK OPTION for view "v1" -- it will be
ok on MySQL
DETAIL: Failing row contains (2).Probably MySQL is wrong (due differet behave than in DB2) -- but who
know http://bugs.mysql.com/bug.php?id=6404What is a correct behave?
I think MySQL is wrong here.
The SQL spec is very specific about how these constraints should be
checked (see "Effect of inserting a table into a viewed table"). The
required behaviour is defined recursively such that if a view has a
LOCAL check option, any separately defined checks on lower level views
are checked first, and then any conditions specified locally on the
view are checked. A LOCAL check shouldn't prevent lower level checks
from running.
Thanks for the review. Updated patch attached.
Regards,
Dean
Attachments:
with-check-option.patchapplication/octet-stream; name=with-check-option.patchDownload
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..8102ec2
*** 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>
*************** CREATE VIEW comedies AS
*** 301,306 ****
--- 403,440 ----
</para>
<para>
+ Create a view with <literal>LOCAL CHECK OPTION</>:
+
+ <programlisting>
+ CREATE VIEW universal_comedies AS
+ SELECT *
+ FROM comedies
+ WHERE classification = 'U'
+ WITH LOCAL CHECK OPTION;
+ </programlisting>
+ This will create a view based on the <literal>comedies</> view, showing
+ only films with <literal>kind = 'Comedy'</> and
+ <literal>classification = 'U'</>. Any attempt to <command>INSERT</> or
+ <command>UPDATE</> a row in the view will be rejected if the new row
+ doesn't have <literal>classification = 'U'</>, but the film
+ <literal>kind</> will not be checked.
+ </para>
+
+ <para>
+ Create a view with <literal>CASCADED CHECK OPTION</>:
+
+ <programlisting>
+ CREATE VIEW pg_comedies AS
+ SELECT *
+ FROM comedies
+ WHERE classification = 'PG'
+ WITH CASCADED CHECK OPTION;
+ </programlisting>
+ This will create a view that checks both the <literal>kind</> and
+ <literal>classification</> of new rows.
+ </para>
+
+ <para>
Create a recursive view consisting of the numbers from 1 to 100:
<programlisting>
CREATE RECURSIVE VIEW nums_1_100 (n) AS
*************** 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>
--- 448,457 ----
<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..5921fba
*** 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 = false;
+ bool security_barrier = false;
+
+ foreach(cell, view_options)
+ {
+ DefElem *defel = (DefElem *) lfirst(cell);
+
+ if (pg_strcasecmp(defel->defname, "check_option") == 0)
+ check_option = true;
+ if (pg_strcasecmp(defel->defname, "security_barrier") == 0)
+ security_barrier = defGetBoolean(defel);
+ }
+
+ if (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..a42b1bc
*** 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,474 ----
errmsg("views must not contain data-modifying statements in WITH")));
/*
+ * If the user specified the WITH CHECK OPTION, add it to the list of
+ * reloptions.
+ */
+ if (stmt->withCheckOption == LOCAL_CHECK_OPTION)
+ stmt->options = lappend(stmt->options,
+ makeDefElem("check_option",
+ (Node *) makeString("local")));
+ else if (stmt->withCheckOption == CASCADED_CHECK_OPTION)
+ stmt->options = lappend(stmt->options,
+ makeDefElem("check_option",
+ (Node *) makeString("cascaded")));
+
+ /*
+ * Check that the view is auto-updatable if WITH CHECK OPTION was
+ * specified.
+ */
+ check_option = false;
+ security_barrier = false;
+
+ foreach(cell, stmt->options)
+ {
+ DefElem *defel = (DefElem *) lfirst(cell);
+
+ if (pg_strcasecmp(defel->defname, "check_option") == 0)
+ check_option = true;
+ if (pg_strcasecmp(defel->defname, "security_barrier") == 0)
+ security_barrier = defGetBoolean(defel);
+ }
+
+ if (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..d87e9e5
*** 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,2627 ----
AddQual(parsetree, (Node *) viewqual);
}
+ /*
+ * For INSERT/UPDATE, if the view has the WITH CHECK OPTION, or any parent
+ * view specified WITH CASCADED CHECK OPTION, add the quals from the view
+ * to the query's withCheckOptions list.
+ */
+ if (parsetree->commandType != CMD_DELETE)
+ {
+ bool has_wco = RelationHasCheckOption(view);
+ bool cascaded = RelationHasCascadedCheckOption(view);
+
+ /**
+ * If the parent view has a cascaded check option, treat this view as
+ * if it also had a cascaded check option.
+ *
+ * New WithCheckOptions are added to the start of the list, so if there
+ * is a cascaded check option, it will be the first item in the list.
+ */
+ if (parsetree->withCheckOptions != NIL)
+ {
+ WithCheckOption *parent_wco =
+ (WithCheckOption *) linitial(parsetree->withCheckOptions);
+
+ if (parent_wco->cascaded)
+ {
+ has_wco = true;
+ cascaded = true;
+ }
+ }
+
+ /*
+ * Add the new WithCheckOption to the start of the list, so that
+ * checks on inner views are run before checks on outer views, as
+ * required by the SQL standard.
+ *
+ * If the new check is CASCADED, we need to add it even if this view
+ * has no quals, since there may be quals on child views. A LOCAL
+ * check can be omitted if this view has no quals.
+ */
+ if (has_wco && (cascaded || viewquery->jointree->quals != NULL))
+ {
+ WithCheckOption *wco;
+
+ wco = makeNode(WithCheckOption);
+ wco->viewname = pstrdup(RelationGetRelationName(view));
+ wco->qual = NULL;
+ wco->cascaded = cascaded;
+
+ parsetree->withCheckOptions = lcons(wco,
+ parsetree->withCheckOptions);
+
+ if (viewquery->jointree->quals != NULL)
+ {
+ wco->qual = (Node *) copyObject(viewquery->jointree->quals);
+ ChangeVarNodes(wco->qual, base_rt_index, new_rt_index, 0);
+
+ /*
+ * Make sure that the query is marked correctly if the added
+ * qual has sublinks. We can skip this check if the query is
+ * already marked, or if the command is an UPDATE, in which
+ * case the same qual will have already been added to the
+ * query's WHERE clause, and AddQual will have already done
+ * this check.
+ */
+ if (!parsetree->hasSubLinks &&
+ parsetree->commandType != CMD_UPDATE)
+ parsetree->hasSubLinks = checkExprHasSubLink(wco->qual);
+ }
+ }
+ }
+
return parsetree;
}
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..87097a4
*** 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_view1"
+ 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_view1"
+ 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_view2"
+ 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;
Dean,
* Stephen Frost (sfrost@snowman.net) wrote:
Thanks! This is really looking quite good, but it's a bit late and I'm
going on vacation tomorrow, so I didn't quite want to commit it yet. :)
Apologies on this taking a bit longer than I expected, but it's been
committed and pushed now. Please take a look and let me know of any
issues you see with the changes that I made.
Thanks!
Stephen
On 18 July 2013 22:27, Stephen Frost <sfrost@snowman.net> wrote:
Dean,
* Stephen Frost (sfrost@snowman.net) wrote:
Thanks! This is really looking quite good, but it's a bit late and I'm
going on vacation tomorrow, so I didn't quite want to commit it yet. :)Apologies on this taking a bit longer than I expected, but it's been
committed and pushed now. Please take a look and let me know of any
issues you see with the changes that I made.
Excellent. Thank you! The changes look good to me.
Cheers,
Dean
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers