diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml index b3fc57d942f8275f4c1a96c7f2abf9e5262bc92b..c61c62f2286e5158cca452f981b5eece677104d9 100644 --- a/doc/src/sgml/ref/allfiles.sgml +++ b/doc/src/sgml/ref/allfiles.sgml @@ -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 index 0000000000000000000000000000000000000000..0a186605a0226f1ed977fce1d03a6eb9b88575dd --- /dev/null +++ b/doc/src/sgml/ref/alter_rule.sgml @@ -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> diff --git a/doc/src/sgml/ref/create_rule.sgml b/doc/src/sgml/ref/create_rule.sgml index 381ea3ed6b4d259f7fedeeb7b77507193dd6cd6c..ab2f1ba55c7924fff6bfe378a2d71e7052fe260a 100644 --- a/doc/src/sgml/ref/create_rule.sgml +++ b/doc/src/sgml/ref/create_rule.sgml @@ -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> diff --git a/doc/src/sgml/ref/drop_rule.sgml b/doc/src/sgml/ref/drop_rule.sgml index ca52347209bda9f8d8f5d46d5d066cfcdb0595ea..c845872566c93baa93dc30f2d5f664697e9bb1d8 100644 --- a/doc/src/sgml/ref/drop_rule.sgml +++ b/doc/src/sgml/ref/drop_rule.sgml @@ -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> diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml index fe90227b4f3a07be32be5a96a9b969f4f0a27732..5b0c7745e39a67715b1d4433f38069d86318868e 100644 --- a/doc/src/sgml/reference.sgml +++ b/doc/src/sgml/reference.sgml @@ -53,6 +53,7 @@ &alterOperatorClass; &alterOperatorFamily; &alterRole; + &alterRule; &alterSchema; &alterSequence; &alterServer; diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c index c2d4bb3ed4d58b0ae4d1ed4f81cb7a3eb4205162..269d19cea6ce7d11b13d094cd8cf769a592b0450 100644 --- a/src/backend/commands/alter.c +++ b/src/backend/commands/alter.c @@ -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); diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 342b7964242036116c94aeb72515041468e8ea42..fee05311c5c5a8ecfaacc3f69799708dd5c5aa2e 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -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); diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c index ac724c3964a51665c27683e739894def521295fd..b37f36b3e6716be16ea47cbb90e63a89102abafd 100644 --- a/src/backend/rewrite/rewriteDefine.c +++ b/src/backend/rewrite/rewriteDefine.c @@ -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; +} diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c index 09396ca59001e0d76312e481256b065b537bd997..edfba677667528acbcc6919919ea3e518dbba270 100644 --- a/src/bin/psql/tab-complete.c +++ b/src/bin/psql/tab-complete.c @@ -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) diff --git a/src/include/rewrite/rewriteDefine.h b/src/include/rewrite/rewriteDefine.h index dda267bf968a35f9be1c774477d564c0b76e5eac..36030c4a9f25e4683efa0d83969fe4ba19dda216 100644 --- a/src/include/rewrite/rewriteDefine.h +++ b/src/include/rewrite/rewriteDefine.h @@ -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); diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 711ae53a617ac924fa4ed01ccc9ed49857e81f98..869ca8c9a9af36ee1dc79f3540d3204ac16d737c 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -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; diff --git a/src/test/regress/sql/rules.sql b/src/test/regress/sql/rules.sql index 458c2f026c0fbce861c7c4dc5898b2749b71af55..b8d67ae9f3d473ccc004aebdaa37c9e9dfd4fc46 100644 --- a/src/test/regress/sql/rules.sql +++ b/src/test/regress/sql/rules.sql @@ -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;