]> granicus.if.org Git - postgresql/blob - src/backend/commands/view.c
For some reason access/tupmacs.h has been #including utils/memutils.h,
[postgresql] / src / backend / commands / view.c
1 /*-------------------------------------------------------------------------
2  *
3  * view.c
4  *        use rewrite rules to construct views
5  *
6  * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
7  * Portions Copyright (c) 1994, Regents of the University of California
8  *
9  *
10  * IDENTIFICATION
11  *        $PostgreSQL: pgsql/src/backend/commands/view.c,v 1.90 2005/04/14 01:38:17 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 "optimizer/clauses.h"
25 #include "parser/parse_expr.h"
26 #include "parser/parse_relation.h"
27 #include "rewrite/rewriteDefine.h"
28 #include "rewrite/rewriteManip.h"
29 #include "rewrite/rewriteSupport.h"
30 #include "utils/acl.h"
31 #include "utils/lsyscache.h"
32
33
34 static void checkViewTupleDesc(TupleDesc newdesc, TupleDesc olddesc);
35 static bool isViewOnTempTable_walker(Node *node, void *context);
36
37 /*---------------------------------------------------------------------
38  * isViewOnTempTable
39  *
40  * Returns true iff any of the relations underlying this view are
41  * temporary tables.
42  *---------------------------------------------------------------------
43  */
44 static bool
45 isViewOnTempTable(Query *viewParse)
46 {
47         return isViewOnTempTable_walker((Node *) viewParse, NULL);
48 }
49
50 static bool
51 isViewOnTempTable_walker(Node *node, void *context)
52 {
53         if (node == NULL)
54                 return false;
55
56         if (IsA(node, Query))
57         {
58                 Query           *query = (Query *) node;
59                 ListCell        *rtable;
60
61                 foreach (rtable, query->rtable)
62                 {
63                         RangeTblEntry *rte = lfirst(rtable);
64                         if (rte->rtekind == RTE_RELATION)
65                         {
66                                 Relation rel = heap_open(rte->relid, AccessShareLock);
67                                 bool istemp = rel->rd_istemp;
68                                 heap_close(rel, AccessShareLock);
69                                 if (istemp)
70                                         return true;
71                         }
72                 }
73
74                 return query_tree_walker(query,
75                                                                  isViewOnTempTable_walker,
76                                                                  context,
77                                                                  QTW_IGNORE_JOINALIASES);
78         }
79
80         return expression_tree_walker(node,
81                                                                   isViewOnTempTable_walker,
82                                                                   context);
83 }
84
85 /*---------------------------------------------------------------------
86  * DefineVirtualRelation
87  *
88  * Create the "view" relation. `DefineRelation' does all the work,
89  * we just provide the correct arguments ... at least when we're
90  * creating a view.  If we're updating an existing view, we have to
91  * work harder.
92  *---------------------------------------------------------------------
93  */
94 static Oid
95 DefineVirtualRelation(const RangeVar *relation, List *tlist, bool replace)
96 {
97         Oid                     viewOid,
98                                 namespaceId;
99         CreateStmt *createStmt = makeNode(CreateStmt);
100         List       *attrList;
101         ListCell   *t;
102
103         /*
104          * create a list of ColumnDef nodes based on the names and types of
105          * the (non-junk) targetlist items from the view's SELECT list.
106          */
107         attrList = NIL;
108         foreach(t, tlist)
109         {
110                 TargetEntry *tle = lfirst(t);
111
112                 if (!tle->resjunk)
113                 {
114                         ColumnDef  *def = makeNode(ColumnDef);
115                         TypeName   *typename = makeNode(TypeName);
116
117                         def->colname = pstrdup(tle->resname);
118
119                         typename->typeid = exprType((Node *) tle->expr);
120                         typename->typmod = exprTypmod((Node *) tle->expr);
121                         def->typename = typename;
122
123                         def->inhcount = 0;
124                         def->is_local = true;
125                         def->is_not_null = false;
126                         def->raw_default = NULL;
127                         def->cooked_default = NULL;
128                         def->constraints = NIL;
129                         def->support = NULL;
130
131                         attrList = lappend(attrList, def);
132                 }
133         }
134
135         if (attrList == NIL)
136                 ereport(ERROR,
137                                 (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
138                                  errmsg("view must have at least one column")));
139
140         /*
141          * Check to see if we want to replace an existing view.
142          */
143         namespaceId = RangeVarGetCreationNamespace(relation);
144         viewOid = get_relname_relid(relation->relname, namespaceId);
145
146         if (OidIsValid(viewOid) && replace)
147         {
148                 Relation        rel;
149                 TupleDesc       descriptor;
150
151                 /*
152                  * Yes.  Get exclusive lock on the existing view ...
153                  */
154                 rel = relation_open(viewOid, AccessExclusiveLock);
155
156                 /*
157                  * Make sure it *is* a view, and do permissions checks.
158                  */
159                 if (rel->rd_rel->relkind != RELKIND_VIEW)
160                         ereport(ERROR,
161                                         (errcode(ERRCODE_WRONG_OBJECT_TYPE),
162                                          errmsg("\"%s\" is not a view",
163                                                         RelationGetRelationName(rel))));
164
165                 if (!pg_class_ownercheck(viewOid, GetUserId()))
166                         aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
167                                                    RelationGetRelationName(rel));
168
169                 /*
170                  * Due to the namespace visibility rules for temporary
171                  * objects, we should only end up replacing a temporary view
172                  * with another temporary view, and vice versa.
173                  */
174                 Assert(relation->istemp == rel->rd_istemp);
175
176                 /*
177                  * Create a tuple descriptor to compare against the existing view,
178                  * and verify it matches.
179                  */
180                 descriptor = BuildDescForRelation(attrList);
181                 checkViewTupleDesc(descriptor, rel->rd_att);
182
183                 /*
184                  * Seems okay, so return the OID of the pre-existing view.
185                  */
186                 relation_close(rel, NoLock);    /* keep the lock! */
187
188                 return viewOid;
189         }
190         else
191         {
192                 /*
193                  * now set the parameters for keys/inheritance etc. All of these
194                  * are uninteresting for views...
195                  */
196                 createStmt->relation = (RangeVar *) relation;
197                 createStmt->tableElts = attrList;
198                 createStmt->inhRelations = NIL;
199                 createStmt->constraints = NIL;
200                 createStmt->hasoids = MUST_NOT_HAVE_OIDS;
201                 createStmt->oncommit = ONCOMMIT_NOOP;
202                 createStmt->tablespacename = NULL;
203
204                 /*
205                  * finally create the relation (this will error out if there's an
206                  * existing view, so we don't need more code to complain if
207                  * "replace" is false).
208                  */
209                 return DefineRelation(createStmt, RELKIND_VIEW);
210         }
211 }
212
213 /*
214  * Verify that tupledesc associated with proposed new view definition
215  * matches tupledesc of old view.  This is basically a cut-down version
216  * of equalTupleDescs(), with code added to generate specific complaints.
217  */
218 static void
219 checkViewTupleDesc(TupleDesc newdesc, TupleDesc olddesc)
220 {
221         int                     i;
222
223         if (newdesc->natts != olddesc->natts)
224                 ereport(ERROR,
225                                 (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
226                                  errmsg("cannot change number of columns in view")));
227         /* we can ignore tdhasoid */
228
229         for (i = 0; i < newdesc->natts; i++)
230         {
231                 Form_pg_attribute newattr = newdesc->attrs[i];
232                 Form_pg_attribute oldattr = olddesc->attrs[i];
233
234                 /* XXX not right, but we don't support DROP COL on view anyway */
235                 if (newattr->attisdropped != oldattr->attisdropped)
236                         ereport(ERROR,
237                                         (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
238                                          errmsg("cannot change number of columns in view")));
239
240                 if (strcmp(NameStr(newattr->attname), NameStr(oldattr->attname)) != 0)
241                         ereport(ERROR,
242                                         (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
243                                          errmsg("cannot change name of view column \"%s\"",
244                                                         NameStr(oldattr->attname))));
245                 /* XXX would it be safe to allow atttypmod to change?  Not sure */
246                 if (newattr->atttypid != oldattr->atttypid ||
247                         newattr->atttypmod != oldattr->atttypmod)
248                         ereport(ERROR,
249                                         (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
250                                   errmsg("cannot change data type of view column \"%s\"",
251                                                  NameStr(oldattr->attname))));
252                 /* We can ignore the remaining attributes of an attribute... */
253         }
254
255         /*
256          * We ignore the constraint fields.  The new view desc can't have any
257          * constraints, and the only ones that could be on the old view are
258          * defaults, which we are happy to leave in place.
259          */
260 }
261
262 static RuleStmt *
263 FormViewRetrieveRule(const RangeVar *view, Query *viewParse, bool replace)
264 {
265         RuleStmt   *rule;
266
267         /*
268          * Create a RuleStmt that corresponds to the suitable rewrite rule
269          * args for DefineQueryRewrite();
270          */
271         rule = makeNode(RuleStmt);
272         rule->relation = copyObject((RangeVar *) view);
273         rule->rulename = pstrdup(ViewSelectRuleName);
274         rule->whereClause = NULL;
275         rule->event = CMD_SELECT;
276         rule->instead = true;
277         rule->actions = list_make1(viewParse);
278         rule->replace = replace;
279
280         return rule;
281 }
282
283 static void
284 DefineViewRules(const RangeVar *view, Query *viewParse, bool replace)
285 {
286         RuleStmt   *retrieve_rule;
287
288 #ifdef NOTYET
289         RuleStmt   *replace_rule;
290         RuleStmt   *append_rule;
291         RuleStmt   *delete_rule;
292 #endif
293
294         retrieve_rule = FormViewRetrieveRule(view, viewParse, replace);
295
296 #ifdef NOTYET
297         replace_rule = FormViewReplaceRule(view, viewParse);
298         append_rule = FormViewAppendRule(view, viewParse);
299         delete_rule = FormViewDeleteRule(view, viewParse);
300 #endif
301
302         DefineQueryRewrite(retrieve_rule);
303
304 #ifdef NOTYET
305         DefineQueryRewrite(replace_rule);
306         DefineQueryRewrite(append_rule);
307         DefineQueryRewrite(delete_rule);
308 #endif
309
310 }
311
312 /*---------------------------------------------------------------
313  * UpdateRangeTableOfViewParse
314  *
315  * Update the range table of the given parsetree.
316  * This update consists of adding two new entries IN THE BEGINNING
317  * of the range table (otherwise the rule system will die a slow,
318  * horrible and painful death, and we do not want that now, do we?)
319  * one for the OLD relation and one for the NEW one (both of
320  * them refer in fact to the "view" relation).
321  *
322  * Of course we must also increase the 'varnos' of all the Var nodes
323  * by 2...
324  *
325  * These extra RT entries are not actually used in the query,
326  * except for run-time permission checking.
327  *---------------------------------------------------------------
328  */
329 static Query *
330 UpdateRangeTableOfViewParse(Oid viewOid, Query *viewParse)
331 {
332         Relation        viewRel;
333         List       *new_rt;
334         RangeTblEntry *rt_entry1,
335                            *rt_entry2;
336
337         /*
338          * Make a copy of the given parsetree.  It's not so much that we don't
339          * want to scribble on our input, it's that the parser has a bad habit
340          * of outputting multiple links to the same subtree for constructs
341          * like BETWEEN, and we mustn't have OffsetVarNodes increment the
342          * varno of a Var node twice.  copyObject will expand any
343          * multiply-referenced subtree into multiple copies.
344          */
345         viewParse = (Query *) copyObject(viewParse);
346
347         /* need to open the rel for addRangeTableEntryForRelation */
348         viewRel = relation_open(viewOid, AccessShareLock);
349
350         /*
351          * Create the 2 new range table entries and form the new range
352          * table... OLD first, then NEW....
353          */
354         rt_entry1 = addRangeTableEntryForRelation(NULL, viewRel,
355                                                                                           makeAlias("*OLD*", NIL),
356                                                                                           false, false);
357         rt_entry2 = addRangeTableEntryForRelation(NULL, viewRel,
358                                                                                           makeAlias("*NEW*", NIL),
359                                                                                           false, false);
360         /* Must override addRangeTableEntry's default access-check flags */
361         rt_entry1->requiredPerms = 0;
362         rt_entry2->requiredPerms = 0;
363
364         new_rt = lcons(rt_entry1, lcons(rt_entry2, viewParse->rtable));
365
366         viewParse->rtable = new_rt;
367
368         /*
369          * Now offset all var nodes by 2, and jointree RT indexes too.
370          */
371         OffsetVarNodes((Node *) viewParse, 2, 0);
372
373         relation_close(viewRel, AccessShareLock);
374
375         return viewParse;
376 }
377
378 /*-------------------------------------------------------------------
379  * DefineView
380  *
381  *              - takes a "viewname", "parsetree" pair and then
382  *              1)              construct the "virtual" relation
383  *              2)              commit the command but NOT the transaction,
384  *                              so that the relation exists
385  *                              before the rules are defined.
386  *              2)              define the "n" rules specified in the PRS2 paper
387  *                              over the "virtual" relation
388  *-------------------------------------------------------------------
389  */
390 void
391 DefineView(RangeVar *view, Query *viewParse, bool replace)
392 {
393         Oid                     viewOid;
394
395         /*
396          * If the user didn't explicitly ask for a temporary view, check
397          * whether we need one implicitly.
398          */
399         if (!view->istemp)
400         {
401                 view->istemp = isViewOnTempTable(viewParse);
402                 if (view->istemp)
403                         ereport(NOTICE,
404                                         (errmsg("view \"%s\" will be a temporary view",
405                                                         view->relname)));
406         }
407                 
408         /*
409          * Create the view relation
410          *
411          * NOTE: if it already exists and replace is false, the xact will be
412          * aborted.
413          */
414         viewOid = DefineVirtualRelation(view, viewParse->targetList, replace);
415
416         /*
417          * The relation we have just created is not visible to any other
418          * commands running with the same transaction & command id. So,
419          * increment the command id counter (but do NOT pfree any memory!!!!)
420          */
421         CommandCounterIncrement();
422
423         /*
424          * The range table of 'viewParse' does not contain entries for the
425          * "OLD" and "NEW" relations. So... add them!
426          */
427         viewParse = UpdateRangeTableOfViewParse(viewOid, viewParse);
428
429         /*
430          * Now create the rules associated with the view.
431          */
432         DefineViewRules(view, viewParse, replace);
433 }
434
435 /*
436  * RemoveView
437  *
438  * Remove a view given its name
439  *
440  * We just have to drop the relation; the associated rules will be
441  * cleaned up automatically.
442  */
443 void
444 RemoveView(const RangeVar *view, DropBehavior behavior)
445 {
446         Oid                     viewOid;
447         ObjectAddress object;
448
449         viewOid = RangeVarGetRelid(view, false);
450
451         object.classId = RelationRelationId;
452         object.objectId = viewOid;
453         object.objectSubId = 0;
454
455         performDeletion(&object, behavior);
456 }