Commit 5fe3da92 authored by Peter Eisentraut's avatar Peter Eisentraut

Revert updatable views

parent c0f92b57
<!-- $PostgreSQL: pgsql/doc/src/sgml/catalogs.sgml,v 2.191 2009/01/22 20:15:59 tgl Exp $ -->
<!-- $PostgreSQL: pgsql/doc/src/sgml/catalogs.sgml,v 2.192 2009/01/27 12:40:14 petere Exp $ -->
<!--
Documentation of the system catalogs, directed toward PostgreSQL developers
-->
......@@ -4154,13 +4154,6 @@
<entry>True if the rule is an <literal>INSTEAD</literal> rule</entry>
</row>
<row>
<entry><structfield>is_auto</structfield></entry>
<entry><type>bool</type></entry>
<entry></entry>
<entry>True if the rule was automatically generated</entry>
</row>
<row>
<entry><structfield>ev_qual</structfield></entry>
<entry><type>text</type></entry>
......
<!-- $PostgreSQL: pgsql/doc/src/sgml/intro.sgml,v 1.33 2009/01/22 17:27:54 petere Exp $ -->
<!-- $PostgreSQL: pgsql/doc/src/sgml/intro.sgml,v 1.34 2009/01/27 12:40:14 petere Exp $ -->
<preface id="preface">
<title>Preface</title>
......@@ -110,7 +110,7 @@
<simpara>triggers</simpara>
</listitem>
<listitem>
<simpara>updatable views</simpara>
<simpara>views</simpara>
</listitem>
<listitem>
<simpara>transactional integrity</simpara>
......
<!--
$PostgreSQL: pgsql/doc/src/sgml/ref/create_view.sgml,v 1.40 2009/01/22 17:27:54 petere Exp $
$PostgreSQL: pgsql/doc/src/sgml/ref/create_view.sgml,v 1.41 2009/01/27 12:40:15 petere Exp $
PostgreSQL documentation
-->
......@@ -115,99 +115,11 @@ CREATE [ OR REPLACE ] [ TEMP | TEMPORARY ] VIEW <replaceable class="PARAMETER">n
<title>Notes</title>
<para>
Some views are updatable, which means that the
commands <command>INSERT</command>, <command>UPDATE</command>,
and <command>DELETE</command> can be used on the view as if it
were a regular table. A view is updatable if it
does <emphasis>not</emphasis> contain:
<itemizedlist>
<listitem>
<para>
more than one underlying table (joins) or no underlying table at all
</para>
</listitem>
<listitem>
<para>
underlying tables/views that are themselves not updatable,
including table value constructors and table functions
</para>
</listitem>
<listitem>
<para>
subqueries in the <literal>FROM</literal> list
</para>
</listitem>
<listitem>
<para>
items in the select list that are not direct references to a
column of the underlying table, such as literals or any
nontrivial value expression
</para>
</listitem>
<listitem>
<para>
references to system columns in the select list
</para>
</listitem>
<listitem>
<para>
more than one reference to the same column in the select list
</para>
</listitem>
<listitem>
<para>aggregate function calls</para>
</listitem>
<listitem>
<para>window function calls</para>
</listitem>
<listitem>
<para>
<literal>WITH</literal> or <literal>WITH RECURSIVE</literal> clauses
</para>
</listitem>
<listitem>
<para>
<literal>DISTINCT</literal>, <literal>GROUP BY</literal>, or
<literal>HAVING</literal> clauses
</para>
</listitem>
<listitem>
<para>
<literal>UNION</literal>, <literal>INTERSECT</literal>, or
<literal>EXCEPT</literal> clauses
</para>
</listitem>
<listitem>
<para>
<literal>LIMIT</literal> or <literal>OFFSET</literal> clauses
(or other equivalent spellings thereof)
</para>
</listitem>
</itemizedlist>
</para>
<para>
The updatable views implementation is based on the rule system.
Because of this, you can also make more complex views updatable or
insertable by creating your own rules that rewrite
the <command>INSERT</command>,
<command>UPDATE</command>, and <command>DELETE</command> actions
on the view into appropriate actions on other tables. You can
also replace the automatically generated rules by your own rules.
For more information on the rule system, refer
to <xref linkend="sql-createrule" endterm="sql-createrule-title">.
Currently, views are read only: the system will not allow an insert,
update, or delete on a view. You can get the effect of an updatable
view by creating rules that rewrite inserts, etc. on the view into
appropriate actions on other tables. For more information see
<xref linkend="sql-createrule" endterm="sql-createrule-title">.
</para>
<para>
......
......@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/commands/view.c,v 1.112 2009/01/22 17:27:54 petere Exp $
* $PostgreSQL: pgsql/src/backend/commands/view.c,v 1.113 2009/01/27 12:40:15 petere Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -27,9 +27,7 @@
#include "parser/parse_relation.h"
#include "rewrite/rewriteDefine.h"
#include "rewrite/rewriteManip.h"
#include "rewrite/rewriteRemove.h"
#include "rewrite/rewriteSupport.h"
#include "rewrite/viewUpdate.h"
#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
......@@ -310,28 +308,13 @@ DefineViewRules(Oid viewOid, Query *viewParse, bool replace)
viewOid,
NULL,
CMD_SELECT,
true, /* is_instead */
true, /* is_auto */
true,
replace,
list_make1(viewParse));
/*
* Delete all implicit rules on replace. CreateViewUpdateRules()
* below will re-create them if appropriate for the new view
* definition.
* Someday: automatic ON INSERT, etc
*/
if (replace)
{
Relation rel = heap_open(viewOid, AccessExclusiveLock);
RemoveAutomaticRulesOnEvent(rel, CMD_INSERT);
RemoveAutomaticRulesOnEvent(rel, CMD_DELETE);
RemoveAutomaticRulesOnEvent(rel, CMD_UPDATE);
heap_close(rel, NoLock);
}
CommandCounterIncrement();
CreateViewUpdateRules(viewOid, viewParse);
}
/*---------------------------------------------------------------
......
......@@ -4,7 +4,7 @@
# Makefile for rewrite
#
# IDENTIFICATION
# $PostgreSQL: pgsql/src/backend/rewrite/Makefile,v 1.18 2009/01/22 17:27:54 petere Exp $
# $PostgreSQL: pgsql/src/backend/rewrite/Makefile,v 1.19 2009/01/27 12:40:15 petere Exp $
#
#-------------------------------------------------------------------------
......@@ -13,7 +13,6 @@ top_builddir = ../../..
include $(top_builddir)/src/Makefile.global
OBJS = rewriteRemove.o rewriteDefine.o \
rewriteHandler.o rewriteManip.o rewriteSupport.o \
viewUpdate.o
rewriteHandler.o rewriteManip.o rewriteSupport.o
include $(top_srcdir)/src/backend/common.mk
......@@ -8,14 +8,13 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/rewrite/rewriteDefine.c,v 1.135 2009/01/22 17:27:54 petere Exp $
* $PostgreSQL: pgsql/src/backend/rewrite/rewriteDefine.c,v 1.136 2009/01/27 12:40:15 petere Exp $
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "access/heapam.h"
#include "access/xact.h"
#include "catalog/dependency.h"
#include "catalog/indexing.h"
#include "catalog/namespace.h"
......@@ -26,7 +25,6 @@
#include "parser/parse_utilcmd.h"
#include "rewrite/rewriteDefine.h"
#include "rewrite/rewriteManip.h"
#include "rewrite/rewriteRemove.h"
#include "rewrite/rewriteSupport.h"
#include "utils/acl.h"
#include "utils/builtins.h"
......@@ -41,7 +39,6 @@ static void checkRuleResultList(List *targetList, TupleDesc resultDesc,
bool isSelect);
static bool setRuleCheckAsUser_walker(Node *node, Oid *context);
static void setRuleCheckAsUser_Query(Query *qry, Oid userid);
static const char *rule_event_string(CmdType evtype);
/*
......@@ -55,7 +52,6 @@ InsertRule(char *rulname,
Oid eventrel_oid,
AttrNumber evslot_index,
bool evinstead,
bool is_auto,
Node *event_qual,
List *action,
bool replace)
......@@ -88,7 +84,6 @@ InsertRule(char *rulname,
values[i++] = CharGetDatum(evtype + '0'); /* ev_type */
values[i++] = CharGetDatum(RULE_FIRES_ON_ORIGIN); /* ev_enabled */
values[i++] = BoolGetDatum(evinstead); /* is_instead */
values[i++] = BoolGetDatum(is_auto); /* is_auto */
values[i++] = CStringGetTextDatum(evqual); /* ev_qual */
values[i++] = CStringGetTextDatum(actiontree); /* ev_action */
......@@ -107,11 +102,7 @@ InsertRule(char *rulname,
if (HeapTupleIsValid(oldtup))
{
/*
* If REPLACE was not used we still check if the old rule is
* automatic: Then we replace it anyway.
*/
if (!replace && !((Form_pg_rewrite) GETSTRUCT(oldtup))->is_auto)
if (!replace)
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("rule \"%s\" for relation \"%s\" already exists",
......@@ -124,7 +115,6 @@ InsertRule(char *rulname,
replaces[Anum_pg_rewrite_ev_attr - 1] = true;
replaces[Anum_pg_rewrite_ev_type - 1] = true;
replaces[Anum_pg_rewrite_is_instead - 1] = true;
replaces[Anum_pg_rewrite_is_auto - 1] = true;
replaces[Anum_pg_rewrite_ev_qual - 1] = true;
replaces[Anum_pg_rewrite_ev_action - 1] = true;
......@@ -215,7 +205,6 @@ DefineRule(RuleStmt *stmt, const char *queryString)
whereClause,
stmt->event,
stmt->instead,
false, /* not is_auto */
stmt->replace,
actions);
}
......@@ -234,7 +223,6 @@ DefineQueryRewrite(char *rulename,
Node *event_qual,
CmdType event_type,
bool is_instead,
bool is_auto,
bool replace,
List *action)
{
......@@ -458,42 +446,6 @@ DefineQueryRewrite(char *rulename,
RelationGetDescr(event_relation),
false);
}
/*
* If defining a non-automatic DO INSTEAD rule, drop all
* automatic rules on the same event.
*/
if (!is_auto && is_instead)
{
RemoveAutomaticRulesOnEvent(event_relation, event_type);
CommandCounterIncrement();
}
/*
* If defining an automatic rule and there is a manual rule on
* the same event, warn and don't do it.
*/
if (is_auto && event_relation->rd_rules != NULL)
{
int i;
for (i = 0; i < event_relation->rd_rules->numLocks; i++)
{
RewriteRule *rule = event_relation->rd_rules->rules[i];
if (rule->event == event_type && !rule->is_auto && rule->isInstead == is_instead)
{
ereport(WARNING,
(errmsg("automatic %s rule not created because manually created %s rule exists",
rule_event_string(event_type), rule_event_string(event_type)),
errhint("If you prefer to have the automatic rule, drop the manually created rule and run CREATE OR REPLACE VIEW again.")));
heap_close(event_relation, NoLock);
return;
}
}
}
}
/*
......@@ -509,7 +461,6 @@ DefineQueryRewrite(char *rulename,
event_relid,
event_attno,
is_instead,
is_auto,
event_qual,
action,
replace);
......@@ -803,16 +754,3 @@ RenameRewriteRule(Oid owningRel, const char *oldName,
}
#endif
static const char *
rule_event_string(CmdType type)
{
if (type == CMD_INSERT)
return "INSERT";
if (type == CMD_UPDATE)
return "UPDATE";
if (type == CMD_DELETE)
return "DELETE";
return "???";
}
......@@ -7,7 +7,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/rewrite/rewriteHandler.c,v 1.184 2009/01/22 20:16:06 tgl Exp $
* $PostgreSQL: pgsql/src/backend/rewrite/rewriteHandler.c,v 1.185 2009/01/27 12:40:15 petere Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -1899,20 +1899,20 @@ QueryRewrite(Query *parsetree)
{
case CMD_INSERT:
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("view is not updatable"),
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot insert into a view"),
errhint("You need an unconditional ON INSERT DO INSTEAD rule.")));
break;
case CMD_UPDATE:
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("view is not updatable"),
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot update a view"),
errhint("You need an unconditional ON UPDATE DO INSTEAD rule.")));
break;
case CMD_DELETE:
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("view is not updatable"),
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot delete from a view"),
errhint("You need an unconditional ON DELETE DO INSTEAD rule.")));
break;
default:
......
......@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/rewrite/rewriteRemove.c,v 1.76 2009/01/22 17:27:54 petere Exp $
* $PostgreSQL: pgsql/src/backend/rewrite/rewriteRemove.c,v 1.77 2009/01/27 12:40:15 petere Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -90,39 +90,6 @@ RemoveRewriteRule(Oid owningRel, const char *ruleName, DropBehavior behavior,
performDeletion(&object, behavior);
}
/*
* RemoveAutomaticRulesOnEvent
*
* This will delete automatic rules, if any exist, on the event in the
* relation.
*/
void
RemoveAutomaticRulesOnEvent(Relation rel, CmdType event_type)
{
RuleLock *rulelocks = rel->rd_rules;
int i;
/* If there are no rules on the relation, waste no more time. */
if (rulelocks == NULL)
return;
/*
* Look at all rules looking for the ones that are on the event
* and are automatic.
*/
for (i = 0; i < rulelocks->numLocks; i++)
{
RewriteRule *oneLock = rulelocks->rules[i];
if (oneLock->event == event_type && oneLock->is_auto)
{
RemoveRewriteRuleById(oneLock->ruleId);
elog(DEBUG1, "removing automatic rule with OID %u\n",
oneLock->ruleId);
deleteDependencyRecordsFor(RewriteRelationId, oneLock->ruleId);
}
}
}
/*
* Guts of rule deletion.
......
/*-------------------------------------------------------------------------
*
* viewUpdate.c
* routines for translating a view definition into
* INSERT/UPDATE/DELETE rules (i.e. updatable views).
*
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* ORIGINAL AUTHORS
* Bernd Helmle, Jaime Casanova
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/rewrite/viewUpdate.c,v 1.1 2009/01/22 17:27:54 petere Exp $
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "access/heapam.h"
#include "access/xact.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_rewrite.h"
#include "nodes/nodeFuncs.h"
#include "parser/parse_oper.h"
#include "parser/parsetree.h"
#include "rewrite/rewriteDefine.h"
#include "rewrite/viewUpdate.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/syscache.h"
#include "utils/rel.h"
typedef TargetEntry** ViewDefColumnList;
typedef struct ViewBaseRelation
{
List *defs; /* List of all base relations (root starts
* with only one relation because we
* implement only simple updatability) */
Oid parentRelation; /* Oid of parent relation, 0 indicates root */
} ViewBaseRelation;
typedef struct ViewBaseRelationItem
{
Relation rel; /* the Relation itself */
Query *rule; /* _RETURN rule of a view relation */
TargetEntry **tentries; /* saves order of column target list */
} ViewBaseRelationItem;
typedef struct ViewExprContext
{
Index newRTE;
Index oldRTE;
Index baseRTE;
Index subQueryLevel;
ViewDefColumnList tentries;
} ViewExprContext;
static const char *get_auto_rule_name(CmdType type);
static Query *get_return_rule(Relation rel);
static void read_rearranged_cols(ViewBaseRelation *tree);
static bool is_select_query_updatable(const Query *query);
static Oid get_reloid_from_select(const Query *select,
int *rti, RangeTblEntry **rel_entry);
static void create_update_rule(Oid viewOid,
const Query *select,
const Relation baserel,
TargetEntry **tentries,
CmdType ruletype);
static void get_base_base_relations(const Query *view, Oid baserelid, List **list);
static void copyReversedTargetEntryPtr(List *targetList,
ViewDefColumnList targets);
static bool check_reltree(ViewBaseRelation *node);
static Query *form_update_query(const Query *select, ViewDefColumnList tentries, CmdType type);
static RangeTblEntry *get_relation_RTE(const Query *select,
unsigned int *offset);
static Index get_rtindex_for_rel(List *rte_list,
const char *relname);
static bool replace_tlist_varno_walker(Node *node,
ViewExprContext *ctxt);
static OpExpr *create_opexpr(Var *var_left, Var *var_right);
static void form_where_for_updrule(const Query *select, FromExpr **from,
const Relation baserel, Index baserti,
Index oldrti);
static void build_update_target_list(const Query *update, const Query *select,
const Relation baserel);
/*------------------------------------------------------------------------------
* Private functions
* -----------------------------------------------------------------------------
*/
static const char *
get_auto_rule_name(CmdType type)
{
if (type == CMD_INSERT)
return "_INSERT";
if (type == CMD_UPDATE)
return "_UPDATE";
if (type == CMD_DELETE)
return "_DELETE";
return NULL;
}
/*
* Returns the range table index for the specified relname.
*
* XXX This seems pretty grotty ... can't we do this in some other way?
*/
static Index
get_rtindex_for_rel(List *rte_list, const char *relname)
{
ListCell *cell;
int index = 0;
AssertArg(relname);
foreach(cell, rte_list)
{
RangeTblEntry *rte = (RangeTblEntry *) lfirst(cell);
index++;
if (rte && strncmp(rte->eref->aliasname, relname, NAMEDATALEN) == 0)
break;
}
Assert(index > 0);
return (Index) index;
}
/*
* Returns the RangeTblEntry starting at the specified offset. The
* function can be used to iterate over the rtable list of the
* specified select query tree. Returns NULL if nothing is found.
*
* NOTE: The function only returns those RangeTblEntry that do not
* match a *NEW* or *OLD* RangeTblEntry.
*
* The offset is incremented as a side effect.
*/
static RangeTblEntry *
get_relation_RTE(const Query *select, unsigned int *offset)
{
AssertArg(offset);
AssertArg(select);
while (*offset <= list_length(select->rtable))
{
RangeTblEntry *rte = rt_fetch(*offset, select->rtable);
(*offset)++;
/* skip non-table RTEs */
if (rte->rtekind != RTE_RELATION)
continue;
/*
* Skip RTEs named *NEW* and *OLD*.
*
* XXX It would be nice to be able to use something else than just
* the names here ... However, rtekind does not work as expected :-(
*/
if (strncmp(rte->eref->aliasname, "*NEW*", 6) == 0
|| strncmp(rte->eref->aliasname, "*OLD*", 6) == 0)
continue;
return rte;
}
return NULL;
}
/*
* Rewrite varno's and varattno for the specified Var node if it is in
* a reversed order regarding to the underlying relation. The lookup
* table tentries holds all TargetEntries which are on a different
* location in the view definition. If var isn't on a different
* position in the current view than on its original relation, nothing
* is done.
*
* Note: This function assumes that the caller has already checked all
* parameters for NULL.
*/
static void
adjustVarnoIfReversedCol(Var *var,
Index newRTE,
ViewDefColumnList tentries)
{
TargetEntry *entry = tentries[var->varattno - 1];
/*
* tentries holds NULL if given var isn't on a different location
* in the view Only replace if column order is reversed.
*/
if (entry && entry->resno != var->varattno)
{
var->varattno = entry->resno;
var->varoattno = entry->resno;
}
/* Finally, make varno point to the *NEW* range table entry. */
var->varno = newRTE;
var->varnoold = newRTE;
}
/*
* Creates an equal operator expression for the specified Vars. They
* are assumed to be of the same type.
*/
static OpExpr *
create_opexpr(Var *var_left, Var *var_right)
{
OpExpr *result;
HeapTuple tuple;
Form_pg_operator operator;
Oid eqOid;
AssertArg(var_left);
AssertArg(var_right);
Assert(var_left->vartype == var_right->vartype);
get_sort_group_operators(var_left->vartype, false, true, false,
NULL, &eqOid, NULL);
tuple = SearchSysCache(OPEROID, ObjectIdGetDatum(eqOid), 0, 0, 0);
operator = (Form_pg_operator) GETSTRUCT(tuple);
result = makeNode(OpExpr);
result->opno = HeapTupleGetOid(tuple);
result->opfuncid = operator->oprcode;
result->opresulttype = operator->oprresult;
result->opretset = false;
result->args = lappend(result->args, var_left);
result->args = lappend(result->args, var_right);
ReleaseSysCache(tuple);
return result;
}
/*
* Creates an expression tree for a WHERE clause.
*
* If from is not NULL, assigns the root node to the specified
* FromExpr of the target query tree.
*
* Note that the function appends the specified opExpr op to the
* specified anchor (if anchor != NULL) and returns that immediately.
* That way this function could be used to add operator nodes to an
* existing BoolExpr tree or (if from is given), to create a new Query
* qualification list.
*/
static Node *
build_expression_tree(FromExpr *from, Node **anchor, BoolExpr *expr, OpExpr *op)
{
/* Already some nodes there? */
if (*anchor)
{
expr->args = lappend(expr->args, op);
((BoolExpr *)(*anchor))->args = lappend(((BoolExpr *)(*anchor))->args,
expr);
*anchor = (Node *)expr;
}
else
{
/* Currently no nodes ... */
BoolExpr *boolexpr = makeNode(BoolExpr);
expr->args = lappend(expr->args, op);
boolexpr->args = lappend(boolexpr->args, expr);
*anchor = (Node *) boolexpr;
if (from)
from->quals = *anchor;
}
return *anchor;
}
/*
* Forms the WHERE clause for DELETE/UPDATE rules targeted to the
* specified view.
*/
static void
form_where_for_updrule(const Query *select, /* View retrieve rule */
FromExpr **from, /* FromExpr for stmt */
const Relation baserel, /* base relation of view */
Index baserti, /* Index of base relation RTE */
Index oldrti) /* Index of *OLD* RTE */
{
BoolExpr *expr = NULL;
Node *anchor = NULL;
Form_pg_attribute *attrs;
ListCell *cell;
AssertArg(baserti > 0);
AssertArg(oldrti > 0);
AssertArg(*from);
AssertArg(baserel);
attrs = baserel->rd_att->attrs;
foreach(cell, select->targetList)
{
TargetEntry *te = (TargetEntry *) lfirst(cell);
Var *var1;
Var *var2;
OpExpr *op;
BoolExpr *null_condition;
NullTest *nulltest1;
NullTest *nulltest2;
/* If te->expr holds no Var pointer, continue. */
if (!IsA(te->expr, Var))
continue;
null_condition = makeNode(BoolExpr);
nulltest1 = makeNode(NullTest);
nulltest2 = makeNode(NullTest);
/*
* These are the new operands we had to check for equality.
*
* For DELETE/UPDATE rules, var1 points to the *OLD* RTE, var2
* references the base relation.
*/
var1 = copyObject((Var *) (te->expr));
/*
* Look at varoattno to determine whether this attribute has a different
* location in the underlying base table. If that case, retrieve the
* attribute from the base table and assign it to var2; otherwise
* simply copy it to var1.
*/
if (var1->varoattno > 0)
{
var2 = makeNode(Var);
var2->varno = baserti;
var2->varnoold = baserti;
var2->varattno = attrs[var1->varoattno - 1]->attnum;
var2->vartype = attrs[var1->varoattno - 1]->atttypid;
var2->vartypmod = attrs[var1->varoattno - 1]->atttypmod;
var2->varlevelsup = var1->varlevelsup;
var2->varnoold = var2->varno;
var2->varoattno = var2->varattno;
}
else
{
var2 = copyObject(var1);
var2->varno = baserti;
var2->varnoold = baserti;
}
var1->varno = oldrti;
var1->varnoold = oldrti;
/*
* rewrite varattno of var2 to point to the right column in relation
* *OLD* or *NEW*
*/
var2->varattno = te->resorigcol;
var2->varoattno = te->resorigcol;
/*
* rewrite varattno of var1 to point to the right column in base
* relation
*/
var1->varattno = te->resno;
var1->varoattno = te->resno;
op = create_opexpr(var1, var2);
expr = makeNode(BoolExpr);
expr->boolop = OR_EXPR;
null_condition->boolop = AND_EXPR;
nulltest1->arg = (Expr *)var1;
nulltest1->nulltesttype = IS_NULL;
nulltest2->arg = (Expr *)var2;
nulltest2->nulltesttype = IS_NULL;
null_condition->args = lappend(null_condition->args, nulltest1);
null_condition->args = lappend(null_condition->args, nulltest2);
expr->args = lappend(expr->args, null_condition);
anchor = build_expression_tree(*from, (Node **) &anchor, expr, op);
}
}
/*
* Replaces the varnos for the specified targetlist to rtIndex
*/
static bool
replace_tlist_varno_walker(Node *node,
ViewExprContext *ctxt)
{
AssertArg(ctxt);
if (!node)
return false;
switch(node->type)
{
case T_Var:
elog(DEBUG1, "adjusting varno old %d to new %d",
((Var *)(node))->varno,
ctxt->newRTE);
((Var *)(node))->varno = ctxt->newRTE;
adjustVarnoIfReversedCol((Var *)node,
ctxt->newRTE,
ctxt->tentries);
/* nothing more to do */
break;
case T_ArrayRef:
{
ArrayRef *array = (ArrayRef *) node;
/*
* Things are getting complicated here. We have found an
* array subscripting operation. It's necessary to
* examine all varno's found in this operation to make
* sure, we're getting right. This covers cases where a
* view selects a single index or complete array from a
* base table or view.
*/
/*
* Look at expressions that evaluate upper array
* indexes. Make sure all varno's are modified. This is
* done by walking the expression tree recursively.
*/
expression_tree_walker((Node *) array->refupperindexpr,
replace_tlist_varno_walker,
(void *)ctxt);
expression_tree_walker((Node *) array->reflowerindexpr,
replace_tlist_varno_walker,
(void *)ctxt);
expression_tree_walker((Node *) array->refexpr,
replace_tlist_varno_walker,
(void *)ctxt);
expression_tree_walker((Node *) array->refassgnexpr,
replace_tlist_varno_walker,
(void *)ctxt);
}
default:
break;
}
return expression_tree_walker(node, replace_tlist_varno_walker, ctxt);
}
/*
* Adds RTEs to form a query tree.
*
* select has to be a valid initialized view definition query tree
* (the function assumes that this query has passed the
* is_select_query_updatable() function).
*/
static Query *
form_update_query(const Query *select, ViewDefColumnList tentries, CmdType type)
{
RangeTblEntry *rte;
Oid reloid;
Query *newquery;
AssertArg(select);
AssertArg(tentries);
newquery = makeNode(Query);
newquery->commandType = type;
/* copy the range table entries */
newquery->rtable = copyObject(select->rtable);
/* prepare other stuff */
newquery->canSetTag = true;
newquery->jointree = makeNode(FromExpr);
/*
* Set result relation to the base relation.
*
* Since we currently only support updatable views with one
* underlying table, we simply extract the one relation which
* isn't labeled as *OLD* or *NEW*.
*/
reloid = get_reloid_from_select(select, &(newquery->resultRelation), &rte);
if (!OidIsValid(reloid))
elog(ERROR, "could not retrieve base relation OID");
Assert(newquery->resultRelation > 0);
/* adjust inFromCl of result range table entry */
rte->inFromCl = false;
/* We don't need a target list for DELETE. */
if (type != CMD_DELETE)
{
ViewExprContext ctxt;
ListCell *cell;
/* Copy all target entries. */
newquery->targetList = copyObject(select->targetList);
/*
* Replace all varnos to point to the *NEW* node in all targetentry
* expressions.
*/
ctxt.newRTE = PRS2_NEW_VARNO;
ctxt.tentries = tentries;
foreach(cell, newquery->targetList)
{
Node *node = (Node *) lfirst(cell);
expression_tree_walker(node,
replace_tlist_varno_walker,
(void *) &ctxt);
}
}
return newquery;
}
/*
* Rewrite a TargetEntry, based on the given arguments to match
* the new Query tree of the new DELETE/UPDATE/INSERT rule and/or
* its underlying base relation.
*
* form_te_for_update() needs to carefully reassign Varno's of
* all Var expressions assigned to the given TargetEntry and to
* adjust all type info values and attribute index locations so
* that the rewritten TargetEntry corresponds to the correct
* column in the underlying base relation.
*
* Someone should consider that columns could be in reversed
* order in a view definition, so we need to take care to
* "restore" the correct order of all columns in the target list
* of the new view update rules.
*
* There's also some additional overhead if we have an array field
* involved. In this case we have to loop recursively through the
* array expressions to get all target entries right.
*/
static void
form_te_for_update(int2 attnum, Form_pg_attribute attrs, Oid baserelid,
Expr *expr, TargetEntry *te_update)
{
/*
* First, try if this is an array subscripting operation. If true, dive
* recursively into the subscripting tree examining all varnos.
*/
if (IsA(expr, ArrayRef))
{
ArrayRef *array = (ArrayRef *) expr;
if (array->refassgnexpr != NULL)
form_te_for_update(attnum, attrs, baserelid, array->refassgnexpr,
te_update);
if (array->refupperindexpr != NIL)
{
ListCell *cell;
foreach(cell, array->refupperindexpr)
form_te_for_update(attnum, attrs, baserelid, (Expr *) lfirst(cell), te_update);
}
if (array->reflowerindexpr != NIL)
{
ListCell *cell;
foreach(cell, array->reflowerindexpr)
form_te_for_update(attnum, attrs, baserelid, (Expr *) lfirst(cell), te_update);
}
if (array->refexpr != NULL)
form_te_for_update(attnum, attrs, baserelid, array->refexpr,
te_update);
}
else if (IsA(expr, Var))
{
/*
* Base case of recursion: actually build the TargetEntry.
*/
Var *upd_var = (Var *) (te_update->expr);
upd_var->varattno = te_update->resno;
upd_var->varoattno = te_update->resno;
upd_var->vartype = attrs->atttypid;
upd_var->vartypmod = attrs->atttypmod;
upd_var->varnoold = upd_var->varno;
te_update->resno = attnum;
te_update->resname = pstrdup(get_attname(baserelid, attnum));
te_update->ressortgroupref = 0;
te_update->resorigcol = 0;
te_update->resorigtbl = 0;
te_update->resjunk = false;
}
}
/*
* Create the returning list for the given query tree. This allows
* using RETURING in view update actions. Note that the function
* creates the returning list from the target list of the given query
* tree if src is set to NULL. This requires to call
* build_update_target_list() on that query tree before. If src !=
* NULL, the target list is created from this query tree instead.
*/
static void
create_rule_returning_list(Query *query, const Query *src, Index newRTE,
ViewDefColumnList tentries)
{
ViewExprContext ctxt;
ListCell *cell;
ctxt.newRTE = newRTE;
ctxt.tentries = tentries;
/* determine target list source */
if (src)
query->returningList = copyObject(src->targetList);
else
query->returningList = copyObject(query->targetList);
foreach(cell, query->returningList)
expression_tree_walker((Node *) lfirst(cell),
replace_tlist_varno_walker,
(void *) &ctxt);
}
/*
* Build the target list for a view UPDATE rule.
*
* Note: The function assumes a Query tree specified by update, which
* was copied by form_update_query(). We need the original Query tree
* to adjust the properties of each member of the TargetList of the
* new query tree.
*/
static void
build_update_target_list(const Query *update, const Query *select,
Relation baserel)
{
ListCell *cell1;
ListCell *cell2;
/*
* This Assert() is appropriate, since we rely on a query tree
* created by from_query(), which copies the target list from the
* original query tree specified by the argument select, which
* holds the current view definition. So both target lists have
* to be equal in length.
*/
Assert(list_length(update->targetList) == list_length(select->targetList));
/*
* Copy the target list of the view definition to the
* returningList. This is required to support RETURNING clauses
* in view update actions.
*/
forboth(cell1, select->targetList, cell2, update->targetList)
{
TargetEntry *entry = (TargetEntry *) lfirst(cell1);
TargetEntry *upd_entry = (TargetEntry *) lfirst(cell2);
int attindex;
Form_pg_attribute attr;
if (entry->resorigcol > 0)
/*
* This column seems to have a different order than in the base
* table. We get the attribute from the base relation referenced
* by rel and create a new resdom. This new result domain is then
* assigned instead of the old one.
*/
attindex = entry->resorigcol;
else
attindex = entry->resno;
attr = baserel->rd_att->attrs[attindex - 1];
form_te_for_update(attindex, attr, baserel->rd_id, upd_entry->expr,
upd_entry);
}
}
/*
* Examines the columns by the current view and initializes the lookup
* table for all rearranged columns in base relations. The function
* requires a relation tree initialized by get_base_base_relations().
*/
static void
read_rearranged_cols(ViewBaseRelation *tree)
{
AssertArg(tree);
if (tree->defs)
{
int num_items = list_length(tree->defs);
int i;
/*
* Traverse the relation tree and look on all base relations
* for reversed column order in their target lists. We have
* to perform a look-ahead-read on the tree, because we need
* to know how much columns the next base relation has, to
* allocate enough memory in tentries.
*
* Note that if we only have one base relation (a "real"
* table, not a view) exists, we have nothing to do, because
* this base relation cannot have a reversed column order
* caused by a view definition query.
*/
for (i = num_items - 1; i > 0; i--)
{
ViewBaseRelationItem *item_current;
ViewBaseRelationItem *item_next;
ViewBaseRelation *current;
ViewBaseRelation *next;
current = (ViewBaseRelation *) list_nth(tree->defs, i);
/*
* We look ahead for the next base relation. We can do
* this here safely, because the loop condition terminates
* before reaching the list head.
*/
next = (ViewBaseRelation *) list_nth(tree->defs, i - 1);
/*
* Note that the code currently requires a simply updatable
* relation tree. This means we handle one base relation
* per loop, only.
*/
Assert(next);
Assert(current);
Assert(list_length(next->defs) == 1);
Assert(list_length(current->defs) == 1);
item_current = (ViewBaseRelationItem *) list_nth(current->defs, 0);
item_next = (ViewBaseRelationItem *) list_nth(next->defs, 0);
/* allocate tentries buffer */
item_current->tentries = (ViewDefColumnList) palloc(sizeof(TargetEntry *) * RelationGetNumberOfAttributes(item_next->rel));
copyReversedTargetEntryPtr(item_current->rule->targetList,
item_current->tentries);
}
}
}
/*------------------------------------------------------------------------------
* Retrieves all relations from the view that can be considered a "base
* relation". The function returns a list that holds lists of all relation
* OIDs found for the view. The list is filled top down, that means the head of
* the list holds the relations for the "highest" view in the tree.
*
* Consider this view definition tree where each node is a relation the above
* node is based on:
*
* 1
* / \
* 2 3
* / \ \
* 4 5 6
* /
* 7
*
* The function will then return a list with the following layout:
*
* Listindex Node(s)
* --------------------------
* 1 7
* 2 4 5 6
* 3 2 3
*
* As you can see in the table, all relations that are "children" of the
* given root relation (the view relation itself) are saved in the
* tree, except the root node itself.
*------------------------------------------------------------------------------
*/
static void
get_base_base_relations(const Query *view, Oid baserelid, List **list)
{
RangeTblEntry *entry;
unsigned int offset = 1;
ViewBaseRelation *childRel;
if (!view)
return;
childRel = (ViewBaseRelation *) palloc(sizeof(ViewBaseRelation));
childRel->defs = NIL;
childRel->parentRelation = baserelid;
/* Get all OIDs from the RTE list of view. */
while ((entry = get_relation_RTE(view, &offset)) != NULL)
{
Relation rel;
ViewBaseRelationItem *item;
/*
* Is this really a view or relation?
*/
rel = relation_open(entry->relid, AccessShareLock);
if (rel->rd_rel->relkind != RELKIND_RELATION &&
rel->rd_rel->relkind != RELKIND_VIEW)
{
/* don't need this one */
relation_close(rel, AccessShareLock);
continue;
}
item = (ViewBaseRelationItem *) palloc0(sizeof(ViewBaseRelationItem));
item->rel = rel;
item->rule = NULL;
if (rel->rd_rel->relkind == RELKIND_VIEW)
/*
* Get the rule _RETURN expression tree for the specified
* relation OID. We need this to recurse into the view
* base relation tree.
*/
item->rule = get_return_rule(rel);
elog(DEBUG1, "extracted relation %s for relation tree",
RelationGetRelationName(rel));
childRel->defs = lappend(childRel->defs, item);
/* recurse to any other child relations */
if (item->rule)
get_base_base_relations(item->rule, RelationGetRelid(rel), list);
}
if (childRel->defs)
*list = lappend(*list, childRel);
}
static void
copyReversedTargetEntryPtr(List *targetList, ViewDefColumnList targets)
{
ListCell *cell;
AssertArg(targets);
AssertArg(targetList);
/* NOTE: We only reassign pointers. */
foreach(cell, targetList)
{
Node *node = (Node *) lfirst(cell);
if (IsA(node, TargetEntry))
{
/*
* Look at the resdom's resorigcol to determine whether
* this is a reversed column (meaning, it has a different
* column number than the underlying base table).
*/
TargetEntry *entry = (TargetEntry *) node;
if (!IsA(entry->expr, Var))
/* nothing to do here */
continue;
if (entry->resorigcol > 0 && entry->resno != entry->resorigcol)
{
/*
* Save this TargetEntry to the appropiate place in
* the lookup table. Do it only if not already
* occupied (this could happen if the column is
* specified more than one time in the view
* definition).
*/
if (targets[entry->resorigcol - 1] == NULL)
targets[entry->resorigcol - 1] = entry;
}
}
}
}
/*
* Transforms the specified view definition into an INSERT, UPDATE, or
* DELETE rule.
*
* Note: The function assumes that the specified query tree has passed the
* is_select_query_updatable() function.
*/
static void
create_update_rule(Oid viewOid, const Query *select, const Relation baserel,
ViewDefColumnList tentries,
CmdType ruletype)
{
Query *newquery;
Oid baserelid;
Index baserti;
RangeTblEntry *rte;
AssertArg(tentries);
AssertArg(baserel);
AssertArg(select);
AssertArg(ruletype == CMD_INSERT || ruletype == CMD_UPDATE || ruletype == CMD_DELETE);
newquery = form_update_query(select, tentries, ruletype);
/*
* form_update_query() has prepared the jointree of the new UPDATE/DELETE rule.
*
* Now, our UPDATE rule needs range table references for the *NEW*
* and base relation RTEs. A DELETE rule needs range table
* references for the *OLD* and base relation RTEs.
*/
baserelid = get_reloid_from_select(select, NULL, &rte);
if (!OidIsValid(baserelid))
elog(ERROR, "could not get the base relation from the view definition");
baserti = get_rtindex_for_rel(newquery->rtable,
rte->eref->aliasname);
Assert(baserti > 0);
rte = rt_fetch(baserti, newquery->rtable);
if (ruletype != CMD_INSERT)
{
RangeTblRef *oldref;
RangeTblRef *baseref;
oldref = makeNode(RangeTblRef);
oldref->rtindex = PRS2_OLD_VARNO;
baseref = makeNode(RangeTblRef);
baseref->rtindex = baserti;
newquery->jointree->fromlist = list_make2(baseref, oldref);
/* Create the WHERE condition qualification for the rule action. */
form_where_for_updrule(select, &(newquery->jointree),
baserel, baserti, PRS2_OLD_VARNO);
}
if (ruletype != CMD_DELETE)
/*
* We must reorder the columns in the targetlist to match the
* underlying table. We do this after calling
* form_where_for_updrule() because build_update_target_list()
* relies on the original resdoms in the update tree.
*/
build_update_target_list(newquery, select, baserel);
/*
* Create the returning list now that build_update_target_list()
* has done the leg work.
*/
if (ruletype == CMD_DELETE)
create_rule_returning_list(newquery, select, PRS2_OLD_VARNO, tentries);
else
create_rule_returning_list(newquery, NULL, PRS2_NEW_VARNO, tentries);
/* Set ACL bit */
if (ruletype == CMD_INSERT)
rte->requiredPerms |= ACL_INSERT;
else if (ruletype == CMD_UPDATE)
rte->requiredPerms |= ACL_UPDATE;
else if (ruletype == CMD_DELETE)
rte->requiredPerms |= ACL_DELETE;
/* Create system rule */
DefineQueryRewrite(pstrdup(get_auto_rule_name(ruletype)),
viewOid, /* event_relid */
NULL, /* WHERE clause */
ruletype,
true, /* is_instead */
true, /* is_auto */
false, /* replace */
list_make1(newquery) /* action */);
}
/*
* Checks the specified Query for updatability. Currently, "simply
* updatable" rules are implemented.
*/
static bool
is_select_query_updatable(const Query *query)
{
ListCell *cell;
List *seen_attnos;
AssertArg(query);
AssertArg(query->commandType == CMD_SELECT);
/*
* check for unsupported clauses in the view definition
*/
if (query->hasAggs)
{
elog(DEBUG1, "view is not updatable because it uses an aggregate function");
return false;
}
if (query->hasWindowFuncs)
{
elog(DEBUG1, "view is not updatable because it uses a window function");
return false;
}
if (query->hasRecursive)
{
elog(DEBUG1, "view is not updatable because it contains a WITH RECURSIVE clause");
return false;
}
if (query->cteList)
{
elog(DEBUG1, "view is not updatable because it contains a WITH clause");
return false;
}
if (list_length(query->groupClause) >= 1)
{
elog(DEBUG1, "view is not updatable because it contains a GROUP BY clause");
return false;
}
if (query->havingQual)
{
elog(DEBUG1, "view is not updatable because it contains a HAVING clause");
return false;
}
if (list_length(query->distinctClause) >= 1)
{
elog(DEBUG1, "view is not updatable because it contains a DISTINCT clause");
return false;
}
if (query->limitOffset)
{
elog(DEBUG1, "view is not updatable because it contains an OFFSET clause");
return false;
}
if (query->limitCount)
{
elog(DEBUG1, "view is not updatable because it contains a LIMIT clause");
return false;
}
if (query->setOperations)
{
elog(DEBUG1, "view is not updatable because it contains UNION or INTERSECT or EXCEPT");
return false;
}
/*
* Test for number of involved relations. Since we assume to
* operate on a view definition SELECT query tree, we must count 3
* rtable entries. Otherwise this seems not to be a view based on
* a single relation.
*/
if (list_length(query->rtable) > 3)
{
elog(DEBUG1, "view is not updatable because it has more than one underlying table");
return false;
}
/* Any rtable entries involved? */
if (list_length(query->rtable) < 3)
{
elog(DEBUG1, "view is not updatable because it has no underlying tables");
return false;
}
/*
* Walk down the target list and look for nodes that aren't Vars.
* "Simply updatable" doesn't allow functions, host variables, or
* constant expressions in the target list.
*
* Also, check if any of the target list entries are indexed array
* expressions, which aren't supported.
*/
seen_attnos = NIL;
foreach(cell, query->targetList)
{
Node *node = (Node *) lfirst(cell);
if (IsA(node, TargetEntry))
{
TargetEntry *te = (TargetEntry *) node;
/*
* TODO -- it would be nice to support Const nodes here as well
* (but apparently it isn't in the standard)
*/
if (!IsA(te->expr, Var) && !IsA(te->expr, ArrayRef))
{
elog(DEBUG1, "view is not updatable because select list contains a derived column");
return false;
}
/* This is currently only partially implemented, but can be fixed. */
if (IsA(te->expr, ArrayRef))
{
elog(DEBUG1, "view is not updatable because select list contains an array element reference");
return false;
}
if (IsA(te->expr, Var))
{
Var *var = (Var *) te->expr;
/* System columns aren't updatable. */
if (var->varattno < 0)
{
elog(DEBUG1, "view is not updatable because select list references a system column");
return false;
}
if (list_member_int(seen_attnos, var->varattno))
{
elog(DEBUG1, "view is not updatable because select list references the same column more than once");
return false;
}
else
seen_attnos = lappend_int(seen_attnos, var->varattno);
}
}
}
/*
* Finally, check that all RTEs are acceptable. This rejects
* table functions, which cannot ever be updatable, and also WITH
* clauses.
*/
foreach(cell, query->rtable)
{
RangeTblEntry *entry = (RangeTblEntry *) lfirst(cell);
if (entry->rtekind != RTE_RELATION)
{
elog(DEBUG1, "view is not updatable because correlation \"%s\" is not a table",
entry->eref->aliasname);
return false;
}
}
return true;
}
/*
* Traverse the specified relation tree. The function stops at the
* base relations at the leafs of the tree. If any of the relations
* has more than one base relation, it is considered a not simply
* updatable view and false is returned.
*/
static bool
check_reltree(ViewBaseRelation *node)
{
ListCell *cell;
AssertArg(node);
foreach(cell, node->defs)
{
/* Walk down the tree */
ViewBaseRelation *relations = (ViewBaseRelation *) lfirst(cell);
if (list_length(relations->defs) > 1)
{
elog(DEBUG1, "possible JOIN/UNION in view definition: %d", list_length(relations->defs));
return false;
}
else if (list_length(relations->defs) == 1) {
ViewBaseRelationItem *item = (ViewBaseRelationItem *) linitial(relations->defs);
/* if the relation found is a view, check its updatability */
if (item->rel->rd_rel->relkind == RELKIND_VIEW && !is_select_query_updatable(item->rule))
{
elog(DEBUG1, "base view \"%s\" is not updatable",
RelationGetRelationName(item->rel));
return false;
}
}
}
return true;
}
/*
* Given a SELECT query tree, return the OID of the first RTE_RELATION range
* table entry found that is not *NEW* nor *OLD*.
*
* Also sets the RangeTblEntry pointer into rel_entry, and the range
* table index into rti, unless they are NULL.
*
* This function assumes that the specified query tree was checked by a
* previous call to the is_select_query_updatable() function.
*/
static Oid
get_reloid_from_select(const Query *select, int *rti, RangeTblEntry **rel_entry)
{
ListCell *cell;
Oid result = InvalidOid;
int index;
/* Check specified query tree. Return immediately on error. */
if (select == NULL || select->commandType != CMD_SELECT)
return InvalidOid;
/*
* We loop through the RTEs to get information about all involved
* relations. We return the first OID we find in the list that is not
* *NEW* nor *OLD*.
*/
index = 0;
foreach(cell, select->rtable)
{
RangeTblEntry *entry = (RangeTblEntry *) lfirst(cell);
index++;
if (entry == NULL)
elog(ERROR, "null RTE pointer in get_reloid_from_select");
elog(DEBUG1, "extracted range table entry for %u", entry->relid);
/* Return the first RELATION rte we find */
if (entry->rtekind == RTE_RELATION)
{
/*
* XXX This is ugly. The parser prepends two RTEs with rtekind
* RTE_RELATION named *NEW* and *OLD*. We have to exclude them by
* name! It would be much better if it used RTE_SPECIAL
* instead, but other parts of the system stop working if one
* just changes it naively.
*/
if (strncmp(entry->eref->aliasname, "*NEW*", 6) == 0
|| strncmp(entry->eref->aliasname, "*OLD*", 6) == 0)
continue;
result = entry->relid;
if (rti != NULL)
*rti = index;
if (rel_entry != NULL)
*rel_entry = entry;
break;
}
}
return result;
}
/*
* get_return_rule: returns the _RETURN rule of a view as a Query node.
*/
static Query *
get_return_rule(Relation rel)
{
Query *query = NULL;
int i;
AssertArg(rel->rd_rel->relkind == RELKIND_VIEW);
for (i = 0; i < rel->rd_rules->numLocks; i++)
{
RewriteRule *rule = rel->rd_rules->rules[i];
if (rule->event == CMD_SELECT)
{
/* A _RETURN rule should have only one action */
if (list_length(rule->actions) != 1)
elog(ERROR, "invalid _RETURN rule action specification");
query = linitial(rule->actions);
break;
}
}
return query;
}
/*------------------------------------------------------------------------------
* Public functions
*------------------------------------------------------------------------------
*/
/*
* CreateViewUpdateRules
*
* This is the main entry point to creating an updatable view's rules. Given a
* rule definition, examine it, and create the rules if appropiate, or return
* doing nothing if not.
*/
void
CreateViewUpdateRules(Oid viewOid, const Query *viewDef)
{
Relation baserel;
Form_pg_attribute *attrs;
ViewDefColumnList tentries;
Oid baserelid;
MemoryContext cxt;
MemoryContext oldcxt;
ViewBaseRelation *tree;
ListCell *cell;
/*
* The routines in this file leak memory like crazy, so make sure we
* allocate it all in an appropiate context.
*/
cxt = AllocSetContextCreate(TopTransactionContext,
"UpdateRulesContext",
ALLOCSET_DEFAULT_MINSIZE,
ALLOCSET_DEFAULT_INITSIZE,
ALLOCSET_DEFAULT_MAXSIZE);
oldcxt = MemoryContextSwitchTo(cxt);
/*
* Create the lookup table for the view definition target columns. We save
* the RESDOMS in that manner to look quickly for reversed column orders.
*/
baserelid = get_reloid_from_select(viewDef, NULL, NULL);
/* Get relation tree */
tree = (ViewBaseRelation *) palloc(sizeof(ViewBaseRelation));
tree->parentRelation = InvalidOid;
tree->defs = NIL;
get_base_base_relations(viewDef, baserelid, &(tree->defs));
/* Check the query tree for updatability */
if (!check_reltree(tree) || !is_select_query_updatable(viewDef))
{
elog(DEBUG1, "view is not updatable");
goto finish;
}
baserel = heap_open(baserelid, AccessShareLock);
attrs = baserel->rd_att->attrs;
/*
* Copy TargetEntries to match the slot numbers in the target list with
* their original column attribute number. Note that only pointers are
* copied and they are valid only as long as the specified SELECT query
* stays valid!
*/
tentries = (ViewDefColumnList)
palloc0(baserel->rd_rel->relnatts * sizeof(TargetEntry *));
copyReversedTargetEntryPtr(viewDef->targetList, tentries);
/*
* Now do the same for the base relation tree. read_rearranged_cols
* traverses the relation tree and performs a copyReversedTargetEntry()
* call to each base relation.
*/
read_rearranged_cols(tree);
create_update_rule(viewOid, viewDef, baserel, tentries, CMD_INSERT);
create_update_rule(viewOid, viewDef, baserel, tentries, CMD_DELETE);
create_update_rule(viewOid, viewDef, baserel, tentries, CMD_UPDATE);
ereport(NOTICE, (errmsg("CREATE VIEW has created automatic view update rules")));
/* free remaining stuff */
heap_close(baserel, NoLock);
finish:
/* get_base_base_relations leaves some open relations */
foreach(cell, tree->defs)
{
ListCell *cell2;
ViewBaseRelation *vbr = (ViewBaseRelation *) lfirst(cell);
foreach(cell2, vbr->defs)
{
ViewBaseRelationItem *vbri = (ViewBaseRelationItem *) lfirst(cell2);
relation_close(vbri->rel, NoLock);
}
}
MemoryContextSwitchTo(oldcxt);
MemoryContextDelete(cxt);
}
......@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/utils/cache/relcache.c,v 1.283 2009/01/26 19:41:06 alvherre Exp $
* $PostgreSQL: pgsql/src/backend/utils/cache/relcache.c,v 1.284 2009/01/27 12:40:15 petere Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -639,7 +639,6 @@ RelationBuildRuleLock(Relation relation)
rule->attrno = rewrite_form->ev_attr;
rule->enabled = rewrite_form->ev_enabled;
rule->isInstead = rewrite_form->is_instead;
rule->is_auto = rewrite_form->is_auto;
/*
* Must use heap_getattr to fetch ev_action and ev_qual. Also, the
......@@ -763,8 +762,6 @@ equalRuleLocks(RuleLock *rlock1, RuleLock *rlock2)
return false;
if (!equal(rule1->actions, rule2->actions))
return false;
if(rule1->is_auto != rule2->is_auto)
return false;
}
}
else if (rlock2 != NULL)
......
......@@ -12,7 +12,7 @@
* by PostgreSQL
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/bin/pg_dump/pg_dump.c,v 1.516 2009/01/22 20:16:07 tgl Exp $
* $PostgreSQL: pgsql/src/bin/pg_dump/pg_dump.c,v 1.517 2009/01/27 12:40:15 petere Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -4003,17 +4003,7 @@ getRules(int *numRules)
/* Make sure we are in proper schema */
selectSourceSchema("pg_catalog");
if (g_fout->remoteVersion >= 80400)
{
appendPQExpBuffer(query, "SELECT "
"tableoid, oid, rulename, "
"ev_class as ruletable, ev_type, is_instead, "
"ev_enabled "
"FROM pg_rewrite "
"WHERE NOT is_auto "
"ORDER BY oid");
}
else if (g_fout->remoteVersion >= 80300)
if (g_fout->remoteVersion >= 80300)
{
appendPQExpBuffer(query, "SELECT "
"tableoid, oid, rulename, "
......
......@@ -6,7 +6,7 @@
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/bin/pg_dump/pg_dump.h,v 1.148 2009/01/22 17:27:54 petere Exp $
* $PostgreSQL: pgsql/src/bin/pg_dump/pg_dump.h,v 1.149 2009/01/27 12:40:15 petere Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -304,6 +304,7 @@ typedef struct _ruleInfo
bool is_instead;
char ev_enabled;
bool separate; /* TRUE if must dump as separate item */
/* separate is always true for non-ON SELECT rules */
} RuleInfo;
typedef struct _triggerInfo
......
......@@ -37,7 +37,7 @@
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.519 2009/01/22 20:16:08 tgl Exp $
* $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.520 2009/01/27 12:40:15 petere Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -53,6 +53,6 @@
*/
/* yyyymmddN */
#define CATALOG_VERSION_NO 200901221
#define CATALOG_VERSION_NO 200901271
#endif
......@@ -11,7 +11,7 @@
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/catalog/pg_rewrite.h,v 1.32 2009/01/22 17:27:54 petere Exp $
* $PostgreSQL: pgsql/src/include/catalog/pg_rewrite.h,v 1.33 2009/01/27 12:40:15 petere Exp $
*
* NOTES
* the genbki.sh script reads this file and generates .bki
......@@ -40,15 +40,6 @@ CATALOG(pg_rewrite,2618)
char ev_enabled;
bool is_instead;
/*
* is_auto: True if the rule was automatically generated (update
* rules). This is tracked separately from the dependency system,
* because we want to allow overwriting the automatic update
* rules. So both automatically and manually generated rules have
* dependency type AUTO.
*/
bool is_auto;
/* NB: remaining fields must be accessed via heap_getattr */
text ev_qual;
text ev_action;
......@@ -65,15 +56,14 @@ typedef FormData_pg_rewrite *Form_pg_rewrite;
* compiler constants for pg_rewrite
* ----------------
*/
#define Natts_pg_rewrite 9
#define Natts_pg_rewrite 8
#define Anum_pg_rewrite_rulename 1
#define Anum_pg_rewrite_ev_class 2
#define Anum_pg_rewrite_ev_attr 3
#define Anum_pg_rewrite_ev_type 4
#define Anum_pg_rewrite_ev_enabled 5
#define Anum_pg_rewrite_is_instead 6
#define Anum_pg_rewrite_is_auto 7
#define Anum_pg_rewrite_ev_qual 8
#define Anum_pg_rewrite_ev_action 9
#define Anum_pg_rewrite_ev_qual 7
#define Anum_pg_rewrite_ev_action 8
#endif /* PG_REWRITE_H */
......@@ -6,7 +6,7 @@
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/rewrite/prs2lock.h,v 1.26 2009/01/22 17:27:55 petere Exp $
* $PostgreSQL: pgsql/src/include/rewrite/prs2lock.h,v 1.27 2009/01/27 12:40:15 petere Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -30,7 +30,6 @@ typedef struct RewriteRule
List *actions;
char enabled;
bool isInstead;
bool is_auto;
} RewriteRule;
/*
......
......@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/rewrite/rewriteDefine.h,v 1.32 2009/01/22 17:27:55 petere Exp $
* $PostgreSQL: pgsql/src/include/rewrite/rewriteDefine.h,v 1.33 2009/01/27 12:40:15 petere Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -29,7 +29,6 @@ extern void DefineQueryRewrite(char *rulename,
Node *event_qual,
CmdType event_type,
bool is_instead,
bool is_auto,
bool replace,
List *action);
......
......@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/rewrite/rewriteRemove.h,v 1.26 2009/01/22 17:27:55 petere Exp $
* $PostgreSQL: pgsql/src/include/rewrite/rewriteRemove.h,v 1.27 2009/01/27 12:40:15 petere Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -20,6 +20,5 @@
extern void RemoveRewriteRule(Oid owningRel, const char *ruleName,
DropBehavior behavior, bool missing_ok);
extern void RemoveRewriteRuleById(Oid ruleOid);
extern void RemoveAutomaticRulesOnEvent(Relation rel, CmdType event_type);
#endif /* REWRITEREMOVE_H */
/*-------------------------------------------------------------------------
*
* viewUpdate.h
*
* Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/rewrite/viewUpdate.h,v 1.1 2009/01/22 17:27:55 petere Exp $
*
*-------------------------------------------------------------------------
*/
#ifndef VIEW_UPDATE_H
#define VIEW_UPDATE_H
#include "nodes/parsenodes.h"
extern void
CreateViewUpdateRules(Oid viewOid, const Query *viewDef);
#endif /* VIEW_UPDATE_H */
......@@ -132,7 +132,6 @@ ALTER INDEX onek_unique1 RENAME TO tmp_onek_unique1;
ALTER INDEX tmp_onek_unique1 RENAME TO onek_unique1;
-- renaming views
CREATE VIEW tmp_view (unique1) AS SELECT unique1 FROM tenk1;
NOTICE: CREATE VIEW has created automatic view update rules
ALTER TABLE tmp_view RENAME TO tmp_view_new;
-- hack to ensure we get an indexscan here
ANALYZE tenk1;
......@@ -593,7 +592,6 @@ alter table atacc1 alter oid drop not null;
ERROR: cannot alter system column "oid"
-- try creating a view and altering that, should fail
create view myview as select * from atacc1;
NOTICE: CREATE VIEW has created automatic view update rules
alter table myview alter column test drop not null;
ERROR: "myview" is not a table
alter table myview alter column test set not null;
......@@ -661,7 +659,6 @@ ERROR: column "c3" of relation "def_test" does not exist
-- to allow insertions into it, and then alter the view to add
-- a default
create view def_view_test as select * from def_test;
NOTICE: CREATE VIEW has created automatic view update rules
create rule def_view_test_ins as
on insert to def_view_test
do instead insert into def_test select new.*;
......@@ -845,7 +842,6 @@ alter table atacc1 drop xmin;
ERROR: cannot drop system column "xmin"
-- try creating a view and altering that, should fail
create view myview as select * from atacc1;
NOTICE: CREATE VIEW has created automatic view update rules
select * from myview;
b | c | d
---+---+---
......@@ -1440,7 +1436,6 @@ create table alter1.t1(f1 serial primary key, f2 int check (f2 > 0));
NOTICE: CREATE TABLE will create implicit sequence "t1_f1_seq" for serial column "t1.f1"
NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "t1_pkey" for table "t1"
create view alter1.v1 as select * from alter1.t1;
NOTICE: CREATE VIEW has created automatic view update rules
create function alter1.plus1(int) returns int as 'select $1+1' language sql;
create domain alter1.posint integer check (value > 0);
create type alter1.ctype as (f1 int, f2 text);
......
......@@ -27,10 +27,8 @@ CREATE TABLE viewtest_tbl (a int, b int);
COPY viewtest_tbl FROM stdin;
CREATE OR REPLACE VIEW viewtest AS
SELECT * FROM viewtest_tbl;
NOTICE: CREATE VIEW has created automatic view update rules
CREATE OR REPLACE VIEW viewtest AS
SELECT * FROM viewtest_tbl WHERE a > 10;
NOTICE: CREATE VIEW has created automatic view update rules
SELECT * FROM viewtest;
a | b
----+----
......@@ -40,7 +38,6 @@ SELECT * FROM viewtest;
CREATE OR REPLACE VIEW viewtest AS
SELECT a, b FROM viewtest_tbl WHERE a > 5 ORDER BY b DESC;
NOTICE: CREATE VIEW has created automatic view update rules
SELECT * FROM viewtest;
a | b
----+----
......@@ -74,17 +71,13 @@ SET search_path TO temp_view_test, public;
CREATE TEMPORARY TABLE temp_table (a int, id int);
-- should be created in temp_view_test schema
CREATE VIEW v1 AS SELECT * FROM base_table;
NOTICE: CREATE VIEW has created automatic view update rules
-- should be created in temp object schema
CREATE VIEW v1_temp AS SELECT * FROM temp_table;
NOTICE: view "v1_temp" will be a temporary view
NOTICE: CREATE VIEW has created automatic view update rules
-- should be created in temp object schema
CREATE TEMP VIEW v2_temp AS SELECT * FROM base_table;
NOTICE: CREATE VIEW has created automatic view update rules
-- should be created in temp_views schema
CREATE VIEW temp_view_test.v2 AS SELECT * FROM base_table;
NOTICE: CREATE VIEW has created automatic view update rules
-- should fail
CREATE VIEW temp_view_test.v3_temp AS SELECT * FROM temp_table;
NOTICE: view "v3_temp" will be a temporary view
......@@ -114,25 +107,18 @@ CREATE VIEW v5_temp AS
NOTICE: view "v5_temp" will be a temporary view
-- subqueries
CREATE VIEW v4 AS SELECT * FROM base_table WHERE id IN (SELECT id FROM base_table2);
NOTICE: CREATE VIEW has created automatic view update rules
CREATE VIEW v5 AS SELECT t1.id, t2.a FROM base_table t1, (SELECT * FROM base_table2) t2;
CREATE VIEW v6 AS SELECT * FROM base_table WHERE EXISTS (SELECT 1 FROM base_table2);
NOTICE: CREATE VIEW has created automatic view update rules
CREATE VIEW v7 AS SELECT * FROM base_table WHERE NOT EXISTS (SELECT 1 FROM base_table2);
NOTICE: CREATE VIEW has created automatic view update rules
CREATE VIEW v8 AS SELECT * FROM base_table WHERE EXISTS (SELECT 1);
NOTICE: CREATE VIEW has created automatic view update rules
CREATE VIEW v6_temp AS SELECT * FROM base_table WHERE id IN (SELECT id FROM temp_table);
NOTICE: view "v6_temp" will be a temporary view
NOTICE: CREATE VIEW has created automatic view update rules
CREATE VIEW v7_temp AS SELECT t1.id, t2.a FROM base_table t1, (SELECT * FROM temp_table) t2;
NOTICE: view "v7_temp" will be a temporary view
CREATE VIEW v8_temp AS SELECT * FROM base_table WHERE EXISTS (SELECT 1 FROM temp_table);
NOTICE: view "v8_temp" will be a temporary view
NOTICE: CREATE VIEW has created automatic view update rules
CREATE VIEW v9_temp AS SELECT * FROM base_table WHERE NOT EXISTS (SELECT 1 FROM temp_table);
NOTICE: view "v9_temp" will be a temporary view
NOTICE: CREATE VIEW has created automatic view update rules
-- a view should also be temporary if it references a temporary view
CREATE VIEW v10_temp AS SELECT * FROM v7_temp;
NOTICE: view "v10_temp" will be a temporary view
......@@ -144,10 +130,8 @@ NOTICE: view "v12_temp" will be a temporary view
CREATE SEQUENCE seq1;
CREATE TEMPORARY SEQUENCE seq1_temp;
CREATE VIEW v9 AS SELECT seq1.is_called FROM seq1;
NOTICE: CREATE VIEW has created automatic view update rules
CREATE VIEW v13_temp AS SELECT seq1_temp.is_called FROM seq1_temp;
NOTICE: view "v13_temp" will be a temporary view
NOTICE: CREATE VIEW has created automatic view update rules
SELECT relname FROM pg_class
WHERE relname LIKE 'v_'
AND relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = 'temp_view_test')
......@@ -235,7 +219,6 @@ CREATE TEMP TABLE tmptbl (i int, j int);
CREATE VIEW pubview AS SELECT * FROM tbl1 WHERE tbl1.a
BETWEEN (SELECT d FROM tbl2 WHERE c = 1) AND (SELECT e FROM tbl3 WHERE f = 2)
AND EXISTS (SELECT g FROM tbl4 LEFT JOIN tbl3 ON tbl4.h = tbl3.f);
NOTICE: CREATE VIEW has created automatic view update rules
SELECT count(*) FROM pg_class where relname = 'pubview'
AND relnamespace IN (SELECT OID FROM pg_namespace WHERE nspname = 'testviewschm2');
count
......@@ -249,7 +232,6 @@ BETWEEN (SELECT d FROM tbl2 WHERE c = 1) AND (SELECT e FROM tbl3 WHERE f = 2)
AND EXISTS (SELECT g FROM tbl4 LEFT JOIN tbl3 ON tbl4.h = tbl3.f)
AND NOT EXISTS (SELECT g FROM tbl4 LEFT JOIN tmptbl ON tbl4.h = tmptbl.j);
NOTICE: view "mytempview" will be a temporary view
NOTICE: CREATE VIEW has created automatic view update rules
SELECT count(*) FROM pg_class where relname LIKE 'mytempview'
And relnamespace IN (SELECT OID FROM pg_namespace WHERE nspname LIKE 'pg_temp%');
count
......
......@@ -13,7 +13,6 @@ ERROR: view "test_view_exists" does not exist
DROP VIEW IF EXISTS test_view_exists;
NOTICE: view "test_view_exists" does not exist, skipping
CREATE VIEW test_view_exists AS select * from test_exists;
NOTICE: CREATE VIEW has created automatic view update rules
DROP VIEW IF EXISTS test_view_exists;
DROP VIEW test_view_exists;
ERROR: view "test_view_exists" does not exist
......
......@@ -79,7 +79,6 @@ EXECUTE prepstmt2(123);
-- but should trigger invalidation anyway
CREATE TEMP VIEW pcacheview AS
SELECT * FROM pcachetest;
NOTICE: CREATE VIEW has created automatic view update rules
PREPARE vprep AS SELECT * FROM pcacheview;
EXECUTE vprep;
q1 | q2
......@@ -237,9 +236,6 @@ select cachebug();
NOTICE: table "temptable" does not exist, skipping
CONTEXT: SQL statement "drop table if exists temptable cascade"
PL/pgSQL function "cachebug" line 3 at SQL statement
NOTICE: CREATE VIEW has created automatic view update rules
CONTEXT: SQL statement "create temp view vv as select * from temptable"
PL/pgSQL function "cachebug" line 5 at SQL statement
NOTICE: 1
NOTICE: 2
NOTICE: 3
......@@ -252,9 +248,6 @@ select cachebug();
NOTICE: drop cascades to view vv
CONTEXT: SQL statement "drop table if exists temptable cascade"
PL/pgSQL function "cachebug" line 3 at SQL statement
NOTICE: CREATE VIEW has created automatic view update rules
CONTEXT: SQL statement "create temp view vv as select * from temptable"
PL/pgSQL function "cachebug" line 5 at SQL statement
NOTICE: 1
NOTICE: 2
NOTICE: 3
......
......@@ -1229,7 +1229,6 @@ ROLLBACK;
-- WHERE CURRENT OF may someday work with views, but today is not that day.
-- For now, just make sure it errors out cleanly.
CREATE TEMP VIEW ucview AS SELECT * FROM uctest;
NOTICE: CREATE VIEW has created automatic view update rules
CREATE RULE ucrule AS ON DELETE TO ucview DO INSTEAD
DELETE FROM uctest WHERE f1 = OLD.f1;
BEGIN;
......
......@@ -189,12 +189,9 @@ DELETE FROM atest3; -- ok
-- views
SET SESSION AUTHORIZATION regressuser3;
CREATE VIEW atestv1 AS SELECT * FROM atest1; -- ok
NOTICE: CREATE VIEW has created automatic view update rules
/* The next *should* fail, but it's not implemented that way yet. */
CREATE VIEW atestv2 AS SELECT * FROM atest2;
NOTICE: CREATE VIEW has created automatic view update rules
CREATE VIEW atestv3 AS SELECT * FROM atest3; -- ok
NOTICE: CREATE VIEW has created automatic view update rules
SELECT * FROM atestv1; -- ok
a | b
---+-----
......@@ -222,7 +219,6 @@ SELECT * FROM atestv3; -- ok
(0 rows)
CREATE VIEW atestv4 AS SELECT * FROM atestv3; -- nested view
NOTICE: CREATE VIEW has created automatic view update rules
SELECT * FROM atestv4; -- ok
one | two | three
-----+-----+-------
......
......@@ -195,7 +195,6 @@ SELECT * FROM foochild;
DROP TABLE foochild;
-- Rules and views
CREATE TEMP VIEW voo AS SELECT f1, f2 FROM foo;
NOTICE: CREATE VIEW has created automatic view update rules
CREATE RULE voo_i AS ON INSERT TO voo DO INSTEAD
INSERT INTO foo VALUES(new.*, 57);
INSERT INTO voo VALUES(11,'zit');
......
......@@ -10,7 +10,6 @@ create table rtest_t1 (a int4, b int4);
create table rtest_t2 (a int4, b int4);
create table rtest_t3 (a int4, b int4);
create view rtest_v1 as select * from rtest_t1;
NOTICE: CREATE VIEW has created automatic view update rules
create rule rtest_v1_ins as on insert to rtest_v1 do instead
insert into rtest_t1 values (new.a, new.b);
create rule rtest_v1_upd as on update to rtest_v1 do instead
......@@ -756,12 +755,9 @@ create table rtest_view3 (a int4, b text);
create table rtest_view4 (a int4, b text, c int4);
create view rtest_vview1 as select a, b from rtest_view1 X
where 0 < (select count(*) from rtest_view2 Y where Y.a = X.a);
NOTICE: CREATE VIEW has created automatic view update rules
create view rtest_vview2 as select a, b from rtest_view1 where v;
NOTICE: CREATE VIEW has created automatic view update rules
create view rtest_vview3 as select a, b from rtest_vview2 X
where 0 < (select count(*) from rtest_view2 Y where Y.a = X.a);
NOTICE: CREATE VIEW has created automatic view update rules
create view rtest_vview4 as select X.a, X.b, count(Y.a) as refcount
from rtest_view1 X, rtest_view2 Y
where X.a = Y.a
......@@ -1338,7 +1334,7 @@ SELECT viewname, definition FROM pg_views WHERE schemaname <> 'information_schem
SELECT tablename, rulename, definition FROM pg_rules
ORDER BY tablename, rulename;
tablename | rulename | definition
---------------+-----------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
---------------+-----------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
pg_settings | pg_settings_n | CREATE RULE pg_settings_n AS ON UPDATE TO pg_settings DO INSTEAD NOTHING;
pg_settings | pg_settings_u | CREATE RULE pg_settings_u AS ON UPDATE TO pg_settings WHERE (new.name = old.name) DO SELECT set_config(old.name, new.setting, false) AS set_config;
rtest_emp | rtest_emp_del | CREATE RULE rtest_emp_del AS ON DELETE TO rtest_emp DO INSERT INTO rtest_emplog (ename, who, action, newsal, oldsal) VALUES (old.ename, "current_user"(), 'fired'::bpchar, '$0.00'::money, old.salary);
......@@ -1363,21 +1359,12 @@ SELECT tablename, rulename, definition FROM pg_rules
rtest_v1 | rtest_v1_del | CREATE RULE rtest_v1_del AS ON DELETE TO rtest_v1 DO INSTEAD DELETE FROM rtest_t1 WHERE (rtest_t1.a = old.a);
rtest_v1 | rtest_v1_ins | CREATE RULE rtest_v1_ins AS ON INSERT TO rtest_v1 DO INSTEAD INSERT INTO rtest_t1 (a, b) VALUES (new.a, new.b);
rtest_v1 | rtest_v1_upd | CREATE RULE rtest_v1_upd AS ON UPDATE TO rtest_v1 DO INSTEAD UPDATE rtest_t1 SET a = new.a, b = new.b WHERE (rtest_t1.a = old.a);
rtest_vview1 | _DELETE | CREATE RULE "_DELETE" AS ON DELETE TO rtest_vview1 DO INSTEAD DELETE FROM rtest_view1 x WHERE ((((old.a IS NULL) AND (x.a IS NULL)) OR (old.a = x.a)) AND (((old.b IS NULL) AND (x.b IS NULL)) OR (old.b = x.b))) RETURNING old.a, old.b;
rtest_vview1 | _INSERT | CREATE RULE "_INSERT" AS ON INSERT TO rtest_vview1 DO INSTEAD INSERT INTO rtest_view1 (a, b) VALUES (new.a, new.b) RETURNING new.a, new.b;
rtest_vview1 | _UPDATE | CREATE RULE "_UPDATE" AS ON UPDATE TO rtest_vview1 DO INSTEAD UPDATE rtest_view1 x SET a = new.a, b = new.b WHERE ((((old.a IS NULL) AND (x.a IS NULL)) OR (old.a = x.a)) AND (((old.b IS NULL) AND (x.b IS NULL)) OR (old.b = x.b))) RETURNING new.a, new.b;
rtest_vview2 | _DELETE | CREATE RULE "_DELETE" AS ON DELETE TO rtest_vview2 DO INSTEAD DELETE FROM rtest_view1 WHERE ((((old.a IS NULL) AND (rtest_view1.a IS NULL)) OR (old.a = rtest_view1.a)) AND (((old.b IS NULL) AND (rtest_view1.b IS NULL)) OR (old.b = rtest_view1.b))) RETURNING old.a, old.b;
rtest_vview2 | _INSERT | CREATE RULE "_INSERT" AS ON INSERT TO rtest_vview2 DO INSTEAD INSERT INTO rtest_view1 (a, b) VALUES (new.a, new.b) RETURNING new.a, new.b;
rtest_vview2 | _UPDATE | CREATE RULE "_UPDATE" AS ON UPDATE TO rtest_vview2 DO INSTEAD UPDATE rtest_view1 SET a = new.a, b = new.b WHERE ((((old.a IS NULL) AND (rtest_view1.a IS NULL)) OR (old.a = rtest_view1.a)) AND (((old.b IS NULL) AND (rtest_view1.b IS NULL)) OR (old.b = rtest_view1.b))) RETURNING new.a, new.b;
rtest_vview3 | _DELETE | CREATE RULE "_DELETE" AS ON DELETE TO rtest_vview3 DO INSTEAD DELETE FROM rtest_vview2 x WHERE ((((old.a IS NULL) AND (x.a IS NULL)) OR (old.a = x.a)) AND (((old.b IS NULL) AND (x.b IS NULL)) OR (old.b = x.b))) RETURNING old.a, old.b;
rtest_vview3 | _INSERT | CREATE RULE "_INSERT" AS ON INSERT TO rtest_vview3 DO INSTEAD INSERT INTO rtest_vview2 (a, b) VALUES (new.a, new.b) RETURNING new.a, new.b;
rtest_vview3 | _UPDATE | CREATE RULE "_UPDATE" AS ON UPDATE TO rtest_vview3 DO INSTEAD UPDATE rtest_vview2 x SET a = new.a, b = new.b WHERE ((((old.a IS NULL) AND (x.a IS NULL)) OR (old.a = x.a)) AND (((old.b IS NULL) AND (x.b IS NULL)) OR (old.b = x.b))) RETURNING new.a, new.b;
shoelace | shoelace_del | CREATE RULE shoelace_del AS ON DELETE TO shoelace DO INSTEAD DELETE FROM shoelace_data WHERE (shoelace_data.sl_name = old.sl_name);
shoelace | shoelace_ins | CREATE RULE shoelace_ins AS ON INSERT TO shoelace DO INSTEAD INSERT INTO shoelace_data (sl_name, sl_avail, sl_color, sl_len, sl_unit) VALUES (new.sl_name, new.sl_avail, new.sl_color, new.sl_len, new.sl_unit);
shoelace | shoelace_upd | CREATE RULE shoelace_upd AS ON UPDATE TO shoelace DO INSTEAD UPDATE shoelace_data SET sl_name = new.sl_name, sl_avail = new.sl_avail, sl_color = new.sl_color, sl_len = new.sl_len, sl_unit = new.sl_unit WHERE (shoelace_data.sl_name = old.sl_name);
shoelace_data | log_shoelace | CREATE RULE log_shoelace AS ON UPDATE TO shoelace_data WHERE (new.sl_avail <> old.sl_avail) DO INSERT INTO shoelace_log (sl_name, sl_avail, log_who, log_when) VALUES (new.sl_name, new.sl_avail, 'Al Bundy'::name, 'Thu Jan 01 00:00:00 1970'::timestamp without time zone);
shoelace_ok | shoelace_ok_ins | CREATE RULE shoelace_ok_ins AS ON INSERT TO shoelace_ok DO INSTEAD UPDATE shoelace SET sl_avail = (shoelace.sl_avail + new.ok_quant) WHERE (shoelace.sl_name = new.ok_name);
(38 rows)
(29 rows)
--
-- CREATE OR REPLACE RULE
......@@ -1479,7 +1466,6 @@ insert into test_2 (name) values ('Test 4');
insert into test_3 (name) values ('Test 5');
insert into test_3 (name) values ('Test 6');
create view id_ordered as select * from id order by id;
NOTICE: CREATE VIEW has created automatic view update rules
create rule update_id_ordered as on update to id_ordered
do instead update id set name = new.name where id = old.id;
select * from id_ordered;
......
......@@ -349,7 +349,6 @@ create temp table shipped (
);
create temp view shipped_view as
select * from shipped where ttype = 'wt';
NOTICE: CREATE VIEW has created automatic view update rules
create rule shipped_view_insert as on insert to shipped_view do instead
insert into shipped values('wt', new.ordnum, new.partnum, new.value);
insert into parts (partnum, cost) values (1, 1234.56);
......
CREATE TABLE vutest1 (a integer, b text);
INSERT INTO vutest1 VALUES (1, 'one');
INSERT INTO vutest1 VALUES (2, 'two');
-- simple view updatability conditions
CREATE VIEW vutestv1 AS SELECT a, b FROM vutest1;
NOTICE: CREATE VIEW has created automatic view update rules
CREATE VIEW vutestv2 AS SELECT * FROM vutest1;
NOTICE: CREATE VIEW has created automatic view update rules
CREATE VIEW vutestv3 AS SELECT b, a FROM vutest1;
NOTICE: CREATE VIEW has created automatic view update rules
CREATE VIEW vutestv4 AS SELECT a, b FROM vutest1 WHERE a < 5;
NOTICE: CREATE VIEW has created automatic view update rules
-- not updatable tests:
CREATE VIEW vutestv5 AS SELECT sum(a) FROM vutest1; -- aggregate function
CREATE VIEW vutestv6 AS SELECT b FROM vutest1 GROUP BY b; -- GROUP BY
CREATE VIEW vutestv7 AS SELECT l.b AS x, r.b AS y FROM vutest1 l, vutest1 r WHERE r.a = l.a; -- JOIN
CREATE VIEW vutestv8 AS SELECT 42; -- no table
CREATE VIEW vutestv9 AS SELECT a * 2 AS x, b || b AS y FROM vutest1; -- derived columns
CREATE VIEW vutestv10 AS SELECT a AS x, a AS y FROM vutest1; -- column referenced more than once
CREATE VIEW vutestv11 AS SELECT * FROM generate_series(1, 5); -- table function
CREATE VIEW vutestv12 AS SELECT xmin, xmax, a, b FROM vutest1; -- system columns
CREATE VIEW vutestv13 AS SELECT DISTINCT a, b FROM vutest1; -- DISTINCT
CREATE VIEW vutestv14 AS SELECT a, b FROM vutest1 WHERE a > (SELECT avg(a) FROM vutest1); -- *is* updatable, but SQL standard disallows this
NOTICE: CREATE VIEW has created automatic view update rules
CREATE VIEW vutestv15 AS SELECT a, b FROM vutest1 UNION ALL SELECT a, b FROM vutest1; -- UNION
CREATE VIEW vutestv16 AS SELECT x, y FROM (SELECT * FROM vutest1) AS foo (x, y); -- subquery ("derived table"); SQL standard allows this
CREATE VIEW vutestv17 AS SELECT a, 5, b FROM vutest1; -- constant
CREATE VIEW vutestv18 AS SELECT a, b FROM vutest1 LIMIT 1; -- LIMIT
CREATE VIEW vutestv19 AS SELECT a, b FROM vutest1 OFFSET 1; -- OFFSET
CREATE VIEW vutestv101 AS SELECT a, rank() OVER (PARTITION BY a ORDER BY b DESC) FROM vutest1; -- window function
CREATE VIEW vutestv102 AS WITH foo AS (SELECT a, b FROM vutest1) SELECT * FROM foo; -- SQL standard allows this
CREATE VIEW vutestv103 AS WITH RECURSIVE t(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM t) SELECT a FROM vutest1; -- recursive
INSERT INTO vutestv1 VALUES (3, 'three');
INSERT INTO vutestv2 VALUES (4, 'four');
INSERT INTO vutestv3 VALUES (5, 'five'); -- fail
ERROR: invalid input syntax for integer: "five"
LINE 1: INSERT INTO vutestv3 VALUES (5, 'five');
^
INSERT INTO vutestv3 VALUES ('five', 5);
INSERT INTO vutestv3 (a, b) VALUES (6, 'six');
INSERT INTO vutestv4 VALUES (7, 'seven'); -- ok, but would be check option issue
INSERT INTO vutestv5 VALUES (8); -- fail
ERROR: view is not updatable
HINT: You need an unconditional ON INSERT DO INSTEAD rule.
SELECT * FROM vutest1;
a | b
---+-------
1 | one
2 | two
3 | three
4 | four
5 | five
6 | six
7 | seven
(7 rows)
SELECT * FROM vutestv1;
a | b
---+-------
1 | one
2 | two
3 | three
4 | four
5 | five
6 | six
7 | seven
(7 rows)
SELECT * FROM vutestv2;
a | b
---+-------
1 | one
2 | two
3 | three
4 | four
5 | five
6 | six
7 | seven
(7 rows)
SELECT * FROM vutestv3;
b | a
-------+---
one | 1
two | 2
three | 3
four | 4
five | 5
six | 6
seven | 7
(7 rows)
SELECT * FROM vutestv4;
a | b
---+-------
1 | one
2 | two
3 | three
4 | four
(4 rows)
SELECT * FROM vutestv5;
sum
-----
28
(1 row)
UPDATE vutestv1 SET b = 'a lot' WHERE a = 7;
DELETE FROM vutestv2 WHERE a = 1;
UPDATE vutestv4 SET b = b || '!' WHERE a > 1;
DELETE FROM vutestv4 WHERE a > 3;
UPDATE vutestv6 SET b = 37; -- fail
ERROR: view is not updatable
HINT: You need an unconditional ON UPDATE DO INSTEAD rule.
DELETE FROM vutestv5; -- fail
ERROR: view is not updatable
HINT: You need an unconditional ON DELETE DO INSTEAD rule.
SELECT * FROM vutest1 ORDER BY a, b;
a | b
---+--------
2 | two!
3 | three!
5 | five
6 | six
7 | a lot
(5 rows)
SELECT * FROM vutestv1 ORDER BY a, b;
a | b
---+--------
2 | two!
3 | three!
5 | five
6 | six
7 | a lot
(5 rows)
SELECT * FROM vutestv2 ORDER BY a, b;
a | b
---+--------
2 | two!
3 | three!
5 | five
6 | six
7 | a lot
(5 rows)
SELECT * FROM vutestv4 ORDER BY a, b;
a | b
---+--------
2 | two!
3 | three!
(2 rows)
TRUNCATE TABLE vutest1;
-- views on views
CREATE VIEW vutestv20 AS SELECT a AS x, b AS y FROM vutestv1;
NOTICE: CREATE VIEW has created automatic view update rules
CREATE VIEW vutestv21 AS SELECT x AS a FROM vutestv20 WHERE x % 2 = 0;
NOTICE: CREATE VIEW has created automatic view update rules
CREATE VIEW vutestv22 AS SELECT sum(a) FROM vutestv21; -- not updatable
CREATE VIEW vutestv23 AS SELECT * FROM vutestv12; -- not updatable
INSERT INTO vutestv20 (x, y) VALUES (1, 'one');
INSERT INTO vutestv20 (x, y) VALUES (3, 'three');
INSERT INTO vutestv21 VALUES (2);
SELECT * FROM vutest1;
a | b
---+-------
1 | one
3 | three
2 |
(3 rows)
SELECT * FROM vutestv20;
x | y
---+-------
1 | one
3 | three
2 |
(3 rows)
SELECT * FROM vutestv21;
a
---
2
(1 row)
UPDATE vutestv20 SET y = 'eins' WHERE x = 1;
UPDATE vutestv21 SET a = 222;
SELECT * FROM vutest1;
a | b
-----+-------
3 | three
1 | eins
222 |
(3 rows)
SELECT * FROM vutestv20;
x | y
-----+-------
3 | three
1 | eins
222 |
(3 rows)
SELECT * FROM vutestv21;
a
-----
222
(1 row)
DELETE FROM vutestv20 WHERE x = 3;
SELECT * FROM vutest1;
a | b
-----+------
1 | eins
222 |
(2 rows)
SELECT * FROM vutestv20;
x | y
-----+------
1 | eins
222 |
(2 rows)
SELECT * FROM vutestv21;
a
-----
222
(1 row)
-- insert tests
CREATE TABLE vutest2 (a int PRIMARY KEY, b text NOT NULL, c text NOT NULL DEFAULT 'foo');
NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "vutest2_pkey" for table "vutest2"
CREATE VIEW vutestv30 AS SELECT a, b, c FROM vutest2;
NOTICE: CREATE VIEW has created automatic view update rules
CREATE VIEW vutestv31 AS SELECT a, b FROM vutest2;
NOTICE: CREATE VIEW has created automatic view update rules
CREATE VIEW vutestv32 AS SELECT a, c FROM vutest2;
NOTICE: CREATE VIEW has created automatic view update rules
INSERT INTO vutestv30 VALUES (1, 'one', 'eins');
INSERT INTO vutestv31 VALUES (2, 'two');
INSERT INTO vutestv32 VALUES (3, 'drei'); -- fail
ERROR: null value in column "b" violates not-null constraint
UPDATE vutestv31 SET a = 22 WHERE a = 2;
UPDATE vutestv32 SET c = 'drei!' WHERE a = 3;
SELECT rulename, definition FROM pg_rules WHERE tablename LIKE 'vutestv%' ORDER BY tablename, rulename;
rulename | definition
----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
_DELETE | CREATE RULE "_DELETE" AS ON DELETE TO vutestv1 DO INSTEAD DELETE FROM vutest1 WHERE ((((old.a IS NULL) AND (vutest1.a IS NULL)) OR (old.a = vutest1.a)) AND (((old.b IS NULL) AND (vutest1.b IS NULL)) OR (old.b = vutest1.b))) RETURNING old.a, old.b;
_INSERT | CREATE RULE "_INSERT" AS ON INSERT TO vutestv1 DO INSTEAD INSERT INTO vutest1 (a, b) VALUES (new.a, new.b) RETURNING new.a, new.b;
_UPDATE | CREATE RULE "_UPDATE" AS ON UPDATE TO vutestv1 DO INSTEAD UPDATE vutest1 SET a = new.a, b = new.b WHERE ((((old.a IS NULL) AND (vutest1.a IS NULL)) OR (old.a = vutest1.a)) AND (((old.b IS NULL) AND (vutest1.b IS NULL)) OR (old.b = vutest1.b))) RETURNING new.a, new.b;
_DELETE | CREATE RULE "_DELETE" AS ON DELETE TO vutestv14 DO INSTEAD DELETE FROM vutest1 WHERE ((((old.a IS NULL) AND (vutest1.a IS NULL)) OR (old.a = vutest1.a)) AND (((old.b IS NULL) AND (vutest1.b IS NULL)) OR (old.b = vutest1.b))) RETURNING old.a, old.b;
_INSERT | CREATE RULE "_INSERT" AS ON INSERT TO vutestv14 DO INSTEAD INSERT INTO vutest1 (a, b) VALUES (new.a, new.b) RETURNING new.a, new.b;
_UPDATE | CREATE RULE "_UPDATE" AS ON UPDATE TO vutestv14 DO INSTEAD UPDATE vutest1 SET a = new.a, b = new.b WHERE ((((old.a IS NULL) AND (vutest1.a IS NULL)) OR (old.a = vutest1.a)) AND (((old.b IS NULL) AND (vutest1.b IS NULL)) OR (old.b = vutest1.b))) RETURNING new.a, new.b;
_DELETE | CREATE RULE "_DELETE" AS ON DELETE TO vutestv2 DO INSTEAD DELETE FROM vutest1 WHERE ((((old.a IS NULL) AND (vutest1.a IS NULL)) OR (old.a = vutest1.a)) AND (((old.b IS NULL) AND (vutest1.b IS NULL)) OR (old.b = vutest1.b))) RETURNING old.a, old.b;
_INSERT | CREATE RULE "_INSERT" AS ON INSERT TO vutestv2 DO INSTEAD INSERT INTO vutest1 (a, b) VALUES (new.a, new.b) RETURNING new.a, new.b;
_UPDATE | CREATE RULE "_UPDATE" AS ON UPDATE TO vutestv2 DO INSTEAD UPDATE vutest1 SET a = new.a, b = new.b WHERE ((((old.a IS NULL) AND (vutest1.a IS NULL)) OR (old.a = vutest1.a)) AND (((old.b IS NULL) AND (vutest1.b IS NULL)) OR (old.b = vutest1.b))) RETURNING new.a, new.b;
_DELETE | CREATE RULE "_DELETE" AS ON DELETE TO vutestv20 DO INSTEAD DELETE FROM vutestv1 WHERE ((((old.x IS NULL) AND (vutestv1.a IS NULL)) OR (old.x = vutestv1.a)) AND (((old.y IS NULL) AND (vutestv1.b IS NULL)) OR (old.y = vutestv1.b))) RETURNING old.x, old.y;
_INSERT | CREATE RULE "_INSERT" AS ON INSERT TO vutestv20 DO INSTEAD INSERT INTO vutestv1 (a, b) VALUES (new.x, new.y) RETURNING new.x AS a, new.y AS b;
_UPDATE | CREATE RULE "_UPDATE" AS ON UPDATE TO vutestv20 DO INSTEAD UPDATE vutestv1 SET a = new.x, b = new.y WHERE ((((old.x IS NULL) AND (vutestv1.a IS NULL)) OR (old.x = vutestv1.a)) AND (((old.y IS NULL) AND (vutestv1.b IS NULL)) OR (old.y = vutestv1.b))) RETURNING new.x AS a, new.y AS b;
_DELETE | CREATE RULE "_DELETE" AS ON DELETE TO vutestv21 DO INSTEAD DELETE FROM vutestv20 WHERE ((((old.a IS NULL) AND (vutestv20.x IS NULL)) OR (old.a = vutestv20.x))) RETURNING old.a;
_INSERT | CREATE RULE "_INSERT" AS ON INSERT TO vutestv21 DO INSTEAD INSERT INTO vutestv20 (x) VALUES (new.a) RETURNING new.a AS x;
_UPDATE | CREATE RULE "_UPDATE" AS ON UPDATE TO vutestv21 DO INSTEAD UPDATE vutestv20 SET x = new.a WHERE ((((old.a IS NULL) AND (vutestv20.x IS NULL)) OR (old.a = vutestv20.x))) RETURNING new.a AS x;
_DELETE | CREATE RULE "_DELETE" AS ON DELETE TO vutestv3 DO INSTEAD DELETE FROM vutest1 WHERE ((((old.b IS NULL) AND (vutest1.b IS NULL)) OR (old.b = vutest1.b)) AND (((old.a IS NULL) AND (vutest1.a IS NULL)) OR (old.a = vutest1.a))) RETURNING old.b, old.a;
_INSERT | CREATE RULE "_INSERT" AS ON INSERT TO vutestv3 DO INSTEAD INSERT INTO vutest1 (b, a) VALUES (new.b, new.a) RETURNING new.a AS b, new.b AS a;
_UPDATE | CREATE RULE "_UPDATE" AS ON UPDATE TO vutestv3 DO INSTEAD UPDATE vutest1 SET b = new.b, a = new.a WHERE ((((old.b IS NULL) AND (vutest1.b IS NULL)) OR (old.b = vutest1.b)) AND (((old.a IS NULL) AND (vutest1.a IS NULL)) OR (old.a = vutest1.a))) RETURNING new.a AS b, new.b AS a;
_DELETE | CREATE RULE "_DELETE" AS ON DELETE TO vutestv30 DO INSTEAD DELETE FROM vutest2 WHERE ((((old.a IS NULL) AND (vutest2.a IS NULL)) OR (old.a = vutest2.a)) AND (((old.b IS NULL) AND (vutest2.b IS NULL)) OR (old.b = vutest2.b) OR (((old.c IS NULL) AND (vutest2.c IS NULL)) OR (old.c = vutest2.c)))) RETURNING old.a, old.b, old.c;
_INSERT | CREATE RULE "_INSERT" AS ON INSERT TO vutestv30 DO INSTEAD INSERT INTO vutest2 (a, b, c) VALUES (new.a, new.b, new.c) RETURNING new.a, new.b, new.c;
_UPDATE | CREATE RULE "_UPDATE" AS ON UPDATE TO vutestv30 DO INSTEAD UPDATE vutest2 SET a = new.a, b = new.b, c = new.c WHERE ((((old.a IS NULL) AND (vutest2.a IS NULL)) OR (old.a = vutest2.a)) AND (((old.b IS NULL) AND (vutest2.b IS NULL)) OR (old.b = vutest2.b) OR (((old.c IS NULL) AND (vutest2.c IS NULL)) OR (old.c = vutest2.c)))) RETURNING new.a, new.b, new.c;
_DELETE | CREATE RULE "_DELETE" AS ON DELETE TO vutestv31 DO INSTEAD DELETE FROM vutest2 WHERE ((((old.a IS NULL) AND (vutest2.a IS NULL)) OR (old.a = vutest2.a)) AND (((old.b IS NULL) AND (vutest2.b IS NULL)) OR (old.b = vutest2.b))) RETURNING old.a, old.b;
_INSERT | CREATE RULE "_INSERT" AS ON INSERT TO vutestv31 DO INSTEAD INSERT INTO vutest2 (a, b) VALUES (new.a, new.b) RETURNING new.a, new.b;
_UPDATE | CREATE RULE "_UPDATE" AS ON UPDATE TO vutestv31 DO INSTEAD UPDATE vutest2 SET a = new.a, b = new.b WHERE ((((old.a IS NULL) AND (vutest2.a IS NULL)) OR (old.a = vutest2.a)) AND (((old.b IS NULL) AND (vutest2.b IS NULL)) OR (old.b = vutest2.b))) RETURNING new.a, new.b;
_DELETE | CREATE RULE "_DELETE" AS ON DELETE TO vutestv32 DO INSTEAD DELETE FROM vutest2 WHERE ((((old.a IS NULL) AND (vutest2.a IS NULL)) OR (old.a = vutest2.a)) AND (((old.c IS NULL) AND (vutest2.c IS NULL)) OR (old.c = vutest2.c))) RETURNING old.a, old.c;
_INSERT | CREATE RULE "_INSERT" AS ON INSERT TO vutestv32 DO INSTEAD INSERT INTO vutest2 (a, c) VALUES (new.a, new.c) RETURNING new.a, new.c;
_UPDATE | CREATE RULE "_UPDATE" AS ON UPDATE TO vutestv32 DO INSTEAD UPDATE vutest2 SET a = new.a, c = new.c WHERE ((((old.a IS NULL) AND (vutest2.a IS NULL)) OR (old.a = vutest2.a)) AND (((old.c IS NULL) AND (vutest2.c IS NULL)) OR (old.c = vutest2.c))) RETURNING new.a, new.c;
_DELETE | CREATE RULE "_DELETE" AS ON DELETE TO vutestv4 DO INSTEAD DELETE FROM vutest1 WHERE ((((old.a IS NULL) AND (vutest1.a IS NULL)) OR (old.a = vutest1.a)) AND (((old.b IS NULL) AND (vutest1.b IS NULL)) OR (old.b = vutest1.b))) RETURNING old.a, old.b;
_INSERT | CREATE RULE "_INSERT" AS ON INSERT TO vutestv4 DO INSTEAD INSERT INTO vutest1 (a, b) VALUES (new.a, new.b) RETURNING new.a, new.b;
_UPDATE | CREATE RULE "_UPDATE" AS ON UPDATE TO vutestv4 DO INSTEAD UPDATE vutest1 SET a = new.a, b = new.b WHERE ((((old.a IS NULL) AND (vutest1.a IS NULL)) OR (old.a = vutest1.a)) AND (((old.b IS NULL) AND (vutest1.b IS NULL)) OR (old.b = vutest1.b))) RETURNING new.a, new.b;
(30 rows)
-- interaction of manual and automatic rules, view replacement
CREATE VIEW vutestv40 AS SELECT a, b FROM vutest1;
NOTICE: CREATE VIEW has created automatic view update rules
CREATE RULE zmy_update AS ON UPDATE TO vutestv40 DO INSTEAD DELETE FROM vutest1; -- drops automatic _UPDATE rule
CREATE RULE "_INSERT" AS ON INSERT TO vutestv40 DO INSTEAD DELETE FROM vutest1; -- replaces automatic _INSERT rule
CREATE RULE zmy_delete AS ON DELETE TO vutestv40 DO ALSO DELETE FROM vutest1; -- leaves automatic _DELETE rule (because of ALSO)
CREATE VIEW vutestv41 AS SELECT a + 1 AS aa, b FROM vutest1; -- not updatable
CREATE RULE "_UPDATE" AS ON UPDATE TO vutestv41 DO INSTEAD UPDATE vutest1 SET a = new.aa - 1, b = new.b WHERE a = old.aa - 1 AND b = old.b;
CREATE OR REPLACE VIEW vutestv41 AS SELECT a AS aa, b FROM vutest1; -- *now* updatable, manual _UPDATE rule stays
WARNING: automatic UPDATE rule not created because manually created UPDATE rule exists
HINT: If you prefer to have the automatic rule, drop the manually created rule and run CREATE OR REPLACE VIEW again.
NOTICE: CREATE VIEW has created automatic view update rules
CREATE VIEW vutestv42 AS SELECT a + 1 AS aa, b FROM vutest1; -- not updatable
CREATE RULE zmy_update AS ON UPDATE TO vutestv42 DO INSTEAD UPDATE vutest1 SET a = new.aa - 1, b = new.b WHERE a = old.aa - 1 AND b = old.b;
CREATE OR REPLACE VIEW vutestv42 AS SELECT a AS aa, b FROM vutest1; -- *now* updatable, zmy_update stays, no _UPDATE created
WARNING: automatic UPDATE rule not created because manually created UPDATE rule exists
HINT: If you prefer to have the automatic rule, drop the manually created rule and run CREATE OR REPLACE VIEW again.
NOTICE: CREATE VIEW has created automatic view update rules
CREATE VIEW vutestv43 AS SELECT a AS aa, b FROM vutest1; -- updatable
NOTICE: CREATE VIEW has created automatic view update rules
CREATE RULE zmy_update AS ON UPDATE TO vutestv43 DO INSTEAD DELETE FROM vutest1; -- drops automatic _UPDATE rule
CREATE OR REPLACE VIEW vutestv43 AS SELECT a + 1 AS aa, b FROM vutest1; -- no longer updatable, automatic rules are deleted, manual rules kept
CREATE VIEW vutestv44 AS SELECT a, b FROM vutest1; -- updatable
NOTICE: CREATE VIEW has created automatic view update rules
CREATE RULE zmy_update AS ON UPDATE TO vutestv44 DO INSTEAD DELETE FROM vutest1; -- drops automatic _UPDATE rule
CREATE OR REPLACE VIEW vutestv44 AS SELECT a, b FROM vutest2; -- automatic update rules are updated, manual rules kept
WARNING: automatic UPDATE rule not created because manually created UPDATE rule exists
HINT: If you prefer to have the automatic rule, drop the manually created rule and run CREATE OR REPLACE VIEW again.
NOTICE: CREATE VIEW has created automatic view update rules
SELECT rulename, definition FROM pg_rules WHERE tablename LIKE 'vutestv4_' ORDER BY tablename, rulename;
rulename | definition
------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
_DELETE | CREATE RULE "_DELETE" AS ON DELETE TO vutestv40 DO INSTEAD DELETE FROM vutest1 WHERE ((((old.a IS NULL) AND (vutest1.a IS NULL)) OR (old.a = vutest1.a)) AND (((old.b IS NULL) AND (vutest1.b IS NULL)) OR (old.b = vutest1.b))) RETURNING old.a, old.b;
_INSERT | CREATE RULE "_INSERT" AS ON INSERT TO vutestv40 DO INSTEAD DELETE FROM vutest1;
zmy_delete | CREATE RULE zmy_delete AS ON DELETE TO vutestv40 DO DELETE FROM vutest1;
zmy_update | CREATE RULE zmy_update AS ON UPDATE TO vutestv40 DO INSTEAD DELETE FROM vutest1;
_DELETE | CREATE RULE "_DELETE" AS ON DELETE TO vutestv41 DO INSTEAD DELETE FROM vutest1 WHERE ((((old.aa IS NULL) AND (vutest1.a IS NULL)) OR (old.aa = vutest1.a)) AND (((old.b IS NULL) AND (vutest1.b IS NULL)) OR (old.b = vutest1.b))) RETURNING old.aa, old.b;
_INSERT | CREATE RULE "_INSERT" AS ON INSERT TO vutestv41 DO INSTEAD INSERT INTO vutest1 (a, b) VALUES (new.aa, new.b) RETURNING new.aa AS a, new.b;
_UPDATE | CREATE RULE "_UPDATE" AS ON UPDATE TO vutestv41 DO INSTEAD UPDATE vutest1 SET a = (new.aa - 1), b = new.b WHERE ((vutest1.a = (old.aa - 1)) AND (vutest1.b = old.b));
_DELETE | CREATE RULE "_DELETE" AS ON DELETE TO vutestv42 DO INSTEAD DELETE FROM vutest1 WHERE ((((old.aa IS NULL) AND (vutest1.a IS NULL)) OR (old.aa = vutest1.a)) AND (((old.b IS NULL) AND (vutest1.b IS NULL)) OR (old.b = vutest1.b))) RETURNING old.aa, old.b;
_INSERT | CREATE RULE "_INSERT" AS ON INSERT TO vutestv42 DO INSTEAD INSERT INTO vutest1 (a, b) VALUES (new.aa, new.b) RETURNING new.aa AS a, new.b;
zmy_update | CREATE RULE zmy_update AS ON UPDATE TO vutestv42 DO INSTEAD UPDATE vutest1 SET a = (new.aa - 1), b = new.b WHERE ((vutest1.a = (old.aa - 1)) AND (vutest1.b = old.b));
zmy_update | CREATE RULE zmy_update AS ON UPDATE TO vutestv43 DO INSTEAD DELETE FROM vutest1;
_DELETE | CREATE RULE "_DELETE" AS ON DELETE TO vutestv44 DO INSTEAD DELETE FROM vutest2 WHERE ((((old.a IS NULL) AND (vutest2.a IS NULL)) OR (old.a = vutest2.a)) AND (((old.b IS NULL) AND (vutest2.b IS NULL)) OR (old.b = vutest2.b))) RETURNING old.a, old.b;
_INSERT | CREATE RULE "_INSERT" AS ON INSERT TO vutestv44 DO INSTEAD INSERT INTO vutest2 (a, b) VALUES (new.a, new.b) RETURNING new.a, new.b;
zmy_update | CREATE RULE zmy_update AS ON UPDATE TO vutestv44 DO INSTEAD DELETE FROM vutest1;
(14 rows)
-- ACL
CREATE USER regressuser1;
CREATE USER regressuser2;
GRANT SELECT, INSERT, UPDATE ON vutest1 TO regressuser1;
SET ROLE regressuser1;
CREATE VIEW vutestv50 AS SELECT a, b FROM vutest1;
NOTICE: CREATE VIEW has created automatic view update rules
GRANT SELECT, UPDATE, DELETE ON vutestv50 TO regressuser2;
SELECT * FROM vutestv50;
a | b
-----+------
1 | eins
222 |
(2 rows)
INSERT INTO vutestv50 VALUES (0, 'zero');
UPDATE vutestv50 SET a = 1;
UPDATE vutestv50 SET a = 2 WHERE a = 1;
DELETE FROM vutestv50; -- ERROR
ERROR: permission denied for relation vutest1
RESET ROLE;
SET ROLE regressuser2;
SELECT * FROM vutestv50;
a | b
---+------
2 | eins
2 |
2 | zero
(3 rows)
INSERT INTO vutestv50 VALUES (0, 'zero'); -- ERROR
ERROR: permission denied for relation vutestv50
UPDATE vutestv50 SET a = 1;
UPDATE vutestv50 SET a = 2 WHERE a = 1;
DELETE FROM vutestv50; -- ERROR on vutest1
ERROR: permission denied for relation vutest1
RESET ROLE;
DROP VIEW vutestv50;
REVOKE ALL PRIVILEGES ON vutest1 FROM regressuser1;
DROP USER regressuser1, regressuser2;
# ----------
# $PostgreSQL: pgsql/src/test/regress/parallel_schedule,v 1.54 2009/01/22 17:27:55 petere Exp $
# $PostgreSQL: pgsql/src/test/regress/parallel_schedule,v 1.55 2009/01/27 12:40:15 petere Exp $
#
# By convention, we put no more than twenty tests in any one parallel group;
# this limits the number of connections needed to run the tests.
......@@ -79,8 +79,6 @@ test: misc
# ----------
test: select_views portals_p2 rules foreign_key cluster dependency guc bitmapops combocid tsearch tsdicts foreign_data window
test: view_update
# ----------
# Another group of parallel tests
# NB: temp.sql does a reconnect which transiently uses 2 connections,
......
# $PostgreSQL: pgsql/src/test/regress/serial_schedule,v 1.51 2009/01/22 17:27:55 petere Exp $
# $PostgreSQL: pgsql/src/test/regress/serial_schedule,v 1.52 2009/01/27 12:40:15 petere Exp $
# This should probably be in an order similar to parallel_schedule.
test: boolean
test: char
......@@ -100,7 +100,6 @@ test: tsearch
test: tsdicts
test: foreign_data
test: window
test: view_update
test: plancache
test: limit
test: plpgsql
......
CREATE TABLE vutest1 (a integer, b text);
INSERT INTO vutest1 VALUES (1, 'one');
INSERT INTO vutest1 VALUES (2, 'two');
-- simple view updatability conditions
CREATE VIEW vutestv1 AS SELECT a, b FROM vutest1;
CREATE VIEW vutestv2 AS SELECT * FROM vutest1;
CREATE VIEW vutestv3 AS SELECT b, a FROM vutest1;
CREATE VIEW vutestv4 AS SELECT a, b FROM vutest1 WHERE a < 5;
-- not updatable tests:
CREATE VIEW vutestv5 AS SELECT sum(a) FROM vutest1; -- aggregate function
CREATE VIEW vutestv6 AS SELECT b FROM vutest1 GROUP BY b; -- GROUP BY
CREATE VIEW vutestv7 AS SELECT l.b AS x, r.b AS y FROM vutest1 l, vutest1 r WHERE r.a = l.a; -- JOIN
CREATE VIEW vutestv8 AS SELECT 42; -- no table
CREATE VIEW vutestv9 AS SELECT a * 2 AS x, b || b AS y FROM vutest1; -- derived columns
CREATE VIEW vutestv10 AS SELECT a AS x, a AS y FROM vutest1; -- column referenced more than once
CREATE VIEW vutestv11 AS SELECT * FROM generate_series(1, 5); -- table function
CREATE VIEW vutestv12 AS SELECT xmin, xmax, a, b FROM vutest1; -- system columns
CREATE VIEW vutestv13 AS SELECT DISTINCT a, b FROM vutest1; -- DISTINCT
CREATE VIEW vutestv14 AS SELECT a, b FROM vutest1 WHERE a > (SELECT avg(a) FROM vutest1); -- *is* updatable, but SQL standard disallows this
CREATE VIEW vutestv15 AS SELECT a, b FROM vutest1 UNION ALL SELECT a, b FROM vutest1; -- UNION
CREATE VIEW vutestv16 AS SELECT x, y FROM (SELECT * FROM vutest1) AS foo (x, y); -- subquery ("derived table"); SQL standard allows this
CREATE VIEW vutestv17 AS SELECT a, 5, b FROM vutest1; -- constant
CREATE VIEW vutestv18 AS SELECT a, b FROM vutest1 LIMIT 1; -- LIMIT
CREATE VIEW vutestv19 AS SELECT a, b FROM vutest1 OFFSET 1; -- OFFSET
CREATE VIEW vutestv101 AS SELECT a, rank() OVER (PARTITION BY a ORDER BY b DESC) FROM vutest1; -- window function
CREATE VIEW vutestv102 AS WITH foo AS (SELECT a, b FROM vutest1) SELECT * FROM foo; -- SQL standard allows this
CREATE VIEW vutestv103 AS WITH RECURSIVE t(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM t) SELECT a FROM vutest1; -- recursive
INSERT INTO vutestv1 VALUES (3, 'three');
INSERT INTO vutestv2 VALUES (4, 'four');
INSERT INTO vutestv3 VALUES (5, 'five'); -- fail
INSERT INTO vutestv3 VALUES ('five', 5);
INSERT INTO vutestv3 (a, b) VALUES (6, 'six');
INSERT INTO vutestv4 VALUES (7, 'seven'); -- ok, but would be check option issue
INSERT INTO vutestv5 VALUES (8); -- fail
SELECT * FROM vutest1;
SELECT * FROM vutestv1;
SELECT * FROM vutestv2;
SELECT * FROM vutestv3;
SELECT * FROM vutestv4;
SELECT * FROM vutestv5;
UPDATE vutestv1 SET b = 'a lot' WHERE a = 7;
DELETE FROM vutestv2 WHERE a = 1;
UPDATE vutestv4 SET b = b || '!' WHERE a > 1;
DELETE FROM vutestv4 WHERE a > 3;
UPDATE vutestv6 SET b = 37; -- fail
DELETE FROM vutestv5; -- fail
SELECT * FROM vutest1 ORDER BY a, b;
SELECT * FROM vutestv1 ORDER BY a, b;
SELECT * FROM vutestv2 ORDER BY a, b;
SELECT * FROM vutestv4 ORDER BY a, b;
TRUNCATE TABLE vutest1;
-- views on views
CREATE VIEW vutestv20 AS SELECT a AS x, b AS y FROM vutestv1;
CREATE VIEW vutestv21 AS SELECT x AS a FROM vutestv20 WHERE x % 2 = 0;
CREATE VIEW vutestv22 AS SELECT sum(a) FROM vutestv21; -- not updatable
CREATE VIEW vutestv23 AS SELECT * FROM vutestv12; -- not updatable
INSERT INTO vutestv20 (x, y) VALUES (1, 'one');
INSERT INTO vutestv20 (x, y) VALUES (3, 'three');
INSERT INTO vutestv21 VALUES (2);
SELECT * FROM vutest1;
SELECT * FROM vutestv20;
SELECT * FROM vutestv21;
UPDATE vutestv20 SET y = 'eins' WHERE x = 1;
UPDATE vutestv21 SET a = 222;
SELECT * FROM vutest1;
SELECT * FROM vutestv20;
SELECT * FROM vutestv21;
DELETE FROM vutestv20 WHERE x = 3;
SELECT * FROM vutest1;
SELECT * FROM vutestv20;
SELECT * FROM vutestv21;
-- insert tests
CREATE TABLE vutest2 (a int PRIMARY KEY, b text NOT NULL, c text NOT NULL DEFAULT 'foo');
CREATE VIEW vutestv30 AS SELECT a, b, c FROM vutest2;
CREATE VIEW vutestv31 AS SELECT a, b FROM vutest2;
CREATE VIEW vutestv32 AS SELECT a, c FROM vutest2;
INSERT INTO vutestv30 VALUES (1, 'one', 'eins');
INSERT INTO vutestv31 VALUES (2, 'two');
INSERT INTO vutestv32 VALUES (3, 'drei'); -- fail
UPDATE vutestv31 SET a = 22 WHERE a = 2;
UPDATE vutestv32 SET c = 'drei!' WHERE a = 3;
SELECT rulename, definition FROM pg_rules WHERE tablename LIKE 'vutestv%' ORDER BY tablename, rulename;
-- interaction of manual and automatic rules, view replacement
CREATE VIEW vutestv40 AS SELECT a, b FROM vutest1;
CREATE RULE zmy_update AS ON UPDATE TO vutestv40 DO INSTEAD DELETE FROM vutest1; -- drops automatic _UPDATE rule
CREATE RULE "_INSERT" AS ON INSERT TO vutestv40 DO INSTEAD DELETE FROM vutest1; -- replaces automatic _INSERT rule
CREATE RULE zmy_delete AS ON DELETE TO vutestv40 DO ALSO DELETE FROM vutest1; -- leaves automatic _DELETE rule (because of ALSO)
CREATE VIEW vutestv41 AS SELECT a + 1 AS aa, b FROM vutest1; -- not updatable
CREATE RULE "_UPDATE" AS ON UPDATE TO vutestv41 DO INSTEAD UPDATE vutest1 SET a = new.aa - 1, b = new.b WHERE a = old.aa - 1 AND b = old.b;
CREATE OR REPLACE VIEW vutestv41 AS SELECT a AS aa, b FROM vutest1; -- *now* updatable, manual _UPDATE rule stays
CREATE VIEW vutestv42 AS SELECT a + 1 AS aa, b FROM vutest1; -- not updatable
CREATE RULE zmy_update AS ON UPDATE TO vutestv42 DO INSTEAD UPDATE vutest1 SET a = new.aa - 1, b = new.b WHERE a = old.aa - 1 AND b = old.b;
CREATE OR REPLACE VIEW vutestv42 AS SELECT a AS aa, b FROM vutest1; -- *now* updatable, zmy_update stays, no _UPDATE created
CREATE VIEW vutestv43 AS SELECT a AS aa, b FROM vutest1; -- updatable
CREATE RULE zmy_update AS ON UPDATE TO vutestv43 DO INSTEAD DELETE FROM vutest1; -- drops automatic _UPDATE rule
CREATE OR REPLACE VIEW vutestv43 AS SELECT a + 1 AS aa, b FROM vutest1; -- no longer updatable, automatic rules are deleted, manual rules kept
CREATE VIEW vutestv44 AS SELECT a, b FROM vutest1; -- updatable
CREATE RULE zmy_update AS ON UPDATE TO vutestv44 DO INSTEAD DELETE FROM vutest1; -- drops automatic _UPDATE rule
CREATE OR REPLACE VIEW vutestv44 AS SELECT a, b FROM vutest2; -- automatic update rules are updated, manual rules kept
SELECT rulename, definition FROM pg_rules WHERE tablename LIKE 'vutestv4_' ORDER BY tablename, rulename;
-- ACL
CREATE USER regressuser1;
CREATE USER regressuser2;
GRANT SELECT, INSERT, UPDATE ON vutest1 TO regressuser1;
SET ROLE regressuser1;
CREATE VIEW vutestv50 AS SELECT a, b FROM vutest1;
GRANT SELECT, UPDATE, DELETE ON vutestv50 TO regressuser2;
SELECT * FROM vutestv50;
INSERT INTO vutestv50 VALUES (0, 'zero');
UPDATE vutestv50 SET a = 1;
UPDATE vutestv50 SET a = 2 WHERE a = 1;
DELETE FROM vutestv50; -- ERROR
RESET ROLE;
SET ROLE regressuser2;
SELECT * FROM vutestv50;
INSERT INTO vutestv50 VALUES (0, 'zero'); -- ERROR
UPDATE vutestv50 SET a = 1;
UPDATE vutestv50 SET a = 2 WHERE a = 1;
DELETE FROM vutestv50; -- ERROR on vutest1
RESET ROLE;
DROP VIEW vutestv50;
REVOKE ALL PRIVILEGES ON vutest1 FROM regressuser1;
DROP USER regressuser1, regressuser2;
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment