diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c index 84f6849357f..99c3b485aaa 100644 --- a/src/backend/catalog/heap.c +++ b/src/backend/catalog/heap.c @@ -7,7 +7,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/catalog/heap.c,v 1.59 1998/08/20 22:07:32 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/catalog/heap.c,v 1.60 1998/08/24 01:37:46 momjian Exp $ * * INTERFACE ROUTINES * heap_create() - Create an uncataloged heap relation @@ -1448,7 +1448,7 @@ start:; sprintf(str, "select %s%s from %.*s", attrdef->adsrc, cast, NAMEDATALEN, rel->rd_rel->relname.data); setheapoverride(true); - planTree_list = (List *) pg_parse_and_plan(str, NULL, 0, &queryTree_list, None); + planTree_list = (List *) pg_parse_and_plan(str, NULL, 0, &queryTree_list, None, FALSE); setheapoverride(false); query = (Query *) (queryTree_list->qtrees[0]); @@ -1519,7 +1519,7 @@ StoreRelCheck(Relation rel, ConstrCheck *check) sprintf(str, "select 1 from %.*s where %s", NAMEDATALEN, rel->rd_rel->relname.data, check->ccsrc); setheapoverride(true); - planTree_list = (List *) pg_parse_and_plan(str, NULL, 0, &queryTree_list, None); + planTree_list = (List *) pg_parse_and_plan(str, NULL, 0, &queryTree_list, None, FALSE); setheapoverride(false); query = (Query *) (queryTree_list->qtrees[0]); diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c index ecebeb84d19..5cd560907b5 100644 --- a/src/backend/catalog/pg_proc.c +++ b/src/backend/catalog/pg_proc.c @@ -7,7 +7,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/catalog/pg_proc.c,v 1.19 1998/08/19 02:01:37 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/catalog/pg_proc.c,v 1.20 1998/08/24 01:37:47 momjian Exp $ * *------------------------------------------------------------------------- */ @@ -214,7 +214,7 @@ ProcedureCreate(char *procedureName, if (strcmp(languageName, "sql") == 0) { plan_list = pg_parse_and_plan(prosrc, typev, parameterCount, - &querytree_list, dest); + &querytree_list, dest, FALSE); /* typecheck return value */ pg_checkretval(typeObjectId, querytree_list); diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c index fe14f3df728..645a312fbf8 100644 --- a/src/backend/executor/functions.c +++ b/src/backend/executor/functions.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/executor/functions.c,v 1.17 1998/06/15 19:28:20 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/executor/functions.c,v 1.18 1998/08/24 01:37:48 momjian Exp $ * *------------------------------------------------------------------------- */ @@ -113,7 +113,7 @@ init_execution_state(FunctionCachePtr fcache, planTree_list = (List *) - pg_parse_and_plan(fcache->src, fcache->argOidVect, nargs, &queryTree_list, None); + pg_parse_and_plan(fcache->src, fcache->argOidVect, nargs, &queryTree_list, None, FALSE); for (i = 0; i < queryTree_list->len; i++) { diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c index e2e87d88e06..0f559e1bcdf 100644 --- a/src/backend/executor/spi.c +++ b/src/backend/executor/spi.c @@ -642,7 +642,7 @@ _SPI_execute(char *src, int tcount, _SPI_plan *plan) argtypes = plan->argtypes; } ptlist = planTree_list = (List *) - pg_parse_and_plan(src, argtypes, nargs, &queryTree_list, None); + pg_parse_and_plan(src, argtypes, nargs, &queryTree_list, None, FALSE); _SPI_current->qtlist = queryTree_list; diff --git a/src/backend/libpq/be-pqexec.c b/src/backend/libpq/be-pqexec.c index 38b4536ccf9..2d3952bebad 100644 --- a/src/backend/libpq/be-pqexec.c +++ b/src/backend/libpq/be-pqexec.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/libpq/Attic/be-pqexec.c,v 1.17 1998/06/15 19:28:25 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/libpq/Attic/be-pqexec.c,v 1.18 1998/08/24 01:37:52 momjian Exp $ * *------------------------------------------------------------------------- */ @@ -137,7 +137,7 @@ PQexec(char *query) * end up on the top of the portal stack. * ---------------- */ - pg_exec_query_dest(query, Local); + pg_exec_query_dest(query, Local, FALSE); /* ---------------- * pop the portal off the portal stack and return the diff --git a/src/backend/optimizer/path/xfunc.c b/src/backend/optimizer/path/xfunc.c index eb66a4f6e2e..0346dcf4720 100644 --- a/src/backend/optimizer/path/xfunc.c +++ b/src/backend/optimizer/path/xfunc.c @@ -9,7 +9,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/optimizer/path/Attic/xfunc.c,v 1.19 1998/08/19 02:02:13 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/optimizer/path/Attic/xfunc.c,v 1.20 1998/08/24 01:37:53 momjian Exp $ * *------------------------------------------------------------------------- */ @@ -528,7 +528,7 @@ xfunc_func_expense(LispValue node, LispValue args) if (nargs > 0) argOidVect = proc->proargtypes; planlist = (List) pg_parse_and_plan(pq_src, argOidVect, nargs, - &parseTree_list, None); + &parseTree_list, None, FALSE); if (IsA(node, Func)) set_func_planlist((Func) node, planlist); diff --git a/src/backend/parser/keywords.c b/src/backend/parser/keywords.c index c3b53722a31..24cd4e82721 100644 --- a/src/backend/parser/keywords.c +++ b/src/backend/parser/keywords.c @@ -7,7 +7,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/parser/keywords.c,v 1.38 1998/07/24 03:31:24 scrappy Exp $ + * $Header: /cvsroot/pgsql/src/backend/parser/keywords.c,v 1.39 1998/08/24 01:37:55 momjian Exp $ * *------------------------------------------------------------------------- */ @@ -66,7 +66,12 @@ static ScanKeyword ScanKeywords[] = { {"createdb", CREATEDB}, {"createuser", CREATEUSER}, {"cross", CROSS}, - {"current", CURRENT}, + {"current", CURRENT}, /* + * 6.4 to 6.5 is migration time! + * CURRENT will be removed in 6.5! + * Use OLD keyword in rules. + * Jan + */ {"current_date", CURRENT_DATE}, {"current_time", CURRENT_TIME}, {"current_timestamp", CURRENT_TIMESTAMP}, @@ -152,6 +157,7 @@ static ScanKeyword ScanKeywords[] = { {"null", NULL_P}, {"numeric", NUMERIC}, {"oids", OIDS}, + {"old", OLD}, {"on", ON}, {"operator", OPERATOR}, {"option", OPTION}, diff --git a/src/backend/rewrite/locks.c b/src/backend/rewrite/locks.c index 04698be13e3..46727d94c6e 100644 --- a/src/backend/rewrite/locks.c +++ b/src/backend/rewrite/locks.c @@ -6,7 +6,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/rewrite/Attic/locks.c,v 1.10 1998/06/15 19:29:06 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/rewrite/Attic/locks.c,v 1.11 1998/08/24 01:37:56 momjian Exp $ * *------------------------------------------------------------------------- */ @@ -18,6 +18,14 @@ #include "utils/syscache.h" /* for SearchSysCache */ #include "rewrite/locks.h" /* for rewrite specific lock defns */ +#include "access/heapam.h" /* for ACL checking */ +#include "utils/syscache.h" +#include "utils/acl.h" +#include "utils/builtins.h" +#include "catalog/pg_shadow.h" + +static void checkLockPerms(List *locks, Query *parsetree, int rt_index); + /* * ThisLockWasTriggered * @@ -156,5 +164,98 @@ matchLocks(CmdType event, } } + checkLockPerms(real_locks, parsetree, varno); + return (real_locks); } + + +static void +checkLockPerms(List *locks, Query *parsetree, int rt_index) +{ + Relation ev_rel; + HeapTuple usertup; + char *evowner; + RangeTblEntry *rte; + int32 reqperm; + int32 aclcheck_res; + int i; + List *l; + + if (locks == NIL) + return; + + /* + * Get the usename of the rules event relation owner + */ + rte = (RangeTblEntry *)nth(rt_index - 1, parsetree->rtable); + ev_rel = heap_openr(rte->relname); + usertup = SearchSysCacheTuple(USESYSID, + 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); + } + heap_close(ev_rel); + evowner = nameout(&(((Form_pg_shadow) GETSTRUCT(usertup))->usename)); + + /* + * 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 + */ + foreach (action, onelock->actions) { + Query *query = (Query *)lfirst(action); + + /* + * In each action check every rangetable entry + * for read/write permission of the event relations + * owner depending on if it's the result relation + * (write) or not (read) + */ + for (i = 2; i < length(query->rtable); i++) { + if (i + 1 == query->resultRelation) + switch (query->resultRelation) { + case CMD_INSERT: + reqperm = ACL_AP; + break; + default: + reqperm = ACL_WR; + break; + } + else + reqperm = ACL_RD; + + rte = (RangeTblEntry *)nth(i, query->rtable); + aclcheck_res = pg_aclcheck(rte->relname, + evowner, reqperm); + if (aclcheck_res != ACLCHECK_OK) { + elog(ERROR, "%s: %s", + rte->relname, + aclcheck_error_strings[aclcheck_res]); + } + + /* + * So this is allowed due to the permissions + * of the rules event relation owner. But + * let's see if the next one too + */ + rte->skipAcl = TRUE; + } + } + } + + /* + * Phew, that was close + */ + return; +} + + diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c index 4d8ecb8b1e9..c5c3b887867 100644 --- a/src/backend/rewrite/rewriteDefine.c +++ b/src/backend/rewrite/rewriteDefine.c @@ -7,7 +7,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteDefine.c,v 1.18 1998/08/19 02:02:29 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteDefine.c,v 1.19 1998/08/24 01:37:58 momjian Exp $ * *------------------------------------------------------------------------- */ @@ -131,7 +131,7 @@ InsertRule(char *rulname, rulname, evtype, eventrel_oid, evslot_index, actionbuf, qualbuf, is_instead); - pg_exec_query(rulebuf); + pg_exec_query_acl_override(rulebuf); return (LastOidProcessed); } @@ -192,12 +192,61 @@ DefineQueryRewrite(RuleStmt *stmt) Oid event_attype = 0; char *actionP, *event_qualP; + List *l; + Query *query; + /* ---------- + * The current rewrite handler is known to work on relation level + * rules only. And for SELECT events, it expects one non-nothing + * action that is instead. Since we now hand out views and rules + * to regular users, we must deny anything else. + * + * I know that I must write a new rewrite handler from scratch + * for 6.5 so we can remove these checks and allow all the rules. + * + * Jan + * ---------- + */ if (event_obj->attrs) + elog(ERROR, "attribute level rules currently not supported"); + /* eslot_string = strVal(lfirst(event_obj->attrs)); + */ else eslot_string = NULL; + if (action != NIL) + foreach (l, action) { + query = (Query *)lfirst(l); + if (query->resultRelation == 1) { + elog(NOTICE, "rule actions on OLD currently not supported"); + elog(ERROR, " use views or triggers instead"); + } + if (query->resultRelation == 2) { + elog(NOTICE, "rule actions on NEW currently not supported"); + elog(ERROR, " use triggers instead"); + } + } + + if (event_type == CMD_SELECT) { + if (length(action) == 0) { + elog(NOTICE, "instead nothing rules on select currently not supported"); + elog(ERROR, " use views instead"); + } + if (length(action) > 1) { + elog(ERROR, "multiple action rules on select currently not supported"); + } + query = (Query *)lfirst(action); + if (!is_instead || query->commandType != CMD_SELECT) { + elog(ERROR, "only instead-select rules currently supported on select"); + } + } + /* + * This rule is currently allowed - too restricted I know - + * but women and children first + * Jan + */ + event_relation = heap_openr(event_obj->relname); if (event_relation == NULL) elog(ERROR, "virtual relations not supported yet"); diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c index 68169cbf2d2..02bbc69a8ca 100644 --- a/src/backend/rewrite/rewriteHandler.c +++ b/src/backend/rewrite/rewriteHandler.c @@ -6,7 +6,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteHandler.c,v 1.19 1998/08/19 02:02:30 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteHandler.c,v 1.20 1998/08/24 01:37:59 momjian Exp $ * *------------------------------------------------------------------------- */ @@ -45,7 +45,6 @@ fireRules(Query *parsetree, int rt_index, CmdType event, static void QueryRewriteSubLink(Node *node); static List *QueryRewriteOne(Query *parsetree); static List *deepRewriteQuery(Query *parsetree); -static void CheckViewPerms(Relation view, List *rtable); static void RewritePreprocessQuery(Query *parsetree); static Query *RewritePostprocessNonSelect(Query *parsetree); @@ -273,7 +272,6 @@ ApplyRetrieveRule(Query *parsetree, int nothing, rt_length; int badsql = FALSE; - int viewAclOverride = FALSE; rule_qual = rule->qual; if (rule->actions) @@ -291,19 +289,6 @@ ApplyRetrieveRule(Query *parsetree, return; rule_action = copyObject(lfirst(rule->actions)); nothing = FALSE; - - /* - * If this rule is on the relation level, the rule action is a - * select and the rule is instead then it must be a view. - * Permissions for views now follow the owner of the view, not the - * current user. - */ - if (relation_level && rule_action->commandType == CMD_SELECT - && rule->isInstead) - { - CheckViewPerms(relation, rule_action->rtable); - viewAclOverride = TRUE; - } } else nothing = TRUE; @@ -321,28 +306,7 @@ ApplyRetrieveRule(Query *parsetree, } rt_length = length(rtable); - if (viewAclOverride) - { - List *rule_rtable, - *rule_rt; - RangeTblEntry *rte; - - rule_rtable = copyObject(rule_action->rtable); - foreach(rule_rt, rule_rtable) - { - rte = lfirst(rule_rt); - - /* - * tell the executor that the ACL check on this range table - * entry is already done - */ - rte->skipAcl = true; - } - - rtable = nconc(rtable, rule_rtable); - } - else - rtable = nconc(rtable, copyObject(rule_action->rtable)); + rtable = nconc(rtable, copyObject(rule_action->rtable)); parsetree->rtable = rtable; rule_action->rtable = rtable; @@ -425,6 +389,8 @@ ProcessRetrieveQuery(Query *parsetree, if (rule) return NIL; + rt_index = 0; + foreach(rt, rtable) { RangeTblEntry *rt_entry = lfirst(rt); @@ -537,6 +503,44 @@ fireRules(Query *parsetree, List *r; bool orig_instead_flag = *instead_flag; + /* + * Instead rules change the resultRelation of the + * query. So the permission checks on the initial + * resultRelation would never be done (this is + * normally done in the executor deep down). So + * we must do it here. The result relations resulting + * from earlier rewrites are already checked against + * the rules eventrelation owner (during matchLocks) + * and have the skipAcl flag set. + */ + if (rule_lock->isInstead && + parsetree->commandType != CMD_SELECT) { + RangeTblEntry *rte; + int32 acl_rc; + int32 reqperm; + + switch (parsetree->commandType) { + case CMD_INSERT: + reqperm = ACL_AP; + break; + default: + reqperm = ACL_WR; + break; + } + + rte = (RangeTblEntry *)nth(parsetree->resultRelation - 1, + parsetree->rtable); + if (!rte->skipAcl) { + acl_rc = pg_aclcheck(rte->relname, + GetPgUserName(), reqperm); + if (acl_rc != ACLCHECK_OK) { + elog(ERROR, "%s: %s", + rte->relname, + aclcheck_error_strings[acl_rc]); + } + } + } + /* multiple rule action time */ *instead_flag = rule_lock->isInstead; event_qual = rule_lock->qual; @@ -1024,42 +1028,3 @@ deepRewriteQuery(Query *parsetree) return rewritten; } - - -static void -CheckViewPerms(Relation view, List *rtable) -{ - HeapTuple utup; - NameData uname; - List *rt; - RangeTblEntry *rte; - int32 aclcheck_res; - - /* - * get the usename of the view's owner - */ - utup = SearchSysCacheTuple(USESYSID, - ObjectIdGetDatum(view->rd_rel->relowner), - 0, 0, 0); - if (!HeapTupleIsValid(utup)) - { - elog(ERROR, "cache lookup for userid %d failed", - view->rd_rel->relowner); - } - StrNCpy(uname.data, - ((Form_pg_shadow) GETSTRUCT(utup))->usename.data, - NAMEDATALEN); - - /* - * check that we have read access to all the classes in the range - * table of the view - */ - foreach(rt, rtable) - { - rte = (RangeTblEntry *) lfirst(rt); - - aclcheck_res = pg_aclcheck(rte->relname, uname.data, ACL_RD); - if (aclcheck_res != ACLCHECK_OK) - elog(ERROR, "%s: %s", rte->relname, aclcheck_error_strings[aclcheck_res]); - } -} diff --git a/src/backend/rewrite/rewriteSupport.c b/src/backend/rewrite/rewriteSupport.c index dae10410809..4c17cccb7f7 100644 --- a/src/backend/rewrite/rewriteSupport.c +++ b/src/backend/rewrite/rewriteSupport.c @@ -7,7 +7,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteSupport.c,v 1.25 1998/08/19 02:02:33 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteSupport.c,v 1.26 1998/08/24 01:38:01 momjian Exp $ * *------------------------------------------------------------------------- */ @@ -158,6 +158,10 @@ prs2_addToRelation(Oid relid, */ oldcxt = MemoryContextSwitchTo((MemoryContext) CacheCxt); thisRule = (RewriteRule *) palloc(sizeof(RewriteRule)); + if (qual != NULL) + qual = copyObject(qual); + if (actions != NIL) + actions = copyObject(actions); MemoryContextSwitchTo(oldcxt); thisRule->ruleId = ruleId; diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c index f158a0f1003..51af2f84a5a 100644 --- a/src/backend/tcop/postgres.c +++ b/src/backend/tcop/postgres.c @@ -7,7 +7,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/tcop/postgres.c,v 1.82 1998/08/04 16:44:20 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/tcop/postgres.c,v 1.83 1998/08/24 01:38:02 momjian Exp $ * * NOTES * this is the "main" module of the postgres backend and @@ -380,7 +380,8 @@ pg_parse_and_plan(char *query_string, /* string to execute */ int nargs, /* number of arguments */ QueryTreeList **queryListP, /* pointer to the parse * trees */ - CommandDest dest) /* where results should go */ + CommandDest dest, /* where results should go */ + bool aclOverride) { QueryTreeList *querytree_list; int i; @@ -451,24 +452,24 @@ pg_parse_and_plan(char *query_string, /* string to execute */ /* rewrite queries (retrieve, append, delete, replace) */ rewritten = QueryRewrite(querytree); - /* - * Rewrite the UNIONS. - */ - foreach(rewritten_list, rewritten) - { - Query *qry = (Query *) lfirst(rewritten_list); - - union_result = NIL; - foreach(union_list, qry->unionClause) - union_result = nconc(union_result, QueryRewrite((Query *) lfirst(union_list))); - qry->unionClause = union_result; - } - - if (rewritten != NULL) + if (rewritten != NIL) { int len, k; + /* + * Rewrite the UNIONS. + */ + foreach(rewritten_list, rewritten) + { + Query *qry = (Query *) lfirst(rewritten_list); + + union_result = NIL; + foreach(union_list, qry->unionClause) + union_result = nconc(union_result, QueryRewrite((Query *) lfirst(union_list))); + qry->unionClause = union_result; + } + len = length(rewritten); if (len == 1) new_list->qtrees[j++] = (Query *) lfirst(rewritten); @@ -487,12 +488,40 @@ pg_parse_and_plan(char *query_string, /* string to execute */ } } + /* ---------- + * Due to rewriting, the new list could also have been + * shrunk (do instead nothing). Forget obsolete queries + * at the end. + * ---------- + */ + new_list->len = j; + /* we're done with the original lists, free it */ free(querytree_list->qtrees); free(querytree_list); querytree_list = new_list; + /* + * Override ACL checking if requested + */ + if (aclOverride) { + for (i = 0; i < querytree_list->len; i++) { + RangeTblEntry *rte; + List *l; + + querytree = querytree_list->qtrees[i]; + if (querytree->commandType == CMD_UTILITY) + continue; + + foreach (l, querytree->rtable) { + rte = (RangeTblEntry *)lfirst(l); + + rte->skipAcl = TRUE; + } + } + } + if (DebugPrintRewrittenParsetree == true) { printf("\n---- \tafter rewriting:\n"); @@ -530,7 +559,10 @@ pg_parse_and_plan(char *query_string, /* string to execute */ elog(NOTICE, "(transaction aborted): %s", "queries ignored until END"); - *queryListP = (QueryTreeList *) NULL; + free(querytree_list->qtrees); + free(querytree_list); + if (queryListP) + *queryListP = (QueryTreeList *) NULL; return (List *) NULL; } @@ -573,6 +605,16 @@ pg_parse_and_plan(char *query_string, /* string to execute */ #endif } + /* ---------- + * Check if the rewriting had thrown away anything + * ---------- + */ + if (querytree_list->len == 0) { + free(querytree_list->qtrees); + free(querytree_list); + querytree_list = NULL; + } + if (queryListP) *queryListP = querytree_list; @@ -599,12 +641,22 @@ pg_parse_and_plan(char *query_string, /* string to execute */ void pg_exec_query(char *query_string) { - pg_exec_query_dest(query_string, whereToSendOutput); + pg_exec_query_dest(query_string, whereToSendOutput, FALSE); +} + +void +pg_exec_query_acl_override(char *query_string) +{ + pg_exec_query_dest(query_string, whereToSendOutput, TRUE); } void pg_exec_query_dest(char *query_string, /* string to execute */ - CommandDest dest) /* where results should go */ + CommandDest dest, /* where results should go */ + bool aclOverride) /* to give utility + * commands power of + * superusers + */ { List *plan_list; Plan *plan; @@ -614,7 +666,7 @@ pg_exec_query_dest(char *query_string, /* string to execute */ QueryTreeList *querytree_list; /* plan the queries */ - plan_list = pg_parse_and_plan(query_string, NULL, 0, &querytree_list, dest); + plan_list = pg_parse_and_plan(query_string, NULL, 0, &querytree_list, dest, aclOverride); if (QueryCancel) CancelQuery(); @@ -1339,7 +1391,7 @@ PostgresMain(int argc, char *argv[], int real_argc, char *real_argv[]) if (!IsUnderPostmaster) { puts("\nPOSTGRES backend interactive interface"); - puts("$Revision: 1.82 $ $Date: 1998/08/04 16:44:20 $"); + puts("$Revision: 1.83 $ $Date: 1998/08/24 01:38:02 $"); } /* ---------------- diff --git a/src/backend/utils/adt/Makefile b/src/backend/utils/adt/Makefile index 9d2f4df3e3c..d9812a52367 100644 --- a/src/backend/utils/adt/Makefile +++ b/src/backend/utils/adt/Makefile @@ -4,7 +4,7 @@ # Makefile for utils/adt # # IDENTIFICATION -# $Header: /cvsroot/pgsql/src/backend/utils/adt/Makefile,v 1.16 1998/08/19 02:02:52 momjian Exp $ +# $Header: /cvsroot/pgsql/src/backend/utils/adt/Makefile,v 1.17 1998/08/24 01:38:04 momjian Exp $ # #------------------------------------------------------------------------- @@ -22,8 +22,8 @@ OBJS = acl.o arrayfuncs.o arrayutils.o bool.o cash.o char.o chunk.o \ geo_ops.o geo_selfuncs.o int.o int8.o like.o \ misc.o nabstime.o name.o not_in.o numutils.o \ oid.o oracle_compat.o \ - regexp.o regproc.o selfuncs.o sets.o tid.o timestamp.o \ - varchar.o varlena.o version.o + regexp.o regproc.o ruleutils.o selfuncs.o sets.o \ + tid.o timestamp.o varchar.o varlena.o version.o all: SUBSYS.o diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c new file mode 100644 index 00000000000..3b7c3e98fa2 --- /dev/null +++ b/src/backend/utils/adt/ruleutils.c @@ -0,0 +1,1376 @@ +/********************************************************************** + * get_ruledef.c - Function to get a rules definition text + * out of it's tuple + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/utils/adt/ruleutils.c,v 1.1 1998/08/24 01:38:05 momjian Exp $ + * + * This software is copyrighted by Jan Wieck - Hamburg. + * + * The author hereby grants permission to use, copy, modify, + * distribute, and license this software and its documentation + * for any purpose, provided that existing copyright notices are + * retained in all copies and that this notice is included + * verbatim in any distributions. No written agreement, license, + * or royalty fee is required for any of the authorized uses. + * Modifications to this software may be copyrighted by their + * author and need not follow the licensing terms described + * here, provided that the new terms are clearly indicated on + * the first page of each file where they apply. + * + * IN NO EVENT SHALL THE AUTHOR OR DISTRIBUTORS BE LIABLE TO ANY + * PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR + * CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF THIS + * SOFTWARE, ITS DOCUMENTATION, OR ANY DERIVATIVES THEREOF, EVEN + * IF THE AUTHOR HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * THE AUTHOR AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + * PURPOSE, AND NON-INFRINGEMENT. THIS SOFTWARE IS PROVIDED ON + * AN "AS IS" BASIS, AND THE AUTHOR AND DISTRIBUTORS HAVE NO + * OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, + * ENHANCEMENTS, OR MODIFICATIONS. + * + **********************************************************************/ + +#include +#include +#include +#include +#include +#include + +#include "executor/spi.h" +#include "commands/trigger.h" +#include "utils/elog.h" +#include "utils/builtins.h" +#include "nodes/nodes.h" +#include "optimizer/clauses.h" +#include "utils/syscache.h" +#include "utils/lsyscache.h" +#include "catalog/pg_class.h" +#include "catalog/pg_type.h" +#include "fmgr.h" + + +/* ---------- + * Global data + * ---------- + */ +static char *rulename; +static void *plan_getrule = NULL; +static char *query_getrule = "SELECT * FROM pg_rewrite WHERE rulename = $1"; +static void *plan_getview = NULL; +static char *query_getview = "SELECT * FROM pg_rewrite WHERE rulename = $1 or rulename = $2"; + + +/* ---------- + * Global functions + * ---------- + */ +text *pg_get_ruledef(NameData *rname); +text *pg_get_viewdef(NameData *rname); + + +/* ---------- + * Local functions + * ---------- + */ +static char *make_ruledef(HeapTuple ruletup, TupleDesc rulettc); +static char *make_viewdef(HeapTuple ruletup, TupleDesc rulettc); +static char *get_query_def(Query *query); +static char *get_select_query_def(Query *query); +static char *get_insert_query_def(Query *query); +static char *get_update_query_def(Query *query); +static char *get_delete_query_def(Query *query); +static char *get_rule_expr(List *rtable, int rt_index, Node *node, bool varprefix); +static char *get_func_expr(List *rtable, int rt_index, Expr *expr, bool varprefix); +static char *get_tle_expr(List *rtable, int rt_index, TargetEntry *tle, bool varprefix); +static char *get_const_expr(Const *constval); +static char *get_relation_name(Oid relid); +static char *get_attribute_name(Oid relid, int2 attnum); +static bool check_if_rte_used(int rt_index, Node *node, int sup); + + +/* ---------- + * get_ruledef - Do it all and return a text + * that could be used as a statement + * to recreate the rule + * ---------- + */ +text * +pg_get_ruledef(NameData *rname) +{ + text *ruledef; + Datum args[1]; + char nulls[2]; + int spirc; + HeapTuple ruletup; + TupleDesc rulettc; + char *tmp; + int len; + + /* ---------- + * We need the rules name somewhere deep down + * ---------- + */ + rulename = nameout(rname); + + /* ---------- + * Connect to SPI manager + * ---------- + */ + if (SPI_connect() != SPI_OK_CONNECT) + elog(ERROR, "get_ruledef: cannot connect to SPI manager"); + + /* ---------- + * On the first call prepare the plan to lookup pg_proc. + * We read pg_proc over the SPI manager instead of using + * the syscache to be checked for read access on pg_proc. + * ---------- + */ + if (plan_getrule == NULL) { + Oid argtypes[1]; + void *plan; + + argtypes[0] = NAMEOID; + plan = SPI_prepare(query_getrule, 1, argtypes); + if (plan == NULL) + elog(ERROR, "SPI_prepare() failed for \"%s\"", query_getrule); + plan_getrule = SPI_saveplan(plan); + } + + /* ---------- + * Get the pg_rewrite tuple for this rule + * ---------- + */ + args[0] = PointerGetDatum(rulename); + nulls[0] = (rulename == NULL) ? 'n' : ' '; + nulls[1] = '\0'; + spirc = SPI_execp(plan_getrule, args, nulls, 1); + if (spirc != SPI_OK_SELECT) { + elog(ERROR, "failed to get pg_rewrite tuple for %s", rulename); + } + if (SPI_processed != 1) { + if (SPI_finish() != SPI_OK_FINISH) + elog(ERROR, "get_ruledef: SPI_finish() failed"); + ruledef = SPI_palloc(VARHDRSZ + 1); + VARSIZE(ruledef) = VARHDRSZ + 1; + VARDATA(ruledef)[0] = '-'; + return ruledef; + } + + ruletup = SPI_tuptable->vals[0]; + rulettc = SPI_tuptable->tupdesc; + + /* ---------- + * Get the rules definition and put it into executors memory + * ---------- + */ + tmp = make_ruledef(ruletup, rulettc); + len = strlen(tmp) + VARHDRSZ; + ruledef = SPI_palloc(len); + VARSIZE(ruledef) = len; + memcpy(VARDATA(ruledef), tmp, len - VARHDRSZ); + + /* ---------- + * Disconnect from SPI manager + * ---------- + */ + if (SPI_finish() != SPI_OK_FINISH) + elog(ERROR, "get_ruledef: SPI_finish() failed"); + + /* ---------- + * Easy - isn't it? + * ---------- + */ + return ruledef; +} + + +/* ---------- + * get_viewdef - Mainly the same thing, but we + * only return the SELECT part of a view + * ---------- + */ +text * +pg_get_viewdef(NameData *rname) +{ + text *ruledef; + Datum args[2]; + char nulls[3]; + int spirc; + HeapTuple ruletup; + TupleDesc rulettc; + char *tmp; + int len; + char name1[NAMEDATALEN + 5]; + char name2[NAMEDATALEN + 5]; + + /* ---------- + * We need the rules name somewhere deep down + * ---------- + */ + rulename = nameout(rname); + + /* ---------- + * Connect to SPI manager + * ---------- + */ + if (SPI_connect() != SPI_OK_CONNECT) + elog(ERROR, "get_viewdef: cannot connect to SPI manager"); + + /* ---------- + * On the first call prepare the plan to lookup pg_proc. + * We read pg_proc over the SPI manager instead of using + * the syscache to be checked for read access on pg_proc. + * ---------- + */ + if (plan_getview == NULL) { + Oid argtypes[2]; + void *plan; + + argtypes[0] = NAMEOID; + argtypes[1] = NAMEOID; + plan = SPI_prepare(query_getview, 2, argtypes); + if (plan == NULL) + elog(ERROR, "SPI_prepare() failed for \"%s\"", query_getview); + plan_getview = SPI_saveplan(plan); + } + + /* ---------- + * Get the pg_rewrite tuple for this rule + * ---------- + */ + sprintf(name1, "_RET%s", rulename); + sprintf(name2, "_ret%s", rulename); + args[0] = PointerGetDatum(name1); + args[1] = PointerGetDatum(name2); + nulls[0] = ' '; + nulls[1] = ' '; + nulls[2] = '\0'; + spirc = SPI_execp(plan_getview, args, nulls, 1); + if (spirc != SPI_OK_SELECT) { + elog(ERROR, "failed to get pg_rewrite tuple for view %s", rulename); + } + if (SPI_processed != 1) { + tmp = "Not a view"; + } else { + /* ---------- + * Get the rules definition and put it into executors memory + * ---------- + */ + ruletup = SPI_tuptable->vals[0]; + rulettc = SPI_tuptable->tupdesc; + tmp = make_viewdef(ruletup, rulettc); + } + len = strlen(tmp) + VARHDRSZ; + ruledef = SPI_palloc(len); + VARSIZE(ruledef) = len; + memcpy(VARDATA(ruledef), tmp, len - VARHDRSZ); + + /* ---------- + * Disconnect from SPI manager + * ---------- + */ + if (SPI_finish() != SPI_OK_FINISH) + elog(ERROR, "get_viewdef: SPI_finish() failed"); + + /* ---------- + * Easy - isn't it? + * ---------- + */ + return ruledef; +} + + +/* ---------- + * make_ruledef - reconstruct the CREATE RULE command + * for a given pg_rewrite tuple + * ---------- + */ +static char * +make_ruledef(HeapTuple ruletup, TupleDesc rulettc) +{ + char *buf; + char ev_type; + Oid ev_class; + int2 ev_attr; + bool is_instead; + char *ev_qual; + char *ev_action; + List *actions = NIL; + int fno; + bool isnull; + + /* ---------- + * Allocate space for the returned rule definition text + * ---------- + */ + buf = palloc(8192); + + /* ---------- + * Get the attribute values from the rules tuple + * ---------- + */ + fno = SPI_fnumber(rulettc, "ev_type"); + ev_type = (char)SPI_getbinval(ruletup, rulettc, fno, &isnull); + + fno = SPI_fnumber(rulettc, "ev_class"); + ev_class = (Oid)SPI_getbinval(ruletup, rulettc, fno, &isnull); + + fno = SPI_fnumber(rulettc, "ev_attr"); + ev_attr = (int2)SPI_getbinval(ruletup, rulettc, fno, &isnull); + + fno = SPI_fnumber(rulettc, "is_instead"); + is_instead = (bool)SPI_getbinval(ruletup, rulettc, fno, &isnull); + + fno = SPI_fnumber(rulettc, "ev_qual"); + ev_qual = SPI_getvalue(ruletup, rulettc, fno); + if (isnull) ev_qual = NULL; + + fno = SPI_fnumber(rulettc, "ev_action"); + ev_action = SPI_getvalue(ruletup, rulettc, fno); + if (isnull) ev_action = NULL; + if (ev_action != NULL) { + actions = (List *)stringToNode(ev_action); + } + + /* ---------- + * Build the rules definition text + * ---------- + */ + strcpy(buf, "CREATE RULE "); + + /* The rule name */ + strcat(buf, rulename); + strcat(buf, " AS ON "); + + /* The event the rule is fired for */ + switch (ev_type) { + case '1': strcat(buf, "SELECT TO "); + break; + + case '2': strcat(buf, "UPDATE TO "); + break; + + case '3': strcat(buf, "INSERT TO "); + break; + + case '4': strcat(buf, "DELETE TO "); + break; + + default: + elog(ERROR, "get_ruledef: rule %s has unsupported event type %d", + rulename, ev_type); + break; + } + + /* The relation the rule is fired on */ + strcat(buf, get_relation_name(ev_class)); + if (ev_attr > 0) { + strcat(buf, "."); + strcat(buf, get_attribute_name(ev_class, ev_attr)); + } + + /* If the rule has an event qualification, add it */ + if (ev_qual == NULL) ev_qual = ""; + if (strlen(ev_qual) > 0) { + Node *qual; + Query *query; + + qual = stringToNode(ev_qual); + query = (Query *)lfirst(actions); + + strcat(buf, " WHERE "); + strcat(buf, get_rule_expr(query->rtable, 0, qual, TRUE)); + } + + strcat(buf, " DO "); + + /* The INSTEAD keyword (if so) */ + if (is_instead) + strcat(buf, "INSTEAD "); + + /* Finally the rules actions */ + if (length(actions) > 1) { + List *action; + Query *query; + + strcat(buf, "("); + foreach (action, actions) { + query = (Query *)lfirst(action); + strcat(buf, get_query_def(query)); + strcat(buf, "; "); + } + strcat(buf, ");"); + } else { + if (length(actions) == 0) { + strcat(buf, "NOTHING;"); + } else { + Query *query; + + query = (Query *)lfirst(actions); + strcat(buf, get_query_def(query)); + strcat(buf, ";"); + } + } + + /* ---------- + * That's it + * ---------- + */ + return buf; +} + + +/* ---------- + * make_viewdef - reconstruct the SELECT part of a + * view rewrite rule + * ---------- + */ +static char * +make_viewdef(HeapTuple ruletup, TupleDesc rulettc) +{ + char buf[8192]; + Query *query; + char ev_type; + Oid ev_class; + int2 ev_attr; + bool is_instead; + char *ev_qual; + char *ev_action; + List *actions = NIL; + int fno; + bool isnull; + + /* ---------- + * Get the attribute values from the rules tuple + * ---------- + */ + fno = SPI_fnumber(rulettc, "ev_type"); + ev_type = (char)SPI_getbinval(ruletup, rulettc, fno, &isnull); + + fno = SPI_fnumber(rulettc, "ev_class"); + ev_class = (Oid)SPI_getbinval(ruletup, rulettc, fno, &isnull); + + fno = SPI_fnumber(rulettc, "ev_attr"); + ev_attr = (int2)SPI_getbinval(ruletup, rulettc, fno, &isnull); + + fno = SPI_fnumber(rulettc, "is_instead"); + is_instead = (bool)SPI_getbinval(ruletup, rulettc, fno, &isnull); + + fno = SPI_fnumber(rulettc, "ev_qual"); + ev_qual = SPI_getvalue(ruletup, rulettc, fno); + if (isnull) ev_qual = ""; + + fno = SPI_fnumber(rulettc, "ev_action"); + ev_action = SPI_getvalue(ruletup, rulettc, fno); + if (isnull) ev_action = NULL; + if (ev_action != NULL) { + actions = (List *)stringToNode(ev_action); + } + + if (length(actions) != 1) + return "Not a view"; + + query = (Query *)lfirst(actions); + + if (ev_type != '1' || ev_attr >= 0 || !is_instead || strcmp(ev_qual, "")) + return "Not a view"; + + strcpy(buf, get_select_query_def(query)); + strcat(buf, ";"); + + /* ---------- + * That's it + * ---------- + */ + return pstrdup(buf); +} + + +/* ---------- + * get_query_def - Parse back one action from + * the parsetree in the actions + * list + * ---------- + */ +static char * +get_query_def(Query *query) +{ + switch (query->commandType) { + case CMD_SELECT: + return get_select_query_def(query); + break; + + case CMD_UPDATE: + return get_update_query_def(query); + break; + + case CMD_INSERT: + return get_insert_query_def(query); + break; + + case CMD_DELETE: + return get_delete_query_def(query); + break; + + case CMD_NOTHING: + return "NOTHING"; + break; + + default: + elog(ERROR, "get_ruledef of %s: query command type %d not implemented yet", + rulename, query->commandType); + break; + } + + return NULL; +} + + +/* ---------- + * get_select_query_def - Parse back a SELECT parsetree + * ---------- + */ +static char * +get_select_query_def(Query *query) +{ + char buf[8192]; + char *sep; + TargetEntry *tle; + RangeTblEntry *rte; + bool *rt_used; + int rt_length; + int rt_numused = 0; + bool rt_constonly = TRUE; + int i; + List *l; + + /* ---------- + * First we need need to know which and how many of the + * range table entries in the query are used in the target list + * or queries qualification + * ---------- + */ + rt_length = length(query->rtable); + rt_used = palloc(sizeof(bool) * rt_length); + for (i = 0; i < rt_length; i++) { + if (check_if_rte_used(i + 1, (Node *)(query->targetList), 0)) { + rt_used[i] = TRUE; + rt_numused++; + } else { + if (check_if_rte_used(i + 1, (Node *)(query->qual), 0)) { + rt_used[i] = TRUE; + rt_numused++; + } else { + rt_used[i] = FALSE; + } + } + } + + /* ---------- + * Now check if any of the used rangetable entries is different + * from *NEW* and *CURRENT*. If so we must omit the FROM clause + * later. + * ---------- + */ + i = 0; + foreach (l, query->rtable) { + if (!rt_used[i++]) + continue; + + rte = (RangeTblEntry *)lfirst(l); + if (!strcmp(rte->refname, "*NEW*")) + continue; + if (!strcmp(rte->refname, "*CURRENT*")) + continue; + + rt_constonly = FALSE; + break; + } + + /* ---------- + * Build up the query string - first we say SELECT + * ---------- + */ + strcpy(buf, "SELECT"); + + /* Then we tell what to select (the targetlist) */ + sep = " "; + foreach (l, query->targetList) { + bool tell_as = FALSE; + + tle = (TargetEntry *)lfirst(l); + strcat(buf, sep); + sep = ", "; + + strcat(buf, get_tle_expr(query->rtable, 0, tle, (rt_numused > 1))); + + /* Check if we must say AS ... */ + if (nodeTag(tle->expr) != T_Var) { + tell_as = strcmp(tle->resdom->resname, "?column?"); + } else { + Var *var = (Var *)(tle->expr); + char *attname; + + rte = (RangeTblEntry *)nth(var->varno - 1, query->rtable); + attname = get_attribute_name(rte->relid, var->varattno); + if (strcmp(attname, tle->resdom->resname)) + tell_as = TRUE; + } + + /* and do if so */ + if (tell_as) { + strcat(buf, " AS "); + strcat(buf, tle->resdom->resname); + } + } + + /* If we need other tables that *NEW* or *CURRENT* add the FROM clause */ + if (!rt_constonly && rt_numused > 0) { + strcat(buf, " FROM"); + + i = 0; + sep = " "; + foreach (l, query->rtable) { + if (rt_used[i++]) { + rte = (RangeTblEntry *)lfirst(l); + + if (!strcmp(rte->refname, "*NEW*")) + continue; + + if (!strcmp(rte->refname, "*CURRENT*")) + continue; + + strcat(buf, sep); sep = ", "; + strcat(buf, rte->relname); + if (rt_numused > 1) { + strcat(buf, " "); + strcat(buf, rte->refname); + } + } + } + } + + /* Add the WHERE clause if given */ + if (query->qual != NULL) { + strcat(buf, " WHERE "); + strcat(buf, get_rule_expr(query->rtable, 0, query->qual, (rt_numused > 1))); + } + + /* Add the GROUP BY CLAUSE */ + if (query->groupClause != NULL) { + strcat(buf, " GROUP BY "); + sep = ""; + foreach (l, query->groupClause) { + strcat(buf, sep); sep = ", "; + strcat(buf, get_rule_expr(query->rtable, 0, lfirst(l), (rt_numused > 1))); + } + } + + /* ---------- + * Copy the query string into allocated space and return it + * ---------- + */ + return pstrdup(buf); +} + + +/* ---------- + * get_insert_query_def - Parse back an INSERT parsetree + * ---------- + */ +static char * +get_insert_query_def(Query *query) +{ + char buf[8192]; + char *sep; + TargetEntry *tle; + RangeTblEntry *rte; + bool *rt_used; + int rt_length; + int rt_numused = 0; + bool rt_constonly = TRUE; + int i; + List *l; + + /* ---------- + * We need to know if other tables than *NEW* or *CURRENT* + * are used in the query. If not, it's an INSERT ... VALUES, + * otherwise an INSERT ... SELECT. + * ---------- + */ + rt_length = length(query->rtable); + rt_used = palloc(sizeof(bool) * rt_length); + for (i = 0; i < rt_length; i++) { + if (check_if_rte_used(i + 1, (Node *)(query->targetList), 0)) { + rt_used[i] = TRUE; + rt_numused++; + } else { + if (check_if_rte_used(i + 1, (Node *)(query->qual), 0)) { + rt_used[i] = TRUE; + rt_numused++; + } else { + rt_used[i] = FALSE; + } + } + } + + i = 0; + foreach (l, query->rtable) { + if (!rt_used[i++]) + continue; + + rte = (RangeTblEntry *)lfirst(l); + if (!strcmp(rte->refname, "*NEW*")) + continue; + if (!strcmp(rte->refname, "*CURRENT*")) + continue; + + rt_constonly = FALSE; + break; + } + + /* ---------- + * Start the query with INSERT INTO relname + * ---------- + */ + rte = (RangeTblEntry *)nth(query->resultRelation - 1, query->rtable); + strcpy(buf, "INSERT INTO "); + strcat(buf, rte->relname); + + /* Add the target list */ + sep = " ("; + foreach (l, query->targetList) { + tle = (TargetEntry *)lfirst(l); + + strcat(buf, sep); sep = ", "; + strcat(buf, tle->resdom->resname); + } + strcat(buf, ") "); + + /* Add the VALUES or the SELECT */ + if (rt_constonly && query->qual == NULL) { + strcat(buf, "VALUES ("); + sep = ""; + foreach (l, query->targetList) { + tle = (TargetEntry *)lfirst(l); + + strcat(buf, sep); sep = ", "; + strcat(buf, get_tle_expr(query->rtable, 0, tle, (rt_numused > 1))); + } + strcat(buf, ")"); + } else { + strcat(buf, get_select_query_def(query)); + } + + /* ---------- + * Copy the query string into allocated space and return it + * ---------- + */ + return pstrdup(buf); +} + + +/* ---------- + * get_update_query_def - Parse back an UPDATE parsetree + * ---------- + */ +static char * +get_update_query_def(Query *query) +{ + char buf[8192]; + char *sep; + TargetEntry *tle; + RangeTblEntry *rte; + List *l; + + /* ---------- + * Start the query with UPDATE relname SET + * ---------- + */ + rte = (RangeTblEntry *)nth(query->resultRelation - 1, query->rtable); + strcpy(buf, "UPDATE "); + strcat(buf, rte->relname); + strcat(buf, " SET "); + + /* Add the comma separated list of 'attname = value' */ + sep = ""; + foreach (l, query->targetList) { + tle = (TargetEntry *)lfirst(l); + + strcat(buf, sep); sep = ", "; + strcat(buf, tle->resdom->resname); + strcat(buf, " = "); + strcat(buf, get_tle_expr(query->rtable, query->resultRelation, + tle, TRUE)); + } + + /* Finally add a WHERE clause if given */ + if (query->qual != NULL) { + strcat(buf, " WHERE "); + strcat(buf, get_rule_expr(query->rtable, query->resultRelation, + query->qual, TRUE)); + } + + /* ---------- + * Copy the query string into allocated space and return it + * ---------- + */ + return pstrdup(buf); +} + + +/* ---------- + * get_delete_query_def - Parse back a DELETE parsetree + * ---------- + */ +static char * +get_delete_query_def(Query *query) +{ + char buf[8192]; + RangeTblEntry *rte; + + /* ---------- + * Start the query with DELETE FROM relname + * ---------- + */ + rte = (RangeTblEntry *)nth(query->resultRelation - 1, query->rtable); + strcpy(buf, "DELETE FROM "); + strcat(buf, rte->relname); + + /* Add a WHERE clause if given */ + if (query->qual != NULL) { + strcat(buf, " WHERE "); + strcat(buf, get_rule_expr(query->rtable, 0, query->qual, FALSE)); + } + + /* ---------- + * Copy the query string into allocated space and return it + * ---------- + */ + return pstrdup(buf); +} + + +/* ---------- + * get_rule_expr - Parse back an expression + * ---------- + */ +static char * +get_rule_expr(List *rtable, int rt_index, Node *node, bool varprefix) +{ + char buf[8192]; + + if (node == NULL) + return pstrdup(""); + + buf[0] = '\0'; + + /* ---------- + * Up to now I don't know if all the node types below + * can really occur in rules actions and qualifications. + * There might be some work left. + * ---------- + */ + switch(nodeTag(node)) { + case T_TargetEntry: + { + TargetEntry *tle = (TargetEntry *)node; + + return get_rule_expr(rtable, rt_index, + (Node *)(tle->expr), varprefix); + } + break; + + case T_Aggreg: + { + Aggreg *agg = (Aggreg *)node; + + strcat(buf, agg->aggname); + strcat(buf, "("); + strcat(buf, get_rule_expr(rtable, rt_index, + (Node *)(agg->target), varprefix)); + strcat(buf, ")"); + return pstrdup(buf); + } + break; + + case T_GroupClause: + { + GroupClause *grp = (GroupClause *)node; + + return get_rule_expr(rtable, rt_index, + (Node *)(grp->entry), varprefix); + } + break; + + case T_Expr: + { + Expr *expr = (Expr *)node; + + /* ---------- + * Expr nodes have to be handled a bit detailed + * ---------- + */ + switch (expr->opType) { + case OP_EXPR: + strcat(buf, get_rule_expr(rtable, rt_index, + (Node *)get_leftop(expr), + varprefix)); + strcat(buf, " "); + strcat(buf, get_opname(((Oper *)expr->oper)->opno)); + strcat(buf, " "); + strcat(buf, get_rule_expr(rtable, rt_index, + (Node *)get_rightop(expr), + varprefix)); + return pstrdup(buf); + break; + + case OR_EXPR: + strcat(buf, "("); + strcat(buf, get_rule_expr(rtable, rt_index, + (Node *)get_leftop(expr), + varprefix)); + strcat(buf, ") OR ("); + strcat(buf, get_rule_expr(rtable, rt_index, + (Node *)get_rightop(expr), + varprefix)); + strcat(buf, ")"); + return pstrdup(buf); + break; + + case AND_EXPR: + strcat(buf, "("); + strcat(buf, get_rule_expr(rtable, rt_index, + (Node *)get_leftop(expr), + varprefix)); + strcat(buf, ") AND ("); + strcat(buf, get_rule_expr(rtable, rt_index, + (Node *)get_rightop(expr), + varprefix)); + strcat(buf, ")"); + return pstrdup(buf); + break; + + case NOT_EXPR: + strcat(buf, "NOT ("); + strcat(buf, get_rule_expr(rtable, rt_index, + (Node *)get_leftop(expr), + varprefix)); + strcat(buf, ")"); + return pstrdup(buf); + break; + + case FUNC_EXPR: + return get_func_expr(rtable, rt_index, + (Expr *)node, + varprefix); + break; + + default: + printf("\n%s\n", nodeToString(node)); + elog(ERROR, "Expr not yet supported"); + } + } + break; + + case T_Var: + { + Var *var = (Var *)node; + RangeTblEntry *rte = (RangeTblEntry *)nth(var->varno - 1, rtable); + + if (!strcmp(rte->refname, "*NEW*")) { + strcat(buf, "new."); + } else { + if (!strcmp(rte->refname, "*CURRENT*")) { + strcat(buf, "current."); + } else { + if (varprefix && var->varno != rt_index) { + strcat(buf, rte->refname); + strcat(buf, "."); + } + } + } + strcat(buf, get_attribute_name(rte->relid, var->varattno)); + + return pstrdup(buf); + } + break; + + case T_List: + { + printf("\n%s\n", nodeToString(node)); + elog(ERROR, "List not yet supported"); + } + break; + + case T_SubLink: + { + SubLink *sublink = (SubLink *)node; + Query *query = (Query *)(sublink->subselect); + List *l; + char *sep; + + if (sublink->lefthand != NULL) { + strcat(buf, "("); + sep = ""; + foreach (l, sublink->lefthand) { + strcat(buf, sep); sep = ", "; + strcat(buf, get_rule_expr(rtable, rt_index, + lfirst(l), varprefix)); + } + strcat(buf, ") IN "); + } + + strcat(buf, "("); + strcat(buf, get_query_def(query)); + strcat(buf, ")"); + + return pstrdup(buf); + } + break; + + case T_Const: + { + return get_const_expr((Const *)node); + } + break; + + default: + printf("\n%s\n", nodeToString(node)); + elog(ERROR, "get_ruledef of %s: unknown node type %d get_rule_expr()", + rulename, nodeTag(node)); + break; + } + + return FALSE; +} + + +/* ---------- + * get_func_expr - Parse back a Func node + * ---------- + */ +static char * +get_func_expr(List *rtable, int rt_index, Expr *expr, bool varprefix) +{ + char buf[8192]; + HeapTuple proctup; + Form_pg_proc procStruct; + List *l; + char *sep; + Func *func = (Func *)(expr->oper); + char *proname; + + /* ---------- + * Get the functions pg_proc tuple + * ---------- + */ + proctup = SearchSysCacheTuple(PROOID, + ObjectIdGetDatum(func->funcid), 0, 0, 0); + if (!HeapTupleIsValid(proctup)) + elog(ERROR, "cache lookup for proc %d failed", func->funcid); + + procStruct = (Form_pg_proc) GETSTRUCT(proctup); + proname = nameout(&(procStruct->proname)); + + if (procStruct->pronargs == 1 && procStruct->proargtypes[0] == InvalidOid) { + if (!strcmp(proname, "nullvalue")) { + strcpy(buf, "("); + strcat(buf, get_rule_expr(rtable, rt_index, lfirst(expr->args), + varprefix)); + strcat(buf, ") ISNULL"); + return pstrdup(buf); + } + if (!strcmp(proname, "nonnullvalue")) { + strcpy(buf, "("); + strcat(buf, get_rule_expr(rtable, rt_index, lfirst(expr->args), + varprefix)); + strcat(buf, ") NOTNULL"); + return pstrdup(buf); + } + } + + /* ---------- + * Build a string of proname(args) + * ---------- + */ + strcpy(buf, proname); + strcat(buf, "("); + sep = ""; + foreach (l, expr->args) { + strcat(buf, sep); sep = ", "; + strcat(buf, get_rule_expr(rtable, rt_index, lfirst(l), varprefix)); + } + strcat(buf, ")"); + + /* ---------- + * Copy the function call string into allocated space and return it + * ---------- + */ + return pstrdup(buf); +} + + +/* ---------- + * get_tle_expr - A target list expression is a bit + * different from a normal expression. + * If the target column has an + * an atttypmod, the parser usually + * puts a padding-/cut-function call + * around the expression itself. We + * we must get rid of it, otherwise + * dump/reload/dump... would blow up + * the expressions. + * ---------- + */ +static char * +get_tle_expr(List *rtable, int rt_index, TargetEntry *tle, bool varprefix) +{ + HeapTuple proctup; + Form_pg_proc procStruct; + Expr *expr; + Func *func; + Const *second_arg; + + /* ---------- + * Check if the result has an atttypmod and if the + * expression in the targetlist entry is a function call + * ---------- + */ + if (tle->resdom->restypmod < 0) { + return get_rule_expr(rtable, rt_index, tle->expr, varprefix); + } + if (nodeTag(tle->expr) != T_Expr) { + return get_rule_expr(rtable, rt_index, tle->expr, varprefix); + } + expr = (Expr *)(tle->expr); + if (expr->opType != FUNC_EXPR) { + return get_rule_expr(rtable, rt_index, tle->expr, varprefix); + } + + func = (Func *)(expr->oper); + + /* ---------- + * Get the functions pg_proc tuple + * ---------- + */ + proctup = SearchSysCacheTuple(PROOID, + ObjectIdGetDatum(func->funcid), 0, 0, 0); + if (!HeapTupleIsValid(proctup)) + elog(ERROR, "cache lookup for proc %d failed", func->funcid); + + procStruct = (Form_pg_proc) GETSTRUCT(proctup); + + /* ---------- + * It must be a function with two arguments where the first + * is of the same type as the return value and the second is + * an int4. + * ---------- + */ + if (procStruct->pronargs != 2) { + return get_rule_expr(rtable, rt_index, tle->expr, varprefix); + } + if (procStruct->prorettype != procStruct->proargtypes[0]) { + return get_rule_expr(rtable, rt_index, tle->expr, varprefix); + } + if (procStruct->proargtypes[1] != INT4OID) { + return get_rule_expr(rtable, rt_index, tle->expr, varprefix); + } + + /* ---------- + * Finally (to be totally safe) the second argument must be a + * const and match the value in the results atttypmod. + * ---------- + */ + second_arg = (Const *)nth(1, expr->args); + if (nodeTag((Node *)second_arg) != T_Const) { + return get_rule_expr(rtable, rt_index, tle->expr, varprefix); + } + if ((int4)(second_arg->constvalue) != tle->resdom->restypmod) { + return get_rule_expr(rtable, rt_index, tle->expr, varprefix); + } + + /* ---------- + * Whow - got it. Now get rid of the padding function + * ---------- + */ + return get_rule_expr(rtable, rt_index, lfirst(expr->args), varprefix); +} + + +/* ---------- + * get_const_expr - Make a string representation + * with the type cast out of a Const + * ---------- + */ +char * +get_const_expr(Const *constval) +{ + HeapTuple typetup; + TypeTupleForm typeStruct; + FmgrInfo finfo_output; + char *extval; + bool isnull = FALSE; + char buf[8192]; + + if (constval->constisnull) + return "NULL"; + + typetup = SearchSysCacheTuple(TYPOID, + ObjectIdGetDatum(constval->consttype), 0, 0, 0); + if (!HeapTupleIsValid(typetup)) + elog(ERROR, "cache lookup of type %d failed", constval->consttype); + + typeStruct = (TypeTupleForm) GETSTRUCT(typetup); + + fmgr_info(typeStruct->typoutput, &finfo_output); + extval = (char *)(*fmgr_faddr(&finfo_output))(constval->constvalue, + &isnull, -1); + + sprintf(buf, "'%s'::%s", extval, nameout(&(typeStruct->typname))); + return pstrdup(buf); +} + + +/* ---------- + * get_relation_name - Get a relation name by Oid + * ---------- + */ +static char * +get_relation_name(Oid relid) +{ + HeapTuple classtup; + Form_pg_class classStruct; + + classtup = SearchSysCacheTuple(RELOID, + ObjectIdGetDatum(relid), 0, 0, 0); + if (!HeapTupleIsValid(classtup)) + elog(ERROR, "cache lookup of relation %d failed", relid); + + classStruct = (Form_pg_class) GETSTRUCT(classtup); + return nameout(&(classStruct->relname)); +} + + +/* ---------- + * get_attribute_name - Get an attribute name by it's + * relations Oid and it's attnum + * ---------- + */ +static char * +get_attribute_name(Oid relid, int2 attnum) +{ + HeapTuple atttup; + AttributeTupleForm attStruct; + + atttup = SearchSysCacheTuple(ATTNUM, + ObjectIdGetDatum(relid), (Datum)attnum, 0, 0); + if (!HeapTupleIsValid(atttup)) + elog(ERROR, "cache lookup of attribute %d in relation %d failed", + attnum, relid); + + attStruct = (AttributeTupleForm) GETSTRUCT(atttup); + return nameout(&(attStruct->attname)); +} + + +/* ---------- + * check_if_rte_used - Check a targetlist or qual + * if a given rangetable entry + * is used in it + * ---------- + */ +static bool +check_if_rte_used(int rt_index, Node *node, int sup) +{ + if (node == NULL) + return FALSE; + + switch(nodeTag(node)) { + case T_TargetEntry: + { + TargetEntry *tle = (TargetEntry *)node; + + return check_if_rte_used(rt_index, + (Node *)(tle->expr), sup); + } + break; + + case T_Aggreg: + { + Aggreg *agg = (Aggreg *)node; + return check_if_rte_used(rt_index, + (Node *)(agg->target), sup); + } + break; + + case T_GroupClause: + { + GroupClause *grp = (GroupClause *)node; + return check_if_rte_used(rt_index, + (Node *)(grp->entry), sup); + } + break; + + case T_Expr: + { + Expr *expr = (Expr *)node; + return check_if_rte_used(rt_index, + (Node *)(expr->args), sup); + } + break; + + case T_Var: + { + Var *var = (Var *)node; + return (var->varno == rt_index && var->varlevelsup == sup); + } + break; + + case T_List: + { + List *l; + + foreach (l, (List *)node) { + if (check_if_rte_used(rt_index, lfirst(l), sup)) + return TRUE; + } + return FALSE; + } + break; + + case T_SubLink: + { + SubLink *sublink = (SubLink *)node; + Query *query = (Query *)sublink->subselect; + + if (check_if_rte_used(rt_index, (Node *)(query->qual), sup + 1)) + return TRUE; + + if (check_if_rte_used(rt_index, (Node *)(sublink->lefthand), sup)) + return TRUE; + + return FALSE; + } + break; + + case T_Const: + return FALSE; + break; + + default: + elog(ERROR, "get_ruledef of %s: unknown node type %d in check_if_rte_used()", + rulename, nodeTag(node)); + break; + } + + return FALSE; +} + + diff --git a/src/bin/initdb/initdb.sh b/src/bin/initdb/initdb.sh index df645dcf172..c9cf8d8a60b 100644 --- a/src/bin/initdb/initdb.sh +++ b/src/bin/initdb/initdb.sh @@ -26,7 +26,7 @@ # # # IDENTIFICATION -# $Header: /cvsroot/pgsql/src/bin/initdb/Attic/initdb.sh,v 1.53 1998/08/24 01:14:04 momjian Exp $ +# $Header: /cvsroot/pgsql/src/bin/initdb/Attic/initdb.sh,v 1.54 1998/08/24 01:38:06 momjian Exp $ # #------------------------------------------------------------------------- @@ -433,6 +433,39 @@ echo "CREATE RULE _RETpg_user AS ON SELECT TO pg_user DO INSTEAD \ echo "REVOKE ALL on pg_shadow FROM public" | \ postgres $PGSQL_OPT template1 > /dev/null +echo "Creating view pg_rule" +echo "CREATE TABLE xpg_rule ( \ + rulename name, \ + definition text);" | postgres $PGSQL_OPT template1 > /dev/null +#move it into pg_rule +echo "UPDATE pg_class SET relname = 'pg_rule' WHERE relname = 'xpg_rule';" |\ + postgres $PGSQL_OPT template1 > /dev/null +echo "UPDATE pg_type SET typname = 'pg_rule' WHERE typname = 'xpg_rule';" |\ + postgres $PGSQL_OPT template1 > /dev/null +mv $PGDATA/base/template1/xpg_rule $PGDATA/base/template1/pg_rule + +echo "CREATE RULE _RETpg_rule AS ON SELECT TO pg_rule DO INSTEAD \ + SELECT rulename, pg_get_ruledef(rulename) AS definition \ + FROM pg_rewrite;" | postgres $PGSQL_OPT template1 > /dev/null + +echo "Creating view pg_view" +echo "CREATE TABLE xpg_view ( \ + viewname name, \ + definition text);" | postgres $PGSQL_OPT template1 > /dev/null +#move it into pg_view +echo "UPDATE pg_class SET relname = 'pg_view' WHERE relname = 'xpg_view';" |\ + postgres $PGSQL_OPT template1 > /dev/null +echo "UPDATE pg_type SET typname = 'pg_view' WHERE typname = 'xpg_view';" |\ + postgres $PGSQL_OPT template1 > /dev/null +mv $PGDATA/base/template1/xpg_view $PGDATA/base/template1/pg_view + +echo "CREATE RULE _RETpg_view AS ON SELECT TO pg_view DO INSTEAD \ + SELECT relname AS viewname, \ + pg_get_viewdef(relname) AS definition \ + FROM pg_class WHERE relhasrules AND \ + pg_get_viewdef(relname) != 'Not a view';" | \ + postgres $PGSQL_OPT template1 > /dev/null + echo "Loading pg_description" echo "copy pg_description from '$TEMPLATE_DESCR'" | \ postgres $PGSQL_OPT template1 > /dev/null diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h index 94f74396790..902b057f172 100644 --- a/src/include/catalog/pg_proc.h +++ b/src/include/catalog/pg_proc.h @@ -6,7 +6,7 @@ * * Copyright (c) 1994, Regents of the University of California * - * $Id: pg_proc.h,v 1.66 1998/08/19 02:03:54 momjian Exp $ + * $Id: pg_proc.h,v 1.67 1998/08/24 01:38:08 momjian Exp $ * * NOTES * The script catalog/genbki.sh reads this file and generates .bki @@ -2033,6 +2033,12 @@ DESCR("sequence current value"); /* for multi-byte support */ DATA(insert OID = 1039 ( getdatabaseencoding PGUID 11 f t f 0 f 19 "0" 100 0 0 100 foo bar )); +/* System-view support functions */ +DATA(insert OID = 1640 ( pg_get_ruledef PGUID 11 f t f 1 f 25 "19" 100 0 0 100 foo bar )); +DESCR("source text of a rule"); +DATA(insert OID = 1641 ( pg_get_viewdef PGUID 11 f t f 1 f 25 "19" 100 0 0 100 foo bar )); +DESCR("select statement of a view"); + /* * prototypes for functions pg_proc.c */ diff --git a/src/include/tcop/tcopprot.h b/src/include/tcop/tcopprot.h index 4f4b60e867f..feed580e692 100644 --- a/src/include/tcop/tcopprot.h +++ b/src/include/tcop/tcopprot.h @@ -6,7 +6,7 @@ * * Copyright (c) 1994, Regents of the University of California * - * $Id: tcopprot.h,v 1.14 1998/06/04 17:26:49 momjian Exp $ + * $Id: tcopprot.h,v 1.15 1998/08/24 01:38:11 momjian Exp $ * * OLD COMMENTS * This file was created so that other c files could get the two @@ -24,10 +24,12 @@ #ifndef BOOTSTRAP_INCLUDE extern List * pg_parse_and_plan(char *query_string, Oid *typev, int nargs, - QueryTreeList **queryListP, CommandDest dest); + QueryTreeList **queryListP, CommandDest dest, + bool aclOverride); extern void pg_exec_query(char *query_string); +extern void pg_exec_query_acl_override(char *query_string); extern void -pg_exec_query_dest(char *query_string, CommandDest dest); +pg_exec_query_dest(char *query_string, CommandDest dest, bool aclOverride); #endif /* BOOTSTRAP_HEADER */