mirror of
https://github.com/postgres/postgres.git
synced 2025-06-25 01:02:05 +03:00
Subselects in FROM clause, per ISO syntax: FROM (SELECT ...) [AS] alias.
(Don't forget that an alias is required.) Views reimplemented as expanding to subselect-in-FROM. Grouping, aggregates, DISTINCT in views actually work now (he says optimistically). No UNION support in subselects/views yet, but I have some ideas about that. Rule-related permissions checking moved out of rewriter and into executor. INITDB REQUIRED!
This commit is contained in:
@ -4,7 +4,7 @@
|
||||
# Makefile for rewrite
|
||||
#
|
||||
# IDENTIFICATION
|
||||
# $Header: /cvsroot/pgsql/src/backend/rewrite/Makefile,v 1.13 2000/08/31 16:10:27 petere Exp $
|
||||
# $Header: /cvsroot/pgsql/src/backend/rewrite/Makefile,v 1.14 2000/09/29 18:21:24 tgl Exp $
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
|
||||
@ -13,7 +13,7 @@ top_builddir = ../../..
|
||||
include $(top_builddir)/src/Makefile.global
|
||||
|
||||
OBJS = rewriteRemove.o rewriteDefine.o \
|
||||
rewriteHandler.o rewriteManip.o rewriteSupport.o locks.o
|
||||
rewriteHandler.o rewriteManip.o rewriteSupport.o
|
||||
|
||||
all: SUBSYS.o
|
||||
|
||||
|
@ -1,268 +0,0 @@
|
||||
/*-------------------------------------------------------------------------
|
||||
*
|
||||
* locks.c
|
||||
*
|
||||
* Portions Copyright (c) 1996-2000, PostgreSQL, Inc
|
||||
* Portions Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $Header: /cvsroot/pgsql/src/backend/rewrite/Attic/locks.c,v 1.32 2000/09/12 21:07:02 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
#include "postgres.h"
|
||||
|
||||
#include "access/heapam.h"
|
||||
#include "catalog/pg_shadow.h"
|
||||
#include "optimizer/clauses.h"
|
||||
#include "rewrite/locks.h"
|
||||
#include "parser/parsetree.h"
|
||||
#include "utils/acl.h"
|
||||
#include "utils/syscache.h"
|
||||
|
||||
|
||||
/*
|
||||
* thisLockWasTriggered
|
||||
*
|
||||
* walk the tree, if there we find a varnode,
|
||||
* we check the varattno against the attnum
|
||||
* if we find at least one such match, we return true
|
||||
* otherwise, we return false
|
||||
*
|
||||
* XXX this should be unified with attribute_used()
|
||||
*/
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int varno;
|
||||
int attnum;
|
||||
int sublevels_up;
|
||||
} thisLockWasTriggered_context;
|
||||
|
||||
static bool
|
||||
thisLockWasTriggered_walker(Node *node,
|
||||
thisLockWasTriggered_context *context)
|
||||
{
|
||||
if (node == NULL)
|
||||
return false;
|
||||
if (IsA(node, Var))
|
||||
{
|
||||
Var *var = (Var *) node;
|
||||
|
||||
if (var->varlevelsup == context->sublevels_up &&
|
||||
var->varno == context->varno &&
|
||||
(var->varattno == context->attnum || context->attnum == -1))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
if (IsA(node, Query))
|
||||
{
|
||||
/* Recurse into subselects */
|
||||
bool result;
|
||||
|
||||
context->sublevels_up++;
|
||||
result = query_tree_walker((Query *) node, thisLockWasTriggered_walker,
|
||||
(void *) context);
|
||||
context->sublevels_up--;
|
||||
return result;
|
||||
}
|
||||
return expression_tree_walker(node, thisLockWasTriggered_walker,
|
||||
(void *) context);
|
||||
}
|
||||
|
||||
static bool
|
||||
thisLockWasTriggered(int varno,
|
||||
int attnum,
|
||||
Query *parsetree)
|
||||
{
|
||||
thisLockWasTriggered_context context;
|
||||
|
||||
context.varno = varno;
|
||||
context.attnum = attnum;
|
||||
context.sublevels_up = 0;
|
||||
|
||||
return thisLockWasTriggered_walker((Node *) parsetree, &context);
|
||||
}
|
||||
|
||||
/*
|
||||
* matchLocks -
|
||||
* match the list of locks and returns the matching rules
|
||||
*/
|
||||
List *
|
||||
matchLocks(CmdType event,
|
||||
RuleLock *rulelocks,
|
||||
int varno,
|
||||
Query *parsetree)
|
||||
{
|
||||
List *real_locks = NIL;
|
||||
int nlocks;
|
||||
int i;
|
||||
|
||||
Assert(rulelocks != NULL); /* we get called iff there is some lock */
|
||||
Assert(parsetree != NULL);
|
||||
|
||||
if (parsetree->commandType != CMD_SELECT)
|
||||
{
|
||||
if (parsetree->resultRelation != varno)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
nlocks = rulelocks->numLocks;
|
||||
|
||||
for (i = 0; i < nlocks; i++)
|
||||
{
|
||||
RewriteRule *oneLock = rulelocks->rules[i];
|
||||
|
||||
if (oneLock->event == event)
|
||||
{
|
||||
if (parsetree->commandType != CMD_SELECT ||
|
||||
thisLockWasTriggered(varno,
|
||||
oneLock->attrno,
|
||||
parsetree))
|
||||
real_locks = lappend(real_locks, oneLock);
|
||||
}
|
||||
}
|
||||
|
||||
checkLockPerms(real_locks, parsetree, varno);
|
||||
|
||||
return real_locks;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Check the access permissions of tables that are referred to by a rule.
|
||||
* We want to check the access permissions using the userid of the rule's
|
||||
* owner, *not* of the current user (the one accessing the rule). So, we
|
||||
* do the permission check here and set skipAcl = TRUE in each of the rule's
|
||||
* RTEs, to prevent the executor from running another check with the current
|
||||
* user's ID.
|
||||
*
|
||||
* XXX This routine is called before the rule's query tree has been copied
|
||||
* out of the relcache entry where it is kept. Therefore, when we set
|
||||
* skipAcl = TRUE, we are destructively modifying the relcache entry for
|
||||
* the event relation! This seems fairly harmless because the relcache
|
||||
* querytree is only used as a source for the rewriter, but it's a tad
|
||||
* unclean anyway.
|
||||
*
|
||||
* Note that we must check permissions every time, even if skipAcl was
|
||||
* already set TRUE by a prior call. This ensures that we enforce the
|
||||
* current permission settings for each referenced table, even if they
|
||||
* have changed since the relcache entry was loaded.
|
||||
*/
|
||||
|
||||
typedef struct
|
||||
{
|
||||
Oid evowner;
|
||||
} checkLockPerms_context;
|
||||
|
||||
static bool
|
||||
checkLockPerms_walker(Node *node,
|
||||
checkLockPerms_context *context)
|
||||
{
|
||||
if (node == NULL)
|
||||
return false;
|
||||
if (IsA(node, Query))
|
||||
{
|
||||
Query *qry = (Query *) node;
|
||||
int rtablength = length(qry->rtable);
|
||||
int i;
|
||||
|
||||
/* Check all the RTEs in this query node, except OLD and NEW */
|
||||
for (i = 1; i <= rtablength; i++)
|
||||
{
|
||||
RangeTblEntry *rte = rt_fetch(i, qry->rtable);
|
||||
int32 reqperm;
|
||||
int32 aclcheck_res;
|
||||
|
||||
if (strcmp(rte->eref->relname, "*NEW*") == 0)
|
||||
continue;
|
||||
if (strcmp(rte->eref->relname, "*OLD*") == 0)
|
||||
continue;
|
||||
|
||||
if (i == qry->resultRelation)
|
||||
switch (qry->commandType)
|
||||
{
|
||||
case CMD_INSERT:
|
||||
reqperm = ACL_AP;
|
||||
break;
|
||||
default:
|
||||
reqperm = ACL_WR;
|
||||
break;
|
||||
}
|
||||
else
|
||||
reqperm = ACL_RD;
|
||||
|
||||
aclcheck_res = pg_aclcheck(rte->relname,
|
||||
context->evowner,
|
||||
reqperm);
|
||||
if (aclcheck_res != ACLCHECK_OK)
|
||||
elog(ERROR, "%s: %s",
|
||||
rte->relname,
|
||||
aclcheck_error_strings[aclcheck_res]);
|
||||
|
||||
/*
|
||||
* Mark RTE to prevent executor from checking again with the
|
||||
* current user's ID...
|
||||
*/
|
||||
rte->skipAcl = true;
|
||||
}
|
||||
|
||||
/* If there are sublinks, search for them and check their RTEs */
|
||||
if (qry->hasSubLinks)
|
||||
return query_tree_walker(qry, checkLockPerms_walker,
|
||||
(void *) context);
|
||||
return false;
|
||||
}
|
||||
return expression_tree_walker(node, checkLockPerms_walker,
|
||||
(void *) context);
|
||||
}
|
||||
|
||||
void
|
||||
checkLockPerms(List *locks, Query *parsetree, int rt_index)
|
||||
{
|
||||
RangeTblEntry *rte;
|
||||
Relation ev_rel;
|
||||
HeapTuple usertup;
|
||||
Form_pg_shadow userform;
|
||||
checkLockPerms_context context;
|
||||
List *l;
|
||||
|
||||
if (locks == NIL)
|
||||
return; /* nothing to check */
|
||||
|
||||
/*
|
||||
* Get the userid of the rule's event relation owner
|
||||
*/
|
||||
rte = rt_fetch(rt_index, parsetree->rtable);
|
||||
ev_rel = heap_openr(rte->relname, AccessShareLock);
|
||||
usertup = SearchSysCacheTuple(SHADOWSYSID,
|
||||
ObjectIdGetDatum(ev_rel->rd_rel->relowner),
|
||||
0, 0, 0);
|
||||
if (!HeapTupleIsValid(usertup))
|
||||
elog(ERROR, "cache lookup for userid %d failed",
|
||||
ev_rel->rd_rel->relowner);
|
||||
userform = (Form_pg_shadow) GETSTRUCT(usertup);
|
||||
context.evowner = userform->usesysid;
|
||||
heap_close(ev_rel, AccessShareLock);
|
||||
|
||||
/*
|
||||
* Check all the locks that should get fired on this query
|
||||
*/
|
||||
foreach(l, locks)
|
||||
{
|
||||
RewriteRule *onelock = (RewriteRule *) lfirst(l);
|
||||
List *action;
|
||||
|
||||
/*
|
||||
* In each lock check every action. We must scan the action
|
||||
* recursively in case there are any sub-queries within it.
|
||||
*/
|
||||
foreach(action, onelock->actions)
|
||||
{
|
||||
Query *query = (Query *) lfirst(action);
|
||||
|
||||
checkLockPerms_walker((Node *) query, &context);
|
||||
}
|
||||
}
|
||||
}
|
@ -8,7 +8,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteDefine.c,v 1.52 2000/09/12 20:38:09 tgl Exp $
|
||||
* $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteDefine.c,v 1.53 2000/09/29 18:21:24 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -16,16 +16,21 @@
|
||||
#include "postgres.h"
|
||||
|
||||
#include "access/heapam.h"
|
||||
#include "utils/builtins.h"
|
||||
#include "catalog/catname.h"
|
||||
#include "catalog/indexing.h"
|
||||
#include "catalog/pg_rewrite.h"
|
||||
#include "commands/view.h"
|
||||
#include "miscadmin.h"
|
||||
#include "optimizer/clauses.h"
|
||||
#include "parser/parse_relation.h"
|
||||
#include "rewrite/rewriteDefine.h"
|
||||
#include "rewrite/rewriteSupport.h"
|
||||
#include "utils/syscache.h"
|
||||
#include "storage/smgr.h"
|
||||
#include "commands/view.h"
|
||||
#include "utils/builtins.h"
|
||||
|
||||
|
||||
static void setRuleCheckAsUser(Query *qry, Oid userid);
|
||||
static bool setRuleCheckAsUser_walker(Node *node, Oid *context);
|
||||
|
||||
|
||||
/*
|
||||
@ -52,7 +57,7 @@ InsertRule(char *rulname,
|
||||
Oid rewriteObjectId;
|
||||
|
||||
if (IsDefinedRewriteRule(rulname))
|
||||
elog(ERROR, "Attempt to insert rule '%s' failed: already exists",
|
||||
elog(ERROR, "Attempt to insert rule \"%s\" failed: already exists",
|
||||
rulname);
|
||||
|
||||
/* ----------------
|
||||
@ -217,10 +222,7 @@ DefineQueryRewrite(RuleStmt *stmt)
|
||||
*/
|
||||
if (event_type == CMD_SELECT)
|
||||
{
|
||||
TargetEntry *tle;
|
||||
Resdom *resdom;
|
||||
Form_pg_attribute attr;
|
||||
char *attname;
|
||||
List *tllist;
|
||||
int i;
|
||||
char *expected_name;
|
||||
|
||||
@ -245,6 +247,10 @@ DefineQueryRewrite(RuleStmt *stmt)
|
||||
query = (Query *) lfirst(action);
|
||||
if (!is_instead || query->commandType != CMD_SELECT)
|
||||
elog(ERROR, "only instead-select rules currently supported on select");
|
||||
|
||||
/*
|
||||
* ... there can be no rule qual, ...
|
||||
*/
|
||||
if (event_qual != NULL)
|
||||
elog(ERROR, "event qualifications not supported for rules on select");
|
||||
|
||||
@ -252,26 +258,36 @@ DefineQueryRewrite(RuleStmt *stmt)
|
||||
* ... the targetlist of the SELECT action must exactly match the
|
||||
* event relation, ...
|
||||
*/
|
||||
if (event_relation->rd_att->natts != length(query->targetList))
|
||||
elog(ERROR, "select rules target list must match event relations structure");
|
||||
|
||||
for (i = 1; i <= event_relation->rd_att->natts; i++)
|
||||
i = 0;
|
||||
foreach(tllist, query->targetList)
|
||||
{
|
||||
tle = (TargetEntry *) nth(i - 1, query->targetList);
|
||||
resdom = tle->resdom;
|
||||
TargetEntry *tle = (TargetEntry *) lfirst(tllist);
|
||||
Resdom *resdom = tle->resdom;
|
||||
Form_pg_attribute attr;
|
||||
char *attname;
|
||||
|
||||
if (resdom->resjunk)
|
||||
continue;
|
||||
i++;
|
||||
if (i > event_relation->rd_att->natts)
|
||||
elog(ERROR, "select rule's target list has too many entries");
|
||||
|
||||
attr = event_relation->rd_att->attrs[i - 1];
|
||||
attname = NameStr(attr->attname);
|
||||
|
||||
if (strcmp(resdom->resname, attname) != 0)
|
||||
elog(ERROR, "select rules target entry %d has different column name from %s", i, attname);
|
||||
elog(ERROR, "select rule's target entry %d has different column name from %s", i, attname);
|
||||
|
||||
if (attr->atttypid != resdom->restype)
|
||||
elog(ERROR, "select rules target entry %d has different type from attribute %s", i, attname);
|
||||
elog(ERROR, "select rule's target entry %d has different type from attribute %s", i, attname);
|
||||
|
||||
if (attr->atttypmod != resdom->restypmod)
|
||||
elog(ERROR, "select rules target entry %d has different size from attribute %s", i, attname);
|
||||
elog(ERROR, "select rule's target entry %d has different size from attribute %s", i, attname);
|
||||
}
|
||||
|
||||
if (i != event_relation->rd_att->natts)
|
||||
elog(ERROR, "select rule's target list has too few entries");
|
||||
|
||||
/*
|
||||
* ... there must not be another ON SELECT rule already ...
|
||||
*/
|
||||
@ -283,7 +299,7 @@ DefineQueryRewrite(RuleStmt *stmt)
|
||||
|
||||
rule = event_relation->rd_rules->rules[i];
|
||||
if (rule->event == CMD_SELECT)
|
||||
elog(ERROR, "%s is already a view",
|
||||
elog(ERROR, "\"%s\" is already a view",
|
||||
RelationGetRelationName(event_relation));
|
||||
}
|
||||
}
|
||||
@ -294,25 +310,13 @@ DefineQueryRewrite(RuleStmt *stmt)
|
||||
if (query->limitOffset != NULL || query->limitCount != NULL)
|
||||
elog(ERROR, "LIMIT clause not supported in views");
|
||||
|
||||
/*
|
||||
* DISTINCT on view is not supported
|
||||
*/
|
||||
if (query->distinctClause != NIL)
|
||||
elog(ERROR, "DISTINCT not supported in views");
|
||||
|
||||
/*
|
||||
* ORDER BY in view is not supported
|
||||
*/
|
||||
if (query->sortClause != NIL)
|
||||
elog(ERROR, "ORDER BY not supported in views");
|
||||
|
||||
/*
|
||||
* ... and finally the rule must be named _RETviewname.
|
||||
*/
|
||||
expected_name = MakeRetrieveViewRuleName(event_obj->relname);
|
||||
if (strcmp(expected_name, stmt->rulename) != 0)
|
||||
{
|
||||
elog(ERROR, "view rule for %s must be named %s",
|
||||
elog(ERROR, "view rule for \"%s\" must be named \"%s\"",
|
||||
event_obj->relname, expected_name);
|
||||
}
|
||||
pfree(expected_name);
|
||||
@ -342,7 +346,7 @@ DefineQueryRewrite(RuleStmt *stmt)
|
||||
}
|
||||
|
||||
/*
|
||||
* This rule is allowed - install it.
|
||||
* This rule is allowed - prepare to install it.
|
||||
*/
|
||||
if (eslot_string == NULL)
|
||||
{
|
||||
@ -360,13 +364,21 @@ DefineQueryRewrite(RuleStmt *stmt)
|
||||
eslot_string, event_qual, &action,
|
||||
is_instead, event_attype);
|
||||
|
||||
/*
|
||||
* We want the rule's table references to be checked as though by
|
||||
* the rule owner, not the user referencing the rule. Therefore,
|
||||
* scan through the rule's rtables and set the checkAsUser field
|
||||
* on all rtable entries (except *OLD* and *NEW*).
|
||||
*/
|
||||
foreach(l, action)
|
||||
{
|
||||
query = (Query *) lfirst(l);
|
||||
setRuleCheckAsUser(query, GetUserId());
|
||||
}
|
||||
|
||||
/* discard rule if it's null action and not INSTEAD; it's a no-op */
|
||||
if (action != NIL || is_instead)
|
||||
{
|
||||
Relation relationRelation;
|
||||
HeapTuple tuple;
|
||||
Relation idescs[Num_pg_class_indices];
|
||||
|
||||
event_qualP = nodeToString(event_qual);
|
||||
actionP = nodeToString(action);
|
||||
|
||||
@ -386,34 +398,8 @@ DefineQueryRewrite(RuleStmt *stmt)
|
||||
* Important side effect: an SI notice is broadcast to force all
|
||||
* backends (including me!) to update relcache entries with the new
|
||||
* rule.
|
||||
*
|
||||
* NOTE : Used to call setRelhasrulesInRelation. The code
|
||||
* was inlined so that two updates were not needed. mhh 31-aug-2000
|
||||
*/
|
||||
|
||||
/*
|
||||
* Find the tuple to update in pg_class, using syscache for the lookup.
|
||||
*/
|
||||
relationRelation = heap_openr(RelationRelationName, RowExclusiveLock);
|
||||
tuple = SearchSysCacheTupleCopy(RELOID,
|
||||
ObjectIdGetDatum(ev_relid),
|
||||
0, 0, 0);
|
||||
Assert(HeapTupleIsValid(tuple));
|
||||
|
||||
/* Do the update */
|
||||
((Form_pg_class) GETSTRUCT(tuple))->relhasrules = true;
|
||||
if (RelisBecomingView)
|
||||
((Form_pg_class) GETSTRUCT(tuple))->relkind = RELKIND_VIEW;
|
||||
|
||||
heap_update(relationRelation, &tuple->t_self, tuple, NULL);
|
||||
|
||||
/* Keep the catalog indices up to date */
|
||||
CatalogOpenIndices(Num_pg_class_indices, Name_pg_class_indices, idescs);
|
||||
CatalogIndexInsert(idescs, Num_pg_class_indices, relationRelation, tuple);
|
||||
CatalogCloseIndices(Num_pg_class_indices, idescs);
|
||||
|
||||
heap_freetuple(tuple);
|
||||
heap_close(relationRelation, RowExclusiveLock);
|
||||
SetRelationRuleStatus(ev_relid, true, RelisBecomingView);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -427,3 +413,60 @@ DefineQueryRewrite(RuleStmt *stmt)
|
||||
/* Close rel, but keep lock till commit... */
|
||||
heap_close(event_relation, NoLock);
|
||||
}
|
||||
|
||||
/*
|
||||
* setRuleCheckAsUser
|
||||
* Recursively scan a query and set the checkAsUser field to the
|
||||
* given userid in all rtable entries except *OLD* and *NEW*.
|
||||
*/
|
||||
static void
|
||||
setRuleCheckAsUser(Query *qry, Oid userid)
|
||||
{
|
||||
List *l;
|
||||
|
||||
/* Set all the RTEs in this query node, except OLD and NEW */
|
||||
foreach(l, qry->rtable)
|
||||
{
|
||||
RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
|
||||
|
||||
if (strcmp(rte->eref->relname, "*NEW*") == 0)
|
||||
continue;
|
||||
if (strcmp(rte->eref->relname, "*OLD*") == 0)
|
||||
continue;
|
||||
|
||||
if (rte->subquery)
|
||||
{
|
||||
/*
|
||||
* Recurse into subquery in FROM
|
||||
*/
|
||||
setRuleCheckAsUser(rte->subquery, userid);
|
||||
}
|
||||
else
|
||||
{
|
||||
rte->checkAsUser = userid;
|
||||
}
|
||||
}
|
||||
|
||||
/* If there are sublinks, search for them and process their RTEs */
|
||||
if (qry->hasSubLinks)
|
||||
query_tree_walker(qry, setRuleCheckAsUser_walker, (void *) &userid);
|
||||
}
|
||||
|
||||
/*
|
||||
* Expression-tree walker to find sublink queries
|
||||
*/
|
||||
static bool
|
||||
setRuleCheckAsUser_walker(Node *node, Oid *context)
|
||||
{
|
||||
if (node == NULL)
|
||||
return false;
|
||||
if (IsA(node, Query))
|
||||
{
|
||||
Query *qry = (Query *) node;
|
||||
|
||||
setRuleCheckAsUser(qry, *context);
|
||||
return false;
|
||||
}
|
||||
return expression_tree_walker(node, setRuleCheckAsUser_walker,
|
||||
(void *) context);
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -7,7 +7,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteManip.c,v 1.48 2000/09/12 21:07:04 tgl Exp $
|
||||
* $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteManip.c,v 1.49 2000/09/29 18:21:24 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -473,8 +473,7 @@ attribute_used(Node *node, int rt_index, int attno, int sublevels_up)
|
||||
void
|
||||
AddQual(Query *parsetree, Node *qual)
|
||||
{
|
||||
Node *copy,
|
||||
*old;
|
||||
Node *copy;
|
||||
|
||||
if (qual == NULL)
|
||||
return;
|
||||
@ -482,11 +481,8 @@ AddQual(Query *parsetree, Node *qual)
|
||||
/* INTERSECT want's the original, but we need to copy - Jan */
|
||||
copy = copyObject(qual);
|
||||
|
||||
old = parsetree->qual;
|
||||
if (old == NULL)
|
||||
parsetree->qual = copy;
|
||||
else
|
||||
parsetree->qual = (Node *) make_andclause(makeList(old, copy, -1));
|
||||
parsetree->jointree->quals = make_and_qual(parsetree->jointree->quals,
|
||||
copy);
|
||||
|
||||
/*
|
||||
* Make sure query is marked correctly if added qual has sublinks or
|
||||
@ -504,8 +500,7 @@ AddQual(Query *parsetree, Node *qual)
|
||||
void
|
||||
AddHavingQual(Query *parsetree, Node *havingQual)
|
||||
{
|
||||
Node *copy,
|
||||
*old;
|
||||
Node *copy;
|
||||
|
||||
if (havingQual == NULL)
|
||||
return;
|
||||
@ -513,11 +508,8 @@ AddHavingQual(Query *parsetree, Node *havingQual)
|
||||
/* INTERSECT want's the original, but we need to copy - Jan */
|
||||
copy = copyObject(havingQual);
|
||||
|
||||
old = parsetree->havingQual;
|
||||
if (old == NULL)
|
||||
parsetree->havingQual = copy;
|
||||
else
|
||||
parsetree->havingQual = (Node *) make_andclause(makeList(old, copy, -1));
|
||||
parsetree->havingQual = make_and_qual(parsetree->havingQual,
|
||||
copy);
|
||||
|
||||
/*
|
||||
* Make sure query is marked correctly if added qual has sublinks or
|
||||
@ -560,45 +552,7 @@ AddNotQual(Query *parsetree, Node *qual)
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Add all expressions used by the given GroupClause list to the
|
||||
* parsetree's targetlist and groupclause list.
|
||||
*
|
||||
* tlist is the old targetlist associated with the input groupclauses.
|
||||
*
|
||||
* XXX shouldn't we be checking to see if there are already matching
|
||||
* entries in parsetree->targetlist?
|
||||
*/
|
||||
void
|
||||
AddGroupClause(Query *parsetree, List *group_by, List *tlist)
|
||||
{
|
||||
List *l;
|
||||
|
||||
foreach(l, group_by)
|
||||
{
|
||||
GroupClause *groupclause = (GroupClause *) copyObject(lfirst(l));
|
||||
TargetEntry *tle = get_sortgroupclause_tle(groupclause, tlist);
|
||||
|
||||
/* copy the groupclause's TLE from the old tlist */
|
||||
tle = (TargetEntry *) copyObject(tle);
|
||||
|
||||
/*
|
||||
* The ressortgroupref number in the old tlist might be already
|
||||
* taken in the new tlist, so force assignment of a new number.
|
||||
*/
|
||||
tle->resdom->ressortgroupref = 0;
|
||||
groupclause->tleSortGroupRef =
|
||||
assignSortGroupRef(tle, parsetree->targetList);
|
||||
|
||||
/* Also need to set the resno and mark it resjunk. */
|
||||
tle->resdom->resno = length(parsetree->targetList) + 1;
|
||||
tle->resdom->resjunk = true;
|
||||
|
||||
parsetree->targetList = lappend(parsetree->targetList, tle);
|
||||
parsetree->groupClause = lappend(parsetree->groupClause, groupclause);
|
||||
}
|
||||
}
|
||||
|
||||
/* Build a NULL constant expression of the given type */
|
||||
static Node *
|
||||
make_null(Oid type)
|
||||
{
|
||||
@ -612,28 +566,6 @@ make_null(Oid type)
|
||||
return (Node *) c;
|
||||
}
|
||||
|
||||
#ifdef NOT_USED
|
||||
void
|
||||
FixResdomTypes(List *tlist)
|
||||
{
|
||||
List *i;
|
||||
|
||||
foreach(i, tlist)
|
||||
{
|
||||
TargetEntry *tle = lfirst(i);
|
||||
|
||||
if (nodeTag(tle->expr) == T_Var)
|
||||
{
|
||||
Var *var = (Var *) tle->expr;
|
||||
|
||||
tle->resdom->restype = var->vartype;
|
||||
tle->resdom->restypmod = var->vartypmod;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/* Find a targetlist entry by resno */
|
||||
static Node *
|
||||
FindMatchingNew(List *tlist, int attno)
|
||||
@ -650,6 +582,8 @@ FindMatchingNew(List *tlist, int attno)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#ifdef NOT_USED
|
||||
|
||||
/* Find a targetlist entry by resname */
|
||||
static Node *
|
||||
FindMatchingTLEntry(List *tlist, char *e_attname)
|
||||
@ -662,25 +596,31 @@ FindMatchingTLEntry(List *tlist, char *e_attname)
|
||||
char *resname;
|
||||
|
||||
resname = tle->resdom->resname;
|
||||
if (!strcmp(e_attname, resname))
|
||||
if (strcmp(e_attname, resname) == 0)
|
||||
return tle->expr;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
* ResolveNew - replace Vars with corresponding items from a targetlist
|
||||
*
|
||||
* Vars matching info->new_varno and sublevels_up are replaced by the
|
||||
* Vars matching target_varno and sublevels_up are replaced by the
|
||||
* entry with matching resno from targetlist, if there is one.
|
||||
* If not, we either change the unmatched Var's varno to update_varno
|
||||
* (when event == CMD_UPDATE) or replace it with a constant NULL.
|
||||
*/
|
||||
|
||||
typedef struct
|
||||
{
|
||||
RewriteInfo *info;
|
||||
List *targetlist;
|
||||
int target_varno;
|
||||
int sublevels_up;
|
||||
List *targetlist;
|
||||
int event;
|
||||
int update_varno;
|
||||
} ResolveNew_context;
|
||||
|
||||
static Node *
|
||||
@ -694,7 +634,7 @@ ResolveNew_mutator(Node *node, ResolveNew_context *context)
|
||||
int this_varno = (int) var->varno;
|
||||
int this_varlevelsup = (int) var->varlevelsup;
|
||||
|
||||
if (this_varno == context->info->new_varno &&
|
||||
if (this_varno == context->target_varno &&
|
||||
this_varlevelsup == context->sublevels_up)
|
||||
{
|
||||
Node *n = FindMatchingNew(context->targetlist,
|
||||
@ -702,13 +642,13 @@ ResolveNew_mutator(Node *node, ResolveNew_context *context)
|
||||
|
||||
if (n == NULL)
|
||||
{
|
||||
if (context->info->event == CMD_UPDATE)
|
||||
if (context->event == CMD_UPDATE)
|
||||
{
|
||||
/* For update, just change unmatched var's varno */
|
||||
n = copyObject(node);
|
||||
((Var *) n)->varno = context->info->current_varno;
|
||||
((Var *) n)->varnoold = context->info->current_varno;
|
||||
return n;
|
||||
var = (Var *) copyObject(node);
|
||||
var->varno = context->update_varno;
|
||||
var->varnoold = context->update_varno;
|
||||
return (Node *) var;
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -755,54 +695,68 @@ ResolveNew_mutator(Node *node, ResolveNew_context *context)
|
||||
FLATCOPY(newnode, query, Query);
|
||||
MUTATE(newnode->targetList, query->targetList, List *,
|
||||
ResolveNew_mutator, context);
|
||||
MUTATE(newnode->qual, query->qual, Node *,
|
||||
MUTATE(newnode->jointree, query->jointree, FromExpr *,
|
||||
ResolveNew_mutator, context);
|
||||
MUTATE(newnode->havingQual, query->havingQual, Node *,
|
||||
ResolveNew_mutator, context);
|
||||
MUTATE(newnode->jointree, query->jointree, List *,
|
||||
ResolveNew_mutator, context);
|
||||
return (Node *) newnode;
|
||||
}
|
||||
return expression_tree_mutator(node, ResolveNew_mutator,
|
||||
(void *) context);
|
||||
}
|
||||
|
||||
static Node *
|
||||
ResolveNew(Node *node, RewriteInfo *info, List *targetlist,
|
||||
int sublevels_up)
|
||||
Node *
|
||||
ResolveNew(Node *node, int target_varno, int sublevels_up,
|
||||
List *targetlist, int event, int update_varno)
|
||||
{
|
||||
ResolveNew_context context;
|
||||
|
||||
context.info = info;
|
||||
context.targetlist = targetlist;
|
||||
context.target_varno = target_varno;
|
||||
context.sublevels_up = sublevels_up;
|
||||
context.targetlist = targetlist;
|
||||
context.event = event;
|
||||
context.update_varno = update_varno;
|
||||
|
||||
/*
|
||||
* Note: if an entire Query is passed, the right things will happen,
|
||||
* because ResolveNew_mutator increments sublevels_up when it sees
|
||||
* a SubLink, not a Query.
|
||||
*/
|
||||
return ResolveNew_mutator(node, &context);
|
||||
}
|
||||
|
||||
/*
|
||||
* Alternate interface to ResolveNew: substitute Vars in info->rule_action
|
||||
* with targetlist items from the parsetree's targetlist.
|
||||
*/
|
||||
void
|
||||
FixNew(RewriteInfo *info, Query *parsetree)
|
||||
{
|
||||
ResolveNew_context context;
|
||||
|
||||
context.target_varno = info->new_varno;
|
||||
context.sublevels_up = 0;
|
||||
context.targetlist = parsetree->targetList;
|
||||
context.event = info->event;
|
||||
context.update_varno = info->current_varno;
|
||||
|
||||
info->rule_action->targetList = (List *)
|
||||
ResolveNew((Node *) info->rule_action->targetList,
|
||||
info, parsetree->targetList, 0);
|
||||
info->rule_action->qual = ResolveNew(info->rule_action->qual,
|
||||
info, parsetree->targetList, 0);
|
||||
info->rule_action->havingQual = ResolveNew(info->rule_action->havingQual,
|
||||
info, parsetree->targetList, 0);
|
||||
info->rule_action->jointree = (List *)
|
||||
ResolveNew((Node *) info->rule_action->jointree,
|
||||
info, parsetree->targetList, 0);
|
||||
ResolveNew_mutator((Node *) info->rule_action->targetList, &context);
|
||||
info->rule_action->jointree = (FromExpr *)
|
||||
ResolveNew_mutator((Node *) info->rule_action->jointree, &context);
|
||||
info->rule_action->havingQual =
|
||||
ResolveNew_mutator(info->rule_action->havingQual, &context);
|
||||
}
|
||||
|
||||
|
||||
#ifdef NOT_USED
|
||||
|
||||
/*
|
||||
* HandleRIRAttributeRule
|
||||
* Replace Vars matching a given RT index with copies of TL expressions.
|
||||
*
|
||||
* Handles 'on retrieve to relation.attribute
|
||||
* do instead retrieve (attribute = expression) w/qual'
|
||||
*
|
||||
* XXX Why is this not unified with apply_RIR_view()?
|
||||
*/
|
||||
|
||||
typedef struct
|
||||
@ -897,12 +851,12 @@ HandleRIRAttributeRule_mutator(Node *node,
|
||||
FLATCOPY(newnode, query, Query);
|
||||
MUTATE(newnode->targetList, query->targetList, List *,
|
||||
HandleRIRAttributeRule_mutator, context);
|
||||
MUTATE(newnode->qual, query->qual, Node *,
|
||||
MUTATE(newnode->jointree, query->jointree, FromExpr *,
|
||||
HandleRIRAttributeRule_mutator, context);
|
||||
MUTATE(newnode->havingQual, query->havingQual, Node *,
|
||||
HandleRIRAttributeRule_mutator, context);
|
||||
MUTATE(newnode->jointree, query->jointree, List *,
|
||||
HandleRIRAttributeRule_mutator, context);
|
||||
|
||||
|
||||
return (Node *) newnode;
|
||||
}
|
||||
return expression_tree_mutator(node, HandleRIRAttributeRule_mutator,
|
||||
@ -931,13 +885,12 @@ HandleRIRAttributeRule(Query *parsetree,
|
||||
parsetree->targetList = (List *)
|
||||
HandleRIRAttributeRule_mutator((Node *) parsetree->targetList,
|
||||
&context);
|
||||
parsetree->qual =
|
||||
HandleRIRAttributeRule_mutator(parsetree->qual,
|
||||
parsetree->jointree = (FromExpr *)
|
||||
HandleRIRAttributeRule_mutator((Node *) parsetree->jointree,
|
||||
&context);
|
||||
parsetree->havingQual =
|
||||
HandleRIRAttributeRule_mutator(parsetree->havingQual,
|
||||
&context);
|
||||
parsetree->jointree = (List *)
|
||||
HandleRIRAttributeRule_mutator((Node *) parsetree->jointree,
|
||||
&context);
|
||||
}
|
||||
|
||||
#endif /* NOT_USED */
|
||||
|
@ -8,7 +8,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteRemove.c,v 1.39 2000/09/12 04:49:09 momjian Exp $
|
||||
* $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteRemove.c,v 1.40 2000/09/29 18:21:24 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -40,14 +40,14 @@ RewriteGetRuleEventRel(char *rulename)
|
||||
PointerGetDatum(rulename),
|
||||
0, 0, 0);
|
||||
if (!HeapTupleIsValid(htup))
|
||||
elog(ERROR, "Rule or view '%s' not found",
|
||||
((!strncmp(rulename, "_RET", 4)) ? (rulename + 4) : rulename));
|
||||
elog(ERROR, "Rule or view \"%s\" not found",
|
||||
((strncmp(rulename, "_RET", 4) == 0) ? (rulename + 4) : rulename));
|
||||
eventrel = ((Form_pg_rewrite) GETSTRUCT(htup))->ev_class;
|
||||
htup = SearchSysCacheTuple(RELOID,
|
||||
PointerGetDatum(eventrel),
|
||||
0, 0, 0);
|
||||
if (!HeapTupleIsValid(htup))
|
||||
elog(ERROR, "Class '%u' not found", eventrel);
|
||||
elog(ERROR, "Relation %u not found", eventrel);
|
||||
|
||||
return NameStr(((Form_pg_class) GETSTRUCT(htup))->relname);
|
||||
}
|
||||
@ -85,7 +85,7 @@ RemoveRewriteRule(char *ruleName)
|
||||
if (!HeapTupleIsValid(tuple))
|
||||
{
|
||||
heap_close(RewriteRelation, RowExclusiveLock);
|
||||
elog(ERROR, "Rule '%s' not found\n", ruleName);
|
||||
elog(ERROR, "Rule \"%s\" not found", ruleName);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -105,7 +105,7 @@ RemoveRewriteRule(char *ruleName)
|
||||
|
||||
/* do not allow the removal of a view's SELECT rule */
|
||||
if (event_relation->rd_rel->relkind == RELKIND_VIEW &&
|
||||
((Form_pg_rewrite) GETSTRUCT(tuple))->ev_type == '1' )
|
||||
((Form_pg_rewrite) GETSTRUCT(tuple))->ev_type == '1' )
|
||||
elog(ERROR, "Cannot remove a view's SELECT rule");
|
||||
|
||||
hasMoreRules = event_relation->rd_rules != NULL &&
|
||||
@ -133,7 +133,7 @@ RemoveRewriteRule(char *ruleName)
|
||||
* new rule set. Therefore, must do this even if relhasrules is
|
||||
* still true!
|
||||
*/
|
||||
setRelhasrulesInRelation(eventRelationOid, hasMoreRules);
|
||||
SetRelationRuleStatus(eventRelationOid, hasMoreRules, false);
|
||||
|
||||
/* Close rel, but keep lock till commit... */
|
||||
heap_close(event_relation, NoLock);
|
||||
|
@ -8,7 +8,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteSupport.c,v 1.43 2000/06/30 07:04:23 tgl Exp $
|
||||
* $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteSupport.c,v 1.44 2000/09/29 18:21:24 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -34,10 +34,11 @@ IsDefinedRewriteRule(char *ruleName)
|
||||
}
|
||||
|
||||
/*
|
||||
* setRelhasrulesInRelation
|
||||
* Set the value of the relation's relhasrules field in pg_class.
|
||||
* SetRelationRuleStatus
|
||||
* Set the value of the relation's relhasrules field in pg_class;
|
||||
* if the relation is becoming a view, also adjust its relkind.
|
||||
*
|
||||
* NOTE: caller should be holding an appropriate lock on the relation.
|
||||
* NOTE: caller must be holding an appropriate lock on the relation.
|
||||
*
|
||||
* NOTE: an important side-effect of this operation is that an SI invalidation
|
||||
* message is sent out to all backends --- including me --- causing relcache
|
||||
@ -47,7 +48,8 @@ IsDefinedRewriteRule(char *ruleName)
|
||||
* an SI message in that case.
|
||||
*/
|
||||
void
|
||||
setRelhasrulesInRelation(Oid relationId, bool relhasrules)
|
||||
SetRelationRuleStatus(Oid relationId, bool relHasRules,
|
||||
bool relIsBecomingView)
|
||||
{
|
||||
Relation relationRelation;
|
||||
HeapTuple tuple;
|
||||
@ -63,7 +65,10 @@ setRelhasrulesInRelation(Oid relationId, bool relhasrules)
|
||||
Assert(HeapTupleIsValid(tuple));
|
||||
|
||||
/* Do the update */
|
||||
((Form_pg_class) GETSTRUCT(tuple))->relhasrules = relhasrules;
|
||||
((Form_pg_class) GETSTRUCT(tuple))->relhasrules = relHasRules;
|
||||
if (relIsBecomingView)
|
||||
((Form_pg_class) GETSTRUCT(tuple))->relkind = RELKIND_VIEW;
|
||||
|
||||
heap_update(relationRelation, &tuple->t_self, tuple, NULL);
|
||||
|
||||
/* Keep the catalog indices up to date */
|
||||
|
Reference in New Issue
Block a user