1996-07-09 08:22:35 +02:00
|
|
|
/*-------------------------------------------------------------------------
|
|
|
|
*
|
1999-02-14 00:22:53 +01:00
|
|
|
* rewriteDefine.c
|
1997-09-07 07:04:48 +02:00
|
|
|
* routines for defining a rewrite rule
|
1996-07-09 08:22:35 +02:00
|
|
|
*
|
2012-01-02 00:01:58 +01:00
|
|
|
* Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
|
2000-01-26 06:58:53 +01:00
|
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
1996-07-09 08:22:35 +02:00
|
|
|
*
|
|
|
|
*
|
|
|
|
* IDENTIFICATION
|
2010-09-20 22:08:53 +02:00
|
|
|
* src/backend/rewrite/rewriteDefine.c
|
1996-07-09 08:22:35 +02:00
|
|
|
*
|
|
|
|
*-------------------------------------------------------------------------
|
|
|
|
*/
|
|
|
|
#include "postgres.h"
|
|
|
|
|
1999-07-16 05:14:30 +02:00
|
|
|
#include "access/heapam.h"
|
2009-05-14 00:32:55 +02:00
|
|
|
#include "catalog/catalog.h"
|
2002-07-12 20:43:19 +02:00
|
|
|
#include "catalog/dependency.h"
|
2000-06-28 05:33:33 +02:00
|
|
|
#include "catalog/indexing.h"
|
2007-08-27 05:36:08 +02:00
|
|
|
#include "catalog/namespace.h"
|
2010-11-25 17:48:49 +01:00
|
|
|
#include "catalog/objectaccess.h"
|
2000-06-28 05:33:33 +02:00
|
|
|
#include "catalog/pg_rewrite.h"
|
2008-11-19 11:34:52 +01:00
|
|
|
#include "catalog/storage.h"
|
2000-09-29 20:21:41 +02:00
|
|
|
#include "miscadmin.h"
|
2008-08-26 00:42:34 +02:00
|
|
|
#include "nodes/nodeFuncs.h"
|
2007-06-24 00:12:52 +02:00
|
|
|
#include "parser/parse_utilcmd.h"
|
1996-11-10 04:06:38 +01:00
|
|
|
#include "rewrite/rewriteDefine.h"
|
2000-12-05 20:15:10 +01:00
|
|
|
#include "rewrite/rewriteManip.h"
|
1996-07-09 08:22:35 +02:00
|
|
|
#include "rewrite/rewriteSupport.h"
|
2002-03-22 00:27:25 +01:00
|
|
|
#include "utils/acl.h"
|
2000-09-29 20:21:41 +02:00
|
|
|
#include "utils/builtins.h"
|
2008-03-26 22:10:39 +01:00
|
|
|
#include "utils/inval.h"
|
2003-07-25 02:01:09 +02:00
|
|
|
#include "utils/lsyscache.h"
|
2008-06-19 02:46:06 +02:00
|
|
|
#include "utils/rel.h"
|
2001-08-12 23:35:19 +02:00
|
|
|
#include "utils/syscache.h"
|
2008-03-26 22:10:39 +01:00
|
|
|
#include "utils/tqual.h"
|
2000-09-29 20:21:41 +02:00
|
|
|
|
|
|
|
|
2006-09-02 19:06:52 +02:00
|
|
|
static void checkRuleResultList(List *targetList, TupleDesc resultDesc,
|
2006-10-04 02:30:14 +02:00
|
|
|
bool isSelect);
|
2005-06-28 07:09:14 +02:00
|
|
|
static bool setRuleCheckAsUser_walker(Node *node, Oid *context);
|
2006-09-05 23:08:36 +02:00
|
|
|
static void setRuleCheckAsUser_Query(Query *qry, Oid userid);
|
1996-07-09 08:22:35 +02:00
|
|
|
|
1999-10-21 03:46:24 +02:00
|
|
|
|
1996-07-09 08:22:35 +02:00
|
|
|
/*
|
|
|
|
* InsertRule -
|
2002-04-18 22:01:11 +02:00
|
|
|
* takes the arguments and inserts them as a row into the system
|
1997-09-07 07:04:48 +02:00
|
|
|
* relation "pg_rewrite"
|
1996-07-09 08:22:35 +02:00
|
|
|
*/
|
1997-09-08 04:41:22 +02:00
|
|
|
static Oid
|
1996-07-09 08:22:35 +02:00
|
|
|
InsertRule(char *rulname,
|
1997-09-07 07:04:48 +02:00
|
|
|
int evtype,
|
2000-06-30 09:04:23 +02:00
|
|
|
Oid eventrel_oid,
|
|
|
|
AttrNumber evslot_index,
|
1997-09-07 07:04:48 +02:00
|
|
|
bool evinstead,
|
2002-07-16 07:53:34 +02:00
|
|
|
Node *event_qual,
|
2002-09-02 04:13:02 +02:00
|
|
|
List *action,
|
|
|
|
bool replace)
|
1996-07-09 08:22:35 +02:00
|
|
|
{
|
2002-07-16 07:53:34 +02:00
|
|
|
char *evqual = nodeToString(event_qual);
|
|
|
|
char *actiontree = nodeToString((Node *) action);
|
2000-06-28 05:33:33 +02:00
|
|
|
Datum values[Natts_pg_rewrite];
|
2008-11-02 02:45:28 +01:00
|
|
|
bool nulls[Natts_pg_rewrite];
|
|
|
|
bool replaces[Natts_pg_rewrite];
|
2000-06-28 05:33:33 +02:00
|
|
|
NameData rname;
|
|
|
|
Relation pg_rewrite_desc;
|
2002-09-02 04:13:02 +02:00
|
|
|
HeapTuple tup,
|
|
|
|
oldtup;
|
2000-06-28 05:33:33 +02:00
|
|
|
Oid rewriteObjectId;
|
2002-09-04 22:31:48 +02:00
|
|
|
ObjectAddress myself,
|
|
|
|
referenced;
|
2002-09-02 04:13:02 +02:00
|
|
|
bool is_update = false;
|
1999-10-21 03:46:24 +02:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
|
|
|
* Set up *nulls and *values arrays
|
2000-06-28 05:33:33 +02:00
|
|
|
*/
|
2008-11-02 02:45:28 +01:00
|
|
|
MemSet(nulls, false, sizeof(nulls));
|
2000-06-28 05:33:33 +02:00
|
|
|
|
|
|
|
namestrcpy(&rname, rulname);
|
2011-06-16 23:03:58 +02:00
|
|
|
values[Anum_pg_rewrite_rulename - 1] = NameGetDatum(&rname);
|
|
|
|
values[Anum_pg_rewrite_ev_class - 1] = ObjectIdGetDatum(eventrel_oid);
|
|
|
|
values[Anum_pg_rewrite_ev_attr - 1] = Int16GetDatum(evslot_index);
|
|
|
|
values[Anum_pg_rewrite_ev_type - 1] = CharGetDatum(evtype + '0');
|
|
|
|
values[Anum_pg_rewrite_ev_enabled - 1] = CharGetDatum(RULE_FIRES_ON_ORIGIN);
|
|
|
|
values[Anum_pg_rewrite_is_instead - 1] = BoolGetDatum(evinstead);
|
|
|
|
values[Anum_pg_rewrite_ev_qual - 1] = CStringGetTextDatum(evqual);
|
|
|
|
values[Anum_pg_rewrite_ev_action - 1] = CStringGetTextDatum(actiontree);
|
2000-06-28 05:33:33 +02:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2002-09-02 04:13:02 +02:00
|
|
|
* Ready to store new pg_rewrite tuple
|
2000-06-28 05:33:33 +02:00
|
|
|
*/
|
2005-04-14 22:03:27 +02:00
|
|
|
pg_rewrite_desc = heap_open(RewriteRelationId, RowExclusiveLock);
|
2000-06-28 05:33:33 +02:00
|
|
|
|
2002-09-02 04:13:02 +02:00
|
|
|
/*
|
|
|
|
* Check to see if we are replacing an existing tuple
|
|
|
|
*/
|
2010-02-14 19:42:19 +01:00
|
|
|
oldtup = SearchSysCache2(RULERELNAME,
|
|
|
|
ObjectIdGetDatum(eventrel_oid),
|
|
|
|
PointerGetDatum(rulname));
|
2002-09-02 04:13:02 +02:00
|
|
|
|
|
|
|
if (HeapTupleIsValid(oldtup))
|
|
|
|
{
|
2009-01-27 13:40:15 +01:00
|
|
|
if (!replace)
|
2003-07-25 02:01:09 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_DUPLICATE_OBJECT),
|
2005-10-15 04:49:52 +02:00
|
|
|
errmsg("rule \"%s\" for relation \"%s\" already exists",
|
|
|
|
rulname, get_rel_name(eventrel_oid))));
|
2002-09-02 04:13:02 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
* When replacing, we don't need to replace every attribute
|
|
|
|
*/
|
2008-11-02 02:45:28 +01:00
|
|
|
MemSet(replaces, false, sizeof(replaces));
|
|
|
|
replaces[Anum_pg_rewrite_ev_attr - 1] = true;
|
|
|
|
replaces[Anum_pg_rewrite_ev_type - 1] = true;
|
|
|
|
replaces[Anum_pg_rewrite_is_instead - 1] = true;
|
|
|
|
replaces[Anum_pg_rewrite_ev_qual - 1] = true;
|
|
|
|
replaces[Anum_pg_rewrite_ev_action - 1] = true;
|
|
|
|
|
|
|
|
tup = heap_modify_tuple(oldtup, RelationGetDescr(pg_rewrite_desc),
|
2009-06-11 16:49:15 +02:00
|
|
|
values, nulls, replaces);
|
2002-09-02 04:13:02 +02:00
|
|
|
|
|
|
|
simple_heap_update(pg_rewrite_desc, &tup->t_self, tup);
|
2000-06-28 05:33:33 +02:00
|
|
|
|
2002-09-02 04:13:02 +02:00
|
|
|
ReleaseSysCache(oldtup);
|
|
|
|
|
|
|
|
rewriteObjectId = HeapTupleGetOid(tup);
|
|
|
|
is_update = true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2008-11-02 02:45:28 +01:00
|
|
|
tup = heap_form_tuple(pg_rewrite_desc->rd_att, values, nulls);
|
2000-06-28 05:33:33 +02:00
|
|
|
|
2002-09-02 04:13:02 +02:00
|
|
|
rewriteObjectId = simple_heap_insert(pg_rewrite_desc, tup);
|
|
|
|
}
|
2000-06-28 05:33:33 +02:00
|
|
|
|
2002-09-02 04:13:02 +02:00
|
|
|
/* Need to update indexes in either case */
|
2002-08-05 05:29:17 +02:00
|
|
|
CatalogUpdateIndexes(pg_rewrite_desc, tup);
|
1999-10-21 03:46:24 +02:00
|
|
|
|
2000-06-28 05:33:33 +02:00
|
|
|
heap_freetuple(tup);
|
1999-10-21 04:33:25 +02:00
|
|
|
|
2002-09-02 04:13:02 +02:00
|
|
|
/* If replacing, get rid of old dependencies and make new ones */
|
|
|
|
if (is_update)
|
2011-02-08 22:08:41 +01:00
|
|
|
deleteDependencyRecordsFor(RewriteRelationId, rewriteObjectId, false);
|
2002-09-02 04:13:02 +02:00
|
|
|
|
2002-07-12 20:43:19 +02:00
|
|
|
/*
|
2002-09-04 22:31:48 +02:00
|
|
|
* Install dependency on rule's relation to ensure it will go away on
|
|
|
|
* relation deletion. If the rule is ON SELECT, make the dependency
|
2005-10-15 04:49:52 +02:00
|
|
|
* implicit --- this prevents deleting a view's SELECT rule. Other kinds
|
|
|
|
* of rules can be AUTO.
|
2002-07-12 20:43:19 +02:00
|
|
|
*/
|
2005-04-14 03:38:22 +02:00
|
|
|
myself.classId = RewriteRelationId;
|
2002-07-12 20:43:19 +02:00
|
|
|
myself.objectId = rewriteObjectId;
|
|
|
|
myself.objectSubId = 0;
|
|
|
|
|
2005-04-14 03:38:22 +02:00
|
|
|
referenced.classId = RelationRelationId;
|
2002-07-12 20:43:19 +02:00
|
|
|
referenced.objectId = eventrel_oid;
|
|
|
|
referenced.objectSubId = 0;
|
|
|
|
|
|
|
|
recordDependencyOn(&myself, &referenced,
|
2005-10-15 04:49:52 +02:00
|
|
|
(evtype == CMD_SELECT) ? DEPENDENCY_INTERNAL : DEPENDENCY_AUTO);
|
2002-07-12 20:43:19 +02:00
|
|
|
|
2002-07-16 07:53:34 +02:00
|
|
|
/*
|
|
|
|
* Also install dependencies on objects referenced in action and qual.
|
|
|
|
*/
|
|
|
|
recordDependencyOnExpr(&myself, (Node *) action, NIL,
|
|
|
|
DEPENDENCY_NORMAL);
|
2002-09-02 04:13:02 +02:00
|
|
|
|
2002-07-16 07:53:34 +02:00
|
|
|
if (event_qual != NULL)
|
|
|
|
{
|
|
|
|
/* Find query containing OLD/NEW rtable entries */
|
2004-05-26 06:41:50 +02:00
|
|
|
Query *qry = (Query *) linitial(action);
|
2002-07-16 07:53:34 +02:00
|
|
|
|
|
|
|
qry = getInsertSelectQuery(qry, NULL);
|
|
|
|
recordDependencyOnExpr(&myself, event_qual, qry->rtable,
|
|
|
|
DEPENDENCY_NORMAL);
|
|
|
|
}
|
|
|
|
|
2010-11-25 17:48:49 +01:00
|
|
|
/* Post creation hook for new rule */
|
|
|
|
InvokeObjectAccessHook(OAT_POST_CREATE,
|
2012-03-09 20:34:56 +01:00
|
|
|
RewriteRelationId, rewriteObjectId, 0, NULL);
|
2010-11-25 17:48:49 +01:00
|
|
|
|
2000-06-28 05:33:33 +02:00
|
|
|
heap_close(pg_rewrite_desc, RowExclusiveLock);
|
1997-09-07 07:04:48 +02:00
|
|
|
|
2000-06-28 05:33:33 +02:00
|
|
|
return rewriteObjectId;
|
1996-07-09 08:22:35 +02:00
|
|
|
}
|
|
|
|
|
2007-03-13 01:33:44 +01:00
|
|
|
/*
|
|
|
|
* DefineRule
|
|
|
|
* Execute a CREATE RULE command.
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
DefineRule(RuleStmt *stmt, const char *queryString)
|
|
|
|
{
|
|
|
|
List *actions;
|
|
|
|
Node *whereClause;
|
2007-08-27 05:36:08 +02:00
|
|
|
Oid relId;
|
2007-03-13 01:33:44 +01:00
|
|
|
|
2011-07-09 04:19:30 +02:00
|
|
|
/* Parse analysis. */
|
2007-06-24 00:12:52 +02:00
|
|
|
transformRuleStmt(stmt, queryString, &actions, &whereClause);
|
2007-03-13 01:33:44 +01:00
|
|
|
|
2011-07-09 04:19:30 +02:00
|
|
|
/*
|
|
|
|
* Find and lock the relation. Lock level should match
|
|
|
|
* DefineQueryRewrite.
|
|
|
|
*/
|
Improve table locking behavior in the face of current DDL.
In the previous coding, callers were faced with an awkward choice:
look up the name, do permissions checks, and then lock the table; or
look up the name, lock the table, and then do permissions checks.
The first choice was wrong because the results of the name lookup
and permissions checks might be out-of-date by the time the table
lock was acquired, while the second allowed a user with no privileges
to interfere with access to a table by users who do have privileges
(e.g. if a malicious backend queues up for an AccessExclusiveLock on
a table on which AccessShareLock is already held, further attempts
to access the table will be blocked until the AccessExclusiveLock
is obtained and the malicious backend's transaction rolls back).
To fix, allow callers of RangeVarGetRelid() to pass a callback which
gets executed after performing the name lookup but before acquiring
the relation lock. If the name lookup is retried (because
invalidation messages are received), the callback will be re-executed
as well, so we get the best of both worlds. RangeVarGetRelid() is
renamed to RangeVarGetRelidExtended(); callers not wishing to supply
a callback can continue to invoke it as RangeVarGetRelid(), which is
now a macro. Since the only one caller that uses nowait = true now
passes a callback anyway, the RangeVarGetRelid() macro defaults nowait
as well. The callback can also be used for supplemental locking - for
example, REINDEX INDEX needs to acquire the table lock before the index
lock to reduce deadlock possibilities.
There's a lot more work to be done here to fix all the cases where this
can be a problem, but this commit provides the general infrastructure
and fixes the following specific cases: REINDEX INDEX, REINDEX TABLE,
LOCK TABLE, and and DROP TABLE/INDEX/SEQUENCE/VIEW/FOREIGN TABLE.
Per discussion with Noah Misch and Alvaro Herrera.
2011-11-30 16:12:27 +01:00
|
|
|
relId = RangeVarGetRelid(stmt->relation, AccessExclusiveLock, false);
|
2007-08-27 05:36:08 +02:00
|
|
|
|
|
|
|
/* ... and execute */
|
2007-03-13 01:33:44 +01:00
|
|
|
DefineQueryRewrite(stmt->rulename,
|
2007-08-27 05:36:08 +02:00
|
|
|
relId,
|
2007-03-13 01:33:44 +01:00
|
|
|
whereClause,
|
|
|
|
stmt->event,
|
|
|
|
stmt->instead,
|
|
|
|
stmt->replace,
|
|
|
|
actions);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* DefineQueryRewrite
|
|
|
|
* Create a rule
|
|
|
|
*
|
|
|
|
* This is essentially the same as DefineRule() except that the rule's
|
|
|
|
* action and qual have already been passed through parse analysis.
|
|
|
|
*/
|
1996-07-09 08:22:35 +02:00
|
|
|
void
|
2007-03-13 01:33:44 +01:00
|
|
|
DefineQueryRewrite(char *rulename,
|
2007-08-27 05:36:08 +02:00
|
|
|
Oid event_relid,
|
2007-03-13 01:33:44 +01:00
|
|
|
Node *event_qual,
|
|
|
|
CmdType event_type,
|
|
|
|
bool is_instead,
|
|
|
|
bool replace,
|
|
|
|
List *action)
|
1996-07-09 08:22:35 +02:00
|
|
|
{
|
2000-06-30 09:04:23 +02:00
|
|
|
Relation event_relation;
|
|
|
|
int event_attno;
|
2004-05-26 06:41:50 +02:00
|
|
|
ListCell *l;
|
1998-08-24 03:38:11 +02:00
|
|
|
Query *query;
|
2000-09-12 06:49:17 +02:00
|
|
|
bool RelisBecomingView = false;
|
1997-09-07 07:04:48 +02:00
|
|
|
|
2000-06-30 09:04:23 +02:00
|
|
|
/*
|
|
|
|
* If we are installing an ON SELECT rule, we had better grab
|
2005-10-15 04:49:52 +02:00
|
|
|
* AccessExclusiveLock to ensure no SELECTs are currently running on the
|
2011-07-07 19:14:46 +02:00
|
|
|
* event relation. For other types of rules, it would be sufficient to
|
|
|
|
* grab ShareRowExclusiveLock to lock out insert/update/delete actions and
|
|
|
|
* to ensure that we lock out current CREATE RULE statements; but because
|
|
|
|
* of race conditions in access to catalog entries, we can't do that yet.
|
2011-07-09 04:19:30 +02:00
|
|
|
*
|
|
|
|
* Note that this lock level should match the one used in DefineRule.
|
2000-06-30 09:04:23 +02:00
|
|
|
*/
|
2011-07-07 19:14:46 +02:00
|
|
|
event_relation = heap_open(event_relid, AccessExclusiveLock);
|
2000-06-30 09:04:23 +02:00
|
|
|
|
2009-05-14 00:32:55 +02:00
|
|
|
/*
|
|
|
|
* Verify relation is of a type that rules can sensibly be applied to.
|
|
|
|
*/
|
|
|
|
if (event_relation->rd_rel->relkind != RELKIND_RELATION &&
|
|
|
|
event_relation->rd_rel->relkind != RELKIND_VIEW)
|
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
|
|
|
errmsg("\"%s\" is not a table or view",
|
|
|
|
RelationGetRelationName(event_relation))));
|
|
|
|
|
|
|
|
if (!allowSystemTableMods && IsSystemRelation(event_relation))
|
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
|
|
|
|
errmsg("permission denied: \"%s\" is a system catalog",
|
|
|
|
RelationGetRelationName(event_relation))));
|
|
|
|
|
2002-03-22 00:27:25 +01:00
|
|
|
/*
|
|
|
|
* Check user has permission to apply rules to this relation.
|
|
|
|
*/
|
2007-08-27 05:36:08 +02:00
|
|
|
if (!pg_class_ownercheck(event_relid, GetUserId()))
|
2006-09-05 23:08:36 +02:00
|
|
|
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
|
2003-08-01 02:15:26 +02:00
|
|
|
RelationGetRelationName(event_relation));
|
2002-03-22 00:27:25 +01:00
|
|
|
|
1998-10-02 18:28:04 +02:00
|
|
|
/*
|
|
|
|
* No rule actions that modify OLD or NEW
|
|
|
|
*/
|
2000-06-30 09:04:23 +02:00
|
|
|
foreach(l, action)
|
1998-09-01 06:40:42 +02:00
|
|
|
{
|
|
|
|
query = (Query *) lfirst(l);
|
2000-12-05 20:15:10 +01:00
|
|
|
if (query->resultRelation == 0)
|
|
|
|
continue;
|
|
|
|
/* Don't be fooled by INSERT/SELECT */
|
|
|
|
if (query != getInsertSelectQuery(query, NULL))
|
|
|
|
continue;
|
2000-09-12 22:38:09 +02:00
|
|
|
if (query->resultRelation == PRS2_OLD_VARNO)
|
2003-07-25 02:01:09 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
|
|
errmsg("rule actions on OLD are not implemented"),
|
|
|
|
errhint("Use views or triggers instead.")));
|
2000-09-12 22:38:09 +02:00
|
|
|
if (query->resultRelation == PRS2_NEW_VARNO)
|
2003-07-25 02:01:09 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
|
|
errmsg("rule actions on NEW are not implemented"),
|
|
|
|
errhint("Use triggers instead.")));
|
1998-09-01 06:40:42 +02:00
|
|
|
}
|
1998-08-24 03:38:11 +02:00
|
|
|
|
1998-09-01 06:40:42 +02:00
|
|
|
if (event_type == CMD_SELECT)
|
|
|
|
{
|
1998-10-02 18:28:04 +02:00
|
|
|
/*
|
2006-09-02 19:06:52 +02:00
|
|
|
* Rules ON SELECT are restricted to view definitions
|
|
|
|
*
|
1998-10-02 18:28:04 +02:00
|
|
|
* So there cannot be INSTEAD NOTHING, ...
|
|
|
|
*/
|
2004-05-31 01:40:41 +02:00
|
|
|
if (list_length(action) == 0)
|
2003-07-25 02:01:09 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
2005-10-15 04:49:52 +02:00
|
|
|
errmsg("INSTEAD NOTHING rules on SELECT are not implemented"),
|
2003-07-25 02:01:09 +02:00
|
|
|
errhint("Use views instead.")));
|
1998-10-02 18:28:04 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
* ... there cannot be multiple actions, ...
|
|
|
|
*/
|
2004-05-31 01:40:41 +02:00
|
|
|
if (list_length(action) > 1)
|
2003-07-25 02:01:09 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
2003-09-29 02:05:25 +02:00
|
|
|
errmsg("multiple actions for rules on SELECT are not implemented")));
|
1999-05-25 18:15:34 +02:00
|
|
|
|
1998-10-02 18:28:04 +02:00
|
|
|
/*
|
|
|
|
* ... the one action must be a SELECT, ...
|
|
|
|
*/
|
2004-05-26 06:41:50 +02:00
|
|
|
query = (Query *) linitial(action);
|
2006-08-12 22:05:56 +02:00
|
|
|
if (!is_instead ||
|
2007-04-28 00:05:49 +02:00
|
|
|
query->commandType != CMD_SELECT ||
|
Restructure SELECT INTO's parsetree representation into CreateTableAsStmt.
Making this operation look like a utility statement seems generally a good
idea, and particularly so in light of the desire to provide command
triggers for utility statements. The original choice of representing it as
SELECT with an IntoClause appendage had metastasized into rather a lot of
places, unfortunately, so that this patch is a great deal more complicated
than one might at first expect.
In particular, keeping EXPLAIN working for SELECT INTO and CREATE TABLE AS
subcommands required restructuring some EXPLAIN-related APIs. Add-on code
that calls ExplainOnePlan or ExplainOneUtility, or uses
ExplainOneQuery_hook, will need adjustment.
Also, the cases PREPARE ... SELECT INTO and CREATE RULE ... SELECT INTO,
which formerly were accepted though undocumented, are no longer accepted.
The PREPARE case can be replaced with use of CREATE TABLE AS EXECUTE.
The CREATE RULE case doesn't seem to have much real-world use (since the
rule would work only once before failing with "table already exists"),
so we'll not bother with that one.
Both SELECT INTO and CREATE TABLE AS still return a command tag of
"SELECT nnnn". There was some discussion of returning "CREATE TABLE nnnn",
but for the moment backwards compatibility wins the day.
Andres Freund and Tom Lane
2012-03-20 02:37:19 +01:00
|
|
|
query->utilityStmt != NULL)
|
2003-07-25 02:01:09 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
2005-10-15 04:49:52 +02:00
|
|
|
errmsg("rules on SELECT must have action INSTEAD SELECT")));
|
2000-09-29 20:21:41 +02:00
|
|
|
|
2011-02-26 00:56:23 +01:00
|
|
|
/*
|
|
|
|
* ... it cannot contain data-modifying WITH ...
|
|
|
|
*/
|
|
|
|
if (query->hasModifyingCTE)
|
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
|
|
errmsg("rules on SELECT must not contain data-modifying statements in WITH")));
|
|
|
|
|
2000-09-29 20:21:41 +02:00
|
|
|
/*
|
|
|
|
* ... there can be no rule qual, ...
|
|
|
|
*/
|
1998-10-02 18:28:04 +02:00
|
|
|
if (event_qual != NULL)
|
2003-07-25 02:01:09 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
2003-09-25 08:58:07 +02:00
|
|
|
errmsg("event qualifications are not implemented for rules on SELECT")));
|
1998-10-02 18:28:04 +02:00
|
|
|
|
|
|
|
/*
|
1999-05-25 18:15:34 +02:00
|
|
|
* ... the targetlist of the SELECT action must exactly match the
|
|
|
|
* event relation, ...
|
1998-10-02 18:28:04 +02:00
|
|
|
*/
|
2006-09-02 19:06:52 +02:00
|
|
|
checkRuleResultList(query->targetList,
|
|
|
|
RelationGetDescr(event_relation),
|
|
|
|
true);
|
2000-09-29 20:21:41 +02:00
|
|
|
|
1998-10-02 18:28:04 +02:00
|
|
|
/*
|
1999-05-25 18:15:34 +02:00
|
|
|
* ... there must not be another ON SELECT rule already ...
|
1998-10-02 18:28:04 +02:00
|
|
|
*/
|
2002-09-02 04:13:02 +02:00
|
|
|
if (!replace && event_relation->rd_rules != NULL)
|
1999-05-25 18:15:34 +02:00
|
|
|
{
|
2006-10-04 02:30:14 +02:00
|
|
|
int i;
|
2006-09-02 19:06:52 +02:00
|
|
|
|
1999-05-25 18:15:34 +02:00
|
|
|
for (i = 0; i < event_relation->rd_rules->numLocks; i++)
|
|
|
|
{
|
|
|
|
RewriteRule *rule;
|
1998-10-02 18:28:04 +02:00
|
|
|
|
|
|
|
rule = event_relation->rd_rules->rules[i];
|
|
|
|
if (rule->event == CMD_SELECT)
|
2003-07-25 02:01:09 +02:00
|
|
|
ereport(ERROR,
|
2005-10-15 04:49:52 +02:00
|
|
|
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
|
|
|
|
errmsg("\"%s\" is already a view",
|
|
|
|
RelationGetRelationName(event_relation))));
|
1998-10-02 18:28:04 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
1998-10-07 00:14:21 +02:00
|
|
|
/*
|
2002-04-20 01:13:54 +02:00
|
|
|
* ... and finally the rule must be named _RETURN.
|
1998-10-07 00:14:21 +02:00
|
|
|
*/
|
2007-03-13 01:33:44 +01:00
|
|
|
if (strcmp(rulename, ViewSelectRuleName) != 0)
|
1999-05-25 18:15:34 +02:00
|
|
|
{
|
2002-04-20 01:13:54 +02:00
|
|
|
/*
|
2005-10-15 04:49:52 +02:00
|
|
|
* In versions before 7.3, the expected name was _RETviewname. For
|
|
|
|
* backwards compatibility with old pg_dump output, accept that
|
|
|
|
* and silently change it to _RETURN. Since this is just a quick
|
|
|
|
* backwards-compatibility hack, limit the number of characters
|
|
|
|
* checked to a few less than NAMEDATALEN; this saves having to
|
|
|
|
* worry about where a multibyte character might have gotten
|
|
|
|
* truncated.
|
2002-04-20 01:13:54 +02:00
|
|
|
*/
|
2007-03-13 01:33:44 +01:00
|
|
|
if (strncmp(rulename, "_RET", 4) != 0 ||
|
2007-08-27 05:36:08 +02:00
|
|
|
strncmp(rulename + 4, RelationGetRelationName(event_relation),
|
2002-04-20 01:13:54 +02:00
|
|
|
NAMEDATALEN - 4 - 4) != 0)
|
2003-07-25 02:01:09 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
2005-10-15 04:49:52 +02:00
|
|
|
errmsg("view rule for \"%s\" must be named \"%s\"",
|
2007-08-27 05:36:08 +02:00
|
|
|
RelationGetRelationName(event_relation),
|
|
|
|
ViewSelectRuleName)));
|
2007-03-13 01:33:44 +01:00
|
|
|
rulename = pstrdup(ViewSelectRuleName);
|
1998-10-07 00:14:21 +02:00
|
|
|
}
|
2000-09-12 22:38:09 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Are we converting a relation to a view?
|
|
|
|
*
|
2005-10-15 04:49:52 +02:00
|
|
|
* If so, check that the relation is empty because the storage for the
|
|
|
|
* relation is going to be deleted. Also insist that the rel not have
|
2009-06-11 16:49:15 +02:00
|
|
|
* any triggers, indexes, or child tables. (Note: these tests are too
|
|
|
|
* strict, because they will reject relations that once had such but
|
|
|
|
* don't anymore. But we don't really care, because this whole
|
2008-11-09 22:24:33 +01:00
|
|
|
* business of converting relations to views is just a kluge to allow
|
|
|
|
* loading ancient pg_dump files.)
|
2000-09-12 22:38:09 +02:00
|
|
|
*/
|
|
|
|
if (event_relation->rd_rel->relkind != RELKIND_VIEW)
|
|
|
|
{
|
2001-03-22 05:01:46 +01:00
|
|
|
HeapScanDesc scanDesc;
|
2000-09-12 22:38:09 +02:00
|
|
|
|
2002-05-21 01:51:44 +02:00
|
|
|
scanDesc = heap_beginscan(event_relation, SnapshotNow, 0, NULL);
|
|
|
|
if (heap_getnext(scanDesc, ForwardScanDirection) != NULL)
|
2003-07-25 02:01:09 +02:00
|
|
|
ereport(ERROR,
|
2005-10-15 04:49:52 +02:00
|
|
|
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
|
|
|
|
errmsg("could not convert table \"%s\" to a view because it is not empty",
|
2007-08-27 05:36:08 +02:00
|
|
|
RelationGetRelationName(event_relation))));
|
2000-09-12 22:38:09 +02:00
|
|
|
heap_endscan(scanDesc);
|
|
|
|
|
2008-11-09 22:24:33 +01:00
|
|
|
if (event_relation->rd_rel->relhastriggers)
|
2003-09-17 19:19:17 +02:00
|
|
|
ereport(ERROR,
|
2005-10-15 04:49:52 +02:00
|
|
|
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
|
|
|
|
errmsg("could not convert table \"%s\" to a view because it has triggers",
|
2007-08-27 05:36:08 +02:00
|
|
|
RelationGetRelationName(event_relation)),
|
Wording cleanup for error messages. Also change can't -> cannot.
Standard English uses "may", "can", and "might" in different ways:
may - permission, "You may borrow my rake."
can - ability, "I can lift that log."
might - possibility, "It might rain today."
Unfortunately, in conversational English, their use is often mixed, as
in, "You may use this variable to do X", when in fact, "can" is a better
choice. Similarly, "It may crash" is better stated, "It might crash".
2007-02-01 20:10:30 +01:00
|
|
|
errhint("In particular, the table cannot be involved in any foreign key relationships.")));
|
2003-09-17 19:19:17 +02:00
|
|
|
|
|
|
|
if (event_relation->rd_rel->relhasindex)
|
|
|
|
ereport(ERROR,
|
2005-10-15 04:49:52 +02:00
|
|
|
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
|
|
|
|
errmsg("could not convert table \"%s\" to a view because it has indexes",
|
2007-08-27 05:36:08 +02:00
|
|
|
RelationGetRelationName(event_relation))));
|
2003-09-17 19:19:17 +02:00
|
|
|
|
|
|
|
if (event_relation->rd_rel->relhassubclass)
|
|
|
|
ereport(ERROR,
|
2005-10-15 04:49:52 +02:00
|
|
|
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
|
|
|
|
errmsg("could not convert table \"%s\" to a view because it has child tables",
|
2007-08-27 05:36:08 +02:00
|
|
|
RelationGetRelationName(event_relation))));
|
2003-09-17 19:19:17 +02:00
|
|
|
|
2000-09-12 22:38:09 +02:00
|
|
|
RelisBecomingView = true;
|
|
|
|
}
|
1998-08-24 03:38:11 +02:00
|
|
|
}
|
2006-09-02 19:06:52 +02:00
|
|
|
else
|
|
|
|
{
|
|
|
|
/*
|
2006-10-04 02:30:14 +02:00
|
|
|
* For non-SELECT rules, a RETURNING list can appear in at most one of
|
|
|
|
* the actions ... and there can't be any RETURNING list at all in a
|
|
|
|
* conditional or non-INSTEAD rule. (Actually, there can be at most
|
|
|
|
* one RETURNING list across all rules on the same event, but it seems
|
|
|
|
* best to enforce that at rule expansion time.) If there is a
|
|
|
|
* RETURNING list, it must match the event relation.
|
2006-09-02 19:06:52 +02:00
|
|
|
*/
|
2006-10-04 02:30:14 +02:00
|
|
|
bool haveReturning = false;
|
2006-09-02 19:06:52 +02:00
|
|
|
|
|
|
|
foreach(l, action)
|
|
|
|
{
|
|
|
|
query = (Query *) lfirst(l);
|
|
|
|
|
|
|
|
if (!query->returningList)
|
|
|
|
continue;
|
|
|
|
if (haveReturning)
|
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
2006-10-04 02:30:14 +02:00
|
|
|
errmsg("cannot have multiple RETURNING lists in a rule")));
|
2006-09-02 19:06:52 +02:00
|
|
|
haveReturning = true;
|
|
|
|
if (event_qual != NULL)
|
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
|
|
errmsg("RETURNING lists are not supported in conditional rules")));
|
|
|
|
if (!is_instead)
|
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
|
|
errmsg("RETURNING lists are not supported in non-INSTEAD rules")));
|
|
|
|
checkRuleResultList(query->returningList,
|
|
|
|
RelationGetDescr(event_relation),
|
|
|
|
false);
|
|
|
|
}
|
|
|
|
}
|
1998-09-01 06:40:42 +02:00
|
|
|
|
1998-08-24 03:38:11 +02:00
|
|
|
/*
|
2000-09-29 20:21:41 +02:00
|
|
|
* This rule is allowed - prepare to install it.
|
1998-08-24 03:38:11 +02:00
|
|
|
*/
|
2002-03-21 17:02:16 +01:00
|
|
|
event_attno = -1;
|
1997-09-07 07:04:48 +02:00
|
|
|
|
2000-06-30 09:04:23 +02:00
|
|
|
/* discard rule if it's null action and not INSTEAD; it's a no-op */
|
2000-09-12 22:38:09 +02:00
|
|
|
if (action != NIL || is_instead)
|
1997-09-07 07:04:48 +02:00
|
|
|
{
|
2011-04-11 21:28:45 +02:00
|
|
|
InsertRule(rulename,
|
2011-06-09 20:32:50 +02:00
|
|
|
event_type,
|
|
|
|
event_relid,
|
|
|
|
event_attno,
|
|
|
|
is_instead,
|
|
|
|
event_qual,
|
|
|
|
action,
|
|
|
|
replace);
|
1997-09-07 07:04:48 +02:00
|
|
|
|
2000-06-30 09:04:23 +02:00
|
|
|
/*
|
2001-03-22 05:01:46 +01:00
|
|
|
* Set pg_class 'relhasrules' field TRUE for event relation. If
|
|
|
|
* appropriate, also modify the 'relkind' field to show that the
|
|
|
|
* relation is now a view.
|
2000-06-30 09:04:23 +02:00
|
|
|
*
|
2005-11-22 19:17:34 +01:00
|
|
|
* Important side effect: an SI notice is broadcast to force all
|
|
|
|
* backends (including me!) to update relcache entries with the new
|
|
|
|
* rule.
|
2000-09-12 06:49:17 +02:00
|
|
|
*/
|
2007-08-27 05:36:08 +02:00
|
|
|
SetRelationRuleStatus(event_relid, true, RelisBecomingView);
|
1997-09-07 07:04:48 +02:00
|
|
|
}
|
2000-06-30 09:04:23 +02:00
|
|
|
|
2000-09-12 06:49:17 +02:00
|
|
|
/*
|
2005-10-15 04:49:52 +02:00
|
|
|
* IF the relation is becoming a view, delete the storage files associated
|
|
|
|
* with it. NB: we had better have AccessExclusiveLock to do this ...
|
2003-09-17 19:19:17 +02:00
|
|
|
*
|
|
|
|
* XXX what about getting rid of its TOAST table? For now, we don't.
|
2000-09-12 06:49:17 +02:00
|
|
|
*/
|
|
|
|
if (RelisBecomingView)
|
2008-11-19 11:34:52 +01:00
|
|
|
RelationDropStorage(event_relation);
|
2000-09-12 06:49:17 +02:00
|
|
|
|
2000-06-30 09:04:23 +02:00
|
|
|
/* Close rel, but keep lock till commit... */
|
|
|
|
heap_close(event_relation, NoLock);
|
1997-09-07 07:04:48 +02:00
|
|
|
}
|
2000-09-29 20:21:41 +02:00
|
|
|
|
2006-09-02 19:06:52 +02:00
|
|
|
/*
|
|
|
|
* checkRuleResultList
|
|
|
|
* Verify that targetList produces output compatible with a tupledesc
|
|
|
|
*
|
|
|
|
* The targetList might be either a SELECT targetlist, or a RETURNING list;
|
|
|
|
* isSelect tells which. (This is mostly used for choosing error messages,
|
|
|
|
* but also we don't enforce column name matching for RETURNING.)
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
checkRuleResultList(List *targetList, TupleDesc resultDesc, bool isSelect)
|
|
|
|
{
|
|
|
|
ListCell *tllist;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
i = 0;
|
|
|
|
foreach(tllist, targetList)
|
|
|
|
{
|
|
|
|
TargetEntry *tle = (TargetEntry *) lfirst(tllist);
|
|
|
|
int32 tletypmod;
|
|
|
|
Form_pg_attribute attr;
|
|
|
|
char *attname;
|
|
|
|
|
|
|
|
/* resjunk entries may be ignored */
|
|
|
|
if (tle->resjunk)
|
|
|
|
continue;
|
|
|
|
i++;
|
|
|
|
if (i > resultDesc->natts)
|
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
|
|
|
isSelect ?
|
2006-10-04 02:30:14 +02:00
|
|
|
errmsg("SELECT rule's target list has too many entries") :
|
2006-09-02 19:06:52 +02:00
|
|
|
errmsg("RETURNING list has too many entries")));
|
|
|
|
|
|
|
|
attr = resultDesc->attrs[i - 1];
|
|
|
|
attname = NameStr(attr->attname);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Disallow dropped columns in the relation. This won't happen in the
|
|
|
|
* cases we actually care about (namely creating a view via CREATE
|
|
|
|
* TABLE then CREATE RULE, or adding a RETURNING rule to a view).
|
|
|
|
* Trying to cope with it is much more trouble than it's worth,
|
|
|
|
* because we'd have to modify the rule to insert dummy NULLs at the
|
|
|
|
* right positions.
|
|
|
|
*/
|
|
|
|
if (attr->attisdropped)
|
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
|
|
errmsg("cannot convert relation containing dropped columns to view")));
|
|
|
|
|
|
|
|
if (isSelect && strcmp(tle->resname, attname) != 0)
|
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
|
|
|
errmsg("SELECT rule's target entry %d has different column name from \"%s\"", i, attname)));
|
|
|
|
|
|
|
|
if (attr->atttypid != exprType((Node *) tle->expr))
|
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
|
|
|
isSelect ?
|
|
|
|
errmsg("SELECT rule's target entry %d has different type from column \"%s\"",
|
|
|
|
i, attname) :
|
|
|
|
errmsg("RETURNING list's entry %d has different type from column \"%s\"",
|
|
|
|
i, attname)));
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Allow typmods to be different only if one of them is -1, ie,
|
2006-10-04 02:30:14 +02:00
|
|
|
* "unspecified". This is necessary for cases like "numeric", where
|
|
|
|
* the table will have a filled-in default length but the select
|
|
|
|
* rule's expression will probably have typmod = -1.
|
2006-09-02 19:06:52 +02:00
|
|
|
*/
|
|
|
|
tletypmod = exprTypmod((Node *) tle->expr);
|
|
|
|
if (attr->atttypmod != tletypmod &&
|
|
|
|
attr->atttypmod != -1 && tletypmod != -1)
|
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
|
|
|
isSelect ?
|
|
|
|
errmsg("SELECT rule's target entry %d has different size from column \"%s\"",
|
|
|
|
i, attname) :
|
|
|
|
errmsg("RETURNING list's entry %d has different size from column \"%s\"",
|
|
|
|
i, attname)));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (i != resultDesc->natts)
|
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
|
|
|
isSelect ?
|
|
|
|
errmsg("SELECT rule's target list has too few entries") :
|
|
|
|
errmsg("RETURNING list has too few entries")));
|
|
|
|
}
|
|
|
|
|
2000-09-29 20:21:41 +02:00
|
|
|
/*
|
2006-09-05 23:08:36 +02:00
|
|
|
* setRuleCheckAsUser
|
|
|
|
* Recursively scan a query or expression tree and set the checkAsUser
|
|
|
|
* field to the given userid in all rtable entries.
|
2001-05-03 23:16:48 +02:00
|
|
|
*
|
2009-11-06 00:24:27 +01:00
|
|
|
* Note: for a view (ON SELECT rule), the checkAsUser field of the OLD
|
2001-05-03 23:16:48 +02:00
|
|
|
* RTE entry will be overridden when the view rule is expanded, and the
|
2009-11-06 00:24:27 +01:00
|
|
|
* checkAsUser field of the NEW entry is irrelevant because that entry's
|
2004-08-29 07:07:03 +02:00
|
|
|
* requiredPerms bits will always be zero. However, for other types of rules
|
2004-01-15 00:01:55 +01:00
|
|
|
* it's important to set these fields to match the rule owner. So we just set
|
2001-05-03 23:16:48 +02:00
|
|
|
* them always.
|
2000-09-29 20:21:41 +02:00
|
|
|
*/
|
2006-09-05 23:08:36 +02:00
|
|
|
void
|
|
|
|
setRuleCheckAsUser(Node *node, Oid userid)
|
|
|
|
{
|
|
|
|
(void) setRuleCheckAsUser_walker(node, &userid);
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool
|
|
|
|
setRuleCheckAsUser_walker(Node *node, Oid *context)
|
|
|
|
{
|
|
|
|
if (node == NULL)
|
|
|
|
return false;
|
|
|
|
if (IsA(node, Query))
|
|
|
|
{
|
|
|
|
setRuleCheckAsUser_Query((Query *) node, *context);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return expression_tree_walker(node, setRuleCheckAsUser_walker,
|
|
|
|
(void *) context);
|
|
|
|
}
|
|
|
|
|
2000-09-29 20:21:41 +02:00
|
|
|
static void
|
2005-06-28 07:09:14 +02:00
|
|
|
setRuleCheckAsUser_Query(Query *qry, Oid userid)
|
2000-09-29 20:21:41 +02:00
|
|
|
{
|
2004-05-26 06:41:50 +02:00
|
|
|
ListCell *l;
|
2000-09-29 20:21:41 +02:00
|
|
|
|
2001-05-03 23:16:48 +02:00
|
|
|
/* Set all the RTEs in this query node */
|
2000-09-29 20:21:41 +02:00
|
|
|
foreach(l, qry->rtable)
|
|
|
|
{
|
|
|
|
RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
|
|
|
|
|
2002-05-12 22:10:05 +02:00
|
|
|
if (rte->rtekind == RTE_SUBQUERY)
|
2000-09-29 20:21:41 +02:00
|
|
|
{
|
2001-05-03 23:16:48 +02:00
|
|
|
/* Recurse into subquery in FROM */
|
2004-05-19 00:49:51 +02:00
|
|
|
setRuleCheckAsUser_Query(rte->subquery, userid);
|
2000-09-29 20:21:41 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
rte->checkAsUser = userid;
|
|
|
|
}
|
|
|
|
|
2008-10-04 23:56:55 +02:00
|
|
|
/* Recurse into subquery-in-WITH */
|
|
|
|
foreach(l, qry->cteList)
|
|
|
|
{
|
|
|
|
CommonTableExpr *cte = (CommonTableExpr *) lfirst(l);
|
|
|
|
|
|
|
|
setRuleCheckAsUser_Query((Query *) cte->ctequery, userid);
|
|
|
|
}
|
|
|
|
|
2000-09-29 20:21:41 +02:00
|
|
|
/* If there are sublinks, search for them and process their RTEs */
|
|
|
|
if (qry->hasSubLinks)
|
2000-10-05 21:11:39 +02:00
|
|
|
query_tree_walker(qry, setRuleCheckAsUser_walker, (void *) &userid,
|
2008-10-04 23:56:55 +02:00
|
|
|
QTW_IGNORE_RC_SUBQUERIES);
|
2000-09-29 20:21:41 +02:00
|
|
|
}
|
|
|
|
|
2001-08-12 23:35:19 +02:00
|
|
|
|
2007-03-20 00:38:32 +01:00
|
|
|
/*
|
|
|
|
* Change the firing semantics of an existing rule.
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
EnableDisableRule(Relation rel, const char *rulename,
|
|
|
|
char fires_when)
|
|
|
|
{
|
|
|
|
Relation pg_rewrite_desc;
|
|
|
|
Oid owningRel = RelationGetRelid(rel);
|
|
|
|
Oid eventRelationOid;
|
|
|
|
HeapTuple ruletup;
|
|
|
|
bool changed = false;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Find the rule tuple to change.
|
|
|
|
*/
|
|
|
|
pg_rewrite_desc = heap_open(RewriteRelationId, RowExclusiveLock);
|
2010-02-14 19:42:19 +01:00
|
|
|
ruletup = SearchSysCacheCopy2(RULERELNAME,
|
|
|
|
ObjectIdGetDatum(owningRel),
|
|
|
|
PointerGetDatum(rulename));
|
2007-03-20 00:38:32 +01:00
|
|
|
if (!HeapTupleIsValid(ruletup))
|
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_UNDEFINED_OBJECT),
|
|
|
|
errmsg("rule \"%s\" for relation \"%s\" does not exist",
|
|
|
|
rulename, get_rel_name(owningRel))));
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Verify that the user has appropriate permissions.
|
|
|
|
*/
|
|
|
|
eventRelationOid = ((Form_pg_rewrite) GETSTRUCT(ruletup))->ev_class;
|
|
|
|
Assert(eventRelationOid == owningRel);
|
|
|
|
if (!pg_class_ownercheck(eventRelationOid, GetUserId()))
|
|
|
|
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
|
2007-11-15 22:14:46 +01:00
|
|
|
get_rel_name(eventRelationOid));
|
|
|
|
|
2007-03-20 00:38:32 +01:00
|
|
|
/*
|
|
|
|
* Change ev_enabled if it is different from the desired new state.
|
|
|
|
*/
|
|
|
|
if (DatumGetChar(((Form_pg_rewrite) GETSTRUCT(ruletup))->ev_enabled) !=
|
2007-11-15 22:14:46 +01:00
|
|
|
fires_when)
|
|
|
|
{
|
2007-03-20 00:38:32 +01:00
|
|
|
((Form_pg_rewrite) GETSTRUCT(ruletup))->ev_enabled =
|
2007-11-15 22:14:46 +01:00
|
|
|
CharGetDatum(fires_when);
|
2007-03-20 00:38:32 +01:00
|
|
|
simple_heap_update(pg_rewrite_desc, &ruletup->t_self, ruletup);
|
|
|
|
|
|
|
|
/* keep system catalog indexes current */
|
|
|
|
CatalogUpdateIndexes(pg_rewrite_desc, ruletup);
|
|
|
|
|
|
|
|
changed = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
heap_freetuple(ruletup);
|
|
|
|
heap_close(pg_rewrite_desc, RowExclusiveLock);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If we changed anything, broadcast a SI inval message to force each
|
|
|
|
* backend (including our own!) to rebuild relation's relcache entry.
|
|
|
|
* Otherwise they will fail to apply the change promptly.
|
|
|
|
*/
|
|
|
|
if (changed)
|
|
|
|
CacheInvalidateRelcache(rel);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2001-08-12 23:35:19 +02:00
|
|
|
/*
|
|
|
|
* Rename an existing rewrite rule.
|
|
|
|
*
|
2007-08-27 05:36:08 +02:00
|
|
|
* This is unused code at the moment. Note that it lacks a permissions check.
|
2001-08-12 23:35:19 +02:00
|
|
|
*/
|
2006-07-18 19:42:01 +02:00
|
|
|
#ifdef NOT_USED
|
2001-08-12 23:35:19 +02:00
|
|
|
void
|
2002-04-18 22:01:11 +02:00
|
|
|
RenameRewriteRule(Oid owningRel, const char *oldName,
|
|
|
|
const char *newName)
|
2001-08-12 23:35:19 +02:00
|
|
|
{
|
|
|
|
Relation pg_rewrite_desc;
|
|
|
|
HeapTuple ruletup;
|
|
|
|
|
2005-04-14 22:03:27 +02:00
|
|
|
pg_rewrite_desc = heap_open(RewriteRelationId, RowExclusiveLock);
|
2001-08-12 23:35:19 +02:00
|
|
|
|
2010-02-14 19:42:19 +01:00
|
|
|
ruletup = SearchSysCacheCopy2(RULERELNAME,
|
|
|
|
ObjectIdGetDatum(owningRel),
|
|
|
|
PointerGetDatum(oldName));
|
2001-08-12 23:35:19 +02:00
|
|
|
if (!HeapTupleIsValid(ruletup))
|
2003-07-25 02:01:09 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_UNDEFINED_OBJECT),
|
|
|
|
errmsg("rule \"%s\" for relation \"%s\" does not exist",
|
|
|
|
oldName, get_rel_name(owningRel))));
|
2001-08-12 23:35:19 +02:00
|
|
|
|
|
|
|
/* should not already exist */
|
2002-04-18 22:01:11 +02:00
|
|
|
if (IsDefinedRewriteRule(owningRel, newName))
|
2003-07-25 02:01:09 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_DUPLICATE_OBJECT),
|
|
|
|
errmsg("rule \"%s\" for relation \"%s\" already exists",
|
|
|
|
newName, get_rel_name(owningRel))));
|
2001-08-12 23:35:19 +02:00
|
|
|
|
2002-04-18 22:01:11 +02:00
|
|
|
namestrcpy(&(((Form_pg_rewrite) GETSTRUCT(ruletup))->rulename), newName);
|
2001-08-12 23:35:19 +02:00
|
|
|
|
|
|
|
simple_heap_update(pg_rewrite_desc, &ruletup->t_self, ruletup);
|
|
|
|
|
2002-08-05 05:29:17 +02:00
|
|
|
/* keep system catalog indexes current */
|
|
|
|
CatalogUpdateIndexes(pg_rewrite_desc, ruletup);
|
2001-08-12 23:35:19 +02:00
|
|
|
|
|
|
|
heap_freetuple(ruletup);
|
|
|
|
heap_close(pg_rewrite_desc, RowExclusiveLock);
|
|
|
|
}
|
2006-10-04 02:30:14 +02:00
|
|
|
|
2006-07-18 19:42:01 +02:00
|
|
|
#endif
|