Ali Dar, reviewed by Dean Rasheed.
<!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">
--- /dev/null
+<!--
+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>
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>
<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>
<simplelist type="inline">
<member><xref linkend="sql-createrule"></member>
+ <member><xref linkend="sql-alterrule"></member>
</simplelist>
</refsect1>
&alterOperatorClass;
&alterOperatorFamily;
&alterRole;
+ &alterRule;
&alterSchema;
&alterSequence;
&alterServer;
#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"
case OBJECT_ATTRIBUTE:
return renameatt(stmt);
+ case OBJECT_RULE:
+ return RenameRewriteRule(stmt->relation, stmt->subname,
+ stmt->newname);
+
case OBJECT_TRIGGER:
return renametrig(stmt);
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);
}
+/*
+ * 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);
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;
+}
" (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) "\
{"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};
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)
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);
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;
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;