Commit c61e26ee authored by Tom Lane's avatar Tom Lane

Add support for ALTER RULE ... RENAME TO.

Ali Dar, reviewed by Dean Rasheed.
parent f806c191
......@@ -25,6 +25,7 @@ Complete list of usable sgml source files in this directory.
<!ENTITY alterOperatorClass SYSTEM "alter_opclass.sgml">
<!ENTITY alterOperatorFamily SYSTEM "alter_opfamily.sgml">
<!ENTITY alterRole SYSTEM "alter_role.sgml">
<!ENTITY alterRule SYSTEM "alter_rule.sgml">
<!ENTITY alterSchema SYSTEM "alter_schema.sgml">
<!ENTITY alterServer SYSTEM "alter_server.sgml">
<!ENTITY alterSequence SYSTEM "alter_sequence.sgml">
......
<!--
doc/src/sgml/ref/alter_rule.sgml
PostgreSQL documentation
-->
<refentry id="SQL-ALTERRULE">
<refmeta>
<refentrytitle>ALTER RULE</refentrytitle>
<manvolnum>7</manvolnum>
<refmiscinfo>SQL - Language Statements</refmiscinfo>
</refmeta>
<refnamediv>
<refname>ALTER RULE</refname>
<refpurpose>change the definition of a rule</refpurpose>
</refnamediv>
<indexterm zone="sql-alterrule">
<primary>ALTER RULE</primary>
</indexterm>
<refsynopsisdiv>
<synopsis>
ALTER RULE <replaceable class="PARAMETER">name</replaceable> ON <replaceable class="PARAMETER">table_name</replaceable> RENAME TO <replaceable class="PARAMETER">new_name</replaceable>
</synopsis>
</refsynopsisdiv>
<refsect1>
<title>Description</title>
<para>
<command>ALTER RULE</command> changes properties of an existing
rule. Currently, the only available action is to change the rule's name.
</para>
<para>
To use <command>ALTER RULE</command>, you must own the table or view that
the rule applies to.
</para>
</refsect1>
<refsect1>
<title>Parameters</title>
<variablelist>
<varlistentry>
<term><replaceable class="PARAMETER">name</replaceable></term>
<listitem>
<para>
The name of an existing rule to alter.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><replaceable class="PARAMETER">table_name</replaceable></term>
<listitem>
<para>
The name (optionally schema-qualified) of the table or view that the
rule applies to.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><replaceable class="PARAMETER">new_name</replaceable></term>
<listitem>
<para>
The new name for the rule.
</para>
</listitem>
</varlistentry>
</variablelist>
</refsect1>
<refsect1>
<title>Examples</title>
<para>
To rename an existing rule:
<programlisting>
ALTER RULE notify_all ON emp RENAME TO notify_me;
</programlisting></para>
</refsect1>
<refsect1>
<title>Compatibility</title>
<para>
<command>ALTER RULE</command> is a
<productname>PostgreSQL</productname> language extension, as is the
entire query rewrite system.
</para>
</refsect1>
<refsect1>
<title>See Also</title>
<simplelist type="inline">
<member><xref linkend="sql-createrule"></member>
<member><xref linkend="sql-droprule"></member>
</simplelist>
</refsect1>
</refentry>
......@@ -284,4 +284,14 @@ UPDATE mytable SET name = 'foo' WHERE id = 42;
entire query rewrite system.
</para>
</refsect1>
<refsect1>
<title>See Also</title>
<simplelist type="inline">
<member><xref linkend="sql-alterrule"></member>
<member><xref linkend="sql-droprule"></member>
</simplelist>
</refsect1>
</refentry>
......@@ -103,7 +103,9 @@ DROP RULE newrule ON mytable;
<title>Compatibility</title>
<para>
There is no <command>DROP RULE</command> statement in the SQL standard.
<command>DROP RULE</command> is a
<productname>PostgreSQL</productname> language extension, as is the
entire query rewrite system.
</para>
</refsect1>
......@@ -112,6 +114,7 @@ DROP RULE newrule ON mytable;
<simplelist type="inline">
<member><xref linkend="sql-createrule"></member>
<member><xref linkend="sql-alterrule"></member>
</simplelist>
</refsect1>
......
......@@ -53,6 +53,7 @@
&alterOperatorClass;
&alterOperatorFamily;
&alterRole;
&alterRule;
&alterSchema;
&alterSequence;
&alterServer;
......
......@@ -51,6 +51,7 @@
#include "commands/user.h"
#include "parser/parse_func.h"
#include "miscadmin.h"
#include "rewrite/rewriteDefine.h"
#include "tcop/utility.h"
#include "utils/builtins.h"
#include "utils/fmgroids.h"
......@@ -324,6 +325,10 @@ ExecRenameStmt(RenameStmt *stmt)
case OBJECT_ATTRIBUTE:
return renameatt(stmt);
case OBJECT_RULE:
return RenameRewriteRule(stmt->relation, stmt->subname,
stmt->newname);
case OBJECT_TRIGGER:
return renametrig(stmt);
......
......@@ -7003,6 +7003,16 @@ RenameStmt: ALTER AGGREGATE func_name aggr_args RENAME TO name
n->missing_ok = true;
$$ = (Node *)n;
}
| ALTER RULE name ON qualified_name RENAME TO name
{
RenameStmt *n = makeNode(RenameStmt);
n->renameType = OBJECT_RULE;
n->relation = $5;
n->subname = $3;
n->newname = $8;
n->missing_ok = false;
$$ = (Node *)n;
}
| ALTER TRIGGER name ON qualified_name RENAME TO name
{
RenameStmt *n = makeNode(RenameStmt);
......
......@@ -751,38 +751,99 @@ EnableDisableRule(Relation rel, const char *rulename,
}
/*
* Perform permissions and integrity checks before acquiring a relation lock.
*/
static void
RangeVarCallbackForRenameRule(const RangeVar *rv, Oid relid, Oid oldrelid,
void *arg)
{
HeapTuple tuple;
Form_pg_class form;
tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
if (!HeapTupleIsValid(tuple))
return; /* concurrently dropped */
form = (Form_pg_class) GETSTRUCT(tuple);
/* only tables and views can have rules */
if (form->relkind != RELKIND_RELATION && form->relkind != RELKIND_VIEW)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table or view", rv->relname)));
if (!allowSystemTableMods && IsSystemClass(form))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied: \"%s\" is a system catalog",
rv->relname)));
/* you must own the table to rename one of its rules */
if (!pg_class_ownercheck(relid, GetUserId()))
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS, rv->relname);
ReleaseSysCache(tuple);
}
/*
* Rename an existing rewrite rule.
*
* This is unused code at the moment. Note that it lacks a permissions check.
*/
#ifdef NOT_USED
void
RenameRewriteRule(Oid owningRel, const char *oldName,
Oid
RenameRewriteRule(RangeVar *relation, const char *oldName,
const char *newName)
{
Oid relid;
Relation targetrel;
Relation pg_rewrite_desc;
HeapTuple ruletup;
Form_pg_rewrite ruleform;
Oid ruleOid;
/*
* Look up name, check permissions, and acquire lock (which we will NOT
* release until end of transaction).
*/
relid = RangeVarGetRelidExtended(relation, AccessExclusiveLock,
false, false,
RangeVarCallbackForRenameRule,
NULL);
/* Have lock already, so just need to build relcache entry. */
targetrel = relation_open(relid, NoLock);
/* Prepare to modify pg_rewrite */
pg_rewrite_desc = heap_open(RewriteRelationId, RowExclusiveLock);
/* Fetch the rule's entry (it had better exist) */
ruletup = SearchSysCacheCopy2(RULERELNAME,
ObjectIdGetDatum(owningRel),
ObjectIdGetDatum(relid),
PointerGetDatum(oldName));
if (!HeapTupleIsValid(ruletup))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("rule \"%s\" for relation \"%s\" does not exist",
oldName, get_rel_name(owningRel))));
oldName, RelationGetRelationName(targetrel))));
ruleform = (Form_pg_rewrite) GETSTRUCT(ruletup);
ruleOid = HeapTupleGetOid(ruletup);
/* should not already exist */
if (IsDefinedRewriteRule(owningRel, newName))
/* rule with the new name should not already exist */
if (IsDefinedRewriteRule(relid, newName))
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("rule \"%s\" for relation \"%s\" already exists",
newName, get_rel_name(owningRel))));
newName, RelationGetRelationName(targetrel))));
/*
* We disallow renaming ON SELECT rules, because they should always be
* named "_RETURN".
*/
if (ruleform->ev_type == CMD_SELECT + '0')
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("renaming an ON SELECT rule is not allowed")));
namestrcpy(&(((Form_pg_rewrite) GETSTRUCT(ruletup))->rulename), newName);
/* OK, do the update */
namestrcpy(&(ruleform->rulename), newName);
simple_heap_update(pg_rewrite_desc, &ruletup->t_self, ruletup);
......@@ -791,6 +852,18 @@ RenameRewriteRule(Oid owningRel, const char *oldName,
heap_freetuple(ruletup);
heap_close(pg_rewrite_desc, RowExclusiveLock);
}
#endif
/*
* Invalidate relation's relcache entry so that other backends (and this
* one too!) are sent SI message to make them rebuild relcache entries.
* (Ideally this should happen automatically...)
*/
CacheInvalidateRelcache(targetrel);
/*
* Close rel, but keep exclusive lock!
*/
relation_close(targetrel, NoLock);
return ruleOid;
}
......@@ -624,6 +624,15 @@ static const SchemaQuery Query_for_list_of_views = {
" (SELECT conrelid FROM pg_catalog.pg_constraint "\
" WHERE pg_catalog.quote_ident(conname)='%s')"
/* the silly-looking length condition is just to eat up the current word */
#define Query_for_list_of_tables_for_rule \
"SELECT pg_catalog.quote_ident(relname) "\
" FROM pg_catalog.pg_class"\
" WHERE (%d = pg_catalog.length('%s'))"\
" AND oid IN "\
" (SELECT ev_class FROM pg_catalog.pg_rewrite "\
" WHERE pg_catalog.quote_ident(rulename)='%s')"
/* the silly-looking length condition is just to eat up the current word */
#define Query_for_list_of_tables_for_trigger \
"SELECT pg_catalog.quote_ident(relname) "\
......@@ -925,7 +934,7 @@ psql_completion(char *text, int start, int end)
{"AGGREGATE", "COLLATION", "CONVERSION", "DATABASE", "DEFAULT PRIVILEGES", "DOMAIN",
"EXTENSION", "FOREIGN DATA WRAPPER", "FOREIGN TABLE", "FUNCTION",
"GROUP", "INDEX", "LANGUAGE", "LARGE OBJECT", "OPERATOR",
"ROLE", "SCHEMA", "SERVER", "SEQUENCE", "TABLE",
"ROLE", "RULE", "SCHEMA", "SERVER", "SEQUENCE", "TABLE",
"TABLESPACE", "TEXT SEARCH", "TRIGGER", "TYPE",
"USER", "USER MAPPING FOR", "VIEW", NULL};
......@@ -1259,6 +1268,26 @@ psql_completion(char *text, int start, int end)
COMPLETE_WITH_LIST(list_ALTERVIEW);
}
/* ALTER RULE <name>, add ON */
else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
pg_strcasecmp(prev2_wd, "RULE") == 0)
COMPLETE_WITH_CONST("ON");
/* If we have ALTER RULE <name> ON, then add the correct tablename */
else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
pg_strcasecmp(prev3_wd, "RULE") == 0 &&
pg_strcasecmp(prev_wd, "ON") == 0)
{
completion_info_charp = prev2_wd;
COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_rule);
}
/* ALTER RULE <name> ON <name> */
else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
pg_strcasecmp(prev4_wd, "RULE") == 0)
COMPLETE_WITH_CONST("RENAME TO");
/* ALTER TRIGGER <name>, add ON */
else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
pg_strcasecmp(prev2_wd, "TRIGGER") == 0)
......
......@@ -32,7 +32,7 @@ extern Oid DefineQueryRewrite(char *rulename,
bool replace,
List *action);
extern void RenameRewriteRule(Oid owningRel, const char *oldName,
extern Oid RenameRewriteRule(RangeVar *relation, const char *oldName,
const char *newName);
extern void setRuleCheckAsUser(Node *node, Oid userid);
......
......@@ -2559,3 +2559,44 @@ Rules:
ON UPDATE TO rules_src DO VALUES (old.f1,old.f2,'old'::text), (new.f1,new.f2,'new'::text)
Has OIDs: no
--
-- check alter rename rule
--
CREATE TABLE rule_t1 (a INT);
CREATE VIEW rule_v1 AS SELECT * FROM rule_t1;
CREATE RULE InsertRule AS
ON INSERT TO rule_v1
DO INSTEAD
INSERT INTO rule_t1 VALUES(new.a);
ALTER RULE InsertRule ON rule_v1 RENAME to NewInsertRule;
INSERT INTO rule_v1 VALUES(1);
SELECT * FROM rule_v1;
a
---
1
(1 row)
\d+ rule_v1
View "public.rule_v1"
Column | Type | Modifiers | Storage | Description
--------+---------+-----------+---------+-------------
a | integer | | plain |
View definition:
SELECT rule_t1.a
FROM rule_t1;
Rules:
newinsertrule AS
ON INSERT TO rule_v1 DO INSTEAD INSERT INTO rule_t1 (a)
VALUES (new.a)
--
-- error conditions for alter rename rule
--
ALTER RULE InsertRule ON rule_v1 RENAME TO NewInsertRule; -- doesn't exist
ERROR: rule "insertrule" for relation "rule_v1" does not exist
ALTER RULE NewInsertRule ON rule_v1 RENAME TO "_RETURN"; -- already exists
ERROR: rule "_RETURN" for relation "rule_v1" already exists
ALTER RULE "_RETURN" ON rule_v1 RENAME TO abc; -- ON SELECT rule cannot be renamed
ERROR: renaming an ON SELECT rule is not allowed
DROP VIEW rule_v1;
DROP TABLE rule_t1;
......@@ -968,3 +968,31 @@ update rules_src set f2 = f2 / 10;
select * from rules_src;
select * from rules_log;
\d+ rules_src
--
-- check alter rename rule
--
CREATE TABLE rule_t1 (a INT);
CREATE VIEW rule_v1 AS SELECT * FROM rule_t1;
CREATE RULE InsertRule AS
ON INSERT TO rule_v1
DO INSTEAD
INSERT INTO rule_t1 VALUES(new.a);
ALTER RULE InsertRule ON rule_v1 RENAME to NewInsertRule;
INSERT INTO rule_v1 VALUES(1);
SELECT * FROM rule_v1;
\d+ rule_v1
--
-- error conditions for alter rename rule
--
ALTER RULE InsertRule ON rule_v1 RENAME TO NewInsertRule; -- doesn't exist
ALTER RULE NewInsertRule ON rule_v1 RENAME TO "_RETURN"; -- already exists
ALTER RULE "_RETURN" ON rule_v1 RENAME TO abc; -- ON SELECT rule cannot be renamed
DROP VIEW rule_v1;
DROP TABLE rule_t1;
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment