From dd7e54a17ffed5767cd9af175024c60df2b1951c Mon Sep 17 00:00:00 2001 From: Peter Eisentraut Date: Thu, 22 Jan 2009 17:27:55 +0000 Subject: [PATCH] Automatic view update rules Bernd Helmle --- doc/src/sgml/catalogs.sgml | 9 +- doc/src/sgml/intro.sgml | 4 +- doc/src/sgml/ref/create_view.sgml | 100 +- src/backend/commands/view.c | 23 +- src/backend/rewrite/Makefile | 5 +- src/backend/rewrite/rewriteDefine.c | 66 +- src/backend/rewrite/rewriteHandler.c | 14 +- src/backend/rewrite/rewriteRemove.c | 35 +- src/backend/rewrite/viewUpdate.c | 1401 ++++++++++++++++++ src/backend/utils/cache/relcache.c | 5 +- src/bin/pg_dump/pg_dump.c | 14 +- src/bin/pg_dump/pg_dump.h | 3 +- src/include/catalog/pg_rewrite.h | 18 +- src/include/rewrite/prs2lock.h | 3 +- src/include/rewrite/rewriteDefine.h | 3 +- src/include/rewrite/rewriteRemove.h | 3 +- src/include/rewrite/viewUpdate.h | 21 + src/test/regress/expected/alter_table.out | 5 + src/test/regress/expected/create_view.out | 18 + src/test/regress/expected/drop_if_exists.out | 1 + src/test/regress/expected/plancache.out | 7 + src/test/regress/expected/portals.out | 1 + src/test/regress/expected/privileges.out | 4 + src/test/regress/expected/returning.out | 1 + src/test/regress/expected/rules.out | 20 +- src/test/regress/expected/subselect.out | 1 + src/test/regress/expected/view_update.out | 370 +++++ src/test/regress/parallel_schedule | 4 +- src/test/regress/serial_schedule | 3 +- src/test/regress/sql/view_update.sql | 168 +++ 30 files changed, 2289 insertions(+), 41 deletions(-) create mode 100644 src/backend/rewrite/viewUpdate.c create mode 100644 src/include/rewrite/viewUpdate.h create mode 100644 src/test/regress/expected/view_update.out create mode 100644 src/test/regress/sql/view_update.sql diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index b590d0f4f7..bd267dcb75 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -1,4 +1,4 @@ - + @@ -4144,6 +4144,13 @@ True if the rule is an INSTEAD rule + + is_auto + bool + + True if the rule was automatically generated + + ev_qual text diff --git a/doc/src/sgml/intro.sgml b/doc/src/sgml/intro.sgml index 78e8013d66..298ba910a9 100644 --- a/doc/src/sgml/intro.sgml +++ b/doc/src/sgml/intro.sgml @@ -1,4 +1,4 @@ - + Preface @@ -110,7 +110,7 @@ triggers - views + updatable views transactional integrity diff --git a/doc/src/sgml/ref/create_view.sgml b/doc/src/sgml/ref/create_view.sgml index e0347a1919..daea5a97cb 100644 --- a/doc/src/sgml/ref/create_view.sgml +++ b/doc/src/sgml/ref/create_view.sgml @@ -1,5 +1,5 @@ @@ -115,11 +115,99 @@ CREATE [ OR REPLACE ] [ TEMP | TEMPORARY ] VIEW n Notes - 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 - . + Some views are updatable, which means that the + commands INSERT, UPDATE, + and DELETE can be used on the view as if it + were a regular table. A view is updatable if it + does not contain: + + + + + more than one underlying table (joins) or no underlying table at all + + + + + + underlying tables/views that are themselves not updatable, + including table value constructors and table functions + + + + + + subqueries in the FROM list + + + + + + 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 + + + + + + references to system columns in the select list + + + + + + more than one reference to the same column in the select list + + + + + aggregate function calls + + + + window function calls + + + + + WITH or WITH RECURSIVE clauses + + + + + + DISTINCT, GROUP BY, or + HAVING clauses + + + + + + UNION, INTERSECT, or + EXCEPT clauses + + + + + + LIMIT or OFFSET clauses + (or other equivalent spellings thereof) + + + + + + + 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 INSERT, + UPDATE, and DELETE 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 . diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c index c29e88abdd..de1834ab1c 100644 --- a/src/backend/commands/view.c +++ b/src/backend/commands/view.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/view.c,v 1.111 2009/01/01 17:23:40 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/commands/view.c,v 1.112 2009/01/22 17:27:54 petere Exp $ * *------------------------------------------------------------------------- */ @@ -27,7 +27,9 @@ #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" @@ -308,13 +310,28 @@ DefineViewRules(Oid viewOid, Query *viewParse, bool replace) viewOid, NULL, CMD_SELECT, - true, + true, /* is_instead */ + true, /* is_auto */ replace, list_make1(viewParse)); /* - * Someday: automatic ON INSERT, etc + * Delete all implicit rules on replace. CreateViewUpdateRules() + * below will re-create them if appropriate for the new view + * definition. */ + 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); } /*--------------------------------------------------------------- diff --git a/src/backend/rewrite/Makefile b/src/backend/rewrite/Makefile index 4f2eae5e99..196765aa6c 100644 --- a/src/backend/rewrite/Makefile +++ b/src/backend/rewrite/Makefile @@ -4,7 +4,7 @@ # Makefile for rewrite # # IDENTIFICATION -# $PostgreSQL: pgsql/src/backend/rewrite/Makefile,v 1.17 2008/02/19 10:30:08 petere Exp $ +# $PostgreSQL: pgsql/src/backend/rewrite/Makefile,v 1.18 2009/01/22 17:27:54 petere Exp $ # #------------------------------------------------------------------------- @@ -13,6 +13,7 @@ top_builddir = ../../.. include $(top_builddir)/src/Makefile.global OBJS = rewriteRemove.o rewriteDefine.o \ - rewriteHandler.o rewriteManip.o rewriteSupport.o + rewriteHandler.o rewriteManip.o rewriteSupport.o \ + viewUpdate.o include $(top_srcdir)/src/backend/common.mk diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c index 6693e18d19..fe81a9ff13 100644 --- a/src/backend/rewrite/rewriteDefine.c +++ b/src/backend/rewrite/rewriteDefine.c @@ -8,13 +8,14 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/rewrite/rewriteDefine.c,v 1.134 2009/01/01 17:23:47 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/rewrite/rewriteDefine.c,v 1.135 2009/01/22 17:27:54 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" @@ -25,6 +26,7 @@ #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" @@ -39,6 +41,7 @@ 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); /* @@ -52,6 +55,7 @@ InsertRule(char *rulname, Oid eventrel_oid, AttrNumber evslot_index, bool evinstead, + bool is_auto, Node *event_qual, List *action, bool replace) @@ -84,6 +88,7 @@ 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 */ @@ -102,7 +107,11 @@ InsertRule(char *rulname, if (HeapTupleIsValid(oldtup)) { - if (!replace) + /* + * 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) ereport(ERROR, (errcode(ERRCODE_DUPLICATE_OBJECT), errmsg("rule \"%s\" for relation \"%s\" already exists", @@ -115,6 +124,7 @@ 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; @@ -205,6 +215,7 @@ DefineRule(RuleStmt *stmt, const char *queryString) whereClause, stmt->event, stmt->instead, + false, /* not is_auto */ stmt->replace, actions); } @@ -223,6 +234,7 @@ DefineQueryRewrite(char *rulename, Node *event_qual, CmdType event_type, bool is_instead, + bool is_auto, bool replace, List *action) { @@ -446,6 +458,42 @@ 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; + } + } + } + } /* @@ -461,6 +509,7 @@ DefineQueryRewrite(char *rulename, event_relid, event_attno, is_instead, + is_auto, event_qual, action, replace); @@ -754,3 +803,16 @@ 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 "???"; +} diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c index 2e21fed213..56549a5509 100644 --- a/src/backend/rewrite/rewriteHandler.c +++ b/src/backend/rewrite/rewriteHandler.c @@ -7,7 +7,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/rewrite/rewriteHandler.c,v 1.182 2009/01/01 17:23:47 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/rewrite/rewriteHandler.c,v 1.183 2009/01/22 17:27:54 petere Exp $ * *------------------------------------------------------------------------- */ @@ -1895,20 +1895,20 @@ QueryRewrite(Query *parsetree) { case CMD_INSERT: ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("cannot insert into a view"), + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("view is not updatable"), errhint("You need an unconditional ON INSERT DO INSTEAD rule."))); break; case CMD_UPDATE: ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("cannot update a view"), + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("view is not updatable"), errhint("You need an unconditional ON UPDATE DO INSTEAD rule."))); break; case CMD_DELETE: ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("cannot delete from a view"), + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("view is not updatable"), errhint("You need an unconditional ON DELETE DO INSTEAD rule."))); break; default: diff --git a/src/backend/rewrite/rewriteRemove.c b/src/backend/rewrite/rewriteRemove.c index 6d7f85514f..90e99bd8ac 100644 --- a/src/backend/rewrite/rewriteRemove.c +++ b/src/backend/rewrite/rewriteRemove.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/rewrite/rewriteRemove.c,v 1.75 2009/01/01 17:23:47 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/rewrite/rewriteRemove.c,v 1.76 2009/01/22 17:27:54 petere Exp $ * *------------------------------------------------------------------------- */ @@ -90,6 +90,39 @@ 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. diff --git a/src/backend/rewrite/viewUpdate.c b/src/backend/rewrite/viewUpdate.c new file mode 100644 index 0000000000..3687f19429 --- /dev/null +++ b/src/backend/rewrite/viewUpdate.c @@ -0,0 +1,1401 @@ +/*------------------------------------------------------------------------- + * + * 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); +} diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c index 04dba39f26..4be6e08606 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/utils/cache/relcache.c,v 1.280 2009/01/01 17:23:50 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/utils/cache/relcache.c,v 1.281 2009/01/22 17:27:54 petere Exp $ * *------------------------------------------------------------------------- */ @@ -662,6 +662,7 @@ 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 @@ -785,6 +786,8 @@ 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) diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index f3bcba9143..76558856e2 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -12,7 +12,7 @@ * by PostgreSQL * * IDENTIFICATION - * $PostgreSQL: pgsql/src/bin/pg_dump/pg_dump.c,v 1.514 2009/01/18 20:44:45 tgl Exp $ + * $PostgreSQL: pgsql/src/bin/pg_dump/pg_dump.c,v 1.515 2009/01/22 17:27:54 petere Exp $ * *------------------------------------------------------------------------- */ @@ -4003,7 +4003,17 @@ getRules(int *numRules) /* Make sure we are in proper schema */ selectSourceSchema("pg_catalog"); - if (g_fout->remoteVersion >= 80300) + 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) { appendPQExpBuffer(query, "SELECT " "tableoid, oid, rulename, " diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h index d77d7d32ad..35b6b844d9 100644 --- a/src/bin/pg_dump/pg_dump.h +++ b/src/bin/pg_dump/pg_dump.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/bin/pg_dump/pg_dump.h,v 1.147 2009/01/18 20:44:45 tgl Exp $ + * $PostgreSQL: pgsql/src/bin/pg_dump/pg_dump.h,v 1.148 2009/01/22 17:27:54 petere Exp $ * *------------------------------------------------------------------------- */ @@ -304,7 +304,6 @@ 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 diff --git a/src/include/catalog/pg_rewrite.h b/src/include/catalog/pg_rewrite.h index 313ce32731..2dc621e5fe 100644 --- a/src/include/catalog/pg_rewrite.h +++ b/src/include/catalog/pg_rewrite.h @@ -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.31 2009/01/01 17:23:58 momjian Exp $ + * $PostgreSQL: pgsql/src/include/catalog/pg_rewrite.h,v 1.32 2009/01/22 17:27:54 petere Exp $ * * NOTES * the genbki.sh script reads this file and generates .bki @@ -40,6 +40,15 @@ 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; @@ -56,14 +65,15 @@ typedef FormData_pg_rewrite *Form_pg_rewrite; * compiler constants for pg_rewrite * ---------------- */ -#define Natts_pg_rewrite 8 +#define Natts_pg_rewrite 9 #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_ev_qual 7 -#define Anum_pg_rewrite_ev_action 8 +#define Anum_pg_rewrite_is_auto 7 +#define Anum_pg_rewrite_ev_qual 8 +#define Anum_pg_rewrite_ev_action 9 #endif /* PG_REWRITE_H */ diff --git a/src/include/rewrite/prs2lock.h b/src/include/rewrite/prs2lock.h index 1e47cb696b..5d039c21c0 100644 --- a/src/include/rewrite/prs2lock.h +++ b/src/include/rewrite/prs2lock.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.25 2009/01/01 17:24:01 momjian Exp $ + * $PostgreSQL: pgsql/src/include/rewrite/prs2lock.h,v 1.26 2009/01/22 17:27:55 petere Exp $ * *------------------------------------------------------------------------- */ @@ -30,6 +30,7 @@ typedef struct RewriteRule List *actions; char enabled; bool isInstead; + bool is_auto; } RewriteRule; /* diff --git a/src/include/rewrite/rewriteDefine.h b/src/include/rewrite/rewriteDefine.h index 22e6267f4c..10936fab84 100644 --- a/src/include/rewrite/rewriteDefine.h +++ b/src/include/rewrite/rewriteDefine.h @@ -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.31 2009/01/01 17:24:01 momjian Exp $ + * $PostgreSQL: pgsql/src/include/rewrite/rewriteDefine.h,v 1.32 2009/01/22 17:27:55 petere Exp $ * *------------------------------------------------------------------------- */ @@ -29,6 +29,7 @@ extern void DefineQueryRewrite(char *rulename, Node *event_qual, CmdType event_type, bool is_instead, + bool is_auto, bool replace, List *action); diff --git a/src/include/rewrite/rewriteRemove.h b/src/include/rewrite/rewriteRemove.h index d90c780b9f..f7d3c35465 100644 --- a/src/include/rewrite/rewriteRemove.h +++ b/src/include/rewrite/rewriteRemove.h @@ -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.25 2009/01/01 17:24:01 momjian Exp $ + * $PostgreSQL: pgsql/src/include/rewrite/rewriteRemove.h,v 1.26 2009/01/22 17:27:55 petere Exp $ * *------------------------------------------------------------------------- */ @@ -20,5 +20,6 @@ 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 */ diff --git a/src/include/rewrite/viewUpdate.h b/src/include/rewrite/viewUpdate.h new file mode 100644 index 0000000000..1423870846 --- /dev/null +++ b/src/include/rewrite/viewUpdate.h @@ -0,0 +1,21 @@ + +/*------------------------------------------------------------------------- + * + * 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 */ diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out index 4768c8b186..5ac24e5b3d 100644 --- a/src/test/regress/expected/alter_table.out +++ b/src/test/regress/expected/alter_table.out @@ -132,6 +132,7 @@ 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; @@ -592,6 +593,7 @@ 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; @@ -659,6 +661,7 @@ 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.*; @@ -842,6 +845,7 @@ 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 ---+---+--- @@ -1436,6 +1440,7 @@ 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); diff --git a/src/test/regress/expected/create_view.out b/src/test/regress/expected/create_view.out index 04383e43d2..232bacfd6d 100644 --- a/src/test/regress/expected/create_view.out +++ b/src/test/regress/expected/create_view.out @@ -27,8 +27,10 @@ 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 ----+---- @@ -38,6 +40,7 @@ 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 ----+---- @@ -71,13 +74,17 @@ 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 @@ -107,18 +114,25 @@ 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 @@ -130,8 +144,10 @@ 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') @@ -219,6 +235,7 @@ 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 @@ -232,6 +249,7 @@ 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 diff --git a/src/test/regress/expected/drop_if_exists.out b/src/test/regress/expected/drop_if_exists.out index 092c90403a..90f1bb86e1 100644 --- a/src/test/regress/expected/drop_if_exists.out +++ b/src/test/regress/expected/drop_if_exists.out @@ -13,6 +13,7 @@ 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 diff --git a/src/test/regress/expected/plancache.out b/src/test/regress/expected/plancache.out index c0681d26e3..853ae94fdb 100644 --- a/src/test/regress/expected/plancache.out +++ b/src/test/regress/expected/plancache.out @@ -79,6 +79,7 @@ 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 @@ -236,6 +237,9 @@ 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 @@ -248,6 +252,9 @@ 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 diff --git a/src/test/regress/expected/portals.out b/src/test/regress/expected/portals.out index 95dcea5a1d..be5c476548 100644 --- a/src/test/regress/expected/portals.out +++ b/src/test/regress/expected/portals.out @@ -1229,6 +1229,7 @@ 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; diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out index 607c591c0e..f4a2bd8d8f 100644 --- a/src/test/regress/expected/privileges.out +++ b/src/test/regress/expected/privileges.out @@ -189,9 +189,12 @@ 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 ---+----- @@ -219,6 +222,7 @@ 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 -----+-----+------- diff --git a/src/test/regress/expected/returning.out b/src/test/regress/expected/returning.out index b04f6f1a0e..05afef0f19 100644 --- a/src/test/regress/expected/returning.out +++ b/src/test/regress/expected/returning.out @@ -195,6 +195,7 @@ 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'); diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2daa79d732..e0618abd67 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -10,6 +10,7 @@ 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 @@ -755,9 +756,12 @@ 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 @@ -1333,8 +1337,8 @@ SELECT viewname, definition FROM pg_views WHERE schemaname <> 'information_schem SELECT tablename, rulename, definition FROM pg_rules ORDER BY tablename, rulename; - tablename | rulename | definition ----------------+-----------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + 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); @@ -1359,12 +1363,21 @@ 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); -(29 rows) +(38 rows) -- -- CREATE OR REPLACE RULE @@ -1466,6 +1479,7 @@ 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; diff --git a/src/test/regress/expected/subselect.out b/src/test/regress/expected/subselect.out index f6dbc0212c..b30fdcbb80 100644 --- a/src/test/regress/expected/subselect.out +++ b/src/test/regress/expected/subselect.out @@ -349,6 +349,7 @@ 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); diff --git a/src/test/regress/expected/view_update.out b/src/test/regress/expected/view_update.out new file mode 100644 index 0000000000..09c2d4f6f5 --- /dev/null +++ b/src/test/regress/expected/view_update.out @@ -0,0 +1,370 @@ +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; diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule index 4c362957c5..300a182cee 100644 --- a/src/test/regress/parallel_schedule +++ b/src/test/regress/parallel_schedule @@ -1,5 +1,5 @@ # ---------- -# $PostgreSQL: pgsql/src/test/regress/parallel_schedule,v 1.53 2008/12/30 17:11:26 tgl Exp $ +# $PostgreSQL: pgsql/src/test/regress/parallel_schedule,v 1.54 2009/01/22 17:27:55 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,6 +79,8 @@ 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, diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule index 38ead933a0..2773cf891f 100644 --- a/src/test/regress/serial_schedule +++ b/src/test/regress/serial_schedule @@ -1,4 +1,4 @@ -# $PostgreSQL: pgsql/src/test/regress/serial_schedule,v 1.50 2008/12/30 17:11:26 tgl Exp $ +# $PostgreSQL: pgsql/src/test/regress/serial_schedule,v 1.51 2009/01/22 17:27:55 petere Exp $ # This should probably be in an order similar to parallel_schedule. test: boolean test: char @@ -100,6 +100,7 @@ test: tsearch test: tsdicts test: foreign_data test: window +test: view_update test: plancache test: limit test: plpgsql diff --git a/src/test/regress/sql/view_update.sql b/src/test/regress/sql/view_update.sql new file mode 100644 index 0000000000..d6ece26b15 --- /dev/null +++ b/src/test/regress/sql/view_update.sql @@ -0,0 +1,168 @@ +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; -- GitLab