Add support for ALTER RULE ... RENAME TO.
authorTom Lane <tgl@sss.pgh.pa.us>
Sat, 9 Feb 2013 04:58:40 +0000 (23:58 -0500)
committerTom Lane <tgl@sss.pgh.pa.us>
Sat, 9 Feb 2013 04:58:40 +0000 (23:58 -0500)
Ali Dar, reviewed by Dean Rasheed.

12 files changed:
doc/src/sgml/ref/allfiles.sgml
doc/src/sgml/ref/alter_rule.sgml [new file with mode: 0644]
doc/src/sgml/ref/create_rule.sgml
doc/src/sgml/ref/drop_rule.sgml
doc/src/sgml/reference.sgml
src/backend/commands/alter.c
src/backend/parser/gram.y
src/backend/rewrite/rewriteDefine.c
src/bin/psql/tab-complete.c
src/include/rewrite/rewriteDefine.h
src/test/regress/expected/rules.out
src/test/regress/sql/rules.sql

index b3fc57d942f8275f4c1a96c7f2abf9e5262bc92b..c61c62f2286e5158cca452f981b5eece677104d9 100644 (file)
@@ -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">
diff --git a/doc/src/sgml/ref/alter_rule.sgml b/doc/src/sgml/ref/alter_rule.sgml
new file mode 100644 (file)
index 0000000..0a18660
--- /dev/null
@@ -0,0 +1,105 @@
+<!--
+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>
index 381ea3ed6b4d259f7fedeeb7b77507193dd6cd6c..ab2f1ba55c7924fff6bfe378a2d71e7052fe260a 100644 (file)
@@ -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>
index ca52347209bda9f8d8f5d46d5d066cfcdb0595ea..c845872566c93baa93dc30f2d5f664697e9bb1d8 100644 (file)
@@ -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>
 
index fe90227b4f3a07be32be5a96a9b969f4f0a27732..5b0c7745e39a67715b1d4433f38069d86318868e 100644 (file)
@@ -53,6 +53,7 @@
    &alterOperatorClass;
    &alterOperatorFamily;
    &alterRole;
+   &alterRule;
    &alterSchema;
    &alterSequence;
    &alterServer;
index c2d4bb3ed4d58b0ae4d1ed4f81cb7a3eb4205162..269d19cea6ce7d11b13d094cd8cf769a592b0450 100644 (file)
@@ -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);
 
index 342b7964242036116c94aeb72515041468e8ea42..fee05311c5c5a8ecfaacc3f69799708dd5c5aa2e 100644 (file)
@@ -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);
index ac724c3964a51665c27683e739894def521295fd..b37f36b3e6716be16ea47cbb90e63a89102abafd 100644 (file)
@@ -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;
+}
index 09396ca59001e0d76312e481256b065b537bd997..edfba677667528acbcc6919919ea3e518dbba270 100644 (file)
@@ -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)
index dda267bf968a35f9be1c774477d564c0b76e5eac..36030c4a9f25e4683efa0d83969fe4ba19dda216 100644 (file)
@@ -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);
index 711ae53a617ac924fa4ed01ccc9ed49857e81f98..869ca8c9a9af36ee1dc79f3540d3204ac16d737c 100644 (file)
@@ -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;
index 458c2f026c0fbce861c7c4dc5898b2749b71af55..b8d67ae9f3d473ccc004aebdaa37c9e9dfd4fc46 100644 (file)
@@ -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;