OSDN Git Service

Department of second thoughts: make checks for replacing a view slightly
[pg-rex/syncrep.git] / src / backend / commands / view.c
1 /*-------------------------------------------------------------------------
2  *
3  * view.c
4  *        use rewrite rules to construct views
5  *
6  * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
7  * Portions Copyright (c) 1994, Regents of the University of California
8  *
9  *
10  * IDENTIFICATION
11  *        $Header: /cvsroot/pgsql/src/backend/commands/view.c,v 1.70 2002/09/02 20:04:40 tgl Exp $
12  *
13  *-------------------------------------------------------------------------
14  */
15 #include "postgres.h"
16
17 #include "access/heapam.h"
18 #include "catalog/dependency.h"
19 #include "catalog/namespace.h"
20 #include "commands/tablecmds.h"
21 #include "commands/view.h"
22 #include "miscadmin.h"
23 #include "nodes/makefuncs.h"
24 #include "parser/parse_relation.h"
25 #include "rewrite/rewriteDefine.h"
26 #include "rewrite/rewriteManip.h"
27 #include "rewrite/rewriteSupport.h"
28 #include "utils/acl.h"
29 #include "utils/lsyscache.h"
30
31
32 static void checkViewTupleDesc(TupleDesc newdesc, TupleDesc olddesc);
33
34
35 /*---------------------------------------------------------------------
36  * DefineVirtualRelation
37  *
38  * Create the "view" relation. `DefineRelation' does all the work,
39  * we just provide the correct arguments ... at least when we're
40  * creating a view.  If we're updating an existing view, we have to
41  * work harder.
42  *---------------------------------------------------------------------
43  */
44 static Oid
45 DefineVirtualRelation(const RangeVar *relation, List *tlist, bool replace)
46 {
47         Oid                     viewOid,
48                                 namespaceId;
49         CreateStmt *createStmt = makeNode(CreateStmt);
50         List       *attrList,
51                            *t;
52
53         /*
54          * create a list of ColumnDef nodes based on the names and types of
55          * the (non-junk) targetlist items from the view's SELECT list.
56          */
57         attrList = NIL;
58         foreach (t, tlist)
59         {
60                 TargetEntry *entry = lfirst(t);
61                 Resdom     *res = entry->resdom;
62
63                 if (!res->resjunk)
64                 {
65                         ColumnDef  *def = makeNode(ColumnDef);
66                         TypeName   *typename = makeNode(TypeName);
67
68                         def->colname = pstrdup(res->resname);
69
70                         typename->typeid = res->restype;
71                         typename->typmod = res->restypmod;
72                         def->typename = typename;
73
74                         def->is_inherited = false;
75                         def->is_not_null = false;
76                         def->raw_default = NULL;
77                         def->cooked_default = NULL;
78                         def->constraints = NIL;
79                         def->support = NULL;
80
81                         attrList = lappend(attrList, def);
82                 }
83         }
84
85         if (attrList == NIL)
86                 elog(ERROR, "attempted to define virtual relation with no attrs");
87
88         /*
89          * Check to see if we want to replace an existing view.
90          */
91         namespaceId = RangeVarGetCreationNamespace(relation);
92         viewOid = get_relname_relid(relation->relname, namespaceId);
93
94         if (OidIsValid(viewOid) && replace)
95         {
96                 Relation        rel;
97                 TupleDesc       descriptor;
98
99                 /*
100                  * Yes.  Get exclusive lock on the existing view ...
101                  */
102                 rel = relation_open(viewOid, AccessExclusiveLock);
103
104                 /*
105                  * Make sure it *is* a view, and do permissions checks.
106                  */
107                 if (rel->rd_rel->relkind != RELKIND_VIEW)
108                         elog(ERROR, "%s is not a view",
109                                  RelationGetRelationName(rel));
110
111                 if (!pg_class_ownercheck(viewOid, GetUserId()))
112                         aclcheck_error(ACLCHECK_NOT_OWNER, RelationGetRelationName(rel));
113
114                 /*
115                  * Create a tuple descriptor to compare against the existing view,
116                  * and verify it matches.
117                  */
118             descriptor = BuildDescForRelation(attrList);
119                 checkViewTupleDesc(descriptor, rel->rd_att);
120
121                 /*
122                  * Seems okay, so return the OID of the pre-existing view.
123                  */
124                 relation_close(rel, NoLock); /* keep the lock! */
125
126                 return viewOid;
127         }
128         else
129         {
130                 /*
131                  * now create the parameters for keys/inheritance etc. All of them are
132                  * nil...
133                  */
134                 createStmt->relation = (RangeVar *) relation;
135                 createStmt->tableElts = attrList;
136                 createStmt->inhRelations = NIL;
137                 createStmt->constraints = NIL;
138                 createStmt->hasoids = false;
139         
140                 /*
141                  * finally create the relation (this will error out if there's
142                  * an existing view, so we don't need more code to complain
143                  * if "replace" is false).
144                  */
145                 return DefineRelation(createStmt, RELKIND_VIEW);
146         }
147 }
148
149 /*
150  * Verify that tupledesc associated with proposed new view definition
151  * matches tupledesc of old view.  This is basically a cut-down version
152  * of equalTupleDescs(), with code added to generate specific complaints.
153  */
154 static void
155 checkViewTupleDesc(TupleDesc newdesc, TupleDesc olddesc)
156 {
157         int                     i;
158
159         if (newdesc->natts != olddesc->natts)
160                 elog(ERROR, "Cannot change number of columns in view");
161         /* we can ignore tdhasoid */
162
163         for (i = 0; i < newdesc->natts; i++)
164         {
165                 Form_pg_attribute newattr = newdesc->attrs[i];
166                 Form_pg_attribute oldattr = olddesc->attrs[i];
167
168                 /* XXX not right, but we don't support DROP COL on view anyway */
169                 if (newattr->attisdropped != oldattr->attisdropped)
170                         elog(ERROR, "Cannot change number of columns in view");
171
172                 if (strcmp(NameStr(newattr->attname), NameStr(oldattr->attname)) != 0)
173                         elog(ERROR, "Cannot change name of view column \"%s\"",
174                                  NameStr(oldattr->attname));
175                 /* XXX would it be safe to allow atttypmod to change?  Not sure */
176                 if (newattr->atttypid != oldattr->atttypid ||
177                         newattr->atttypmod != oldattr->atttypmod)
178                         elog(ERROR, "Cannot change datatype of view column \"%s\"",
179                                  NameStr(oldattr->attname));
180                 /* We can ignore the remaining attributes of an attribute... */
181         }
182         /*
183          * We ignore the constraint fields.  The new view desc can't have any
184          * constraints, and the only ones that could be on the old view are
185          * defaults, which we are happy to leave in place.
186          */
187 }
188
189 static RuleStmt *
190 FormViewRetrieveRule(const RangeVar *view, Query *viewParse, bool replace)
191 {
192         RuleStmt   *rule;
193
194         /*
195          * Create a RuleStmt that corresponds to the suitable rewrite rule
196          * args for DefineQueryRewrite();
197          */
198         rule = makeNode(RuleStmt);
199         rule->relation = copyObject((RangeVar *) view);
200         rule->rulename = pstrdup(ViewSelectRuleName);
201         rule->whereClause = NULL;
202         rule->event = CMD_SELECT;
203         rule->instead = true;
204         rule->actions = makeList1(viewParse);
205         rule->replace = replace;
206
207         return rule;
208 }
209
210 static void
211 DefineViewRules(const RangeVar *view, Query *viewParse, bool replace)
212 {
213         RuleStmt   *retrieve_rule;
214
215 #ifdef NOTYET
216         RuleStmt   *replace_rule;
217         RuleStmt   *append_rule;
218         RuleStmt   *delete_rule;
219 #endif
220
221         retrieve_rule = FormViewRetrieveRule(view, viewParse, replace);
222
223 #ifdef NOTYET
224         replace_rule = FormViewReplaceRule(view, viewParse);
225         append_rule = FormViewAppendRule(view, viewParse);
226         delete_rule = FormViewDeleteRule(view, viewParse);
227 #endif
228
229         DefineQueryRewrite(retrieve_rule);
230
231 #ifdef NOTYET
232         DefineQueryRewrite(replace_rule);
233         DefineQueryRewrite(append_rule);
234         DefineQueryRewrite(delete_rule);
235 #endif
236
237 }
238
239 /*---------------------------------------------------------------
240  * UpdateRangeTableOfViewParse
241  *
242  * Update the range table of the given parsetree.
243  * This update consists of adding two new entries IN THE BEGINNING
244  * of the range table (otherwise the rule system will die a slow,
245  * horrible and painful death, and we do not want that now, do we?)
246  * one for the OLD relation and one for the NEW one (both of
247  * them refer in fact to the "view" relation).
248  *
249  * Of course we must also increase the 'varnos' of all the Var nodes
250  * by 2...
251  *
252  * These extra RT entries are not actually used in the query,
253  * except for run-time permission checking.
254  *---------------------------------------------------------------
255  */
256 static Query *
257 UpdateRangeTableOfViewParse(Oid viewOid, Query *viewParse)
258 {
259         List       *new_rt;
260         RangeTblEntry *rt_entry1,
261                            *rt_entry2;
262
263         /*
264          * Make a copy of the given parsetree.  It's not so much that we don't
265          * want to scribble on our input, it's that the parser has a bad habit
266          * of outputting multiple links to the same subtree for constructs
267          * like BETWEEN, and we mustn't have OffsetVarNodes increment the
268          * varno of a Var node twice.  copyObject will expand any
269          * multiply-referenced subtree into multiple copies.
270          */
271         viewParse = (Query *) copyObject(viewParse);
272
273         /*
274          * Create the 2 new range table entries and form the new range
275          * table... OLD first, then NEW....
276          */
277         rt_entry1 = addRangeTableEntryForRelation(NULL, viewOid,
278                                                                                           makeAlias("*OLD*", NIL),
279                                                                                           false, false);
280         rt_entry2 = addRangeTableEntryForRelation(NULL, viewOid,
281                                                                                           makeAlias("*NEW*", NIL),
282                                                                                           false, false);
283         /* Must override addRangeTableEntry's default access-check flags */
284         rt_entry1->checkForRead = false;
285         rt_entry2->checkForRead = false;
286
287         new_rt = lcons(rt_entry1, lcons(rt_entry2, viewParse->rtable));
288
289         viewParse->rtable = new_rt;
290
291         /*
292          * Now offset all var nodes by 2, and jointree RT indexes too.
293          */
294         OffsetVarNodes((Node *) viewParse, 2, 0);
295
296         return viewParse;
297 }
298
299 /*-------------------------------------------------------------------
300  * DefineView
301  *
302  *              - takes a "viewname", "parsetree" pair and then
303  *              1)              construct the "virtual" relation
304  *              2)              commit the command but NOT the transaction,
305  *                              so that the relation exists
306  *                              before the rules are defined.
307  *              2)              define the "n" rules specified in the PRS2 paper
308  *                              over the "virtual" relation
309  *-------------------------------------------------------------------
310  */
311 void
312 DefineView(const RangeVar *view, Query *viewParse, bool replace)
313 {
314         Oid                     viewOid;
315
316         /*
317          * Create the view relation
318          *
319          * NOTE: if it already exists and replace is false, the xact will 
320          * be aborted.
321          */
322
323         viewOid = DefineVirtualRelation(view, viewParse->targetList, replace);
324
325         /*
326          * The relation we have just created is not visible to any other
327          * commands running with the same transaction & command id. So,
328          * increment the command id counter (but do NOT pfree any memory!!!!)
329          */
330         CommandCounterIncrement();
331
332         /*
333          * The range table of 'viewParse' does not contain entries for the
334          * "OLD" and "NEW" relations. So... add them!
335          */
336         viewParse = UpdateRangeTableOfViewParse(viewOid, viewParse);
337
338         /*
339          * Now create the rules associated with the view.
340          */
341         DefineViewRules(view, viewParse, replace);
342 }
343
344 /*
345  * RemoveView
346  *
347  * Remove a view given its name
348  *
349  * We just have to drop the relation; the associated rules will be
350  * cleaned up automatically.
351  */
352 void
353 RemoveView(const RangeVar *view, DropBehavior behavior)
354 {
355         Oid                     viewOid;
356         ObjectAddress object;
357
358         viewOid = RangeVarGetRelid(view, false);
359
360         object.classId = RelOid_pg_class;
361         object.objectId = viewOid;
362         object.objectSubId = 0;
363
364         performDeletion(&object, behavior);
365 }