Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtp (Exim 4.80) (envelope-from ) id 1YMr89-0005np-Nu for pgsql-hackers@arkaria.postgresql.org; Sun, 15 Feb 2015 04:48:31 +0000 Received: from localhost ([127.0.0.1] helo=postgresql.org) by malur.postgresql.org with smtp (Exim 4.80) (envelope-from ) id 1YMr88-0006ri-3N for pgsql-hackers@arkaria.postgresql.org; Sun, 15 Feb 2015 04:48:28 +0000 Received: from makus.postgresql.org ([2001:4800:1501:1::229]) by malur.postgresql.org with esmtps (TLS1.2:DHE_RSA_AES_256_CBC_SHA256:256) (Exim 4.80) (envelope-from ) id 1YMr7y-0006rU-9X for pgsql-hackers@postgresql.org; Sun, 15 Feb 2015 04:48:19 +0000 Received: from smtprelay0192.b.hostedemail.com ([64.98.42.192] helo=smtprelay.b.hostedemail.com) by makus.postgresql.org with esmtp (Exim 4.80) (envelope-from ) id 1YMr7b-0003Y7-Bg for pgsql-hackers@postgresql.org; Sun, 15 Feb 2015 04:48:15 +0000 Received: from filter.hostedemail.com (10.5.19.248.rfc1918.com [10.5.19.248]) by smtprelay01.b.hostedemail.com (Postfix) with ESMTP id ED3082D2A11 for ; Sun, 15 Feb 2015 04:47:53 +0000 (UTC) X-Session-Marker: 616C76686572726540616C76682E6E6F2D69702E6F7267 X-Spam-Summary: 69, 4.5, 0, , d41d8cd98f00b204, alvherre@alvh.no-ip.org, :, RULES_HIT:41:355:379:421:871:967:968:973:988:989:1260:1311:1312:1313:1314:1345:1381:1437:1515:1516:1518:1519:1544:1575:1594:1595:1596:1605:1711:1730:1747:1764:1777:1792:2393:2525:2560:2563:2682:2685:2689:2693:2859:2894:2910:2933:2937:2939:2942:2945:2947:2951:2954:3022:3138:3139:3140:3141:3142:3865:3866:3867:3868:3870:3871:3872:3874:3934:3936:3938:3941:3944:3947:3950:3953:3956:3959:4184:4250:4470:4605:4659:5007:6119:6261:6618:6748:7281:7398:7809:7903:8784:8957:9025:9121:10011:10394:10848:11026:11256:11257:11604:11658:11914:12296:12438:12517:12519:13053:13160:13161:13229:13618:13894:13895:14180:14181:14182:14183:21060:21063:21080, 0, RBL:none, CacheIP:none, Bayesian:0.5, 0.5, 0.5, Netcheck:none, DomainCache:0, MSF:not bulk, SPF:fn, MSBL:0, DNSBL:none, Custom_rules:0:2:0 X-HE-Tag: quill71_61a3594c63300 X-Filterd-Recvd-Size: 427969 Received: from alvin.alvh.no-ip.org (pc-247-219-215-201.cm.vtr.net [201.215.219.247]) (Authenticated sender: alvherre@alvh.no-ip.org) by omf13.b.hostedemail.com (Postfix) with ESMTPA for ; Sun, 15 Feb 2015 04:47:50 +0000 (UTC) Received: by alvin.alvh.no-ip.org (Postfix, from userid 1000) id 12FAD1B21; Sun, 15 Feb 2015 01:48:15 -0300 (CLST) Date: Sun, 15 Feb 2015 01:48:15 -0300 From: Alvaro Herrera To: Pg Hackers Subject: deparsing utility commands Message-ID: <20150215044814.GL3391@alvh.no-ip.org> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="gatW/ieO32f1wygP" Content-Disposition: inline Content-Transfer-Encoding: 8bit User-Agent: Mutt/1.5.23 (2014-03-12) X-Pg-Spam-Score: -0.8 (/) List-Archive: List-Help: List-ID: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: X-Mailing-List: pgsql-hackers Precedence: bulk Sender: pgsql-hackers-owner@postgresql.org --gatW/ieO32f1wygP Content-Type: text/plain; charset=iso-8859-1 Content-Disposition: inline Content-Transfer-Encoding: 8bit Hi, This is a repost of the patch to add CREATE command deparsing support to event triggers. It now supports not only CREATE but also ALTER and other commands such as GRANT/REVOKE, COMMENT ON and SECURITY LABEL. This patch series is broken up in a rather large number of patches, but my intention would be to commit separately only the first 6 patches; the remaining parts are split up only so that it's easy to see how deparsing each patch is implemented, and would be committed as a single changeset (but before that I need a bunch of extra fixes and additions to other parts.) One of the worries regarding this code is whether we will be able to keep it up to date as the grammar changes. We have come up with a testing harness for this that's pretty simple: we will have a new make target in src/test/regress that creates a new pg_regress schedule file, then run pg_regress with it. That schedule contains a "deparse initialization" test, then the contents of serial_schedule, then a final deparse test. The initialization test creates a table and an event trigger, and stores all deparsed commands in the table; the final deparse test is about executing those commands. (There are actually two event triggers; the other one stores DROP commands for objects dropped, using the sql_drop event.) The deparse_init.sql file is attached. Just by storing the deparsed command in the table we're doing the first level of deparse testing: make sure that all commands deparse to something. Running the commands is the second level: make sure that the commands generated by the deparsing step works correctly. (The third level is making sure that each command generated by deparse creates an object idential to the one generated by the original command. The plan here is to run two databases through pg_dump and compare.) One line of defense which I just tought about is that instead of sprinkling EventTriggerStashCommand() calls all over ProcessUtilitySlow, we should add only one at the bottom. There are some issues remaining: 1. ruleutils.c seems to deparse temp schemas as pg_temp_X instead of just pg_temp; so some commands like CREATE VIEW..AS SELECT FROM temp_table fail in an ugly way. Maybe the solution is to tell ruleutils that the temp schema is always visible; or maybe the solution is to tell it that it's spelled pg_temp instead. 2. I need to complete support for all object types in getObjectIdentityParts. The ones not implemented cause the generated DROP commands to fail. 3. Some commands are still not supported by the deparsing code: 1 + WARNING: state: XX000 errm: unimplemented deparse of ALTER TABLE OPTIONS (...) 2 + WARNING: state: XX000 errm: unimplemented deparse of ALTER TABLE RESET 2 + WARNING: state: XX000 errm: unimplemented deparse of ALTER TABLE SET OPTIONS 2 + WARNING: state: XX000 errm: unimplemented deparse of CREATE FOREIGN TABLE 2 + WARNING: state: XX000 errm: unimplemented deparse of CREATE LANGUAGE 3 + WARNING: hash indexes are not WAL-logged and their use is discouraged 3 + WARNING: state: XX000 errm: unimplemented deparse of ALTER TABLE ALTER COLUMN OPTIONS 3 + WARNING: state: XX000 errm: unimplemented deparse of CREATE TABLE AS EXECUTE 4 + WARNING: state: XX000 errm: unimplemented deparse of ALTER TABLE ALTER CONSTRAINT 4 + WARNING: state: XX000 errm: unimplemented deparse of ALTER TABLE TYPE USING 4 + WARNING: state: XX000 errm: unimplemented deparse of ALTER TEXT SEARCH CONFIGURATION 4 + WARNING: state: XX000 errm: unimplemented deparse of ALTER USER MAPPING 5 + WARNING: state: XX000 errm: unimplemented deparse of CREATE OPERATOR CLASS 5 + WARNING: state: XX000 errm: unsupported deparse of ALTER FUNCTION SET 5 + WARNING: state: XX000 errm: unsupported deparse of CREATE SERVER ... OPTIONS 7 + WARNING: state: XX000 errm: unimplemented deparse of ALTER FOREIGN DATA WRAPPER 7 + WARNING: state: XX000 errm: unimplemented deparse of ALTER SERVER 8 + WARNING: state: XX000 errm: unimplemented deparse of ALTER POLICY 9 + WARNING: state: XX000 errm: unimplemented deparse of ALTER TABLE SET 10 + WARNING: state: XX000 errm: unimplemented deparse of CREATE FOREIGN DATA WRAPPER 11 + WARNING: state: XX000 errm: can't recreate text search configuration with mappings 12 + WARNING: state: XX000 errm: unimplemented deparse of CREATE USER MAPPING 14 + WARNING: state: XX000 errm: unimplemented deparse of ALTER OPERATOR FAMILY I expect most of these are simple to implement. The only one that seems to present a challenge is ALTER OPERATOR FAMILY: it needs to pass back the OID of all the added/removed operator class members. -- Álvaro Herrera http://www.2ndQuadrant.com/ PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services --gatW/ieO32f1wygP Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="0001-getObjectIdentity-Fix-opclass-opfamily-names.patch" From 68a47c527080f46c7e00f1da888937913e675da1 Mon Sep 17 00:00:00 2001 From: Alvaro Herrera Date: Sat, 14 Feb 2015 15:22:27 -0300 Subject: [PATCH 01/42] getObjectIdentity: Fix opclass/opfamily names The original representation uses "opcname for amname", which is good enough; but if we replace "for" with "using", we can apply the returned identity directly in a DROP command, as in DROP OPERATOR CLASS opcname USING amname This slightly simplify code using the identities to programatically execute commands on operator classes and families. Note backwards-incompatible change: This dates back to 9.3 when object identities were introduced by commit f8348ea3, but we don't want to change the behavior of released branches and so this is not backpatched. --- src/backend/catalog/objectaddress.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c index 2f40733..d899dd7 100644 --- a/src/backend/catalog/objectaddress.c +++ b/src/backend/catalog/objectaddress.c @@ -3533,7 +3533,7 @@ getObjectIdentityParts(const ObjectAddress *object, appendStringInfoString(&buffer, quote_qualified_identifier(schema, NameStr(opcForm->opcname))); - appendStringInfo(&buffer, " for %s", + appendStringInfo(&buffer, " USING %s", quote_identifier(NameStr(amForm->amname))); if (objname) { @@ -4070,7 +4070,7 @@ getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **objname, List **objargs amForm = (Form_pg_am) GETSTRUCT(amTup); schema = get_namespace_name(opfForm->opfnamespace); - appendStringInfo(buffer, "%s for %s", + appendStringInfo(buffer, "%s USING %s", quote_qualified_identifier(schema, NameStr(opfForm->opfname)), NameStr(amForm->amname)); -- 2.1.4 --gatW/ieO32f1wygP Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="0002-deparse-core-return-affected-attnum-in-RENAME.patch" From 11c7bad562dbf744681827044ac73d697a583ce9 Mon Sep 17 00:00:00 2001 From: Alvaro Herrera Date: Thu, 25 Sep 2014 16:34:50 -0300 Subject: [PATCH 02/42] deparse/core: return affected attnum in RENAME The various ALTER OBJECT RENAME already return the OID of the affected object, but when it is a column being renamed, we were lacking its attnum to properly identify it for later processing. This patch doesn't change any user-visible behavior, but the attnum is available for later patches. --- src/backend/commands/alter.c | 7 +++++-- src/backend/commands/tablecmds.c | 26 +++++++++++++++++--------- src/backend/tcop/utility.c | 4 ++-- src/include/commands/alter.h | 2 +- src/include/commands/tablecmds.h | 2 +- 5 files changed, 26 insertions(+), 15 deletions(-) diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c index 78b54b4..dfd57e0 100644 --- a/src/backend/commands/alter.c +++ b/src/backend/commands/alter.c @@ -299,9 +299,12 @@ AlterObjectRename_internal(Relation rel, Oid objectId, const char *new_name) /* * Executes an ALTER OBJECT / RENAME TO statement. Based on the object * type, the function appropriate to that type is executed. + * + * Return value is the OID of the renamed object. The objectSubId, if any, + * is returned in objsubid. */ Oid -ExecRenameStmt(RenameStmt *stmt) +ExecRenameStmt(RenameStmt *stmt, int *objsubid) { switch (stmt->renameType) { @@ -331,7 +334,7 @@ ExecRenameStmt(RenameStmt *stmt) case OBJECT_COLUMN: case OBJECT_ATTRIBUTE: - return renameatt(stmt); + return renameatt(stmt, objsubid); case OBJECT_RULE: return RenameRewriteRule(stmt->relation, stmt->subname, diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 66d5083..1ee2901 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -2155,8 +2155,10 @@ renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing) /* * renameatt_internal - workhorse for renameatt + * + * Return value is the column number of the attribute in the 'myrelid' relation. */ -static void +static int renameatt_internal(Oid myrelid, const char *oldattname, const char *newattname, @@ -2297,6 +2299,8 @@ renameatt_internal(Oid myrelid, heap_close(attrelation, RowExclusiveLock); relation_close(targetrelation, NoLock); /* close rel but keep lock */ + + return attnum; } /* @@ -2321,9 +2325,10 @@ RangeVarCallbackForRenameAttribute(const RangeVar *rv, Oid relid, Oid oldrelid, * renameatt - changes the name of a attribute in a relation */ Oid -renameatt(RenameStmt *stmt) +renameatt(RenameStmt *stmt, int *objsubid) { Oid relid; + int attnum; /* lock level taken here should match renameatt_internal */ relid = RangeVarGetRelidExtended(stmt->relation, AccessExclusiveLock, @@ -2339,13 +2344,16 @@ renameatt(RenameStmt *stmt) return InvalidOid; } - renameatt_internal(relid, - stmt->subname, /* old att name */ - stmt->newname, /* new att name */ - interpretInhOption(stmt->relation->inhOpt), /* recursive? */ - false, /* recursing? */ - 0, /* expected inhcount */ - stmt->behavior); + attnum = + renameatt_internal(relid, + stmt->subname, /* old att name */ + stmt->newname, /* new att name */ + interpretInhOption(stmt->relation->inhOpt), /* recursive? */ + false, /* recursing? */ + 0, /* expected inhcount */ + stmt->behavior); + if (objsubid) + *objsubid = attnum; /* This is an ALTER TABLE command so it's about the relid */ return relid; diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 3533cfa..88dcd47 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -805,7 +805,7 @@ standard_ProcessUtility(Node *parsetree, context, params, dest, completionTag); else - ExecRenameStmt(stmt); + ExecRenameStmt(stmt, NULL); } break; @@ -1304,7 +1304,7 @@ ProcessUtilitySlow(Node *parsetree, break; case T_RenameStmt: - ExecRenameStmt((RenameStmt *) parsetree); + ExecRenameStmt((RenameStmt *) parsetree, NULL); break; case T_AlterObjectSchemaStmt: diff --git a/src/include/commands/alter.h b/src/include/commands/alter.h index 14d24f1..0382037 100644 --- a/src/include/commands/alter.h +++ b/src/include/commands/alter.h @@ -18,7 +18,7 @@ #include "nodes/parsenodes.h" #include "utils/relcache.h" -extern Oid ExecRenameStmt(RenameStmt *stmt); +extern Oid ExecRenameStmt(RenameStmt *stmt, int *objsubid); extern Oid ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt); extern Oid AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid, diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h index a55e8d4..c6e75e4 100644 --- a/src/include/commands/tablecmds.h +++ b/src/include/commands/tablecmds.h @@ -53,7 +53,7 @@ extern void ExecuteTruncate(TruncateStmt *stmt); extern void SetRelationHasSubclass(Oid relationId, bool relhassubclass); -extern Oid renameatt(RenameStmt *stmt); +extern Oid renameatt(RenameStmt *stmt, int *attnum); extern Oid RenameConstraint(RenameStmt *stmt); -- 2.1.4 --gatW/ieO32f1wygP Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="0003-deparse-core-event-triggers-support-GRANT-REVOKE.patch" From 20717a7a42bb8e7cf569e311b5f4ba31e88efa1d Mon Sep 17 00:00:00 2001 From: Alvaro Herrera Date: Fri, 9 May 2014 18:32:23 -0400 Subject: [PATCH 03/42] deparse/core: event triggers support GRANT/REVOKE Have triggers on events ddl_command_start and ddl_command_end be invoked on execution of GRANT and REVOKE. --- doc/src/sgml/event-trigger.sgml | 17 ++++++++++++++++- src/backend/commands/event_trigger.c | 30 ++++++++++++++++++++++++++++++ src/backend/tcop/utility.c | 21 ++++++++++++++++----- src/include/commands/event_trigger.h | 1 + 4 files changed, 63 insertions(+), 6 deletions(-) diff --git a/doc/src/sgml/event-trigger.sgml b/doc/src/sgml/event-trigger.sgml index 156c463..ed53587 100644 --- a/doc/src/sgml/event-trigger.sgml +++ b/doc/src/sgml/event-trigger.sgml @@ -36,7 +36,8 @@ The ddl_command_start event occurs just before the - execution of a CREATE, ALTER, or DROP + execution of a CREATE, ALTER, DROP, + GRANT or REVOKE command. No check whether the affected object exists or doesn't exist is performed before the event trigger fires. As an exception, however, this event does not occur for @@ -716,6 +717,13 @@ - + GRANT + X + X + - + - + + IMPORT FOREIGN SCHEMA X X @@ -723,6 +731,13 @@ - + REVOKE + X + X + - + - + + SELECT INTO X X diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c index a33a5ad..2eb6282 100644 --- a/src/backend/commands/event_trigger.c +++ b/src/backend/commands/event_trigger.c @@ -267,6 +267,8 @@ check_ddl_tag(const char *tag) pg_strcasecmp(tag, "REFRESH MATERIALIZED VIEW") == 0 || pg_strcasecmp(tag, "ALTER DEFAULT PRIVILEGES") == 0 || pg_strcasecmp(tag, "ALTER LARGE OBJECT") == 0 || + pg_strcasecmp(tag, "GRANT") == 0 || + pg_strcasecmp(tag, "REVOKE") == 0 || pg_strcasecmp(tag, "DROP OWNED") == 0 || pg_strcasecmp(tag, "IMPORT FOREIGN SCHEMA") == 0) return EVENT_TRIGGER_COMMAND_TAG_OK; @@ -1149,6 +1151,34 @@ EventTriggerSupportsObjectClass(ObjectClass objclass) return true; } +bool +EventTriggerSupportsGrantObjectType(GrantObjectType objtype) +{ + switch (objtype) + { + case ACL_OBJECT_DATABASE: + case ACL_OBJECT_TABLESPACE: + /* no support for global objects */ + return false; + + case ACL_OBJECT_COLUMN: + case ACL_OBJECT_RELATION: + case ACL_OBJECT_SEQUENCE: + case ACL_OBJECT_DOMAIN: + case ACL_OBJECT_FDW: + case ACL_OBJECT_FOREIGN_SERVER: + case ACL_OBJECT_FUNCTION: + case ACL_OBJECT_LANGUAGE: + case ACL_OBJECT_LARGEOBJECT: + case ACL_OBJECT_NAMESPACE: + case ACL_OBJECT_TYPE: + return true; + default: + Assert(false); + return true; + } +} + /* * Prepare event trigger state for a new complete query to run, if necessary; * returns whether this was done. If it was, EventTriggerEndCompleteQuery must diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 88dcd47..9539f4f 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -548,11 +548,6 @@ standard_ProcessUtility(Node *parsetree, DeallocateQuery((DeallocateStmt *) parsetree); break; - case T_GrantStmt: - /* no event triggers for global objects */ - ExecuteGrantStmt((GrantStmt *) parsetree); - break; - case T_GrantRoleStmt: /* no event triggers for global objects */ GrantRole((GrantRoleStmt *) parsetree); @@ -783,6 +778,18 @@ standard_ProcessUtility(Node *parsetree, * in some cases, so we "fast path" them in the other cases. */ + case T_GrantStmt: + { + GrantStmt *stmt = (GrantStmt *) parsetree; + + if (EventTriggerSupportsGrantObjectType(stmt->objtype)) + ProcessUtilitySlow(parsetree, queryString, + context, params, + dest, completionTag); + else + ExecuteGrantStmt((GrantStmt *) parsetree); + } + case T_DropStmt: { DropStmt *stmt = (DropStmt *) parsetree; @@ -1315,6 +1322,10 @@ ProcessUtilitySlow(Node *parsetree, ExecAlterOwnerStmt((AlterOwnerStmt *) parsetree); break; + case T_GrantStmt: + ExecuteGrantStmt((GrantStmt *) parsetree); + break; + case T_DropOwnedStmt: DropOwnedObjects((DropOwnedStmt *) parsetree); break; diff --git a/src/include/commands/event_trigger.h b/src/include/commands/event_trigger.h index e807e65..9ac9fc3 100644 --- a/src/include/commands/event_trigger.h +++ b/src/include/commands/event_trigger.h @@ -48,6 +48,7 @@ extern void AlterEventTriggerOwner_oid(Oid, Oid newOwnerId); extern bool EventTriggerSupportsObjectType(ObjectType obtype); extern bool EventTriggerSupportsObjectClass(ObjectClass objclass); +extern bool EventTriggerSupportsGrantObjectType(GrantObjectType objtype); extern void EventTriggerDDLCommandStart(Node *parsetree); extern void EventTriggerDDLCommandEnd(Node *parsetree); extern void EventTriggerSQLDrop(Node *parsetree); -- 2.1.4 --gatW/ieO32f1wygP Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="0004-deparse-core-event-triggers-support-COMMENT.patch" From 9e0ce5b3c4dae3bfcec04a516fc9c5f3102cbb0a Mon Sep 17 00:00:00 2001 From: Alvaro Herrera Date: Thu, 25 Sep 2014 15:44:44 -0300 Subject: [PATCH 04/42] deparse/core: event triggers support COMMENT Have triggers on events ddl_command_start and ddl_command_end be invoked on execution of COMMENT. --- doc/src/sgml/event-trigger.sgml | 9 ++++++++- src/backend/commands/comment.c | 5 ++++- src/backend/commands/event_trigger.c | 1 + src/backend/tcop/utility.c | 21 +++++++++++++++++---- src/include/commands/comment.h | 2 +- 5 files changed, 31 insertions(+), 7 deletions(-) diff --git a/doc/src/sgml/event-trigger.sgml b/doc/src/sgml/event-trigger.sgml index ed53587..58e0d9d 100644 --- a/doc/src/sgml/event-trigger.sgml +++ b/doc/src/sgml/event-trigger.sgml @@ -37,7 +37,7 @@ The ddl_command_start event occurs just before the execution of a CREATE, ALTER, DROP, - GRANT or REVOKE + COMMENT, GRANT or REVOKE command. No check whether the affected object exists or doesn't exist is performed before the event trigger fires. As an exception, however, this event does not occur for @@ -318,6 +318,13 @@ - + COMMENT + X + X + - + - + + CREATE CAST X X diff --git a/src/backend/commands/comment.c b/src/backend/commands/comment.c index ed1e46e..e6929a6 100644 --- a/src/backend/commands/comment.c +++ b/src/backend/commands/comment.c @@ -37,7 +37,7 @@ * pg_description for the object specified by the given SQL command. */ Oid -CommentObject(CommentStmt *stmt) +CommentObject(CommentStmt *stmt, uint32 *objectSubId) { ObjectAddress address; Relation relation; @@ -126,6 +126,9 @@ CommentObject(CommentStmt *stmt) if (relation != NULL) relation_close(relation, NoLock); + if (objectSubId) + *objectSubId = address.objectSubId; + return address.objectId; } diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c index 2eb6282..87b606c 100644 --- a/src/backend/commands/event_trigger.c +++ b/src/backend/commands/event_trigger.c @@ -267,6 +267,7 @@ check_ddl_tag(const char *tag) pg_strcasecmp(tag, "REFRESH MATERIALIZED VIEW") == 0 || pg_strcasecmp(tag, "ALTER DEFAULT PRIVILEGES") == 0 || pg_strcasecmp(tag, "ALTER LARGE OBJECT") == 0 || + pg_strcasecmp(tag, "COMMENT") == 0 || pg_strcasecmp(tag, "GRANT") == 0 || pg_strcasecmp(tag, "REVOKE") == 0 || pg_strcasecmp(tag, "DROP OWNED") == 0 || diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 9539f4f..ab13bfd 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -513,10 +513,6 @@ standard_ProcessUtility(Node *parsetree, ExecuteTruncate((TruncateStmt *) parsetree); break; - case T_CommentStmt: - CommentObject((CommentStmt *) parsetree); - break; - case T_SecLabelStmt: ExecSecLabelStmt((SecLabelStmt *) parsetree); break; @@ -842,6 +838,19 @@ standard_ProcessUtility(Node *parsetree, } break; + case T_CommentStmt: + { + CommentStmt *stmt = (CommentStmt *) parsetree; + + if (EventTriggerSupportsObjectType(stmt->objtype)) + ProcessUtilitySlow(parsetree, queryString, + context, params, + dest, completionTag); + else + CommentObject((CommentStmt *) parsetree, NULL); + break; + } + default: /* All other statement types have event trigger support */ ProcessUtilitySlow(parsetree, queryString, @@ -1322,6 +1331,10 @@ ProcessUtilitySlow(Node *parsetree, ExecAlterOwnerStmt((AlterOwnerStmt *) parsetree); break; + case T_CommentStmt: + CommentObject((CommentStmt *) parsetree, NULL); + break; + case T_GrantStmt: ExecuteGrantStmt((GrantStmt *) parsetree); break; diff --git a/src/include/commands/comment.h b/src/include/commands/comment.h index 3d61b44..edc94d1 100644 --- a/src/include/commands/comment.h +++ b/src/include/commands/comment.h @@ -29,7 +29,7 @@ *------------------------------------------------------------------ */ -extern Oid CommentObject(CommentStmt *stmt); +extern Oid CommentObject(CommentStmt *stmt, uint32 *objectSubId); extern void DeleteComments(Oid oid, Oid classoid, int32 subid); -- 2.1.4 --gatW/ieO32f1wygP Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="0005-deparse-core-have-event-triggers-support-SECURITY-LA.patch" From fc85e88f91b109d72ef99822e2909808f005cc4e Mon Sep 17 00:00:00 2001 From: Alvaro Herrera Date: Tue, 10 Feb 2015 17:35:15 -0300 Subject: [PATCH 05/42] deparse/core: have event triggers support SECURITY LABEL Have triggers on events ddl_command_start and ddl_command_end be invoked on execution of SECURITY LABEL. Author: Andres Freund --- doc/src/sgml/event-trigger.sgml | 8 ++++++++ src/backend/commands/event_trigger.c | 3 ++- src/backend/tcop/utility.c | 25 +++++++++++++++++++++---- 3 files changed, 31 insertions(+), 5 deletions(-) diff --git a/doc/src/sgml/event-trigger.sgml b/doc/src/sgml/event-trigger.sgml index 58e0d9d..4472400 100644 --- a/doc/src/sgml/event-trigger.sgml +++ b/doc/src/sgml/event-trigger.sgml @@ -37,6 +37,7 @@ The ddl_command_start event occurs just before the execution of a CREATE, ALTER, DROP, + SECURITY LABEL, COMMENT, GRANT or REVOKE command. No check whether the affected object exists or doesn't exist is performed before the event trigger fires. @@ -745,6 +746,13 @@ - + SECURITY LABEL + X + X + - + - + + SELECT INTO X X diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c index 87b606c..dcf5b98 100644 --- a/src/backend/commands/event_trigger.c +++ b/src/backend/commands/event_trigger.c @@ -271,7 +271,8 @@ check_ddl_tag(const char *tag) pg_strcasecmp(tag, "GRANT") == 0 || pg_strcasecmp(tag, "REVOKE") == 0 || pg_strcasecmp(tag, "DROP OWNED") == 0 || - pg_strcasecmp(tag, "IMPORT FOREIGN SCHEMA") == 0) + pg_strcasecmp(tag, "IMPORT FOREIGN SCHEMA") == 0 || + pg_strcasecmp(tag, "SECURITY LABEL") == 0) return EVENT_TRIGGER_COMMAND_TAG_OK; /* diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index ab13bfd..13d03c5 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -513,10 +513,6 @@ standard_ProcessUtility(Node *parsetree, ExecuteTruncate((TruncateStmt *) parsetree); break; - case T_SecLabelStmt: - ExecSecLabelStmt((SecLabelStmt *) parsetree); - break; - case T_CopyStmt: { uint64 processed; @@ -851,6 +847,19 @@ standard_ProcessUtility(Node *parsetree, break; } + case T_SecLabelStmt: + { + SecLabelStmt *stmt = (SecLabelStmt *) parsetree; + + if (EventTriggerSupportsObjectType(stmt->objtype)) + ProcessUtilitySlow(parsetree, queryString, + context, params, + dest, completionTag); + else + ExecSecLabelStmt(stmt); + break; + } + default: /* All other statement types have event trigger support */ ProcessUtilitySlow(parsetree, queryString, @@ -1355,6 +1364,14 @@ ProcessUtilitySlow(Node *parsetree, AlterPolicy((AlterPolicyStmt *) parsetree); break; + case T_SecLabelStmt: + { + SecLabelStmt *stmt = (SecLabelStmt *) parsetree; + + ExecSecLabelStmt(stmt); + } + break; + default: elog(ERROR, "unrecognized node type: %d", (int) nodeTag(parsetree)); -- 2.1.4 --gatW/ieO32f1wygP Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="0006-deparse-core-have-ALTER-TABLE-return-table-OID-and-o.patch" From 347f36e346714c92ff673541da566d43025ebb6c Mon Sep 17 00:00:00 2001 From: Alvaro Herrera Date: Fri, 25 Apr 2014 17:15:49 -0300 Subject: [PATCH 06/42] deparse/core: have ALTER TABLE return table OID and other data Currently, most if not all CREATE commands return the OID of the created object. This is useful for event triggers to try to figure out exactly what happened. This patch extends this idea a bit further to cover ALTER TABLE as well: auxilliary info such as a second OID or a column number are also returned for each subcommand of the ALTER TABLE. This OID or attnum can later be used to determine, for instance, which constraint was added by ALTER TABLE ADD CONSTRAINT, or which parent got dropped from the inherit-from parents list by NO INHERIT. This makes it possible to decode with precision exactly what happened during execution of any ALTER TABLE command. There is no user-visible change here; this patch makes the info available for later. --- src/backend/catalog/heap.c | 40 +++-- src/backend/catalog/index.c | 7 +- src/backend/catalog/pg_constraint.c | 2 + src/backend/commands/tablecmds.c | 307 ++++++++++++++++++++++++------------ src/include/catalog/heap.h | 3 +- src/include/catalog/index.h | 2 +- 6 files changed, 244 insertions(+), 117 deletions(-) diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c index 17f7266..0247da6 100644 --- a/src/backend/catalog/heap.c +++ b/src/backend/catalog/heap.c @@ -97,7 +97,7 @@ static Oid AddNewRelationType(const char *typeName, Oid new_row_type, Oid new_array_type); static void RelationRemoveInheritance(Oid relid); -static void StoreRelCheck(Relation rel, char *ccname, Node *expr, +static Oid StoreRelCheck(Relation rel, char *ccname, Node *expr, bool is_validated, bool is_local, int inhcount, bool is_no_inherit, bool is_internal); static void StoreConstraints(Relation rel, List *cooked_constraints, @@ -1844,7 +1844,7 @@ heap_drop_with_catalog(Oid relid) /* * Store a default expression for column attnum of relation rel. */ -void +Oid StoreAttrDefault(Relation rel, AttrNumber attnum, Node *expr, bool is_internal) { @@ -1949,6 +1949,8 @@ StoreAttrDefault(Relation rel, AttrNumber attnum, */ InvokeObjectPostCreateHookArg(AttrDefaultRelationId, RelationGetRelid(rel), attnum, is_internal); + + return attrdefOid; } /* @@ -1956,8 +1958,10 @@ StoreAttrDefault(Relation rel, AttrNumber attnum, * * Caller is responsible for updating the count of constraints * in the pg_class entry for the relation. + * + * The OID of the new constraint is returned. */ -static void +static Oid StoreRelCheck(Relation rel, char *ccname, Node *expr, bool is_validated, bool is_local, int inhcount, bool is_no_inherit, bool is_internal) @@ -1967,6 +1971,7 @@ StoreRelCheck(Relation rel, char *ccname, Node *expr, List *varList; int keycount; int16 *attNos; + Oid constrOid; /* * Flatten expression to string form for storage. @@ -2018,7 +2023,7 @@ StoreRelCheck(Relation rel, char *ccname, Node *expr, /* * Create the Check Constraint */ - CreateConstraintEntry(ccname, /* Constraint Name */ + constrOid = CreateConstraintEntry(ccname, /* Constraint Name */ RelationGetNamespace(rel), /* namespace */ CONSTRAINT_CHECK, /* Constraint Type */ false, /* Is Deferrable */ @@ -2049,11 +2054,15 @@ StoreRelCheck(Relation rel, char *ccname, Node *expr, pfree(ccbin); pfree(ccsrc); + + return constrOid; } /* * Store defaults and constraints (passed as a list of CookedConstraint). * + * Each CookedConstraint struct is modified to store the new catalog tuple OID. + * * NOTE: only pre-cooked expressions will be passed this way, which is to * say expressions inherited from an existing relation. Newly parsed * expressions can be added later, by direct calls to StoreAttrDefault @@ -2065,7 +2074,7 @@ StoreConstraints(Relation rel, List *cooked_constraints, bool is_internal) int numchecks = 0; ListCell *lc; - if (!cooked_constraints) + if (list_length(cooked_constraints) == 0) return; /* nothing to do */ /* @@ -2082,12 +2091,14 @@ StoreConstraints(Relation rel, List *cooked_constraints, bool is_internal) switch (con->contype) { case CONSTR_DEFAULT: - StoreAttrDefault(rel, con->attnum, con->expr, is_internal); + con->conoid = StoreAttrDefault(rel, con->attnum, con->expr, + is_internal); break; case CONSTR_CHECK: - StoreRelCheck(rel, con->name, con->expr, !con->skip_validation, - con->is_local, con->inhcount, - con->is_no_inherit, is_internal); + con->conoid = + StoreRelCheck(rel, con->name, con->expr, + !con->skip_validation, con->is_local, con->inhcount, + con->is_no_inherit, is_internal); numchecks++; break; default: @@ -2175,6 +2186,7 @@ AddRelationNewConstraints(Relation rel, { RawColumnDefault *colDef = (RawColumnDefault *) lfirst(cell); Form_pg_attribute atp = rel->rd_att->attrs[colDef->attnum - 1]; + Oid defOid; expr = cookDefault(pstate, colDef->raw_default, atp->atttypid, atp->atttypmod, @@ -2195,10 +2207,11 @@ AddRelationNewConstraints(Relation rel, (IsA(expr, Const) &&((Const *) expr)->constisnull)) continue; - StoreAttrDefault(rel, colDef->attnum, expr, is_internal); + defOid = StoreAttrDefault(rel, colDef->attnum, expr, is_internal); cooked = (CookedConstraint *) palloc(sizeof(CookedConstraint)); cooked->contype = CONSTR_DEFAULT; + cooked->conoid = defOid; cooked->name = NULL; cooked->attnum = colDef->attnum; cooked->expr = expr; @@ -2218,6 +2231,7 @@ AddRelationNewConstraints(Relation rel, { Constraint *cdef = (Constraint *) lfirst(cell); char *ccname; + Oid constrOid; if (cdef->contype != CONSTR_CHECK) continue; @@ -2327,13 +2341,15 @@ AddRelationNewConstraints(Relation rel, /* * OK, store it. */ - StoreRelCheck(rel, ccname, expr, !cdef->skip_validation, is_local, - is_local ? 0 : 1, cdef->is_no_inherit, is_internal); + constrOid = + StoreRelCheck(rel, ccname, expr, !cdef->skip_validation, is_local, + is_local ? 0 : 1, cdef->is_no_inherit, is_internal); numchecks++; cooked = (CookedConstraint *) palloc(sizeof(CookedConstraint)); cooked->contype = CONSTR_CHECK; + cooked->conoid = constrOid; cooked->name = ccname; cooked->attnum = 0; cooked->expr = expr; diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c index f85ed93..5773298 100644 --- a/src/backend/catalog/index.c +++ b/src/backend/catalog/index.c @@ -1111,7 +1111,8 @@ index_create(Relation heapRelation, /* * index_constraint_create * - * Set up a constraint associated with an index + * Set up a constraint associated with an index. Return the new constraint's + * OID. * * heapRelation: table owning the index (must be suitably locked by caller) * indexRelationId: OID of the index @@ -1128,7 +1129,7 @@ index_create(Relation heapRelation, * allow_system_table_mods: allow table to be a system catalog * is_internal: index is constructed due to internal process */ -void +Oid index_constraint_create(Relation heapRelation, Oid indexRelationId, IndexInfo *indexInfo, @@ -1316,6 +1317,8 @@ index_constraint_create(Relation heapRelation, heap_freetuple(indexTuple); heap_close(pg_index, RowExclusiveLock); } + + return conOid; } /* diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c index 77fe918..3c756f8 100644 --- a/src/backend/catalog/pg_constraint.c +++ b/src/backend/catalog/pg_constraint.c @@ -40,6 +40,8 @@ * Subsidiary records (such as triggers or indexes to implement the * constraint) are *not* created here. But we do make dependency links * from the constraint to the things it depends on. + * + * The new constraint's OID is returned. */ Oid CreateConstraintEntry(const char *constraintName, diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 1ee2901..4b7b032 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -281,9 +281,9 @@ static void AlterIndexNamespaces(Relation classRel, Relation rel, static void AlterSeqNamespaces(Relation classRel, Relation rel, Oid oldNspOid, Oid newNspOid, ObjectAddresses *objsMoved, LOCKMODE lockmode); -static void ATExecAlterConstraint(Relation rel, AlterTableCmd *cmd, +static Oid ATExecAlterConstraint(Relation rel, AlterTableCmd *cmd, bool recurse, bool recursing, LOCKMODE lockmode); -static void ATExecValidateConstraint(Relation rel, char *constrName, +static Oid ATExecValidateConstraint(Relation rel, char *constrName, bool recurse, bool recursing, LOCKMODE lockmode); static int transformColumnNameList(Oid relId, List *colList, int16 *attnums, Oid *atttypids); @@ -325,26 +325,26 @@ static List *find_typed_table_dependencies(Oid typeOid, const char *typeName, DropBehavior behavior); static void ATPrepAddColumn(List **wqueue, Relation rel, bool recurse, bool recursing, AlterTableCmd *cmd, LOCKMODE lockmode); -static void ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel, - ColumnDef *colDef, bool isOid, +static AttrNumber ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, + Relation rel, ColumnDef *colDef, bool isOid, bool recurse, bool recursing, LOCKMODE lockmode); static void check_for_column_name_collision(Relation rel, const char *colname); static void add_column_datatype_dependency(Oid relid, int32 attnum, Oid typid); static void add_column_collation_dependency(Oid relid, int32 attnum, Oid collid); static void ATPrepAddOids(List **wqueue, Relation rel, bool recurse, AlterTableCmd *cmd, LOCKMODE lockmode); -static void ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode); -static void ATExecSetNotNull(AlteredTableInfo *tab, Relation rel, +static AttrNumber ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode); +static AttrNumber ATExecSetNotNull(AlteredTableInfo *tab, Relation rel, const char *colName, LOCKMODE lockmode); -static void ATExecColumnDefault(Relation rel, const char *colName, +static AttrNumber ATExecColumnDefault(Relation rel, const char *colName, Node *newDefault, LOCKMODE lockmode); static void ATPrepSetStatistics(Relation rel, const char *colName, Node *newValue, LOCKMODE lockmode); -static void ATExecSetStatistics(Relation rel, const char *colName, +static AttrNumber ATExecSetStatistics(Relation rel, const char *colName, Node *newValue, LOCKMODE lockmode); -static void ATExecSetOptions(Relation rel, const char *colName, +static AttrNumber ATExecSetOptions(Relation rel, const char *colName, Node *options, bool isReset, LOCKMODE lockmode); -static void ATExecSetStorage(Relation rel, const char *colName, +static AttrNumber ATExecSetStorage(Relation rel, const char *colName, Node *newValue, LOCKMODE lockmode); static void ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing, AlterTableCmd *cmd, LOCKMODE lockmode); @@ -352,20 +352,20 @@ static void ATExecDropColumn(List **wqueue, Relation rel, const char *colName, DropBehavior behavior, bool recurse, bool recursing, bool missing_ok, LOCKMODE lockmode); -static void ATExecAddIndex(AlteredTableInfo *tab, Relation rel, +static Oid ATExecAddIndex(AlteredTableInfo *tab, Relation rel, IndexStmt *stmt, bool is_rebuild, LOCKMODE lockmode); -static void ATExecAddConstraint(List **wqueue, +static Oid ATExecAddConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel, Constraint *newConstraint, bool recurse, bool is_readd, LOCKMODE lockmode); -static void ATExecAddIndexConstraint(AlteredTableInfo *tab, Relation rel, +static Oid ATExecAddIndexConstraint(AlteredTableInfo *tab, Relation rel, IndexStmt *stmt, LOCKMODE lockmode); -static void ATAddCheckConstraint(List **wqueue, +static Oid ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel, Constraint *constr, bool recurse, bool recursing, bool is_readd, LOCKMODE lockmode); -static void ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel, +static Oid ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel, Constraint *fkconstraint, LOCKMODE lockmode); static void ATExecDropConstraint(Relation rel, const char *constrName, DropBehavior behavior, @@ -376,9 +376,9 @@ static void ATPrepAlterColumnType(List **wqueue, bool recurse, bool recursing, AlterTableCmd *cmd, LOCKMODE lockmode); static bool ATColumnChangeRequiresRewrite(Node *expr, AttrNumber varattno); -static void ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel, +static AttrNumber ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel, AlterTableCmd *cmd, LOCKMODE lockmode); -static void ATExecAlterColumnGenericOptions(Relation rel, const char *colName, +static AttrNumber ATExecAlterColumnGenericOptions(Relation rel, const char *colName, List *options, LOCKMODE lockmode); static void ATPostAlterTypeCleanup(List **wqueue, AlteredTableInfo *tab, LOCKMODE lockmode); @@ -391,7 +391,7 @@ static void change_owner_fix_column_acls(Oid relationOid, Oid oldOwnerId, Oid newOwnerId); static void change_owner_recurse_to_sequences(Oid relationOid, Oid newOwnerId, LOCKMODE lockmode); -static void ATExecClusterOn(Relation rel, const char *indexName, +static Oid ATExecClusterOn(Relation rel, const char *indexName, LOCKMODE lockmode); static void ATExecDropCluster(Relation rel, LOCKMODE lockmode); static bool ATPrepChangePersistence(Relation rel, bool toLogged); @@ -406,10 +406,10 @@ static void ATExecEnableDisableTrigger(Relation rel, char *trigname, static void ATExecEnableDisableRule(Relation rel, char *rulename, char fires_when, LOCKMODE lockmode); static void ATPrepAddInherit(Relation child_rel); -static void ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode); -static void ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode); +static Oid ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode); +static Oid ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode); static void drop_parent_dependency(Oid relid, Oid refclassid, Oid refobjid); -static void ATExecAddOf(Relation rel, const TypeName *ofTypename, LOCKMODE lockmode); +static Oid ATExecAddOf(Relation rel, const TypeName *ofTypename, LOCKMODE lockmode); static void ATExecDropOf(Relation rel, LOCKMODE lockmode); static void ATExecReplicaIdentity(Relation rel, ReplicaIdentityStmt *stmt, LOCKMODE lockmode); static void ATExecGenericOptions(Relation rel, List *options); @@ -619,6 +619,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId) cooked = (CookedConstraint *) palloc(sizeof(CookedConstraint)); cooked->contype = CONSTR_DEFAULT; + cooked->conoid = InvalidOid; cooked->name = NULL; cooked->attnum = attnum; cooked->expr = colDef->cooked_default; @@ -1732,6 +1733,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence, cooked = (CookedConstraint *) palloc(sizeof(CookedConstraint)); cooked->contype = CONSTR_CHECK; + cooked->conoid = InvalidOid; cooked->name = pstrdup(name); cooked->attnum = 0; /* not used for constraints */ cooked->expr = expr; @@ -3398,78 +3400,95 @@ static void ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel, AlterTableCmd *cmd, LOCKMODE lockmode) { + AttrNumber colno = InvalidAttrNumber; + Oid newoid = InvalidOid; + + /* temporary measure to silence unused-variable compiler warnings */ + (void) colno; + (void) newoid; + switch (cmd->subtype) { case AT_AddColumn: /* ADD COLUMN */ case AT_AddColumnToView: /* add column via CREATE OR REPLACE * VIEW */ - ATExecAddColumn(wqueue, tab, rel, (ColumnDef *) cmd->def, - false, false, false, lockmode); + colno = ATExecAddColumn(wqueue, tab, rel, (ColumnDef *) cmd->def, + false, false, false, lockmode); break; case AT_AddColumnRecurse: - ATExecAddColumn(wqueue, tab, rel, (ColumnDef *) cmd->def, - false, true, false, lockmode); + colno = ATExecAddColumn(wqueue, tab, rel, (ColumnDef *) cmd->def, + false, true, false, lockmode); break; case AT_ColumnDefault: /* ALTER COLUMN DEFAULT */ - ATExecColumnDefault(rel, cmd->name, cmd->def, lockmode); + colno = ATExecColumnDefault(rel, cmd->name, cmd->def, lockmode); break; case AT_DropNotNull: /* ALTER COLUMN DROP NOT NULL */ - ATExecDropNotNull(rel, cmd->name, lockmode); + colno = ATExecDropNotNull(rel, cmd->name, lockmode); break; case AT_SetNotNull: /* ALTER COLUMN SET NOT NULL */ - ATExecSetNotNull(tab, rel, cmd->name, lockmode); + colno = ATExecSetNotNull(tab, rel, cmd->name, lockmode); break; case AT_SetStatistics: /* ALTER COLUMN SET STATISTICS */ - ATExecSetStatistics(rel, cmd->name, cmd->def, lockmode); + colno = ATExecSetStatistics(rel, cmd->name, cmd->def, lockmode); break; case AT_SetOptions: /* ALTER COLUMN SET ( options ) */ - ATExecSetOptions(rel, cmd->name, cmd->def, false, lockmode); + colno = ATExecSetOptions(rel, cmd->name, cmd->def, false, lockmode); break; case AT_ResetOptions: /* ALTER COLUMN RESET ( options ) */ - ATExecSetOptions(rel, cmd->name, cmd->def, true, lockmode); + colno = ATExecSetOptions(rel, cmd->name, cmd->def, true, lockmode); break; case AT_SetStorage: /* ALTER COLUMN SET STORAGE */ - ATExecSetStorage(rel, cmd->name, cmd->def, lockmode); + colno = ATExecSetStorage(rel, cmd->name, cmd->def, lockmode); break; case AT_DropColumn: /* DROP COLUMN */ ATExecDropColumn(wqueue, rel, cmd->name, - cmd->behavior, false, false, cmd->missing_ok, lockmode); + cmd->behavior, false, false, + cmd->missing_ok, lockmode); break; case AT_DropColumnRecurse: /* DROP COLUMN with recursion */ ATExecDropColumn(wqueue, rel, cmd->name, - cmd->behavior, true, false, cmd->missing_ok, lockmode); + cmd->behavior, true, false, + cmd->missing_ok, lockmode); break; case AT_AddIndex: /* ADD INDEX */ - ATExecAddIndex(tab, rel, (IndexStmt *) cmd->def, false, lockmode); + newoid = ATExecAddIndex(tab, rel, (IndexStmt *) cmd->def, false, + lockmode); break; case AT_ReAddIndex: /* ADD INDEX */ - ATExecAddIndex(tab, rel, (IndexStmt *) cmd->def, true, lockmode); + newoid = ATExecAddIndex(tab, rel, (IndexStmt *) cmd->def, true, + lockmode); break; case AT_AddConstraint: /* ADD CONSTRAINT */ - ATExecAddConstraint(wqueue, tab, rel, (Constraint *) cmd->def, - false, false, lockmode); + newoid = + ATExecAddConstraint(wqueue, tab, rel, (Constraint *) cmd->def, + false, false, lockmode); break; case AT_AddConstraintRecurse: /* ADD CONSTRAINT with recursion */ - ATExecAddConstraint(wqueue, tab, rel, (Constraint *) cmd->def, - true, false, lockmode); + newoid = + ATExecAddConstraint(wqueue, tab, rel, (Constraint *) cmd->def, + true, false, lockmode); break; case AT_ReAddConstraint: /* Re-add pre-existing check * constraint */ - ATExecAddConstraint(wqueue, tab, rel, (Constraint *) cmd->def, - false, true, lockmode); + newoid = + ATExecAddConstraint(wqueue, tab, rel, (Constraint *) cmd->def, + false, true, lockmode); break; case AT_AddIndexConstraint: /* ADD CONSTRAINT USING INDEX */ - ATExecAddIndexConstraint(tab, rel, (IndexStmt *) cmd->def, lockmode); + newoid = ATExecAddIndexConstraint(tab, rel, (IndexStmt *) cmd->def, + lockmode); break; case AT_AlterConstraint: /* ALTER CONSTRAINT */ - ATExecAlterConstraint(rel, cmd, false, false, lockmode); + newoid = ATExecAlterConstraint(rel, cmd, false, false, lockmode); break; case AT_ValidateConstraint: /* VALIDATE CONSTRAINT */ - ATExecValidateConstraint(rel, cmd->name, false, false, lockmode); + newoid = ATExecValidateConstraint(rel, cmd->name, false, false, + lockmode); break; case AT_ValidateConstraintRecurse: /* VALIDATE CONSTRAINT with * recursion */ - ATExecValidateConstraint(rel, cmd->name, true, false, lockmode); + newoid = ATExecValidateConstraint(rel, cmd->name, true, false, + lockmode); break; case AT_DropConstraint: /* DROP CONSTRAINT */ ATExecDropConstraint(rel, cmd->name, cmd->behavior, @@ -3482,10 +3501,12 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel, cmd->missing_ok, lockmode); break; case AT_AlterColumnType: /* ALTER COLUMN TYPE */ - ATExecAlterColumnType(tab, rel, cmd, lockmode); + colno = ATExecAlterColumnType(tab, rel, cmd, lockmode); break; case AT_AlterColumnGenericOptions: /* ALTER COLUMN OPTIONS */ - ATExecAlterColumnGenericOptions(rel, cmd->name, (List *) cmd->def, lockmode); + colno = + ATExecAlterColumnGenericOptions(rel, cmd->name, + (List *) cmd->def, lockmode); break; case AT_ChangeOwner: /* ALTER OWNER */ ATExecChangeOwner(RelationGetRelid(rel), @@ -3493,7 +3514,7 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel, false, lockmode); break; case AT_ClusterOn: /* CLUSTER ON */ - ATExecClusterOn(rel, cmd->name, lockmode); + newoid = ATExecClusterOn(rel, cmd->name, lockmode); break; case AT_DropCluster: /* SET WITHOUT CLUSTER */ ATExecDropCluster(rel, lockmode); @@ -3582,19 +3603,20 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel, break; case AT_AddInherit: - ATExecAddInherit(rel, (RangeVar *) cmd->def, lockmode); + newoid = ATExecAddInherit(rel, (RangeVar *) cmd->def, lockmode); break; case AT_DropInherit: - ATExecDropInherit(rel, (RangeVar *) cmd->def, lockmode); + newoid = ATExecDropInherit(rel, (RangeVar *) cmd->def, lockmode); break; case AT_AddOf: - ATExecAddOf(rel, (TypeName *) cmd->def, lockmode); + newoid = ATExecAddOf(rel, (TypeName *) cmd->def, lockmode); break; case AT_DropOf: ATExecDropOf(rel, lockmode); break; case AT_ReplicaIdentity: - ATExecReplicaIdentity(rel, (ReplicaIdentityStmt *) cmd->def, lockmode); + ATExecReplicaIdentity(rel, (ReplicaIdentityStmt *) cmd->def, + lockmode); break; case AT_EnableRowSecurity: ATExecEnableRowSecurity(rel); @@ -4595,7 +4617,7 @@ ATPrepAddColumn(List **wqueue, Relation rel, bool recurse, bool recursing, cmd->subtype = AT_AddColumnRecurse; } -static void +static AttrNumber ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel, ColumnDef *colDef, bool isOid, bool recurse, bool recursing, LOCKMODE lockmode) @@ -4680,7 +4702,7 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel, colDef->colname, RelationGetRelationName(rel)))); heap_close(attrdesc, RowExclusiveLock); - return; + return InvalidAttrNumber; } } @@ -4926,6 +4948,8 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel, heap_close(childrel, NoLock); } + + return newattnum; } /* @@ -5040,7 +5064,7 @@ ATPrepAddOids(List **wqueue, Relation rel, bool recurse, AlterTableCmd *cmd, LOC /* * ALTER TABLE ALTER COLUMN DROP NOT NULL */ -static void +static AttrNumber ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode) { HeapTuple tuple; @@ -5125,18 +5149,22 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode) /* keep the system catalog indexes current */ CatalogUpdateIndexes(attr_rel, tuple); + + /* XXX should we return InvalidAttrNumber if there was no change? */ } InvokeObjectPostAlterHook(RelationRelationId, RelationGetRelid(rel), attnum); heap_close(attr_rel, RowExclusiveLock); + + return attnum; } /* * ALTER TABLE ALTER COLUMN SET NOT NULL */ -static void +static AttrNumber ATExecSetNotNull(AlteredTableInfo *tab, Relation rel, const char *colName, LOCKMODE lockmode) { @@ -5180,18 +5208,22 @@ ATExecSetNotNull(AlteredTableInfo *tab, Relation rel, /* Tell Phase 3 it needs to test the constraint */ tab->new_notnull = true; + + /* XXX should we return InvalidAttrNumber if there was no change? */ } InvokeObjectPostAlterHook(RelationRelationId, RelationGetRelid(rel), attnum); heap_close(attr_rel, RowExclusiveLock); + + return attnum; } /* * ALTER TABLE ALTER COLUMN SET/DROP DEFAULT */ -static void +static AttrNumber ATExecColumnDefault(Relation rel, const char *colName, Node *newDefault, LOCKMODE lockmode) { @@ -5242,6 +5274,8 @@ ATExecColumnDefault(Relation rel, const char *colName, AddRelationNewConstraints(rel, list_make1(rawEnt), NIL, false, true, false); } + + return attnum; } /* @@ -5271,13 +5305,14 @@ ATPrepSetStatistics(Relation rel, const char *colName, Node *newValue, LOCKMODE RelationGetRelationName(rel)); } -static void +static AttrNumber ATExecSetStatistics(Relation rel, const char *colName, Node *newValue, LOCKMODE lockmode) { int newtarget; Relation attrelation; HeapTuple tuple; Form_pg_attribute attrtuple; + AttrNumber attnum; Assert(IsA(newValue, Integer)); newtarget = intVal(newValue); @@ -5312,7 +5347,8 @@ ATExecSetStatistics(Relation rel, const char *colName, Node *newValue, LOCKMODE colName, RelationGetRelationName(rel)))); attrtuple = (Form_pg_attribute) GETSTRUCT(tuple); - if (attrtuple->attnum <= 0) + attnum = attrtuple->attnum; + if (attnum <= 0) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot alter system column \"%s\"", @@ -5331,9 +5367,11 @@ ATExecSetStatistics(Relation rel, const char *colName, Node *newValue, LOCKMODE heap_freetuple(tuple); heap_close(attrelation, RowExclusiveLock); + + return attnum; } -static void +static AttrNumber ATExecSetOptions(Relation rel, const char *colName, Node *options, bool isReset, LOCKMODE lockmode) { @@ -5341,6 +5379,7 @@ ATExecSetOptions(Relation rel, const char *colName, Node *options, HeapTuple tuple, newtuple; Form_pg_attribute attrtuple; + AttrNumber attnum; Datum datum, newOptions; bool isnull; @@ -5359,7 +5398,8 @@ ATExecSetOptions(Relation rel, const char *colName, Node *options, colName, RelationGetRelationName(rel)))); attrtuple = (Form_pg_attribute) GETSTRUCT(tuple); - if (attrtuple->attnum <= 0) + attnum = attrtuple->attnum; + if (attnum <= 0) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot alter system column \"%s\"", @@ -5398,12 +5438,14 @@ ATExecSetOptions(Relation rel, const char *colName, Node *options, ReleaseSysCache(tuple); heap_close(attrelation, RowExclusiveLock); + + return attnum; } /* * ALTER TABLE ALTER COLUMN SET STORAGE */ -static void +static AttrNumber ATExecSetStorage(Relation rel, const char *colName, Node *newValue, LOCKMODE lockmode) { char *storagemode; @@ -5411,6 +5453,7 @@ ATExecSetStorage(Relation rel, const char *colName, Node *newValue, LOCKMODE loc Relation attrelation; HeapTuple tuple; Form_pg_attribute attrtuple; + AttrNumber attnum; Assert(IsA(newValue, String)); storagemode = strVal(newValue); @@ -5443,7 +5486,8 @@ ATExecSetStorage(Relation rel, const char *colName, Node *newValue, LOCKMODE loc colName, RelationGetRelationName(rel)))); attrtuple = (Form_pg_attribute) GETSTRUCT(tuple); - if (attrtuple->attnum <= 0) + attnum = attrtuple->attnum; + if (attnum <= 0) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot alter system column \"%s\"", @@ -5473,6 +5517,8 @@ ATExecSetStorage(Relation rel, const char *colName, Node *newValue, LOCKMODE loc heap_freetuple(tuple); heap_close(attrelation, RowExclusiveLock); + + return attnum; } @@ -5697,8 +5743,10 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName, * There is no such command in the grammar, but parse_utilcmd.c converts * UNIQUE and PRIMARY KEY constraints into AT_AddIndex subcommands. This lets * us schedule creation of the index at the appropriate time during ALTER. + * + * Return value is the OID of the new index. */ -static void +static Oid ATExecAddIndex(AlteredTableInfo *tab, Relation rel, IndexStmt *stmt, bool is_rebuild, LOCKMODE lockmode) { @@ -5740,12 +5788,14 @@ ATExecAddIndex(AlteredTableInfo *tab, Relation rel, RelationPreserveStorage(irel->rd_node, true); index_close(irel, NoLock); } + + return new_index; } /* * ALTER TABLE ADD CONSTRAINT USING INDEX */ -static void +static Oid ATExecAddIndexConstraint(AlteredTableInfo *tab, Relation rel, IndexStmt *stmt, LOCKMODE lockmode) { @@ -5755,6 +5805,7 @@ ATExecAddIndexConstraint(AlteredTableInfo *tab, Relation rel, IndexInfo *indexInfo; char *constraintName; char constraintType; + Oid conOid; Assert(IsA(stmt, IndexStmt)); Assert(OidIsValid(index_oid)); @@ -5799,30 +5850,34 @@ ATExecAddIndexConstraint(AlteredTableInfo *tab, Relation rel, constraintType = CONSTRAINT_UNIQUE; /* Create the catalog entries for the constraint */ - index_constraint_create(rel, - index_oid, - indexInfo, - constraintName, - constraintType, - stmt->deferrable, - stmt->initdeferred, - stmt->primary, - true, /* update pg_index */ - true, /* remove old dependencies */ - allowSystemTableMods, - false); /* is_internal */ + conOid = index_constraint_create(rel, + index_oid, + indexInfo, + constraintName, + constraintType, + stmt->deferrable, + stmt->initdeferred, + stmt->primary, + true, /* update pg_index */ + true, /* remove old dependencies */ + allowSystemTableMods, + false); /* is_internal */ index_close(indexRel, NoLock); + + return conOid; } /* * ALTER TABLE ADD CONSTRAINT */ -static void +static Oid ATExecAddConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel, Constraint *newConstraint, bool recurse, bool is_readd, LOCKMODE lockmode) { + Oid constrOid = InvalidOid; + Assert(IsA(newConstraint, Constraint)); /* @@ -5833,9 +5888,10 @@ ATExecAddConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel, switch (newConstraint->contype) { case CONSTR_CHECK: - ATAddCheckConstraint(wqueue, tab, rel, - newConstraint, recurse, false, is_readd, - lockmode); + constrOid = + ATAddCheckConstraint(wqueue, tab, rel, + newConstraint, recurse, false, is_readd, + lockmode); break; case CONSTR_FOREIGN: @@ -5866,17 +5922,22 @@ ATExecAddConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel, RelationGetNamespace(rel), NIL); - ATAddForeignKeyConstraint(tab, rel, newConstraint, lockmode); + constrOid = ATAddForeignKeyConstraint(tab, rel, newConstraint, + lockmode); break; default: elog(ERROR, "unrecognized constraint type: %d", (int) newConstraint->contype); } + + return constrOid; } /* - * Add a check constraint to a single table and its children + * Add a check constraint to a single table and its children. Returns the + * OID of the constraint added to the parent relation, if one gets added, + * or InvalidOid otherwise. * * Subroutine for ATExecAddConstraint. * @@ -5895,7 +5956,7 @@ ATExecAddConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel, * "is_readd" flag for that; just setting recurse=false would result in an * error if there are child tables. */ -static void +static Oid ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel, Constraint *constr, bool recurse, bool recursing, bool is_readd, LOCKMODE lockmode) @@ -5904,6 +5965,7 @@ ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel, ListCell *lcon; List *children; ListCell *child; + Oid constrOid = InvalidOid; /* At top level, permission check was done in ATPrepCmd, else do it */ if (recursing) @@ -5946,6 +6008,13 @@ ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel, /* Save the actually assigned name if it was defaulted */ if (constr->conname == NULL) constr->conname = ccon->name; + + /* + * Save our return value. Note we don't expect more than one element in + * this list. + */ + Assert(constrOid == InvalidOid); + constrOid = ccon->conoid; } /* At this point we must have a locked-down name to use */ @@ -5961,7 +6030,7 @@ ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel, * incorrect value for coninhcount. */ if (newcons == NIL) - return; + return InvalidOid; /* * If adding a NO INHERIT constraint, no need to find our children. @@ -5969,7 +6038,7 @@ ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel, * handled at higher levels). */ if (constr->is_no_inherit || is_readd) - return; + return constrOid; /* * Propagate to children as appropriate. Unlike most other ALTER @@ -6007,16 +6076,19 @@ ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel, heap_close(childrel, NoLock); } + + return constrOid; } /* - * Add a foreign-key constraint to a single table + * Add a foreign-key constraint to a single table; return the new constraint's + * OID. * * Subroutine for ATExecAddConstraint. Must already hold exclusive * lock on the rel, and have done appropriate validity checks for it. * We do permissions checks here, however. */ -static void +static Oid ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel, Constraint *fkconstraint, LOCKMODE lockmode) { @@ -6415,6 +6487,8 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel, * Close pk table, but keep lock until we've committed. */ heap_close(pkrel, NoLock); + + return constrOid; } /* @@ -6427,7 +6501,7 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel, * recursion bit here, but we keep the API the same for when * other constraint types are supported. */ -static void +static Oid ATExecAlterConstraint(Relation rel, AlterTableCmd *cmd, bool recurse, bool recursing, LOCKMODE lockmode) { @@ -6438,6 +6512,7 @@ ATExecAlterConstraint(Relation rel, AlterTableCmd *cmd, Form_pg_constraint currcon = NULL; Constraint *cmdcon = NULL; bool found = false; + Oid constrOid = InvalidOid; Assert(IsA(cmd->def, Constraint)); cmdcon = (Constraint *) cmd->def; @@ -6497,6 +6572,8 @@ ATExecAlterConstraint(Relation rel, AlterTableCmd *cmd, simple_heap_update(conrel, ©Tuple->t_self, copyTuple); CatalogUpdateIndexes(conrel, copyTuple); + constrOid = HeapTupleGetOid(contuple); + InvokeObjectPostAlterHook(ConstraintRelationId, HeapTupleGetOid(contuple), 0); @@ -6544,6 +6621,8 @@ ATExecAlterConstraint(Relation rel, AlterTableCmd *cmd, systable_endscan(scan); heap_close(conrel, RowExclusiveLock); + + return constrOid; } /* @@ -6554,7 +6633,7 @@ ATExecAlterConstraint(Relation rel, AlterTableCmd *cmd, * no need to lock children in that case, yet we wouldn't be able to avoid * doing so at that level. */ -static void +static Oid ATExecValidateConstraint(Relation rel, char *constrName, bool recurse, bool recursing, LOCKMODE lockmode) { @@ -6564,6 +6643,7 @@ ATExecValidateConstraint(Relation rel, char *constrName, bool recurse, HeapTuple tuple; Form_pg_constraint con = NULL; bool found = false; + Oid constrOid = InvalidOid; conrel = heap_open(ConstraintRelationId, RowExclusiveLock); @@ -6605,9 +6685,10 @@ ATExecValidateConstraint(Relation rel, char *constrName, bool recurse, HeapTuple copyTuple; Form_pg_constraint copy_con; + constrOid = HeapTupleGetOid(tuple); + if (con->contype == CONSTRAINT_FOREIGN) { - Oid conid = HeapTupleGetOid(tuple); Relation refrel; /* @@ -6622,7 +6703,7 @@ ATExecValidateConstraint(Relation rel, char *constrName, bool recurse, validateForeignKeyConstraint(constrName, rel, refrel, con->conindid, - conid); + constrOid); heap_close(refrel, NoLock); /* @@ -6703,6 +6784,8 @@ ATExecValidateConstraint(Relation rel, char *constrName, bool recurse, systable_endscan(scan); heap_close(conrel, RowExclusiveLock); + + return constrOid; } @@ -7789,7 +7872,7 @@ ATColumnChangeRequiresRewrite(Node *expr, AttrNumber varattno) } } -static void +static AttrNumber ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel, AlterTableCmd *cmd, LOCKMODE lockmode) { @@ -8183,9 +8266,11 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel, /* Cleanup */ heap_freetuple(heapTup); + + return attnum; } -static void +static AttrNumber ATExecAlterColumnGenericOptions(Relation rel, const char *colName, List *options, @@ -8204,9 +8289,10 @@ ATExecAlterColumnGenericOptions(Relation rel, Datum datum; Form_pg_foreign_table fttableform; Form_pg_attribute atttableform; + AttrNumber attnum; if (options == NIL) - return; + return InvalidAttrNumber; /* First, determine FDW validator associated to the foreign table. */ ftrel = heap_open(ForeignTableRelationId, AccessShareLock); @@ -8233,7 +8319,8 @@ ATExecAlterColumnGenericOptions(Relation rel, /* Prevent them from altering a system attribute */ atttableform = (Form_pg_attribute) GETSTRUCT(tuple); - if (atttableform->attnum <= 0) + attnum = atttableform->attnum; + if (attnum <= 0) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot alter system column \"%s\"", colName))); @@ -8282,6 +8369,8 @@ ATExecAlterColumnGenericOptions(Relation rel, heap_close(attrel, RowExclusiveLock); heap_freetuple(newtuple); + + return attnum; } /* @@ -8923,7 +9012,7 @@ change_owner_recurse_to_sequences(Oid relationOid, Oid newOwnerId, LOCKMODE lock * * The only thing we have to do is to change the indisclustered bits. */ -static void +static Oid ATExecClusterOn(Relation rel, const char *indexName, LOCKMODE lockmode) { Oid indexOid; @@ -8941,6 +9030,8 @@ ATExecClusterOn(Relation rel, const char *indexName, LOCKMODE lockmode) /* And do the work */ mark_index_clustered(rel, indexOid, false); + + return indexOid; } /* @@ -9641,11 +9732,12 @@ ATPrepAddInherit(Relation child_rel) errmsg("cannot change inheritance of typed table"))); } -static void +static Oid ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode) { Relation parent_rel, catalogRelation; + Oid parent_oid; SysScanDesc scan; ScanKeyData key; HeapTuple inheritsTuple; @@ -9765,11 +9857,15 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode) inhseqno + 1, catalogRelation); + parent_oid = RelationGetRelid(parent_rel); + /* Now we're done with pg_inherits */ heap_close(catalogRelation, RowExclusiveLock); /* keep our lock on the parent relation until commit */ heap_close(parent_rel, NoLock); + + return parent_oid; } /* @@ -10038,10 +10134,11 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel) * coninhcount and conislocal for inherited constraints are adjusted in * exactly the same way. */ -static void +static Oid ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode) { Relation parent_rel; + Oid parent_oid; Relation catalogRelation; SysScanDesc scan; ScanKeyData key[3]; @@ -10210,6 +10307,8 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode) } } + parent_oid = RelationGetRelid(parent_rel); + systable_endscan(scan); heap_close(catalogRelation, RowExclusiveLock); @@ -10228,6 +10327,8 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode) /* keep our lock on the parent relation until commit */ heap_close(parent_rel, NoLock); + + return parent_oid; } /* @@ -10285,8 +10386,10 @@ drop_parent_dependency(Oid relid, Oid refclassid, Oid refobjid) * TABLE OF. All attname, atttypid, atttypmod and attcollation must match. The * subject table must not have inheritance parents. These restrictions ensure * that you cannot create a configuration impossible with CREATE TABLE OF alone. + * + * The OID of the type is returned. */ -static void +static Oid ATExecAddOf(Relation rel, const TypeName *ofTypename, LOCKMODE lockmode) { Oid relid = RelationGetRelid(rel); @@ -10415,6 +10518,8 @@ ATExecAddOf(Relation rel, const TypeName *ofTypename, LOCKMODE lockmode) heap_close(relationRelation, RowExclusiveLock); ReleaseSysCache(typetuple); + + return typeid; } /* diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h index e5c204d..fe1ddd9 100644 --- a/src/include/catalog/heap.h +++ b/src/include/catalog/heap.h @@ -27,6 +27,7 @@ typedef struct RawColumnDefault typedef struct CookedConstraint { ConstrType contype; /* CONSTR_DEFAULT or CONSTR_CHECK */ + Oid conoid; /* OID of the new element */ char *name; /* name, or NULL if none */ AttrNumber attnum; /* which attr (only for DEFAULT) */ Node *expr; /* transformed default or check expr */ @@ -99,7 +100,7 @@ extern List *AddRelationNewConstraints(Relation rel, bool is_local, bool is_internal); -extern void StoreAttrDefault(Relation rel, AttrNumber attnum, +extern Oid StoreAttrDefault(Relation rel, AttrNumber attnum, Node *expr, bool is_internal); extern Node *cookDefault(ParseState *pstate, diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h index e7cc7a0..e22ef8f 100644 --- a/src/include/catalog/index.h +++ b/src/include/catalog/index.h @@ -63,7 +63,7 @@ extern Oid index_create(Relation heapRelation, bool is_internal, bool if_not_exists); -extern void index_constraint_create(Relation heapRelation, +extern Oid index_constraint_create(Relation heapRelation, Oid indexRelationId, IndexInfo *indexInfo, const char *constraintName, -- 2.1.4 --gatW/ieO32f1wygP Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="0007-deparse-infrastructure-needed-for-command-deparsing.patch" From d03a8edcefd8f0ea1c46b315c446f96c61146a85 Mon Sep 17 00:00:00 2001 From: Alvaro Herrera Date: Wed, 24 Sep 2014 15:53:04 -0300 Subject: [PATCH 07/42] deparse: infrastructure needed for command deparsing This patch introduces unused infrastructure for command deparsing. There are three main parts: 1. A list (stash) of executed commands in Node format, stored in the event trigger state. At ddl_command_end, the stored items can be extracted. For now this only support "basic" commands (in particular not ALTER TABLE or GRANT). It's useful enough to cover all CREATE command as well as many simple ALTER forms. 2. Support code to enable writing routines that convert the Node format into a JSON blob. This JSON representation allows extracting and modifying individual parts of the command. 3. Code to convert the JSON blobs back into plain strings. No actual routines to convert Node into JSON is provided by this patch; that is left to later patches. This split is only presented as is for ease of review. --- src/backend/catalog/objectaddress.c | 118 +++++ src/backend/commands/event_trigger.c | 220 +++++++- src/backend/tcop/Makefile | 2 +- src/backend/tcop/deparse_utility.c | 957 +++++++++++++++++++++++++++++++++++ src/backend/tcop/utility.c | 2 + src/backend/utils/adt/Makefile | 2 +- src/backend/utils/adt/ddl_json.c | 676 +++++++++++++++++++++++++ src/backend/utils/adt/format_type.c | 113 ++++- src/backend/utils/adt/jsonb.c | 4 +- src/include/catalog/objectaddress.h | 2 + src/include/catalog/pg_proc.h | 5 +- src/include/commands/event_trigger.h | 3 + src/include/commands/extension.h | 2 +- src/include/nodes/parsenodes.h | 2 + src/include/tcop/deparse_utility.h | 61 +++ src/include/utils/builtins.h | 7 + 16 files changed, 2159 insertions(+), 17 deletions(-) create mode 100644 src/backend/tcop/deparse_utility.c create mode 100644 src/backend/utils/adt/ddl_json.c create mode 100644 src/include/tcop/deparse_utility.h diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c index d899dd7..700533c 100644 --- a/src/backend/catalog/objectaddress.c +++ b/src/backend/catalog/objectaddress.c @@ -849,6 +849,124 @@ get_object_address(ObjectType objtype, List *objname, List *objargs, } /* + * Return the OID of the catalog corresponding to the given object type + */ +Oid +get_objtype_catalog_oid(ObjectType objtype) +{ + Oid catalog_id; + + switch (objtype) + { + case OBJECT_INDEX: + case OBJECT_COMPOSITE: + case OBJECT_SEQUENCE: + case OBJECT_TABLE: + case OBJECT_VIEW: + case OBJECT_MATVIEW: + case OBJECT_FOREIGN_TABLE: + case OBJECT_COLUMN: + catalog_id = RelationRelationId; + break; + case OBJECT_RULE: + catalog_id = RewriteRelationId; + break; + case OBJECT_TRIGGER: + catalog_id = TriggerRelationId; + break; + case OBJECT_DOMCONSTRAINT: + case OBJECT_TABCONSTRAINT: + catalog_id = ConstraintRelationId; + break; + case OBJECT_POLICY: + catalog_id = PolicyRelationId; + break; + case OBJECT_DATABASE: + catalog_id = DatabaseRelationId; + break; + case OBJECT_EXTENSION: + catalog_id = ExtensionRelationId; + break; + case OBJECT_TABLESPACE: + catalog_id = TableSpaceRelationId; + break; + case OBJECT_ROLE: + catalog_id = AuthIdRelationId; + break; + case OBJECT_SCHEMA: + catalog_id = NamespaceRelationId; + break; + case OBJECT_LANGUAGE: + catalog_id = LanguageRelationId; + break; + case OBJECT_FDW: + catalog_id = ForeignDataWrapperRelationId; + break; + case OBJECT_FOREIGN_SERVER: + catalog_id = ForeignServerRelationId; + break; + case OBJECT_USER_MAPPING: + catalog_id = UserMappingRelationId; + break; + case OBJECT_EVENT_TRIGGER: + catalog_id = EventTriggerRelationId; + break; + case OBJECT_TYPE: + case OBJECT_DOMAIN: + catalog_id = TypeRelationId; + break; + case OBJECT_ATTRIBUTE: + catalog_id = TypeRelationId; /* XXX? */ + break; + case OBJECT_AGGREGATE: + catalog_id = ProcedureRelationId; + break; + case OBJECT_FUNCTION: + catalog_id = ProcedureRelationId; + break; + case OBJECT_OPERATOR: + catalog_id = OperatorRelationId; + break; + case OBJECT_COLLATION: + catalog_id = CollationRelationId; + break; + case OBJECT_CONVERSION: + catalog_id = ConversionRelationId; + break; + case OBJECT_OPCLASS: + catalog_id = OperatorClassRelationId; + break; + case OBJECT_OPFAMILY: + catalog_id = OperatorFamilyRelationId; + break; + case OBJECT_LARGEOBJECT: + catalog_id = LargeObjectRelationId; + break; + case OBJECT_CAST: + catalog_id = CastRelationId; + break; + case OBJECT_TSPARSER: + catalog_id = TSParserRelationId; + break; + case OBJECT_TSDICTIONARY: + catalog_id = TSDictionaryRelationId; + break; + case OBJECT_TSTEMPLATE: + catalog_id = TSTemplateRelationId; + break; + case OBJECT_TSCONFIGURATION: + catalog_id = TSConfigRelationId; + break; + default: + elog(ERROR, "unrecognized objtype: %d", (int) objtype); + /* placate compiler, in case it thinks elog might return */ + catalog_id = InvalidOid; + } + + return catalog_id; +} + +/* * Find an ObjectAddress for a type of object that is identified by an * unqualified name. */ diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c index dcf5b98..b258d72 100644 --- a/src/backend/commands/event_trigger.c +++ b/src/backend/commands/event_trigger.c @@ -25,16 +25,20 @@ #include "catalog/pg_type.h" #include "commands/dbcommands.h" #include "commands/event_trigger.h" +#include "commands/extension.h" #include "commands/trigger.h" #include "funcapi.h" #include "parser/parse_func.h" #include "pgstat.h" #include "lib/ilist.h" #include "miscadmin.h" +#include "tcop/deparse_utility.h" #include "utils/acl.h" #include "utils/builtins.h" #include "utils/evtcache.h" #include "utils/fmgroids.h" +#include "utils/json.h" +#include "utils/jsonb.h" #include "utils/lsyscache.h" #include "utils/memutils.h" #include "utils/rel.h" @@ -53,6 +57,7 @@ typedef struct EventTriggerQueryState int table_rewrite_reason; /* AT_REWRITE reason */ MemoryContext cxt; + List *stash; /* list of StashedCommand; see deparse_utility.h */ struct EventTriggerQueryState *previous; } EventTriggerQueryState; @@ -71,6 +76,7 @@ typedef enum EVENT_TRIGGER_COMMAND_TAG_NOT_RECOGNIZED } event_trigger_command_tag_check_result; +/* XXX merge this with ObjectTypeMap? */ static event_trigger_support_data event_trigger_support[] = { {"AGGREGATE", true}, {"CAST", true}, @@ -1060,6 +1066,7 @@ EventTriggerSupportsObjectType(ObjectType obtype) case OBJECT_CAST: case OBJECT_COLUMN: case OBJECT_COLLATION: + case OBJECT_COMPOSITE: case OBJECT_CONVERSION: case OBJECT_DEFAULT: case OBJECT_DOMAIN: @@ -1088,6 +1095,7 @@ EventTriggerSupportsObjectType(ObjectType obtype) case OBJECT_TSPARSER: case OBJECT_TSTEMPLATE: case OBJECT_TYPE: + case OBJECT_USER_MAPPING: case OBJECT_VIEW: return true; } @@ -1193,14 +1201,6 @@ EventTriggerBeginCompleteQuery(void) EventTriggerQueryState *state; MemoryContext cxt; - /* - * Currently, sql_drop and table_rewrite events are the only reason to - * have event trigger state at all; so if there are none, don't install - * one. - */ - if (!trackDroppedObjectsNeeded()) - return false; - cxt = AllocSetContextCreate(TopMemoryContext, "event trigger state", ALLOCSET_DEFAULT_MINSIZE, @@ -1211,7 +1211,7 @@ EventTriggerBeginCompleteQuery(void) slist_init(&(state->SQLDropList)); state->in_sql_drop = false; state->table_rewrite_oid = InvalidOid; - + state->stash = NIL; state->previous = currentEventTriggerState; currentEventTriggerState = state; @@ -1536,3 +1536,205 @@ pg_event_trigger_table_rewrite_reason(PG_FUNCTION_ARGS) PG_RETURN_INT32(currentEventTriggerState->table_rewrite_reason); } + +/* + * EventTriggerStashCommand + * Save data about a simple DDL command that was just executed + */ +void +EventTriggerStashCommand(Oid objectId, uint32 objectSubId, ObjectType objtype, + Oid secondaryOid, Node *parsetree) +{ + MemoryContext oldcxt; + StashedCommand *stashed; + + oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt); + + stashed = palloc(sizeof(StashedCommand)); + + stashed->type = SCT_Simple; + stashed->in_extension = creating_extension; + + stashed->d.simple.objectId = objectId; + stashed->d.simple.objtype = objtype; + stashed->d.simple.objectSubId = objectSubId; + stashed->d.simple.secondaryOid = secondaryOid; + stashed->parsetree = copyObject(parsetree); + + currentEventTriggerState->stash = lappend(currentEventTriggerState->stash, + stashed); + + MemoryContextSwitchTo(oldcxt); +} + +Datum +pg_event_trigger_get_creation_commands(PG_FUNCTION_ARGS) +{ + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + TupleDesc tupdesc; + Tuplestorestate *tupstore; + MemoryContext per_query_ctx; + MemoryContext oldcontext; + ListCell *lc; + + /* + * Protect this function from being called out of context + */ + if (!currentEventTriggerState) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("%s can only be called in an event trigger function", + "pg_event_trigger_get_creation_commands()"))); + + /* check to see if caller supports us returning a tuplestore */ + if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("set-valued function called in context that cannot accept a set"))); + if (!(rsinfo->allowedModes & SFRM_Materialize)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("materialize mode required, but it is not allowed in this context"))); + + /* Build a tuple descriptor for our result type */ + if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) + elog(ERROR, "return type must be a row type"); + + /* Build tuplestore to hold the result rows */ + per_query_ctx = rsinfo->econtext->ecxt_per_query_memory; + oldcontext = MemoryContextSwitchTo(per_query_ctx); + + tupstore = tuplestore_begin_heap(true, false, work_mem); + rsinfo->returnMode = SFRM_Materialize; + rsinfo->setResult = tupstore; + rsinfo->setDesc = tupdesc; + + MemoryContextSwitchTo(oldcontext); + + foreach(lc, currentEventTriggerState->stash) + { + StashedCommand *cmd = lfirst(lc); + char *command; + + /* + * For IF NOT EXISTS commands that attempt to create an existing + * object, the returned OID is Invalid; in those cases, return an empty + * command instead of trying to soldier on. + * + * One might think that a viable alternative would be to look up the + * Oid of the existing object and run the deparse with that. But since + * the parse tree might be different from the one that created the + * object in the first place, we might not end up in a consistent state + * anyway. + */ + if (cmd->type == SCT_Simple && + !OidIsValid(cmd->d.simple.objectId)) + continue; + + command = deparse_utility_command(cmd); + + /* + * Some parse trees return NULL when deparse is attempted; we don't + * emit anything for them. + */ + if (command != NULL) + { + Datum values[9]; + bool nulls[9]; + ObjectAddress addr; + int i = 0; + + MemSet(nulls, 0, sizeof(nulls)); + + if (cmd->type == SCT_Simple) + { + Oid classId; + Oid objId; + uint32 objSubId; + const char *tag; + char *identity; + char *type; + char *schema = NULL; + + if (cmd->type == SCT_Simple) + { + classId = get_objtype_catalog_oid(cmd->d.simple.objtype); + objId = cmd->d.simple.objectId; + objSubId = cmd->d.simple.objectSubId; + } + + tag = CreateCommandTag(cmd->parsetree); + addr.classId = classId; + addr.objectId = objId; + addr.objectSubId = objSubId; + + type = getObjectTypeDescription(&addr); + identity = getObjectIdentity(&addr); + + /* + * Obtain schema name, if any ("pg_temp" if a temp object) + */ + if (is_objectclass_supported(addr.classId)) + { + AttrNumber nspAttnum; + + nspAttnum = get_object_attnum_namespace(addr.classId); + if (nspAttnum != InvalidAttrNumber) + { + Relation catalog; + HeapTuple objtup; + Oid schema_oid; + bool isnull; + + catalog = heap_open(addr.classId, AccessShareLock); + objtup = get_catalog_object_by_oid(catalog, + addr.objectId); + if (!HeapTupleIsValid(objtup)) + elog(ERROR, "cache lookup failed for object %u/%u", + addr.classId, addr.objectId); + schema_oid = heap_getattr(objtup, nspAttnum, + RelationGetDescr(catalog), &isnull); + if (isnull) + elog(ERROR, "invalid null namespace in object %u/%u/%d", + addr.classId, addr.objectId, addr.objectSubId); + if (isAnyTempNamespace(schema_oid)) + schema = pstrdup("pg_temp"); + else + schema = get_namespace_name(schema_oid); + + heap_close(catalog, AccessShareLock); + } + } + + /* classid */ + values[i++] = ObjectIdGetDatum(addr.classId); + /* objid */ + values[i++] = ObjectIdGetDatum(addr.objectId); + /* objsubid */ + values[i++] = Int32GetDatum(addr.objectSubId); + /* command tag */ + values[i++] = CStringGetTextDatum(tag); + /* object_type */ + values[i++] = CStringGetTextDatum(type); + /* schema */ + if (schema == NULL) + nulls[i++] = true; + else + values[i++] = CStringGetTextDatum(schema); + /* identity */ + values[i++] = CStringGetTextDatum(identity); + /* in_extension */ + values[i++] = BoolGetDatum(cmd->in_extension); + /* command */ + values[i++] = CStringGetTextDatum(command); + } + + tuplestore_putvalues(tupstore, tupdesc, values, nulls); + } + } + + /* clean up and return the tuplestore */ + tuplestore_donestoring(tupstore); + + PG_RETURN_VOID(); +} diff --git a/src/backend/tcop/Makefile b/src/backend/tcop/Makefile index 674302f..34acdce 100644 --- a/src/backend/tcop/Makefile +++ b/src/backend/tcop/Makefile @@ -12,7 +12,7 @@ subdir = src/backend/tcop top_builddir = ../../.. include $(top_builddir)/src/Makefile.global -OBJS= dest.o fastpath.o postgres.o pquery.o utility.o +OBJS= dest.o deparse_utility.o fastpath.o postgres.o pquery.o utility.o ifneq (,$(filter $(PORTNAME),cygwin win32)) override CPPFLAGS += -DWIN32_STACK_RLIMIT=$(WIN32_STACK_RLIMIT) diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c new file mode 100644 index 0000000..8463321 --- /dev/null +++ b/src/backend/tcop/deparse_utility.c @@ -0,0 +1,957 @@ +/*------------------------------------------------------------------------- + * + * deparse_utility.c + * Functions to convert utility commands to machine-parseable + * representation + * + * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * NOTES + * + * This is intended to provide JSON blobs representing DDL commands, which can + * later be re-processed into plain strings by well-defined sprintf-like + * expansion. These JSON objects are intended to allow for machine-editing of + * the commands, by replacing certain nodes within the objects. + * + * Much of the information in the output blob actually comes from system + * catalogs, not from the command parse node, as it is impossible to reliably + * construct a fully-specified command (i.e. one not dependent on search_path + * etc) looking only at the parse node. + * + * IDENTIFICATION + * src/backend/tcop/deparse_utility.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/htup_details.h" +#include "catalog/heap.h" +#include "catalog/index.h" +#include "catalog/namespace.h" +#include "catalog/pg_aggregate.h" +#include "catalog/pg_class.h" +#include "catalog/pg_collation.h" +#include "catalog/pg_constraint.h" +#include "catalog/pg_depend.h" +#include "catalog/pg_inherits.h" +#include "catalog/pg_operator.h" +#include "catalog/pg_proc.h" +#include "catalog/pg_trigger.h" +#include "catalog/pg_type.h" +#include "commands/defrem.h" +#include "commands/sequence.h" +#include "funcapi.h" +#include "lib/ilist.h" +#include "lib/stringinfo.h" +#include "nodes/makefuncs.h" +#include "nodes/parsenodes.h" +#include "parser/analyze.h" +#include "parser/parse_collate.h" +#include "parser/parse_expr.h" +#include "parser/parse_relation.h" +#include "parser/parse_type.h" +#include "rewrite/rewriteHandler.h" +#include "tcop/deparse_utility.h" +#include "tcop/utility.h" +#include "utils/builtins.h" +#include "utils/fmgroids.h" +#include "utils/jsonb.h" +#include "utils/lsyscache.h" +#include "utils/memutils.h" +#include "utils/rel.h" +#include "utils/ruleutils.h" +#include "utils/syscache.h" + + +/* + * Before they are turned into JSONB representation, each command is + * represented as an object tree, using the structs below. + */ +typedef enum +{ + ObjTypeNull, + ObjTypeBool, + ObjTypeString, + ObjTypeArray, + ObjTypeInteger, + ObjTypeFloat, + ObjTypeObject +} ObjType; + +typedef struct ObjTree +{ + slist_head params; + int numParams; +} ObjTree; + +typedef struct ObjElem +{ + char *name; + ObjType objtype; + + union + { + bool boolean; + char *string; + int64 integer; + float8 flt; + ObjTree *object; + List *array; + } value; + slist_node node; +} ObjElem; + +static ObjElem *new_null_object(void); +static ObjElem *new_bool_object(bool value); +static ObjElem *new_string_object(char *value); +static ObjElem *new_object_object(ObjTree *value); +static ObjElem *new_array_object(List *array); +static ObjElem *new_integer_object(int64 value); +static ObjElem *new_float_object(float8 value); +static void append_null_object(ObjTree *tree, char *name); +static void append_bool_object(ObjTree *tree, char *name, bool value); +static void append_string_object(ObjTree *tree, char *name, char *value); +static void append_object_object(ObjTree *tree, char *name, ObjTree *value); +static void append_array_object(ObjTree *tree, char *name, List *array); +#ifdef NOT_USED +static void append_integer_object(ObjTree *tree, char *name, int64 value); +#endif +static void append_float_object(ObjTree *tree, char *name, float8 value); +static inline void append_premade_object(ObjTree *tree, ObjElem *elem); +static JsonbValue *objtree_to_jsonb_rec(ObjTree *tree, JsonbParseState *state); + +/* + * Allocate a new object tree to store parameter values. + */ +static ObjTree * +new_objtree(void) +{ + ObjTree *params; + + params = palloc(sizeof(ObjTree)); + params->numParams = 0; + slist_init(¶ms->params); + + return params; +} + +/* + * Allocate a new object tree to store parameter values -- varargs version. + * + * The "fmt" argument is used to append as a "fmt" element in the output blob. + * numobjs indicates the number of extra elements to append; for each one, a + * name (string), type (from the ObjType enum) and value must be supplied. The + * value must match the type given; for instance, ObjTypeInteger requires an + * int64, ObjTypeString requires a char *, ObjTypeArray requires a list (of + * ObjElem), ObjTypeObject requires an ObjTree, and so on. Each element type * + * must match the conversion specifier given in the format string, as described + * in pg_event_trigger_expand_command, q.v. + * + * Note we don't have the luxury of sprintf-like compiler warnings for + * malformed argument lists. + */ +static ObjTree * +new_objtree_VA(char *fmt, int numobjs,...) +{ + ObjTree *tree; + va_list args; + int i; + + /* Set up the toplevel object and its "fmt" */ + tree = new_objtree(); + append_string_object(tree, "fmt", fmt); + + /* And process the given varargs */ + va_start(args, numobjs); + for (i = 0; i < numobjs; i++) + { + ObjTree *value; + ObjType type; + ObjElem *elem; + char *name; + char *strval; + bool boolval; + List *list; + int64 number; + float8 fnumber; + + name = va_arg(args, char *); + type = va_arg(args, ObjType); + + /* Null params don't have a value (obviously) */ + if (type == ObjTypeNull) + { + append_null_object(tree, name); + continue; + } + + /* + * For all other param types there must be a value in the varargs. + * Fetch it and add the fully formed subobject into the main object. + */ + switch (type) + { + case ObjTypeBool: + boolval = va_arg(args, int); + elem = new_bool_object(boolval); + break; + case ObjTypeString: + strval = va_arg(args, char *); + elem = new_string_object(strval); + break; + case ObjTypeObject: + value = va_arg(args, ObjTree *); + elem = new_object_object(value); + break; + case ObjTypeArray: + list = va_arg(args, List *); + elem = new_array_object(list); + break; + case ObjTypeInteger: + number = va_arg(args, int64); + elem = new_integer_object(number); + break; + case ObjTypeFloat: + fnumber = va_arg(args, double); + elem = new_float_object(fnumber); + break; + default: + elog(ERROR, "invalid parameter type %d", type); + } + + elem->name = name; + append_premade_object(tree, elem); + } + + va_end(args); + return tree; +} + +/* Allocate a new parameter with a NULL value */ +static ObjElem * +new_null_object(void) +{ + ObjElem *param; + + param = palloc0(sizeof(ObjElem)); + + param->name = NULL; + param->objtype = ObjTypeNull; + + return param; +} + +/* Append a NULL object to a tree */ +static void +append_null_object(ObjTree *tree, char *name) +{ + ObjElem *param; + + param = new_null_object(); + param->name = name; + append_premade_object(tree, param); +} + +/* Allocate a new boolean parameter */ +static ObjElem * +new_bool_object(bool value) +{ + ObjElem *param; + + param = palloc0(sizeof(ObjElem)); + param->name = NULL; + param->objtype = ObjTypeBool; + param->value.boolean = value; + + return param; +} + +/* Append a boolean parameter to a tree */ +static void +append_bool_object(ObjTree *tree, char *name, bool value) +{ + ObjElem *param; + + param = new_bool_object(value); + param->name = name; + append_premade_object(tree, param); +} + +/* Allocate a new string object */ +static ObjElem * +new_string_object(char *value) +{ + ObjElem *param; + + Assert(value); + + param = palloc0(sizeof(ObjElem)); + param->name = NULL; + param->objtype = ObjTypeString; + param->value.string = value; + + return param; +} + +/* + * Append a string parameter to a tree. + */ +static void +append_string_object(ObjTree *tree, char *name, char *value) +{ + ObjElem *param; + + Assert(name); + param = new_string_object(value); + param->name = name; + append_premade_object(tree, param); +} + +static ObjElem * +new_integer_object(int64 value) +{ + ObjElem *param; + + param = palloc0(sizeof(ObjElem)); + param->name = NULL; + param->objtype = ObjTypeInteger; + param->value.integer = value; + + return param; +} + +static ObjElem * +new_float_object(float8 value) +{ + ObjElem *param; + + param = palloc0(sizeof(ObjElem)); + param->name = NULL; + param->objtype = ObjTypeFloat; + param->value.flt = value; + + return param; +} + +#ifdef NOT_USED +/* + * Append an int64 parameter to a tree. + */ +static void +append_integer_object(ObjTree *tree, char *name, int64 value) +{ + ObjElem *param; + + param = new_integer_object(value); + param->name = name; + append_premade_object(tree, param); +} +#endif + +/* + * Append a float8 parameter to a tree. + */ +static void +append_float_object(ObjTree *tree, char *name, float8 value) +{ + ObjElem *param; + + param = new_float_object(value); + param->name = name; + append_premade_object(tree, param); +} + +/* Allocate a new object parameter */ +static ObjElem * +new_object_object(ObjTree *value) +{ + ObjElem *param; + + param = palloc0(sizeof(ObjElem)); + param->name = NULL; + param->objtype = ObjTypeObject; + param->value.object = value; + + return param; +} + +/* Append an object parameter to a tree */ +static void +append_object_object(ObjTree *tree, char *name, ObjTree *value) +{ + ObjElem *param; + + Assert(name); + param = new_object_object(value); + param->name = name; + append_premade_object(tree, param); +} + +/* Allocate a new array parameter */ +static ObjElem * +new_array_object(List *array) +{ + ObjElem *param; + + param = palloc0(sizeof(ObjElem)); + param->name = NULL; + param->objtype = ObjTypeArray; + param->value.array = array; + + return param; +} + +/* Append an array parameter to a tree */ +static void +append_array_object(ObjTree *tree, char *name, List *array) +{ + ObjElem *param; + + param = new_array_object(array); + param->name = name; + append_premade_object(tree, param); +} + +/* Append a preallocated parameter to a tree */ +static inline void +append_premade_object(ObjTree *tree, ObjElem *elem) +{ + slist_push_head(&tree->params, &elem->node); + tree->numParams++; +} + +/* + * Helper for objtree_to_jsonb: process an individual element from an object or + * an array into the output parse state. + */ +static void +objtree_to_jsonb_element(JsonbParseState *state, ObjElem *object, + JsonbIteratorToken elem_token) +{ + ListCell *cell; + JsonbValue val; + + switch (object->objtype) + { + case ObjTypeNull: + val.type = jbvNull; + pushJsonbValue(&state, elem_token, &val); + break; + + case ObjTypeString: + val.type = jbvString; + val.val.string.len = strlen(object->value.string); + val.val.string.val = object->value.string; + pushJsonbValue(&state, elem_token, &val); + break; + + case ObjTypeInteger: + val.type = jbvNumeric; + val.val.numeric = (Numeric) + DatumGetNumeric(DirectFunctionCall1(int8_numeric, + object->value.integer)); + pushJsonbValue(&state, elem_token, &val); + break; + + case ObjTypeFloat: + val.type = jbvNumeric; + val.val.numeric = (Numeric) + DatumGetNumeric(DirectFunctionCall1(float8_numeric, + object->value.integer)); + pushJsonbValue(&state, elem_token, &val); + break; + + case ObjTypeBool: + val.type = jbvBool; + val.val.boolean = object->value.boolean; + pushJsonbValue(&state, elem_token, &val); + break; + + case ObjTypeObject: + /* recursively add the object into the existing parse state */ + objtree_to_jsonb_rec(object->value.object, state); + break; + + case ObjTypeArray: + pushJsonbValue(&state, WJB_BEGIN_ARRAY, NULL); + foreach(cell, object->value.array) + { + ObjElem *elem = lfirst(cell); + + objtree_to_jsonb_element(state, elem, WJB_ELEM); + } + pushJsonbValue(&state, WJB_END_ARRAY, NULL); + break; + + default: + elog(ERROR, "unrecognized object type %d", object->objtype); + break; + } +} + +/* + * Recursive helper for objtree_to_jsonb + */ +static JsonbValue * +objtree_to_jsonb_rec(ObjTree *tree, JsonbParseState *state) +{ + slist_iter iter; + + pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + + slist_foreach(iter, &tree->params) + { + ObjElem *object = slist_container(ObjElem, node, iter.cur); + JsonbValue key; + + /* Push the key first */ + key.type = jbvString; + key.val.string.len = strlen(object->name); + key.val.string.val = object->name; + pushJsonbValue(&state, WJB_KEY, &key); + + /* Then process the value according to its type */ + objtree_to_jsonb_element(state, object, WJB_VALUE); + } + + return pushJsonbValue(&state, WJB_END_OBJECT, NULL); +} + +/* + * Create a JSONB representation from an ObjTree. + */ +static Jsonb * +objtree_to_jsonb(ObjTree *tree) +{ + JsonbValue *value; + + value = objtree_to_jsonb_rec(tree, NULL); + return JsonbValueToJsonb(value); +} + +/* + * A helper routine to setup %{}T elements. + */ +static ObjTree * +new_objtree_for_type(Oid typeId, int32 typmod) +{ + ObjTree *typeParam; + Oid typnspid; + char *typnsp; + char *typename; + char *typmodstr; + bool is_array; + + format_type_detailed(typeId, typmod, + &typnspid, &typename, &typmodstr, &is_array); + + if (!OidIsValid(typnspid)) + typnsp = pstrdup(""); + else if (isAnyTempNamespace(typnspid)) + typnsp = pstrdup("pg_temp"); + else + typnsp = get_namespace_name(typnspid); + + /* We don't use new_objtree_VA here because types don't have a "fmt" */ + typeParam = new_objtree(); + append_string_object(typeParam, "schemaname", typnsp); + append_string_object(typeParam, "typename", typename); + append_string_object(typeParam, "typmod", typmodstr); + append_bool_object(typeParam, "is_array", is_array); + + return typeParam; +} + +/* + * A helper routine to setup %{}D and %{}O elements + * + * Elements "schemaname" and "objname" are set. If the namespace OID + * corresponds to a temp schema, that's set to "pg_temp". + * + * The difference between those two element types is whether the objname will + * be quoted as an identifier or not, which is not something that this routine + * concerns itself with; that will be up to the expand function. + */ +static ObjTree * +new_objtree_for_qualname(Oid nspid, char *name) +{ + ObjTree *qualified; + char *namespace; + + /* + * We don't use new_objtree_VA here because these names don't have a "fmt" + */ + qualified = new_objtree(); + if (isAnyTempNamespace(nspid)) + namespace = pstrdup("pg_temp"); + else + namespace = get_namespace_name(nspid); + append_string_object(qualified, "schemaname", namespace); + append_string_object(qualified, "objname", pstrdup(name)); + + return qualified; +} + +/* + * A helper routine to setup %{}D and %{}O elements, with the object specified + * by classId/objId + * + * Elements "schemaname" and "objname" are set. If the object is a temporary + * object, the schema name is set to "pg_temp". + */ +static ObjTree * +new_objtree_for_qualname_id(Oid classId, Oid objectId) +{ + ObjTree *qualified; + Relation catalog; + HeapTuple catobj; + Datum objnsp; + Datum objname; + AttrNumber Anum_name; + AttrNumber Anum_namespace; + bool isnull; + + catalog = heap_open(classId, AccessShareLock); + + catobj = get_catalog_object_by_oid(catalog, objectId); + if (!catobj) + elog(ERROR, "cache lookup failed for object %u of catalog \"%s\"", + objectId, RelationGetRelationName(catalog)); + Anum_name = get_object_attnum_name(classId); + Anum_namespace = get_object_attnum_namespace(classId); + + objnsp = heap_getattr(catobj, Anum_namespace, RelationGetDescr(catalog), + &isnull); + if (isnull) + elog(ERROR, "unexpected NULL namespace"); + objname = heap_getattr(catobj, Anum_name, RelationGetDescr(catalog), + &isnull); + if (isnull) + elog(ERROR, "unexpected NULL name"); + + qualified = new_objtree_for_qualname(DatumGetObjectId(objnsp), + NameStr(*DatumGetName(objname))); + heap_close(catalog, AccessShareLock); + + return qualified; +} + +/* + * Handle deparsing of simple commands. + * + * This function contains a large switch that mirrors that in + * ProcessUtilitySlow. All cases covered there should also be covered here. + */ +static ObjTree * +deparse_simple_command(StashedCommand *cmd) +{ + Oid objectId; + Node *parsetree; + ObjTree *command; + + Assert(cmd->type == SCT_Simple); + + parsetree = cmd->parsetree; + objectId = cmd->d.simple.objectId; + + /* This switch needs to handle everything that ProcessUtilitySlow does */ + switch (nodeTag(parsetree)) + { + case T_CreateSchemaStmt: + command = NULL; + break; + + case T_CreateStmt: + command = NULL; + break; + + case T_CreateForeignTableStmt: + elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree)); + break; + + case T_AlterTableStmt: + case T_AlterTableMoveAllStmt: + /* handled elsewhere */ + elog(ERROR, "unexpected command type %s", CreateCommandTag(parsetree)); + break; + + case T_AlterDomainStmt: + elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree)); + break; + + /* other local objects */ + case T_DefineStmt: + command = NULL; + break; + + case T_IndexStmt: + command = NULL; + break; + + case T_CreateExtensionStmt: + command = NULL; + break; + + case T_AlterExtensionStmt: + command = NULL; + break; + + case T_AlterExtensionContentsStmt: + elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree)); + break; + + case T_CreateFdwStmt: + elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree)); + break; + + case T_AlterFdwStmt: + elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree)); + break; + + case T_CreateForeignServerStmt: + elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree)); + break; + + case T_AlterForeignServerStmt: + elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree)); + break; + + case T_CreateUserMappingStmt: + elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree)); + break; + + case T_AlterUserMappingStmt: + elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree)); + break; + + case T_DropUserMappingStmt: + /* goes through performDeletion; no action needed here */ + command = NULL; + break; + + case T_ImportForeignSchemaStmt: + /* generated commands are stashed individually */ + elog(ERROR, "unexpected command %s", CreateCommandTag(parsetree)); + break; + + case T_CompositeTypeStmt: /* CREATE TYPE (composite) */ + command = NULL; + break; + + case T_CreateEnumStmt: /* CREATE TYPE AS ENUM */ + command = NULL; + break; + + case T_CreateRangeStmt: /* CREATE TYPE AS RANGE */ + command = NULL; + break; + + case T_AlterEnumStmt: + command = NULL; + break; + + case T_ViewStmt: /* CREATE VIEW */ + command = NULL; + break; + + case T_CreateFunctionStmt: + command = NULL; + break; + + case T_AlterFunctionStmt: + command = NULL; + break; + + case T_RuleStmt: + command = NULL; + break; + + case T_CreateSeqStmt: + command = NULL; + break; + + case T_AlterSeqStmt: + command = NULL; + break; + + case T_CreateTableAsStmt: + /* XXX handle at least the CREATE MATVIEW case? */ + elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree)); + break; + + case T_RefreshMatViewStmt: + elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree)); + break; + + case T_CreateTrigStmt: + command = NULL; + break; + + case T_CreatePLangStmt: + elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree)); + break; + + case T_CreateDomainStmt: + command = NULL; + break; + + case T_CreateConversionStmt: + command = NULL; + break; + + case T_CreateCastStmt: + elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree)); + break; + + case T_CreateOpClassStmt: + elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree)); + break; + + case T_CreateOpFamilyStmt: + command = NULL; + break; + + case T_AlterOpFamilyStmt: + elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree)); + break; + + case T_AlterTSDictionaryStmt: + elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree)); + break; + + case T_AlterTSConfigurationStmt: + elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree)); + break; + + case T_DropStmt: + /* goes through performDeletion; no action needed here */ + command = NULL; + break; + + case T_RenameStmt: + command = NULL; + break; + + case T_AlterObjectSchemaStmt: + elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree)); + break; + + case T_AlterOwnerStmt: + command = NULL; + break; + + case T_CommentStmt: + command = NULL; + break; + + case T_GrantStmt: + /* handled elsewhere */ + elog(ERROR, "unexpected command type %s", CreateCommandTag(parsetree)); + break; + + case T_DropOwnedStmt: + /* goes through performDeletion; no action needed here */ + command = NULL; + break; + + case T_AlterDefaultPrivilegesStmt: + elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree)); + break; + + case T_CreatePolicyStmt: /* CREATE POLICY */ + elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree)); + break; + + case T_AlterPolicyStmt: /* ALTER POLICY */ + elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree)); + break; + + case T_SecLabelStmt: + elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree)); + break; + + default: + command = NULL; + elog(LOG, "unrecognized node type: %d", + (int) nodeTag(parsetree)); + } + + return command; +} + +/* + * Given a utility command parsetree and the OID of the corresponding object, + * return a JSON representation of the command. + * + * The command is expanded fully, so that there are no ambiguities even in the + * face of search_path changes. + */ +char * +deparse_utility_command(StashedCommand *cmd) +{ + OverrideSearchPath *overridePath; + MemoryContext oldcxt; + MemoryContext tmpcxt; + ObjTree *tree; + char *command; + StringInfoData str; + + /* + * Allocate everything done by the deparsing routines into a temp context, + * to avoid having to sprinkle them with memory handling code; but allocate + * the output StringInfo before switching. + */ + initStringInfo(&str); + tmpcxt = AllocSetContextCreate(CurrentMemoryContext, + "deparse ctx", + ALLOCSET_DEFAULT_MINSIZE, + ALLOCSET_DEFAULT_INITSIZE, + ALLOCSET_DEFAULT_MAXSIZE); + oldcxt = MemoryContextSwitchTo(tmpcxt); + + /* + * Many routines underlying this one will invoke ruleutils.c functionality + * in order to obtain deparsed versions of expressions. In such results, + * we want all object names to be qualified, so that results are "portable" + * to environments with different search_path settings. Rather than inject + * what would be repetitive calls to override search path all over the + * place, we do it centrally here. + */ + overridePath = GetOverrideSearchPath(CurrentMemoryContext); + overridePath->schemas = NIL; + overridePath->addCatalog = false; + overridePath->addTemp = false; + PushOverrideSearchPath(overridePath); + + switch (cmd->type) + { + case SCT_Simple: + tree = deparse_simple_command(cmd); + break; + default: + elog(ERROR, "unexpected deparse node type %d", cmd->type); + } + + PopOverrideSearchPath(); + + if (tree) + { + Jsonb *jsonb; + + jsonb = objtree_to_jsonb(tree); + command = JsonbToCString(&str, &jsonb->root, 128); + } + else + command = NULL; + + /* + * Clean up. Note that since we created the StringInfo in the caller's + * context, the output string is not deleted here. + */ + MemoryContextSwitchTo(oldcxt); + MemoryContextDelete(tmpcxt); + + return command; +} diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 13d03c5..ee6799a 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -873,6 +873,8 @@ standard_ProcessUtility(Node *parsetree, * The "Slow" variant of ProcessUtility should only receive statements * supported by the event triggers facility. Therefore, we always * perform the trigger support calls if the context allows it. + * + * See deparse_utility_command, which must be kept in sync with this. */ static void ProcessUtilitySlow(Node *parsetree, diff --git a/src/backend/utils/adt/Makefile b/src/backend/utils/adt/Makefile index 20e5ff1..110c6ee 100644 --- a/src/backend/utils/adt/Makefile +++ b/src/backend/utils/adt/Makefile @@ -18,7 +18,7 @@ endif # keep this list arranged alphabetically or it gets to be a mess OBJS = acl.o arrayfuncs.o array_selfuncs.o array_typanalyze.o \ array_userfuncs.o arrayutils.o ascii.o bool.o \ - cash.o char.o date.o datetime.o datum.o dbsize.o domains.o \ + cash.o char.o date.o datetime.o datum.o dbsize.o ddl_json.o domains.o \ encode.o enum.o float.o format_type.o formatting.o genfile.o \ geo_ops.o geo_selfuncs.o inet_cidr_ntop.o inet_net_pton.o int.o \ int8.o json.o jsonb.o jsonb_gin.o jsonb_op.o jsonb_util.o \ diff --git a/src/backend/utils/adt/ddl_json.c b/src/backend/utils/adt/ddl_json.c new file mode 100644 index 0000000..5b0326c --- /dev/null +++ b/src/backend/utils/adt/ddl_json.c @@ -0,0 +1,676 @@ +/*------------------------------------------------------------------------- + * + * ddl_json.c + * JSON code related to DDL command deparsing + * + * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/backend/utils/adt/ddl_json.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "lib/stringinfo.h" +#include "utils/builtins.h" +#include "utils/jsonb.h" + + +typedef enum +{ + SpecTypename, + SpecOperatorname, + SpecDottedName, + SpecString, + SpecNumber, + SpecStringLiteral, + SpecIdentifier +} convSpecifier; + +typedef enum +{ + tv_absent, + tv_true, + tv_false +} trivalue; + +static void expand_one_jsonb_element(StringInfo out, char *param, + JsonbValue *jsonval, convSpecifier specifier, + const char *fmt); +static void expand_jsonb_array(StringInfo out, char *param, + JsonbValue *jsonarr, char *arraysep, + convSpecifier specifier, const char *fmt); +static void fmtstr_error_callback(void *arg); + +static trivalue +find_bool_in_jsonbcontainer(JsonbContainer *container, char *keyname) +{ + JsonbValue key; + JsonbValue *value; + bool result; + + key.type = jbvString; + key.val.string.val = keyname; + key.val.string.len = strlen(keyname); + value = findJsonbValueFromContainer(container, + JB_FOBJECT, &key); + if (value == NULL) + return tv_absent; + if (value->type != jbvBool) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("element \"%s\" is not of type boolean", + keyname))); + result = value->val.boolean ? tv_true : tv_false; + pfree(value); + + return result; +} + +/* + * Given a JsonbContainer, find the JsonbValue with the given key name in it. + * If it's of a type other than jbvString, an error is raised. If it doesn't + * exist, an error is raised if missing_ok; otherwise return NULL. + * + * If it exists and is a string, a freshly palloc'ed copy is returned. + * + * If *length is not NULL, it is set to the length of the string. + */ +static char * +find_string_in_jsonbcontainer(JsonbContainer *container, char *keyname, + bool missing_ok, int *length) +{ + JsonbValue key; + JsonbValue *value; + char *str; + + /* XXX verify that this is an object, not an array */ + + key.type = jbvString; + key.val.string.val = keyname; + key.val.string.len = strlen(keyname); + value = findJsonbValueFromContainer(container, + JB_FOBJECT, &key); + if (value == NULL) + { + if (missing_ok) + return NULL; + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("missing element \"%s\" in json object", keyname))); + } + + str = pnstrdup(value->val.string.val, value->val.string.len); + if (length) + *length = value->val.string.len; + pfree(value); + return str; +} + +#define ADVANCE_PARSE_POINTER(ptr,end_ptr) \ + do { \ + if (++(ptr) >= (end_ptr)) \ + ereport(ERROR, \ + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), \ + errmsg("unterminated format specifier"))); \ + } while (0) + +/* + * Recursive helper for pg_event_trigger_expand_command + * + * Find the "fmt" element in the given container, and expand it into the + * provided StringInfo. + */ +static void +expand_fmt_recursive(JsonbContainer *container, StringInfo out) +{ + JsonbValue key; + JsonbValue *value; + const char *cp; + const char *start_ptr; + const char *end_ptr; + int len; + + start_ptr = find_string_in_jsonbcontainer(container, "fmt", false, &len); + end_ptr = start_ptr + len; + + for (cp = start_ptr; cp < end_ptr; cp++) + { + convSpecifier specifier; + bool is_array; + char *param = NULL; + char *arraysep = NULL; + + if (*cp != '%') + { + appendStringInfoCharMacro(out, *cp); + continue; + } + + is_array = false; + + ADVANCE_PARSE_POINTER(cp, end_ptr); + + /* Easy case: %% outputs a single % */ + if (*cp == '%') + { + appendStringInfoCharMacro(out, *cp); + continue; + } + + /* + * Scan the mandatory element name. Allow for an array separator + * (which may be the empty string) to be specified after colon. + */ + if (*cp == '{') + { + StringInfoData parbuf; + StringInfoData arraysepbuf; + StringInfo appendTo; + + initStringInfo(&parbuf); + appendTo = &parbuf; + + ADVANCE_PARSE_POINTER(cp, end_ptr); + for (; cp < end_ptr;) + { + if (*cp == ':') + { + /* + * found array separator delimiter; element name is now + * complete, start filling the separator. + */ + initStringInfo(&arraysepbuf); + appendTo = &arraysepbuf; + is_array = true; + ADVANCE_PARSE_POINTER(cp, end_ptr); + continue; + } + + if (*cp == '}') + { + ADVANCE_PARSE_POINTER(cp, end_ptr); + break; + } + appendStringInfoCharMacro(appendTo, *cp); + ADVANCE_PARSE_POINTER(cp, end_ptr); + } + param = parbuf.data; + if (is_array) + arraysep = arraysepbuf.data; + } + if (param == NULL) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("missing conversion name in conversion specifier"))); + + switch (*cp) + { + case 'I': + specifier = SpecIdentifier; + break; + case 'D': + specifier = SpecDottedName; + break; + case 's': + specifier = SpecString; + break; + case 'L': + specifier = SpecStringLiteral; + break; + case 'T': + specifier = SpecTypename; + break; + case 'O': + specifier = SpecOperatorname; + break; + case 'n': + specifier = SpecNumber; + break; + default: + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid conversion specifier \"%c\"", *cp))); + } + + /* + * Obtain the element to be expanded. + */ + key.type = jbvString; + key.val.string.val = param; + key.val.string.len = strlen(param); + + value = findJsonbValueFromContainer(container, JB_FOBJECT, &key); + + /* Validate that we got an array if the format string specified one. */ + + /* And finally print out the data */ + if (is_array) + expand_jsonb_array(out, param, value, arraysep, specifier, start_ptr); + else + expand_one_jsonb_element(out, param, value, specifier, start_ptr); + } +} + +/* + * Expand a json value as an identifier. The value must be of type string. + */ +static void +expand_jsonval_identifier(StringInfo buf, JsonbValue *jsonval) +{ + char *str; + + Assert(jsonval->type == jbvString); + + str = pnstrdup(jsonval->val.string.val, + jsonval->val.string.len); + appendStringInfoString(buf, quote_identifier(str)); + pfree(str); +} + +/* + * Expand a json value as a dot-separated-name. The value must be of type + * object and must contain elements "schemaname" (optional), "objname" + * (mandatory), "attrname" (optional). Double quotes are added to each element + * as necessary, and dot separators where needed. + * + * One day we might need a "catalog" element as well, but no current use case + * needs that. + */ +static void +expand_jsonval_dottedname(StringInfo buf, JsonbValue *jsonval) +{ + char *str; + + str = find_string_in_jsonbcontainer(jsonval->val.binary.data, + "schemaname", true, NULL); + if (str) + { + appendStringInfo(buf, "%s.", quote_identifier(str)); + pfree(str); + } + + str = find_string_in_jsonbcontainer(jsonval->val.binary.data, + "objname", false, NULL); + appendStringInfo(buf, "%s", quote_identifier(str)); + pfree(str); + + str = find_string_in_jsonbcontainer(jsonval->val.binary.data, + "attrname", true, NULL); + if (str) + { + appendStringInfo(buf, ".%s", quote_identifier(str)); + pfree(str); + } +} + +/* + * expand a json value as a type name. + */ +static void +expand_jsonval_typename(StringInfo buf, JsonbValue *jsonval) +{ + char *schema = NULL; + char *typename; + char *typmodstr; + trivalue is_array; + char *array_decor; + + /* + * We omit schema-qualifying the output name if the schema element is + * either the empty string or NULL; the difference between those two cases + * is that in the latter we quote the type name, in the former we don't. + * This allows for types with special typmod needs, such as interval and + * timestamp (see format_type_detailed), while at the same time allowing + * for the schema name to be omitted from type names that require quotes + * but are to be obtained from a user schema. + */ + + schema = find_string_in_jsonbcontainer(jsonval->val.binary.data, + "schemaname", true, NULL); + typename = find_string_in_jsonbcontainer(jsonval->val.binary.data, + "typename", false, NULL); + typmodstr = find_string_in_jsonbcontainer(jsonval->val.binary.data, + "typmod", true, NULL); + is_array = find_bool_in_jsonbcontainer(jsonval->val.binary.data, + "is_array"); + switch (is_array) + { + default: + case tv_absent: + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("missing is_array element"))); + break; + case tv_true: + array_decor = "[]"; + break; + case tv_false: + array_decor = ""; + break; + } + + if (schema == NULL) + appendStringInfo(buf, "%s%s%s", + quote_identifier(typename), + typmodstr ? typmodstr : "", + array_decor); + else if (schema[0] == '\0') + appendStringInfo(buf, "%s%s%s", + typename, + typmodstr ? typmodstr : "", + array_decor); + else + appendStringInfo(buf, "%s.%s%s%s", + quote_identifier(schema), + quote_identifier(typename), + typmodstr ? typmodstr : "", + array_decor); +} + +/* + * Expand a json value as an operator name + */ +static void +expand_jsonval_operator(StringInfo buf, JsonbValue *jsonval) +{ + char *str; + + str = find_string_in_jsonbcontainer(jsonval->val.binary.data, + "schemaname", true, NULL); + /* schema might be NULL or empty */ + if (str != NULL && str[0] != '\0') + appendStringInfo(buf, "%s.", quote_identifier(str)); + + str = find_string_in_jsonbcontainer(jsonval->val.binary.data, + "objname", false, NULL); + appendStringInfoString(buf, str); +} + +/* + * Expand a json value as a string. The value must be of type string or of + * type object. In the latter case it must contain a "fmt" element which will + * be recursively expanded; also, if the object contains an element "present" + * and it is set to false, the expansion is the empty string. + */ +static void +expand_jsonval_string(StringInfo buf, JsonbValue *jsonval) +{ + if (jsonval->type == jbvString) + { + appendBinaryStringInfo(buf, jsonval->val.string.val, + jsonval->val.string.len); + } + else if (jsonval->type == jbvBinary) + { + trivalue present; + + present = find_bool_in_jsonbcontainer(jsonval->val.binary.data, + "present"); + /* + * If "present" is set to false, this element expands to empty; + * otherwise (either true or absent), fall through to expand "fmt". + */ + if (present == tv_false) + return; + + expand_fmt_recursive(jsonval->val.binary.data, buf); + } +} + +/* + * Expand a json value as a string literal + */ +static void +expand_jsonval_strlit(StringInfo buf, JsonbValue *jsonval) +{ + char *str; + StringInfoData dqdelim; + static const char dqsuffixes[] = "_XYZZYX_"; + int dqnextchar = 0; + + str = pnstrdup(jsonval->val.string.val, jsonval->val.string.len); + + /* easy case: if there are no ' and no \, just use a single quote */ + if (strchr(str, '\'') == NULL && + strchr(str, '\\') == NULL) + { + appendStringInfo(buf, "'%s'", str); + pfree(str); + return; + } + + /* Otherwise need to find a useful dollar-quote delimiter */ + initStringInfo(&dqdelim); + appendStringInfoString(&dqdelim, "$"); + while (strstr(str, dqdelim.data) != NULL) + { + appendStringInfoChar(&dqdelim, dqsuffixes[dqnextchar++]); + dqnextchar %= sizeof(dqsuffixes) - 1; + } + /* add trailing $ */ + appendStringInfoChar(&dqdelim, '$'); + + /* And finally produce the quoted literal into the output StringInfo */ + appendStringInfo(buf, "%s%s%s", dqdelim.data, str, dqdelim.data); + pfree(dqdelim.data); + pfree(str); +} + +/* + * Expand a json value as an integer quantity + */ +static void +expand_jsonval_number(StringInfo buf, JsonbValue *jsonval) +{ + char *strdatum; + + strdatum = DatumGetCString(DirectFunctionCall1(numeric_out, + NumericGetDatum(jsonval->val.numeric))); + appendStringInfoString(buf, strdatum); +} + +/* + * Expand one json element into the output StringInfo according to the + * conversion specifier. The element type is validated, and an error is raised + * if it doesn't match what we expect for the conversion specifier. + */ +static void +expand_one_jsonb_element(StringInfo out, char *param, JsonbValue *jsonval, + convSpecifier specifier, const char *fmt) +{ + ErrorContextCallback sqlerrcontext; + + /* If we were given a format string, setup an ereport() context callback */ + if (fmt) + { + sqlerrcontext.callback = fmtstr_error_callback; + sqlerrcontext.arg = (void *) fmt; + sqlerrcontext.previous = error_context_stack; + error_context_stack = &sqlerrcontext; + } + + if (!jsonval) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("element \"%s\" not found", param))); + + switch (specifier) + { + case SpecIdentifier: + if (jsonval->type != jbvString) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("expected JSON string for %%I element \"%s\", got %d", + param, jsonval->type))); + expand_jsonval_identifier(out, jsonval); + break; + + case SpecDottedName: + if (jsonval->type != jbvBinary) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("expected JSON object for %%D element \"%s\", got %d", + param, jsonval->type))); + expand_jsonval_dottedname(out, jsonval); + break; + + case SpecString: + if (jsonval->type != jbvString && + jsonval->type != jbvBinary) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("expected JSON string or object for %%s element \"%s\", got %d", + param, jsonval->type))); + expand_jsonval_string(out, jsonval); + break; + + case SpecStringLiteral: + if (jsonval->type != jbvString) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("expected JSON string for %%L element \"%s\", got %d", + param, jsonval->type))); + expand_jsonval_strlit(out, jsonval); + break; + + case SpecTypename: + if (jsonval->type != jbvBinary) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("expected JSON object for %%T element \"%s\", got %d", + param, jsonval->type))); + expand_jsonval_typename(out, jsonval); + break; + + case SpecOperatorname: + if (jsonval->type != jbvBinary) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("expected JSON object for %%O element \"%s\", got %d", + param, jsonval->type))); + expand_jsonval_operator(out, jsonval); + break; + + case SpecNumber: + if (jsonval->type != jbvNumeric) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("expected JSON numeric for %%n element \"%s\", got %d", + param, jsonval->type))); + expand_jsonval_number(out, jsonval); + break; + } + + if (fmt) + error_context_stack = sqlerrcontext.previous; +} + +/* + * Iterate on the elements of a JSON array, expanding each one into the output + * StringInfo per the given conversion specifier, separated by the given + * separator. + */ +static void +expand_jsonb_array(StringInfo out, char *param, + JsonbValue *jsonarr, char *arraysep, convSpecifier specifier, + const char *fmt) +{ + ErrorContextCallback sqlerrcontext; + JsonbContainer *container; + JsonbIterator *it; + JsonbValue v; + int type; + bool first = true; + + /* If we were given a format string, setup an ereport() context callback */ + if (fmt) + { + sqlerrcontext.callback = fmtstr_error_callback; + sqlerrcontext.arg = (void *) fmt; + sqlerrcontext.previous = error_context_stack; + error_context_stack = &sqlerrcontext; + } + + if (jsonarr->type != jbvBinary) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("element \"%s\" is not a JSON array", param))); + + container = jsonarr->val.binary.data; + if ((container->header & JB_FARRAY) == 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("element \"%s\" is not a JSON array", param))); + + it = JsonbIteratorInit(container); + while ((type = JsonbIteratorNext(&it, &v, true)) != WJB_DONE) + { + switch (type) + { + case WJB_ELEM: + if (!first) + appendStringInfoString(out, arraysep); + first = false; + expand_one_jsonb_element(out, param, &v, specifier, NULL); + break; + } + } + + if (fmt) + error_context_stack = sqlerrcontext.previous; +} + +/*------ + * Returns a formatted string from a JSON object. + * + * The starting point is the element named "fmt" (which must be a string). + * This format string may contain zero or more %-escapes, which consist of an + * element name enclosed in { }, possibly followed by a conversion modifier, + * followed by a conversion specifier. Possible conversion specifiers are: + * + * % expand to a literal %. + * I expand as a single, non-qualified identifier + * D expand as a possibly-qualified identifier + * T expand as a type name + * O expand as an operator name + * L expand as a string literal (quote using single quotes) + * s expand as a simple string (no quoting) + * n expand as a simple number (no quoting) + * + * The element name may have an optional separator specification preceded + * by a colon. Its presence indicates that the element is expected to be + * an array; the specified separator is used to join the array elements. + *------ + */ +Datum +pg_event_trigger_expand_command(PG_FUNCTION_ARGS) +{ + text *json = PG_GETARG_TEXT_P(0); + Datum d; + Jsonb *jsonb; + StringInfoData out; + + initStringInfo(&out); + d = DirectFunctionCall1(jsonb_in, + PointerGetDatum(TextDatumGetCString(json))); + jsonb = (Jsonb *) DatumGetPointer(d); + + expand_fmt_recursive(&jsonb->root, &out); + + PG_RETURN_TEXT_P(CStringGetTextDatum(out.data)); +} + +/* + * Error context callback for JSON format string expansion. + * + * Possible improvement: indicate which element we're expanding, if applicable + */ +static void +fmtstr_error_callback(void *arg) +{ + errcontext("while expanding format string \"%s\"", (char *) arg); + +} diff --git a/src/backend/utils/adt/format_type.c b/src/backend/utils/adt/format_type.c index fc816ce..dbf6b60 100644 --- a/src/backend/utils/adt/format_type.c +++ b/src/backend/utils/adt/format_type.c @@ -96,6 +96,9 @@ format_type_be(Oid type_oid) return format_type_internal(type_oid, -1, false, false, false); } +/* + * This version returns a name which is always qualified. + */ char * format_type_be_qualified(Oid type_oid) { @@ -323,6 +326,106 @@ format_type_internal(Oid type_oid, int32 typemod, return buf; } +/* + * Similar to format_type_internal, except we return each bit of information + * separately: + * + * - nspid is the schema OID. For certain SQL-standard types which have weird + * typmod rules, we return InvalidOid; caller is expected to not schema- + * qualify the name nor add quotes to the type name. + * + * - typename is set to the type name, without quotes + * + * - typmod is set to the typemod, if any, as a string with parens + * + * - is_array indicates whether []s must be added + * + * Also, we don't try to decode type names to their standard-mandated names, + * except in the cases of unusual typmod rules, as specified above. + * + * XXX there is a lot of code duplication between this routine and + * format_type_internal. (One thing that doesn't quite match is the whole + * allow_invalid business.) + */ +void +format_type_detailed(Oid type_oid, int32 typemod, + Oid *nspid, char **typname, char **typemodstr, + bool *is_array) +{ + HeapTuple tuple; + Form_pg_type typeform; + Oid array_base_type; + + tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(type_oid)); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for type %u", type_oid); + + typeform = (Form_pg_type) GETSTRUCT(tuple); + + /* + * Special-case crock for types with strange typmod rules. + */ + if (type_oid == INTERVALOID || + type_oid == TIMESTAMPOID || + type_oid == TIMESTAMPTZOID) + { + switch (type_oid) + { + case INTERVALOID: + *typname = pstrdup("INTERVAL"); + break; + case TIMESTAMPOID: + case TIMESTAMPTZOID: + /* the TZ part is added by typmod */ + *typname = pstrdup("TIMESTAMP"); + break; + } + *nspid = InvalidOid; + + if (typemod >= 0) + *typemodstr = printTypmod(NULL, typemod, typeform->typmodout); + else + *typemodstr = pstrdup(""); + + *is_array = false; + + ReleaseSysCache(tuple); + return; + } + + /* + * Check if it's a regular (variable length) array type. As above, + * fixed-length array types such as "name" shouldn't get deconstructed. + */ + array_base_type = typeform->typelem; + + if (array_base_type != InvalidOid && + typeform->typstorage != 'p') + { + /* Switch our attention to the array element type */ + ReleaseSysCache(tuple); + tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(array_base_type)); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for type %u", type_oid); + + typeform = (Form_pg_type) GETSTRUCT(tuple); + type_oid = array_base_type; + *is_array = true; + } + else + *is_array = false; + + *nspid = typeform->typnamespace; + *typname = pstrdup(NameStr(typeform->typname)); + + if (typemod >= 0) + *typemodstr = printTypmod(NULL, typemod, typeform->typmodout); + else + *typemodstr = pstrdup(""); + + ReleaseSysCache(tuple); +} + /* * Add typmod decoration to the basic type name @@ -338,7 +441,10 @@ printTypmod(const char *typname, int32 typmod, Oid typmodout) if (typmodout == InvalidOid) { /* Default behavior: just print the integer typmod with parens */ - res = psprintf("%s(%d)", typname, (int) typmod); + if (typname == NULL) + res = psprintf("(%d)", (int) typmod); + else + res = psprintf("%s(%d)", typname, (int) typmod); } else { @@ -347,7 +453,10 @@ printTypmod(const char *typname, int32 typmod, Oid typmodout) tmstr = DatumGetCString(OidFunctionCall1(typmodout, Int32GetDatum(typmod))); - res = psprintf("%s%s", typname, tmstr); + if (typname == NULL) + res = psprintf("%s", tmstr); + else + res = psprintf("%s%s", typname, tmstr); } return res; diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c index 6fbe283..a842ef2 100644 --- a/src/backend/utils/adt/jsonb.c +++ b/src/backend/utils/adt/jsonb.c @@ -416,7 +416,7 @@ JsonbToCString(StringInfo out, JsonbContainer *in, int estimated_len) { bool first = true; JsonbIterator *it; - int type = 0; + JsonbIteratorToken type = WJB_DONE; JsonbValue v; int level = 0; bool redo_switch = false; @@ -498,7 +498,7 @@ JsonbToCString(StringInfo out, JsonbContainer *in, int estimated_len) first = false; break; default: - elog(ERROR, "unknown flag of jsonb iterator"); + elog(ERROR, "unknown jsonb iterator token type"); } } diff --git a/src/include/catalog/objectaddress.h b/src/include/catalog/objectaddress.h index 6f4dbab..cf0e9c4 100644 --- a/src/include/catalog/objectaddress.h +++ b/src/include/catalog/objectaddress.h @@ -32,6 +32,8 @@ extern ObjectAddress get_object_address(ObjectType objtype, List *objname, List *objargs, Relation *relp, LOCKMODE lockmode, bool missing_ok); +extern Oid get_objtype_catalog_oid(ObjectType objtype); + extern void check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address, List *objname, List *objargs, Relation relation); diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h index 9edfdb8..4efcde0 100644 --- a/src/include/catalog/pg_proc.h +++ b/src/include/catalog/pg_proc.h @@ -5082,12 +5082,15 @@ DESCR("peek at binary changes from replication slot"); /* event triggers */ DATA(insert OID = 3566 ( pg_event_trigger_dropped_objects PGNSP PGUID 12 10 100 0 0 f f f f t t s 0 0 2249 "" "{26,26,23,16,16,25,25,25,25,1009,1009}" "{o,o,o,o,o,o,o,o,o,o,o}" "{classid, objid, objsubid, original, normal, object_type, schema_name, object_name, object_identity, address_names, address_args}" _null_ pg_event_trigger_dropped_objects _null_ _null_ _null_ )); - DESCR("list objects dropped by the current command"); DATA(insert OID = 4566 ( pg_event_trigger_table_rewrite_oid PGNSP PGUID 12 1 0 0 0 f f f f t f s 0 0 26 "" "{26}" "{o}" "{oid}" _null_ pg_event_trigger_table_rewrite_oid _null_ _null_ _null_ )); DESCR("return Oid of the table getting rewritten"); DATA(insert OID = 4567 ( pg_event_trigger_table_rewrite_reason PGNSP PGUID 12 1 0 0 0 f f f f t f s 0 0 23 "" _null_ _null_ _null_ _null_ pg_event_trigger_table_rewrite_reason _null_ _null_ _null_ )); DESCR("return reason code for table getting rewritten"); +DATA(insert OID = 3590 ( pg_event_trigger_get_creation_commands PGNSP PGUID 12 10 100 0 0 f f f f t t s 0 0 2249 "" "{26,26,23,25,25,25,25,16,114}" "{o,o,o,o,o,o,o,o,o}" "{classid,objid,objsubid,command_tag,object_type,schema,identity,in_extension,command}" _null_ pg_event_trigger_get_creation_commands _null_ _null_ _null_ )); +DESCR("list JSON-formatted commands executed by the current command"); +DATA(insert OID = 3591 ( pg_event_trigger_expand_command PGNSP PGUID 12 10 0 0 0 f f f f t f s 1 0 25 "114" _null_ _null_ _null_ _null_ pg_event_trigger_expand_command _null_ _null_ _null_ )); +DESCR("format JSON command"); /* generic transition functions for ordered-set aggregates */ DATA(insert OID = 3970 ( ordered_set_transition PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 2276" _null_ _null_ _null_ _null_ ordered_set_transition _null_ _null_ _null_ )); diff --git a/src/include/commands/event_trigger.h b/src/include/commands/event_trigger.h index 9ac9fc3..c07f04c 100644 --- a/src/include/commands/event_trigger.h +++ b/src/include/commands/event_trigger.h @@ -60,4 +60,7 @@ extern bool trackDroppedObjectsNeeded(void); extern void EventTriggerSQLDropAddObject(const ObjectAddress *object, bool original, bool normal); +extern void EventTriggerStashCommand(Oid objectId, uint32 objectSubId, + ObjectType objtype, Oid secondaryOid, Node *parsetree); + #endif /* EVENT_TRIGGER_H */ diff --git a/src/include/commands/extension.h b/src/include/commands/extension.h index a349d6a..4259072 100644 --- a/src/include/commands/extension.h +++ b/src/include/commands/extension.h @@ -23,7 +23,7 @@ * on the current pg_extension object for each SQL object created by its * installation script. */ -extern bool creating_extension; +extern PGDLLIMPORT bool creating_extension; extern Oid CurrentExtensionObject; diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index b1dfa85..086bc82 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -1209,6 +1209,7 @@ typedef enum ObjectType OBJECT_CAST, OBJECT_COLUMN, OBJECT_COLLATION, + OBJECT_COMPOSITE, OBJECT_CONVERSION, OBJECT_DATABASE, OBJECT_DEFAULT, @@ -1241,6 +1242,7 @@ typedef enum ObjectType OBJECT_TSPARSER, OBJECT_TSTEMPLATE, OBJECT_TYPE, + OBJECT_USER_MAPPING, OBJECT_VIEW } ObjectType; diff --git a/src/include/tcop/deparse_utility.h b/src/include/tcop/deparse_utility.h new file mode 100644 index 0000000..bcc8611 --- /dev/null +++ b/src/include/tcop/deparse_utility.h @@ -0,0 +1,61 @@ +/*------------------------------------------------------------------------- + * + * deparse_utility.h + * + * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/tcop/deparse_utility.h + * + *------------------------------------------------------------------------- + */ +#ifndef DEPARSE_UTILITY_H +#define DEPARSE_UTILITY_H + +#include "access/attnum.h" +#include "nodes/nodes.h" + +/* + * Support for keeping track of a command to deparse. + * + * When a command is run, we collect some information about it for later + * deparsing; deparse_utility_command can later be used to obtain a usable + * representation of it. + */ + +typedef enum StashedCommandType +{ + SCT_Simple, +} StashedCommandType; + +/* + * For ALTER TABLE commands, we keep a list of the subcommands therein. + */ +typedef struct StashedATSubcmd +{ + AttrNumber attnum; /* affected column number */ + Oid oid; /* affected constraint, default value or index */ + Node *parsetree; +} StashedATSubcmd; + +typedef struct StashedCommand +{ + StashedCommandType type; + bool in_extension; + Node *parsetree; + + union + { + struct SimpleCommand + { + Oid objectId; + uint32 objectSubId; + ObjectType objtype; + Oid secondaryOid; + } simple; + } d; +} StashedCommand; + +extern char *deparse_utility_command(StashedCommand *cmd); + +#endif /* DEPARSE_UTILITY_H */ diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h index bc4517d..470a325 100644 --- a/src/include/utils/builtins.h +++ b/src/include/utils/builtins.h @@ -1079,6 +1079,9 @@ extern char *format_type_be_qualified(Oid type_oid); extern char *format_type_with_typemod(Oid type_oid, int32 typemod); extern Datum oidvectortypes(PG_FUNCTION_ARGS); extern int32 type_maximum_size(Oid type_oid, int32 typemod); +extern void format_type_detailed(Oid type_oid, int32 typemod, + Oid *nspid, char **typname, + char **typemodstr, bool *is_array); /* quote.c */ extern Datum quote_ident(PG_FUNCTION_ARGS); @@ -1210,6 +1213,10 @@ extern Datum unique_key_recheck(PG_FUNCTION_ARGS); extern Datum pg_event_trigger_dropped_objects(PG_FUNCTION_ARGS); extern Datum pg_event_trigger_table_rewrite_oid(PG_FUNCTION_ARGS); extern Datum pg_event_trigger_table_rewrite_reason(PG_FUNCTION_ARGS); +extern Datum pg_event_trigger_get_creation_commands(PG_FUNCTION_ARGS); + +/* utils/adt/ddl_json.c */ +extern Datum pg_event_trigger_expand_command(PG_FUNCTION_ARGS); /* commands/extension.c */ extern Datum pg_available_extensions(PG_FUNCTION_ARGS); -- 2.1.4 --gatW/ieO32f1wygP Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="0008-deparse-add-EventTriggerStashCommand-calls-to-Proces.patch" From 70b4174d3e365829ea53a7798b206fe4a33d841e Mon Sep 17 00:00:00 2001 From: Alvaro Herrera Date: Thu, 25 Sep 2014 15:50:37 -0300 Subject: [PATCH 08/42] deparse: add EventTriggerStashCommand() calls to ProcessUtilitySlow These calls add each executed command to the event trigger command stash. --- src/backend/commands/schemacmds.c | 9 ++ src/backend/tcop/utility.c | 260 ++++++++++++++++++++++++++------------ 2 files changed, 186 insertions(+), 83 deletions(-) diff --git a/src/backend/commands/schemacmds.c b/src/backend/commands/schemacmds.c index a44dbf4..4238034 100644 --- a/src/backend/commands/schemacmds.c +++ b/src/backend/commands/schemacmds.c @@ -24,6 +24,7 @@ #include "catalog/objectaccess.h" #include "catalog/pg_namespace.h" #include "commands/dbcommands.h" +#include "commands/event_trigger.h" #include "commands/schemacmds.h" #include "miscadmin.h" #include "parser/parse_utilcmd.h" @@ -130,6 +131,14 @@ CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString) PushOverrideSearchPath(overridePath); /* + * Report the new schema to possibly interested event triggers. Note we + * must do this here and not in ProcessUtilitySlow because otherwise the + * objects created below are reported before the schema, which would be + * wrong. + */ + EventTriggerStashCommand(namespaceId, 0, OBJECT_SCHEMA, InvalidOid, (Node *) stmt); + + /* * Examine the list of commands embedded in the CREATE SCHEMA command, and * reorganize them into a sequentially executable order with no forward * references. Note that the result is still a list of raw parsetrees --- diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index ee6799a..2e21659 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -887,6 +887,8 @@ ProcessUtilitySlow(Node *parsetree, bool isTopLevel = (context == PROCESS_UTILITY_TOPLEVEL); bool isCompleteQuery = (context <= PROCESS_UTILITY_QUERY); bool needCleanup; + Oid objectId; + Oid secondaryOid; /* All event trigger calls are done only when isCompleteQuery is true */ needCleanup = isCompleteQuery && EventTriggerBeginCompleteQuery(); @@ -905,6 +907,10 @@ ProcessUtilitySlow(Node *parsetree, case T_CreateSchemaStmt: CreateSchemaCommand((CreateSchemaStmt *) parsetree, queryString); + /* + * CreateSchemaCommand calls EventTriggerStashCommand + * internally, for reasons explained there. + */ break; case T_CreateStmt: @@ -912,7 +918,6 @@ ProcessUtilitySlow(Node *parsetree, { List *stmts; ListCell *l; - Oid relOid; /* Run parse analysis ... */ stmts = transformCreateStmt((CreateStmt *) parsetree, @@ -929,9 +934,11 @@ ProcessUtilitySlow(Node *parsetree, static char *validnsps[] = HEAP_RELOPT_NAMESPACES; /* Create the table itself */ - relOid = DefineRelation((CreateStmt *) stmt, - RELKIND_RELATION, - InvalidOid); + objectId = DefineRelation((CreateStmt *) stmt, + RELKIND_RELATION, + InvalidOid); + EventTriggerStashCommand(objectId, 0, OBJECT_TABLE, + InvalidOid, stmt); /* * Let NewRelationCreateToastTable decide if this @@ -953,20 +960,27 @@ ProcessUtilitySlow(Node *parsetree, toast_options, true); - NewRelationCreateToastTable(relOid, toast_options); + NewRelationCreateToastTable(objectId, toast_options); } else if (IsA(stmt, CreateForeignTableStmt)) { /* Create the table itself */ - relOid = DefineRelation((CreateStmt *) stmt, - RELKIND_FOREIGN_TABLE, - InvalidOid); + objectId = DefineRelation((CreateStmt *) stmt, + RELKIND_FOREIGN_TABLE, + InvalidOid); CreateForeignTable((CreateForeignTableStmt *) stmt, - relOid); + objectId); + EventTriggerStashCommand(objectId, 0, + OBJECT_FOREIGN_TABLE, + InvalidOid, stmt); } else { - /* Recurse for anything else */ + /* + * Recurse for anything else. Note the recursive + * call will stash the objects so created into our + * event trigger context. + */ ProcessUtility(stmt, queryString, PROCESS_UTILITY_SUBCOMMAND, @@ -1055,36 +1069,44 @@ ProcessUtilitySlow(Node *parsetree, * Recursively alter column default for table and, * if requested, for descendants */ - AlterDomainDefault(stmt->typeName, - stmt->def); + objectId = + AlterDomainDefault(stmt->typeName, + stmt->def); break; case 'N': /* ALTER DOMAIN DROP NOT NULL */ - AlterDomainNotNull(stmt->typeName, - false); + objectId = + AlterDomainNotNull(stmt->typeName, + false); break; case 'O': /* ALTER DOMAIN SET NOT NULL */ - AlterDomainNotNull(stmt->typeName, - true); + objectId = + AlterDomainNotNull(stmt->typeName, + true); break; case 'C': /* ADD CONSTRAINT */ - AlterDomainAddConstraint(stmt->typeName, - stmt->def); + objectId = + AlterDomainAddConstraint(stmt->typeName, + stmt->def); break; case 'X': /* DROP CONSTRAINT */ - AlterDomainDropConstraint(stmt->typeName, - stmt->name, - stmt->behavior, - stmt->missing_ok); + objectId = + AlterDomainDropConstraint(stmt->typeName, + stmt->name, + stmt->behavior, + stmt->missing_ok); break; case 'V': /* VALIDATE CONSTRAINT */ - AlterDomainValidateConstraint(stmt->typeName, - stmt->name); + objectId = + AlterDomainValidateConstraint(stmt->typeName, + stmt->name); break; default: /* oops */ elog(ERROR, "unrecognized alter domain type: %d", (int) stmt->subtype); break; } + EventTriggerStashCommand(objectId, 0, OBJECT_DOMAIN, + secondaryOid, parsetree); } break; @@ -1098,46 +1120,53 @@ ProcessUtilitySlow(Node *parsetree, switch (stmt->kind) { case OBJECT_AGGREGATE: - DefineAggregate(stmt->defnames, stmt->args, - stmt->oldstyle, stmt->definition, - queryString); + objectId = + DefineAggregate(stmt->defnames, stmt->args, + stmt->oldstyle, + stmt->definition, queryString); break; case OBJECT_OPERATOR: Assert(stmt->args == NIL); - DefineOperator(stmt->defnames, stmt->definition); + objectId = DefineOperator(stmt->defnames, + stmt->definition); break; case OBJECT_TYPE: Assert(stmt->args == NIL); - DefineType(stmt->defnames, stmt->definition); + objectId = DefineType(stmt->defnames, + stmt->definition); break; case OBJECT_TSPARSER: Assert(stmt->args == NIL); - DefineTSParser(stmt->defnames, stmt->definition); + objectId = DefineTSParser(stmt->defnames, + stmt->definition); break; case OBJECT_TSDICTIONARY: Assert(stmt->args == NIL); - DefineTSDictionary(stmt->defnames, - stmt->definition); + objectId = DefineTSDictionary(stmt->defnames, + stmt->definition); break; case OBJECT_TSTEMPLATE: Assert(stmt->args == NIL); - DefineTSTemplate(stmt->defnames, - stmt->definition); + objectId = DefineTSTemplate(stmt->defnames, + stmt->definition); break; case OBJECT_TSCONFIGURATION: Assert(stmt->args == NIL); - DefineTSConfiguration(stmt->defnames, - stmt->definition); + objectId = DefineTSConfiguration(stmt->defnames, + stmt->definition); break; case OBJECT_COLLATION: Assert(stmt->args == NIL); - DefineCollation(stmt->defnames, stmt->definition); + objectId = DefineCollation(stmt->defnames, + stmt->definition); break; default: elog(ERROR, "unrecognized define stmt type: %d", (int) stmt->kind); break; } + + EventTriggerStashCommand(objectId, 0, stmt->kind, InvalidOid, parsetree); } break; @@ -1172,50 +1201,66 @@ ProcessUtilitySlow(Node *parsetree, stmt = transformIndexStmt(relid, stmt, queryString); /* ... and do it */ - DefineIndex(relid, /* OID of heap relation */ - stmt, - InvalidOid, /* no predefined OID */ - false, /* is_alter_table */ - true, /* check_rights */ - false, /* skip_build */ - false); /* quiet */ + objectId = + DefineIndex(relid, /* OID of heap relation */ + stmt, + InvalidOid, /* no predefined OID */ + false, /* is_alter_table */ + true, /* check_rights */ + false, /* skip_build */ + false); /* quiet */ + EventTriggerStashCommand(objectId, 0, OBJECT_INDEX, + InvalidOid, parsetree); } break; case T_CreateExtensionStmt: - CreateExtension((CreateExtensionStmt *) parsetree); + objectId = CreateExtension((CreateExtensionStmt *) parsetree); + EventTriggerStashCommand(objectId, 0, OBJECT_EXTENSION, InvalidOid, parsetree); break; case T_AlterExtensionStmt: - ExecAlterExtensionStmt((AlterExtensionStmt *) parsetree); + objectId = ExecAlterExtensionStmt((AlterExtensionStmt *) parsetree); + EventTriggerStashCommand(objectId, 0, OBJECT_EXTENSION, InvalidOid, parsetree); break; case T_AlterExtensionContentsStmt: - ExecAlterExtensionContentsStmt((AlterExtensionContentsStmt *) parsetree); + objectId = ExecAlterExtensionContentsStmt((AlterExtensionContentsStmt *) parsetree); + EventTriggerStashCommand(objectId, 0, OBJECT_EXTENSION, InvalidOid, parsetree); break; case T_CreateFdwStmt: - CreateForeignDataWrapper((CreateFdwStmt *) parsetree); + objectId = CreateForeignDataWrapper((CreateFdwStmt *) parsetree); + EventTriggerStashCommand(objectId, 0, OBJECT_FDW, InvalidOid, parsetree); break; case T_AlterFdwStmt: - AlterForeignDataWrapper((AlterFdwStmt *) parsetree); + objectId = AlterForeignDataWrapper((AlterFdwStmt *) parsetree); + EventTriggerStashCommand(objectId, 0, OBJECT_FDW, InvalidOid, parsetree); break; case T_CreateForeignServerStmt: - CreateForeignServer((CreateForeignServerStmt *) parsetree); + objectId = CreateForeignServer((CreateForeignServerStmt *) parsetree); + EventTriggerStashCommand(objectId, 0, OBJECT_FOREIGN_SERVER, + InvalidOid, parsetree); break; case T_AlterForeignServerStmt: - AlterForeignServer((AlterForeignServerStmt *) parsetree); + objectId = AlterForeignServer((AlterForeignServerStmt *) parsetree); + EventTriggerStashCommand(objectId, 0, OBJECT_FOREIGN_SERVER, + InvalidOid, parsetree); break; case T_CreateUserMappingStmt: - CreateUserMapping((CreateUserMappingStmt *) parsetree); + objectId = CreateUserMapping((CreateUserMappingStmt *) parsetree); + EventTriggerStashCommand(objectId, 0, OBJECT_USER_MAPPING, + InvalidOid, parsetree); break; case T_AlterUserMappingStmt: - AlterUserMapping((AlterUserMappingStmt *) parsetree); + objectId = AlterUserMapping((AlterUserMappingStmt *) parsetree); + EventTriggerStashCommand(objectId, 0, OBJECT_USER_MAPPING, + InvalidOid, parsetree); break; case T_DropUserMappingStmt: @@ -1224,105 +1269,131 @@ ProcessUtilitySlow(Node *parsetree, case T_ImportForeignSchemaStmt: ImportForeignSchema((ImportForeignSchemaStmt *) parsetree); + /* commands are stashed by recursing */ break; case T_CompositeTypeStmt: /* CREATE TYPE (composite) */ { CompositeTypeStmt *stmt = (CompositeTypeStmt *) parsetree; - DefineCompositeType(stmt->typevar, stmt->coldeflist); + objectId = DefineCompositeType(stmt->typevar, + stmt->coldeflist); + EventTriggerStashCommand(objectId, 0, OBJECT_COMPOSITE, + InvalidOid, parsetree); } break; case T_CreateEnumStmt: /* CREATE TYPE AS ENUM */ - DefineEnum((CreateEnumStmt *) parsetree); + objectId = DefineEnum((CreateEnumStmt *) parsetree); + EventTriggerStashCommand(objectId, 0, OBJECT_TYPE, InvalidOid, parsetree); break; case T_CreateRangeStmt: /* CREATE TYPE AS RANGE */ - DefineRange((CreateRangeStmt *) parsetree); + objectId = DefineRange((CreateRangeStmt *) parsetree); + EventTriggerStashCommand(objectId, 0, OBJECT_TYPE, InvalidOid, parsetree); break; case T_AlterEnumStmt: /* ALTER TYPE (enum) */ - AlterEnum((AlterEnumStmt *) parsetree, isTopLevel); + objectId = AlterEnum((AlterEnumStmt *) parsetree, isTopLevel); + EventTriggerStashCommand(objectId, 0, OBJECT_TYPE, InvalidOid, parsetree); break; case T_ViewStmt: /* CREATE VIEW */ - DefineView((ViewStmt *) parsetree, queryString); + objectId = DefineView((ViewStmt *) parsetree, queryString); + EventTriggerStashCommand(objectId, 0, OBJECT_VIEW, InvalidOid, parsetree); break; case T_CreateFunctionStmt: /* CREATE FUNCTION */ - CreateFunction((CreateFunctionStmt *) parsetree, queryString); + objectId = CreateFunction((CreateFunctionStmt *) parsetree, queryString); + EventTriggerStashCommand(objectId, 0, OBJECT_FUNCTION, InvalidOid, parsetree); break; case T_AlterFunctionStmt: /* ALTER FUNCTION */ - AlterFunction((AlterFunctionStmt *) parsetree); + objectId = AlterFunction((AlterFunctionStmt *) parsetree); + EventTriggerStashCommand(objectId, 0, OBJECT_FUNCTION, InvalidOid, parsetree); break; case T_RuleStmt: /* CREATE RULE */ - DefineRule((RuleStmt *) parsetree, queryString); + objectId = DefineRule((RuleStmt *) parsetree, queryString); + EventTriggerStashCommand(objectId, 0, OBJECT_RULE, InvalidOid, parsetree); break; case T_CreateSeqStmt: - DefineSequence((CreateSeqStmt *) parsetree); + objectId = DefineSequence((CreateSeqStmt *) parsetree); + EventTriggerStashCommand(objectId, 0, OBJECT_SEQUENCE, InvalidOid, parsetree); break; case T_AlterSeqStmt: - AlterSequence((AlterSeqStmt *) parsetree); + objectId = AlterSequence((AlterSeqStmt *) parsetree); + EventTriggerStashCommand(objectId, 0, OBJECT_SEQUENCE, InvalidOid, parsetree); break; case T_CreateTableAsStmt: - ExecCreateTableAs((CreateTableAsStmt *) parsetree, + objectId = ExecCreateTableAs((CreateTableAsStmt *) parsetree, queryString, params, completionTag); + EventTriggerStashCommand(objectId, 0, OBJECT_TABLE, InvalidOid, parsetree); break; case T_RefreshMatViewStmt: - ExecRefreshMatView((RefreshMatViewStmt *) parsetree, + objectId = ExecRefreshMatView((RefreshMatViewStmt *) parsetree, queryString, params, completionTag); + EventTriggerStashCommand(objectId, 0, OBJECT_MATVIEW, InvalidOid, parsetree); break; case T_CreateTrigStmt: - (void) CreateTrigger((CreateTrigStmt *) parsetree, queryString, - InvalidOid, InvalidOid, InvalidOid, - InvalidOid, false); + objectId = CreateTrigger((CreateTrigStmt *) parsetree, + queryString, InvalidOid, InvalidOid, + InvalidOid, InvalidOid, false); + EventTriggerStashCommand(objectId, 0, OBJECT_TRIGGER, InvalidOid, parsetree); break; case T_CreatePLangStmt: - CreateProceduralLanguage((CreatePLangStmt *) parsetree); + objectId = CreateProceduralLanguage((CreatePLangStmt *) parsetree); + EventTriggerStashCommand(objectId, 0, OBJECT_LANGUAGE, InvalidOid, parsetree); break; case T_CreateDomainStmt: - DefineDomain((CreateDomainStmt *) parsetree); + objectId = DefineDomain((CreateDomainStmt *) parsetree); + EventTriggerStashCommand(objectId, 0, OBJECT_DOMAIN, InvalidOid, parsetree); break; case T_CreateConversionStmt: - CreateConversionCommand((CreateConversionStmt *) parsetree); + objectId = CreateConversionCommand((CreateConversionStmt *) parsetree); + EventTriggerStashCommand(objectId, 0, OBJECT_CONVERSION, InvalidOid, parsetree); break; case T_CreateCastStmt: - CreateCast((CreateCastStmt *) parsetree); + objectId = CreateCast((CreateCastStmt *) parsetree); + EventTriggerStashCommand(objectId, 0, OBJECT_CAST, InvalidOid, parsetree); break; case T_CreateOpClassStmt: - DefineOpClass((CreateOpClassStmt *) parsetree); + objectId = DefineOpClass((CreateOpClassStmt *) parsetree); + EventTriggerStashCommand(objectId, 0, OBJECT_OPCLASS, InvalidOid, parsetree); break; case T_CreateOpFamilyStmt: - DefineOpFamily((CreateOpFamilyStmt *) parsetree); + objectId = DefineOpFamily((CreateOpFamilyStmt *) parsetree); + EventTriggerStashCommand(objectId, 0, OBJECT_OPFAMILY, InvalidOid, parsetree); break; case T_AlterOpFamilyStmt: - AlterOpFamily((AlterOpFamilyStmt *) parsetree); + objectId = AlterOpFamily((AlterOpFamilyStmt *) parsetree); + EventTriggerStashCommand(objectId, 0, OBJECT_OPFAMILY, InvalidOid, parsetree); break; case T_AlterTSDictionaryStmt: - AlterTSDictionary((AlterTSDictionaryStmt *) parsetree); + objectId = AlterTSDictionary((AlterTSDictionaryStmt *) parsetree); + EventTriggerStashCommand(objectId, 0, OBJECT_TSDICTIONARY, InvalidOid, parsetree); break; case T_AlterTSConfigurationStmt: - AlterTSConfiguration((AlterTSConfigurationStmt *) parsetree); + objectId = AlterTSConfiguration((AlterTSConfigurationStmt *) parsetree); + EventTriggerStashCommand(objectId, 0, OBJECT_TSCONFIGURATION, InvalidOid, parsetree); break; case T_AlterTableMoveAllStmt: + /* commands are stashed in AlterTableMoveAll */ AlterTableMoveAll((AlterTableMoveAllStmt *) parsetree); break; @@ -1331,23 +1402,44 @@ ProcessUtilitySlow(Node *parsetree, break; case T_RenameStmt: - ExecRenameStmt((RenameStmt *) parsetree, NULL); + { + RenameStmt *stmt = (RenameStmt *) parsetree; + int objsubid = 0; + + objectId = ExecRenameStmt(stmt, &objsubid); + EventTriggerStashCommand(objectId, objsubid, + stmt->renameType, InvalidOid, parsetree); + } break; case T_AlterObjectSchemaStmt: - ExecAlterObjectSchemaStmt((AlterObjectSchemaStmt *) parsetree); + objectId = ExecAlterObjectSchemaStmt((AlterObjectSchemaStmt *) parsetree); + EventTriggerStashCommand(objectId, 0, + ((AlterObjectSchemaStmt *) parsetree)->objectType, + parsetree); break; case T_AlterOwnerStmt: - ExecAlterOwnerStmt((AlterOwnerStmt *) parsetree); + objectId = ExecAlterOwnerStmt((AlterOwnerStmt *) parsetree); + EventTriggerStashCommand(objectId, 0, + ((AlterOwnerStmt *) parsetree)->objectType, + InvalidOid, parsetree); break; case T_CommentStmt: - CommentObject((CommentStmt *) parsetree, NULL); + { + uint32 objsubid; + + objectId = CommentObject((CommentStmt *) parsetree, &objsubid); + EventTriggerStashCommand(objectId, objsubid, + ((CommentStmt *) parsetree)->objtype, + InvalidOid, parsetree); + } break; case T_GrantStmt: ExecuteGrantStmt((GrantStmt *) parsetree); + /* commands are stashed in ExecGrantStmt_oids */ break; case T_DropOwnedStmt: @@ -1359,11 +1451,13 @@ ProcessUtilitySlow(Node *parsetree, break; case T_CreatePolicyStmt: /* CREATE POLICY */ - CreatePolicy((CreatePolicyStmt *) parsetree); + objectId = CreatePolicy((CreatePolicyStmt *) parsetree); + EventTriggerStashCommand(objectId, 0, OBJECT_POLICY, InvalidOid, parsetree); break; case T_AlterPolicyStmt: /* ALTER POLICY */ - AlterPolicy((AlterPolicyStmt *) parsetree); + objectId = AlterPolicy((AlterPolicyStmt *) parsetree); + EventTriggerStashCommand(objectId, 0, OBJECT_POLICY, InvalidOid, parsetree); break; case T_SecLabelStmt: -- 2.1.4 --gatW/ieO32f1wygP Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="0009-deparse-Support-CREATE-TYPE-AS.patch" From dcb353c8c9bd93778a62719ff8bf32b9a419e16d Mon Sep 17 00:00:00 2001 From: Alvaro Herrera Date: Thu, 25 Sep 2014 15:54:00 -0300 Subject: [PATCH 09/42] deparse: Support CREATE TYPE AS --- src/backend/tcop/deparse_utility.c | 264 ++++++++++++++++++++++++++++++++++++- src/backend/utils/adt/ruleutils.c | 18 +++ src/include/utils/ruleutils.h | 3 + 3 files changed, 284 insertions(+), 1 deletion(-) diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c index 8463321..becff4a 100644 --- a/src/backend/tcop/deparse_utility.c +++ b/src/backend/tcop/deparse_utility.c @@ -639,6 +639,268 @@ new_objtree_for_qualname_id(Oid classId, Oid objectId) } /* + * deparse_ColumnDef + * Subroutine for CREATE TABLE deparsing + * + * Deparse a ColumnDef node within a regular (non typed) table creation. + * + * NOT NULL constraints in the column definition are emitted directly in the + * column definition by this routine; other constraints must be emitted + * elsewhere (the info in the parse node is incomplete anyway.) + */ +static ObjTree * +deparse_ColumnDef(Relation relation, List *dpcontext, bool composite, + ColumnDef *coldef) +{ + ObjTree *column; + ObjTree *tmp; + Oid relid = RelationGetRelid(relation); + HeapTuple attrTup; + Form_pg_attribute attrForm; + Oid typid; + int32 typmod; + Oid typcollation; + bool saw_notnull; + ListCell *cell; + + /* + * Inherited columns without local definitions must not be emitted. XXX -- + * maybe it is useful to have them with "present = false" or some such? + */ + if (!coldef->is_local) + return NULL; + + attrTup = SearchSysCacheAttName(relid, coldef->colname); + if (!HeapTupleIsValid(attrTup)) + elog(ERROR, "could not find cache entry for column \"%s\" of relation %u", + coldef->colname, relid); + attrForm = (Form_pg_attribute) GETSTRUCT(attrTup); + + get_atttypetypmodcoll(relid, attrForm->attnum, + &typid, &typmod, &typcollation); + + /* Composite types use a slightly simpler format string */ + if (composite) + column = new_objtree_VA("%{name}I %{coltype}T %{collation}s", + 3, + "type", ObjTypeString, "column", + "name", ObjTypeString, coldef->colname, + "coltype", ObjTypeObject, + new_objtree_for_type(typid, typmod)); + else + column = new_objtree_VA("%{name}I %{coltype}T %{default}s %{not_null}s %{collation}s", + 3, + "type", ObjTypeString, "column", + "name", ObjTypeString, coldef->colname, + "coltype", ObjTypeObject, + new_objtree_for_type(typid, typmod)); + + tmp = new_objtree_VA("COLLATE %{name}D", 0); + if (OidIsValid(typcollation)) + { + ObjTree *collname; + + collname = new_objtree_for_qualname_id(CollationRelationId, + typcollation); + append_object_object(tmp, "name", collname); + } + else + append_bool_object(tmp, "present", false); + append_object_object(column, "collation", tmp); + + if (!composite) + { + /* + * Emit a NOT NULL declaration if necessary. Note that we cannot trust + * pg_attribute.attnotnull here, because that bit is also set when + * primary keys are specified; and we must not emit a NOT NULL + * constraint in that case, unless explicitely specified. Therefore, + * we scan the list of constraints attached to this column to determine + * whether we need to emit anything. + * (Fortunately, NOT NULL constraints cannot be table constraints.) + */ + saw_notnull = false; + foreach(cell, coldef->constraints) + { + Constraint *constr = (Constraint *) lfirst(cell); + + if (constr->contype == CONSTR_NOTNULL) + saw_notnull = true; + } + + if (saw_notnull) + append_string_object(column, "not_null", "NOT NULL"); + else + append_string_object(column, "not_null", ""); + + tmp = new_objtree_VA("DEFAULT %{default}s", 0); + if (attrForm->atthasdef) + { + char *defstr; + + defstr = RelationGetColumnDefault(relation, attrForm->attnum, + dpcontext); + + append_string_object(tmp, "default", defstr); + } + else + append_bool_object(tmp, "present", false); + append_object_object(column, "default", tmp); + } + + ReleaseSysCache(attrTup); + + return column; +} + +/* + * deparse_ColumnDef_Typed + * Subroutine for CREATE TABLE OF deparsing + * + * Deparse a ColumnDef node within a typed table creation. This is simpler + * than the regular case, because we don't have to emit the type declaration, + * collation, or default. Here we only return something if the column is being + * declared NOT NULL. + * + * As in deparse_ColumnDef, any other constraint is processed elsewhere. + * + * FIXME --- actually, what about default values? + */ +static ObjTree * +deparse_ColumnDef_typed(Relation relation, List *dpcontext, ColumnDef *coldef) +{ + ObjTree *column = NULL; + Oid relid = RelationGetRelid(relation); + HeapTuple attrTup; + Form_pg_attribute attrForm; + Oid typid; + int32 typmod; + Oid typcollation; + bool saw_notnull; + ListCell *cell; + + attrTup = SearchSysCacheAttName(relid, coldef->colname); + if (!HeapTupleIsValid(attrTup)) + elog(ERROR, "could not find cache entry for column \"%s\" of relation %u", + coldef->colname, relid); + attrForm = (Form_pg_attribute) GETSTRUCT(attrTup); + + get_atttypetypmodcoll(relid, attrForm->attnum, + &typid, &typmod, &typcollation); + + /* + * Search for a NOT NULL declaration. As in deparse_ColumnDef, we rely on + * finding a constraint on the column rather than coldef->is_not_null. + */ + saw_notnull = false; + foreach(cell, coldef->constraints) + { + Constraint *constr = (Constraint *) lfirst(cell); + + if (constr->contype == CONSTR_NOTNULL) + { + saw_notnull = true; + break; + } + } + + if (saw_notnull) + column = new_objtree_VA("%{name}I WITH OPTIONS NOT NULL", 2, + "type", ObjTypeString, "column_notnull", + "name", ObjTypeString, coldef->colname); + + ReleaseSysCache(attrTup); + + return column; +} + +/* + * deparseTableElements + * Subroutine for CREATE TABLE deparsing + * + * Deal with all the table elements (columns and constraints). + * + * Note we ignore constraints in the parse node here; they are extracted from + * system catalogs instead. + */ +static List * +deparseTableElements(Relation relation, List *tableElements, List *dpcontext, + bool typed, bool composite) +{ + List *elements = NIL; + ListCell *lc; + + foreach(lc, tableElements) + { + Node *elt = (Node *) lfirst(lc); + + switch (nodeTag(elt)) + { + case T_ColumnDef: + { + ObjTree *tree; + + tree = typed ? + deparse_ColumnDef_typed(relation, dpcontext, + (ColumnDef *) elt) : + deparse_ColumnDef(relation, dpcontext, + composite, (ColumnDef *) elt); + if (tree != NULL) + { + ObjElem *column; + + column = new_object_object(tree); + elements = lappend(elements, column); + } + } + break; + case T_Constraint: + break; + default: + elog(ERROR, "invalid node type %d", nodeTag(elt)); + } + } + + return elements; +} + +/* + * deparse_CompositeTypeStmt + * Deparse a CompositeTypeStmt (CREATE TYPE AS) + * + * Given a type OID and the parsetree that created it, return an ObjTree + * representing the creation command. + */ +static ObjTree * +deparse_CompositeTypeStmt(Oid objectId, Node *parsetree) +{ + CompositeTypeStmt *node = (CompositeTypeStmt *) parsetree; + ObjTree *composite; + Relation typerel = relation_open(objectId, AccessShareLock); + List *dpcontext; + List *tableelts = NIL; + + dpcontext = deparse_context_for(RelationGetRelationName(typerel), + objectId); + + composite = new_objtree_VA("CREATE TYPE %{identity}D AS (%{columns:, }s)", + 0); + append_object_object(composite, "identity", + new_objtree_for_qualname_id(RelationRelationId, + objectId)); + + tableelts = deparseTableElements(typerel, node->coldeflist, dpcontext, + false, /* not typed */ + true); /* composite type */ + + append_array_object(composite, "columns", tableelts); + + heap_close(typerel, AccessShareLock); + + return composite; +} + +/* * Handle deparsing of simple commands. * * This function contains a large switch that mirrors that in @@ -737,7 +999,7 @@ deparse_simple_command(StashedCommand *cmd) break; case T_CompositeTypeStmt: /* CREATE TYPE (composite) */ - command = NULL; + command = deparse_CompositeTypeStmt(objectId, parsetree); break; case T_CreateEnumStmt: /* CREATE TYPE AS ENUM */ diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 30cc3c7..bbbe3ac 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -9352,3 +9352,21 @@ flatten_reloptions(Oid relid) return result; } + +/* + * Obtain the deparsed default value for the given column of the given table. + * + * Caller must have set a correct deparse context. + */ +char * +RelationGetColumnDefault(Relation rel, AttrNumber attno, List *dpcontext) +{ + Node *defval; + char *defstr; + + defval = build_column_default(rel, attno); + defstr = deparse_expression_pretty(defval, dpcontext, false, false, + 0, 0); + + return defstr; +} diff --git a/src/include/utils/ruleutils.h b/src/include/utils/ruleutils.h index fed9c7b..1673e3e 100644 --- a/src/include/utils/ruleutils.h +++ b/src/include/utils/ruleutils.h @@ -32,4 +32,7 @@ extern List *select_rtable_names_for_explain(List *rtable, Bitmapset *rels_used); extern char *generate_collation_name(Oid collid); +extern char *RelationGetColumnDefault(Relation rel, AttrNumber attno, + List *dpcontext); + #endif /* RULEUTILS_H */ -- 2.1.4 --gatW/ieO32f1wygP Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="0010-deparse-Support-CREATE-TYPE-AS-ENUM.patch" From 7943924f34c394e1e86062be87737905be158cd2 Mon Sep 17 00:00:00 2001 From: Alvaro Herrera Date: Thu, 25 Sep 2014 15:55:43 -0300 Subject: [PATCH 10/42] deparse: Support CREATE TYPE AS ENUM --- src/backend/tcop/deparse_utility.c | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c index becff4a..3bcb260 100644 --- a/src/backend/tcop/deparse_utility.c +++ b/src/backend/tcop/deparse_utility.c @@ -901,6 +901,38 @@ deparse_CompositeTypeStmt(Oid objectId, Node *parsetree) } /* + * deparse_CreateEnumStmt + * Deparse a CreateEnumStmt (CREATE TYPE AS ENUM) + * + * Given a type OID and the parsetree that created it, return an ObjTree + * representing the creation command. + */ +static ObjTree * +deparse_CreateEnumStmt(Oid objectId, Node *parsetree) +{ + CreateEnumStmt *node = (CreateEnumStmt *) parsetree; + ObjTree *enumtype; + List *values; + ListCell *cell; + + enumtype = new_objtree_VA("CREATE TYPE %{identity}D AS ENUM (%{values:, }L)", + 0); + append_object_object(enumtype, "identity", + new_objtree_for_qualname_id(TypeRelationId, + objectId)); + values = NIL; + foreach(cell, node->vals) + { + Value *val = (Value *) lfirst(cell); + + values = lappend(values, new_string_object(strVal(val))); + } + append_array_object(enumtype, "values", values); + + return enumtype; +} + +/* * Handle deparsing of simple commands. * * This function contains a large switch that mirrors that in @@ -1003,7 +1035,7 @@ deparse_simple_command(StashedCommand *cmd) break; case T_CreateEnumStmt: /* CREATE TYPE AS ENUM */ - command = NULL; + command = deparse_CreateEnumStmt(objectId, parsetree); break; case T_CreateRangeStmt: /* CREATE TYPE AS RANGE */ -- 2.1.4 --gatW/ieO32f1wygP Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="0011-deparse-Support-CREATE-SCHEMA-TABLE-SEQUENCE-INDEX-T.patch" From 5d576ad6582d4980bbd78ae4e4e61f7d1d91926d Mon Sep 17 00:00:00 2001 From: Alvaro Herrera Date: Thu, 25 Sep 2014 15:57:48 -0300 Subject: [PATCH 11/42] deparse: Support CREATE SCHEMA/TABLE/SEQUENCE/INDEX/TRIGGER --- src/backend/commands/sequence.c | 34 ++ src/backend/commands/tablecmds.c | 3 +- src/backend/tcop/deparse_utility.c | 956 ++++++++++++++++++++++++++++++++++++- src/backend/utils/adt/ruleutils.c | 355 ++++++++++++-- src/include/commands/sequence.h | 1 + src/include/utils/ruleutils.h | 11 +- 6 files changed, 1300 insertions(+), 60 deletions(-) diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c index 622ccf7..acfa370 100644 --- a/src/backend/commands/sequence.c +++ b/src/backend/commands/sequence.c @@ -1475,6 +1475,40 @@ process_owned_by(Relation seqrel, List *owned_by) relation_close(tablerel, NoLock); } +/* + * Return sequence parameters, detailed + */ +Form_pg_sequence +get_sequence_values(Oid sequenceId) +{ + Buffer buf; + SeqTable elm; + Relation seqrel; + HeapTupleData seqtuple; + Form_pg_sequence seq; + Form_pg_sequence retSeq; + + retSeq = palloc(sizeof(FormData_pg_sequence)); + + /* open and AccessShareLock sequence */ + init_sequence(sequenceId, &elm, &seqrel); + + if (pg_class_aclcheck(sequenceId, GetUserId(), + ACL_SELECT | ACL_UPDATE | ACL_USAGE) != ACLCHECK_OK) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("permission denied for sequence %s", + RelationGetRelationName(seqrel)))); + + seq = read_seq_tuple(elm, seqrel, &buf, &seqtuple); + + memcpy(retSeq, seq, sizeof(FormData_pg_sequence)); + + UnlockReleaseBuffer(buf); + relation_close(seqrel, NoLock); + + return retSeq; +} /* * Return sequence parameters, for use by information schema diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 4b7b032..31fd7ba 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -8037,7 +8037,8 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel, if (!list_member_oid(tab->changedConstraintOids, foundObject.objectId)) { - char *defstring = pg_get_constraintdef_string(foundObject.objectId); + char *defstring = pg_get_constraintdef_string(foundObject.objectId, + true); /* * Put NORMAL dependencies at the front of the list and diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c index 3bcb260..59ef0a1 100644 --- a/src/backend/tcop/deparse_utility.c +++ b/src/backend/tcop/deparse_utility.c @@ -639,6 +639,223 @@ new_objtree_for_qualname_id(Oid classId, Oid objectId) } /* + * Return the string representation of the given RELPERSISTENCE value + */ +static char * +get_persistence_str(char persistence) +{ + switch (persistence) + { + case RELPERSISTENCE_TEMP: + return "TEMPORARY"; + case RELPERSISTENCE_UNLOGGED: + return "UNLOGGED"; + case RELPERSISTENCE_PERMANENT: + return ""; + default: + return "???"; + } +} + +/* + * deparse_CreateTrigStmt + * Deparse a CreateTrigStmt (CREATE TRIGGER) + * + * Given a trigger OID and the parsetree that created it, return an ObjTree + * representing the creation command. + */ +static ObjTree * +deparse_CreateTrigStmt(Oid objectId, Node *parsetree) +{ + CreateTrigStmt *node = (CreateTrigStmt *) parsetree; + Relation pg_trigger; + HeapTuple trigTup; + Form_pg_trigger trigForm; + ObjTree *trigger; + ObjTree *tmp; + int tgnargs; + List *list; + List *events; + + pg_trigger = heap_open(TriggerRelationId, AccessShareLock); + + trigTup = get_catalog_object_by_oid(pg_trigger, objectId); + trigForm = (Form_pg_trigger) GETSTRUCT(trigTup); + + /* + * Some of the elements only make sense for CONSTRAINT TRIGGERs, but it + * seems simpler to use a single fmt string for both kinds of triggers. + */ + trigger = + new_objtree_VA("CREATE %{constraint}s TRIGGER %{name}I %{time}s %{events: OR }s " + "ON %{relation}D %{from_table}s %{constraint_attrs: }s " + "FOR EACH %{for_each}s %{when}s EXECUTE PROCEDURE %{function}s", + 2, + "name", ObjTypeString, node->trigname, + "constraint", ObjTypeString, + node->isconstraint ? "CONSTRAINT" : ""); + + if (node->timing == TRIGGER_TYPE_BEFORE) + append_string_object(trigger, "time", "BEFORE"); + else if (node->timing == TRIGGER_TYPE_AFTER) + append_string_object(trigger, "time", "AFTER"); + else if (node->timing == TRIGGER_TYPE_INSTEAD) + append_string_object(trigger, "time", "INSTEAD OF"); + else + elog(ERROR, "unrecognized trigger timing value %d", node->timing); + + /* + * Decode the events that the trigger fires for. The output is a list; + * in most cases it will just be a string with the even name, but when + * there's an UPDATE with a list of columns, we return a JSON object. + */ + events = NIL; + if (node->events & TRIGGER_TYPE_INSERT) + events = lappend(events, new_string_object("INSERT")); + if (node->events & TRIGGER_TYPE_DELETE) + events = lappend(events, new_string_object("DELETE")); + if (node->events & TRIGGER_TYPE_TRUNCATE) + events = lappend(events, new_string_object("TRUNCATE")); + if (node->events & TRIGGER_TYPE_UPDATE) + { + if (node->columns == NIL) + { + events = lappend(events, new_string_object("UPDATE")); + } + else + { + ObjTree *update; + ListCell *cell; + List *cols = NIL; + + /* + * Currently only UPDATE OF can be objects in the output JSON, but + * we add a "kind" element so that user code can distinguish + * possible future new event types. + */ + update = new_objtree_VA("UPDATE OF %{columns:, }I", + 1, "kind", ObjTypeString, "update_of"); + + foreach(cell, node->columns) + { + char *colname = strVal(lfirst(cell)); + + cols = lappend(cols, + new_string_object(colname)); + } + + append_array_object(update, "columns", cols); + + events = lappend(events, + new_object_object(update)); + } + } + append_array_object(trigger, "events", events); + + tmp = new_objtree_for_qualname_id(RelationRelationId, + trigForm->tgrelid); + append_object_object(trigger, "relation", tmp); + + tmp = new_objtree_VA("FROM %{relation}D", 0); + if (trigForm->tgconstrrelid) + { + ObjTree *rel; + + rel = new_objtree_for_qualname_id(RelationRelationId, + trigForm->tgconstrrelid); + append_object_object(tmp, "relation", rel); + } + else + append_bool_object(tmp, "present", false); + append_object_object(trigger, "from_table", tmp); + + list = NIL; + if (node->deferrable) + list = lappend(list, + new_string_object("DEFERRABLE")); + if (node->initdeferred) + list = lappend(list, + new_string_object("INITIALLY DEFERRED")); + append_array_object(trigger, "constraint_attrs", list); + + append_string_object(trigger, "for_each", + node->row ? "ROW" : "STATEMENT"); + + tmp = new_objtree_VA("WHEN (%{clause}s)", 0); + if (node->whenClause) + { + Node *whenClause; + Datum value; + bool isnull; + + value = fastgetattr(trigTup, Anum_pg_trigger_tgqual, + RelationGetDescr(pg_trigger), &isnull); + if (isnull) + elog(ERROR, "bogus NULL tgqual"); + + whenClause = stringToNode(TextDatumGetCString(value)); + append_string_object(tmp, "clause", + pg_get_trigger_whenclause(trigForm, + whenClause, + false)); + } + else + append_bool_object(tmp, "present", false); + append_object_object(trigger, "when", tmp); + + tmp = new_objtree_VA("%{funcname}D(%{args:, }L)", + 1, "funcname", ObjTypeObject, + new_objtree_for_qualname_id(ProcedureRelationId, + trigForm->tgfoid)); + list = NIL; + tgnargs = trigForm->tgnargs; + if (tgnargs > 0) + { + bytea *tgargs; + char *argstr; + bool isnull; + int findx; + int lentgargs; + char *p; + + tgargs = DatumGetByteaP(fastgetattr(trigTup, + Anum_pg_trigger_tgargs, + RelationGetDescr(pg_trigger), + &isnull)); + if (isnull) + elog(ERROR, "invalid NULL tgargs"); + argstr = (char *) VARDATA(tgargs); + lentgargs = VARSIZE_ANY_EXHDR(tgargs); + + p = argstr; + for (findx = 0; findx < tgnargs; findx++) + { + size_t tlen; + + /* verify that the argument encoding is correct */ + tlen = strlen(p); + if (p + tlen >= argstr + lentgargs) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid argument string (%s) for trigger \"%s\"", + argstr, NameStr(trigForm->tgname)))); + + list = lappend(list, new_string_object(p)); + + p += tlen + 1; + } + } + + append_array_object(tmp, "args", list); /* might be NIL */ + + append_object_object(trigger, "function", tmp); + + heap_close(pg_trigger, AccessShareLock); + + return trigger; +} + +/* * deparse_ColumnDef * Subroutine for CREATE TABLE deparsing * @@ -865,6 +1082,334 @@ deparseTableElements(Relation relation, List *tableElements, List *dpcontext, } /* + * obtainConstraints + * Subroutine for CREATE TABLE/CREATE DOMAIN deparsing + * + * Given a table OID or domain OID, obtain its constraints and append them to + * the given elements list. The updated list is returned. + * + * This works for typed tables, regular tables, and domains. + * + * Note that CONSTRAINT_FOREIGN constraints are always ignored. + */ +static List * +obtainConstraints(List *elements, Oid relationId, Oid domainId) +{ + Relation conRel; + ScanKeyData key; + SysScanDesc scan; + HeapTuple tuple; + ObjTree *tmp; + + /* only one may be valid */ + Assert(OidIsValid(relationId) ^ OidIsValid(domainId)); + + /* + * scan pg_constraint to fetch all constraints linked to the given + * relation. + */ + conRel = heap_open(ConstraintRelationId, AccessShareLock); + if (OidIsValid(relationId)) + { + ScanKeyInit(&key, + Anum_pg_constraint_conrelid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(relationId)); + scan = systable_beginscan(conRel, ConstraintRelidIndexId, + true, NULL, 1, &key); + } + else + { + Assert(OidIsValid(domainId)); + ScanKeyInit(&key, + Anum_pg_constraint_contypid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(domainId)); + scan = systable_beginscan(conRel, ConstraintTypidIndexId, + true, NULL, 1, &key); + } + + /* + * For each constraint, add a node to the list of table elements. In + * these nodes we include not only the printable information ("fmt"), but + * also separate attributes to indicate the type of constraint, for + * automatic processing. + */ + while (HeapTupleIsValid(tuple = systable_getnext(scan))) + { + Form_pg_constraint constrForm; + char *contype; + + constrForm = (Form_pg_constraint) GETSTRUCT(tuple); + + switch (constrForm->contype) + { + case CONSTRAINT_CHECK: + contype = "check"; + break; + case CONSTRAINT_FOREIGN: + continue; /* not here */ + case CONSTRAINT_PRIMARY: + contype = "primary key"; + break; + case CONSTRAINT_UNIQUE: + contype = "unique"; + break; + case CONSTRAINT_TRIGGER: + contype = "trigger"; + break; + case CONSTRAINT_EXCLUSION: + contype = "exclusion"; + break; + default: + elog(ERROR, "unrecognized constraint type"); + } + + /* + * "type" and "contype" are not part of the printable output, but are + * useful to programmatically distinguish these from columns and among + * different constraint types. + * + * XXX it might be useful to also list the column names in a PK, etc. + */ + tmp = new_objtree_VA("CONSTRAINT %{name}I %{definition}s", + 4, + "type", ObjTypeString, "constraint", + "contype", ObjTypeString, contype, + "name", ObjTypeString, NameStr(constrForm->conname), + "definition", ObjTypeString, + pg_get_constraintdef_string(HeapTupleGetOid(tuple), + false)); + elements = lappend(elements, new_object_object(tmp)); + } + + systable_endscan(scan); + heap_close(conRel, AccessShareLock); + + return elements; +} + +/* + * deparse_CreateStmt + * Deparse a CreateStmt (CREATE TABLE) + * + * Given a table OID and the parsetree that created it, return an ObjTree + * representing the creation command. + */ +static ObjTree * +deparse_CreateStmt(Oid objectId, Node *parsetree) +{ + CreateStmt *node = (CreateStmt *) parsetree; + Relation relation = relation_open(objectId, AccessShareLock); + List *dpcontext; + ObjTree *createStmt; + ObjTree *tmp; + List *list; + ListCell *cell; + char *fmtstr; + + /* + * Typed tables use a slightly different format string: we must not put + * table_elements with parents directly in the fmt string, because if + * there are no options the parens must not be emitted; and also, typed + * tables do not allow for inheritance. + */ + if (node->ofTypename) + fmtstr = "CREATE %{persistence}s TABLE %{if_not_exists}s %{identity}D " + "OF %{of_type}T %{table_elements}s " + "WITH (%{with:, }s) %{on_commit}s %{tablespace}s"; + else + fmtstr = "CREATE %{persistence}s TABLE %{if_not_exists}s %{identity}D " + "(%{table_elements:, }s) %{inherits}s " + "WITH (%{with:, }s) %{on_commit}s %{tablespace}s"; + + createStmt = + new_objtree_VA(fmtstr, 1, + "persistence", ObjTypeString, + get_persistence_str(relation->rd_rel->relpersistence)); + + tmp = new_objtree_for_qualname(relation->rd_rel->relnamespace, + RelationGetRelationName(relation)); + append_object_object(createStmt, "identity", tmp); + + append_string_object(createStmt, "if_not_exists", + node->if_not_exists ? "IF NOT EXISTS" : ""); + + dpcontext = deparse_context_for(RelationGetRelationName(relation), + objectId); + + if (node->ofTypename) + { + List *tableelts = NIL; + + /* + * We can't put table elements directly in the fmt string as an array + * surrounded by parens here, because an empty clause would cause a + * syntax error. Therefore, we use an indirection element and set + * present=false when there are no elements. + */ + append_string_object(createStmt, "table_kind", "typed"); + + tmp = new_objtree_for_type(relation->rd_rel->reloftype, -1); + append_object_object(createStmt, "of_type", tmp); + + tableelts = deparseTableElements(relation, node->tableElts, dpcontext, + true, /* typed table */ + false); /* not composite */ + tableelts = obtainConstraints(tableelts, objectId, InvalidOid); + if (tableelts == NIL) + tmp = new_objtree_VA("", 1, + "present", ObjTypeBool, false); + else + tmp = new_objtree_VA("(%{elements:, }s)", 1, + "elements", ObjTypeArray, tableelts); + append_object_object(createStmt, "table_elements", tmp); + } + else + { + List *tableelts = NIL; + + /* + * There is no need to process LIKE clauses separately; they have + * already been transformed into columns and constraints. + */ + append_string_object(createStmt, "table_kind", "plain"); + + /* + * Process table elements: column definitions and constraints. Only + * the column definitions are obtained from the parse node itself. To + * get constraints we rely on pg_constraint, because the parse node + * might be missing some things such as the name of the constraints. + */ + tableelts = deparseTableElements(relation, node->tableElts, dpcontext, + false, /* not typed table */ + false); /* not composite */ + tableelts = obtainConstraints(tableelts, objectId, InvalidOid); + + append_array_object(createStmt, "table_elements", tableelts); + + /* + * Add inheritance specification. We cannot simply scan the list of + * parents from the parser node, because that may lack the actual + * qualified names of the parent relations. Rather than trying to + * re-resolve them from the information in the parse node, it seems + * more accurate and convenient to grab it from pg_inherits. + */ + tmp = new_objtree_VA("INHERITS (%{parents:, }D)", 0); + if (list_length(node->inhRelations) > 0) + { + List *parents = NIL; + Relation inhRel; + SysScanDesc scan; + ScanKeyData key; + HeapTuple tuple; + + inhRel = heap_open(InheritsRelationId, RowExclusiveLock); + + ScanKeyInit(&key, + Anum_pg_inherits_inhrelid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(objectId)); + + scan = systable_beginscan(inhRel, InheritsRelidSeqnoIndexId, + true, NULL, 1, &key); + + while (HeapTupleIsValid(tuple = systable_getnext(scan))) + { + ObjTree *parent; + Form_pg_inherits formInh = (Form_pg_inherits) GETSTRUCT(tuple); + + parent = new_objtree_for_qualname_id(RelationRelationId, + formInh->inhparent); + parents = lappend(parents, new_object_object(parent)); + } + + systable_endscan(scan); + heap_close(inhRel, RowExclusiveLock); + + append_array_object(tmp, "parents", parents); + } + else + { + append_null_object(tmp, "parents"); + append_bool_object(tmp, "present", false); + } + append_object_object(createStmt, "inherits", tmp); + } + + tmp = new_objtree_VA("TABLESPACE %{tablespace}I", 0); + if (node->tablespacename) + append_string_object(tmp, "tablespace", node->tablespacename); + else + { + append_null_object(tmp, "tablespace"); + append_bool_object(tmp, "present", false); + } + append_object_object(createStmt, "tablespace", tmp); + + tmp = new_objtree_VA("ON COMMIT %{on_commit_value}s", 0); + switch (node->oncommit) + { + case ONCOMMIT_DROP: + append_string_object(tmp, "on_commit_value", "DROP"); + break; + + case ONCOMMIT_DELETE_ROWS: + append_string_object(tmp, "on_commit_value", "DELETE ROWS"); + break; + + case ONCOMMIT_PRESERVE_ROWS: + append_string_object(tmp, "on_commit_value", "PRESERVE ROWS"); + break; + + case ONCOMMIT_NOOP: + append_null_object(tmp, "on_commit_value"); + append_bool_object(tmp, "present", false); + break; + } + append_object_object(createStmt, "on_commit", tmp); + + /* + * WITH clause. We always emit one, containing at least the OIDS option. + * That way we don't depend on the default value for default_with_oids. + * We can skip emitting other options if there don't appear in the parse + * node. + */ + tmp = new_objtree_VA("oids=%{value}s", 2, + "option", ObjTypeString, "oids", + "value", ObjTypeString, + relation->rd_rel->relhasoids ? "ON" : "OFF"); + list = list_make1(new_object_object(tmp)); + foreach(cell, node->options) + { + DefElem *opt = (DefElem *) lfirst(cell); + char *defname; + char *value; + + /* already handled above */ + if (strcmp(opt->defname, "oids") == 0) + continue; + + if (opt->defnamespace) + defname = psprintf("%s.%s", opt->defnamespace, opt->defname); + else + defname = opt->defname; + + value = opt->arg ? defGetString(opt) : + defGetBoolean(opt) ? "TRUE" : "FALSE"; + tmp = new_objtree_VA("%{option}s=%{value}s", 2, + "option", ObjTypeString, defname, + "value", ObjTypeString, value); + list = lappend(list, new_object_object(tmp)); + } + append_array_object(createStmt, "with", list); + + relation_close(relation, AccessShareLock); + + return createStmt; +} + +/* * deparse_CompositeTypeStmt * Deparse a CompositeTypeStmt (CREATE TYPE AS) * @@ -932,6 +1477,405 @@ deparse_CreateEnumStmt(Oid objectId, Node *parsetree) return enumtype; } +static inline ObjElem * +deparse_Seq_Cache(ObjTree *parent, Form_pg_sequence seqdata) +{ + ObjTree *tmp; + char *tmpstr; + + tmpstr = psprintf(INT64_FORMAT, seqdata->cache_value); + tmp = new_objtree_VA("CACHE %{value}s", + 2, + "clause", ObjTypeString, "cache", + "value", ObjTypeString, tmpstr); + return new_object_object(tmp); +} + +static inline ObjElem * +deparse_Seq_Cycle(ObjTree *parent, Form_pg_sequence seqdata) +{ + ObjTree *tmp; + + tmp = new_objtree_VA("%{no}s CYCLE", + 2, + "clause", ObjTypeString, "cycle", + "no", ObjTypeString, + seqdata->is_cycled ? "" : "NO"); + return new_object_object(tmp); +} + +static inline ObjElem * +deparse_Seq_IncrementBy(ObjTree *parent, Form_pg_sequence seqdata) +{ + ObjTree *tmp; + char *tmpstr; + + tmpstr = psprintf(INT64_FORMAT, seqdata->increment_by); + tmp = new_objtree_VA("INCREMENT BY %{value}s", + 2, + "clause", ObjTypeString, "increment_by", + "value", ObjTypeString, tmpstr); + return new_object_object(tmp); +} + +static inline ObjElem * +deparse_Seq_Minvalue(ObjTree *parent, Form_pg_sequence seqdata) +{ + ObjTree *tmp; + char *tmpstr; + + tmpstr = psprintf(INT64_FORMAT, seqdata->min_value); + tmp = new_objtree_VA("MINVALUE %{value}s", + 2, + "clause", ObjTypeString, "minvalue", + "value", ObjTypeString, tmpstr); + return new_object_object(tmp); +} + +static inline ObjElem * +deparse_Seq_Maxvalue(ObjTree *parent, Form_pg_sequence seqdata) +{ + ObjTree *tmp; + char *tmpstr; + + tmpstr = psprintf(INT64_FORMAT, seqdata->max_value); + tmp = new_objtree_VA("MAXVALUE %{value}s", + 2, + "clause", ObjTypeString, "maxvalue", + "value", ObjTypeString, tmpstr); + return new_object_object(tmp); +} + +static inline ObjElem * +deparse_Seq_Startwith(ObjTree *parent, Form_pg_sequence seqdata) +{ + ObjTree *tmp; + char *tmpstr; + + tmpstr = psprintf(INT64_FORMAT, seqdata->start_value); + tmp = new_objtree_VA("START WITH %{value}s", + 2, + "clause", ObjTypeString, "start", + "value", ObjTypeString, tmpstr); + return new_object_object(tmp); +} + +static inline ObjElem * +deparse_Seq_Restart(ObjTree *parent, Form_pg_sequence seqdata) +{ + ObjTree *tmp; + char *tmpstr; + + tmpstr = psprintf(INT64_FORMAT, seqdata->last_value); + tmp = new_objtree_VA("RESTART %{value}s", + 2, + "clause", ObjTypeString, "restart", + "value", ObjTypeString, tmpstr); + return new_object_object(tmp); +} + +static ObjElem * +deparse_Seq_OwnedBy(ObjTree *parent, Oid sequenceId) +{ + ObjTree *ownedby = NULL; + Relation depRel; + SysScanDesc scan; + ScanKeyData keys[3]; + HeapTuple tuple; + + depRel = heap_open(DependRelationId, AccessShareLock); + ScanKeyInit(&keys[0], + Anum_pg_depend_classid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(RelationRelationId)); + ScanKeyInit(&keys[1], + Anum_pg_depend_objid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(sequenceId)); + ScanKeyInit(&keys[2], + Anum_pg_depend_objsubid, + BTEqualStrategyNumber, F_INT4EQ, + Int32GetDatum(0)); + + scan = systable_beginscan(depRel, DependDependerIndexId, true, + NULL, 3, keys); + while (HeapTupleIsValid(tuple = systable_getnext(scan))) + { + Oid ownerId; + Form_pg_depend depform; + ObjTree *tmp; + char *colname; + + depform = (Form_pg_depend) GETSTRUCT(tuple); + + /* only consider AUTO dependencies on pg_class */ + if (depform->deptype != DEPENDENCY_AUTO) + continue; + if (depform->refclassid != RelationRelationId) + continue; + if (depform->refobjsubid <= 0) + continue; + + ownerId = depform->refobjid; + colname = get_attname(ownerId, depform->refobjsubid); + if (colname == NULL) + continue; + + tmp = new_objtree_for_qualname_id(RelationRelationId, ownerId); + append_string_object(tmp, "attrname", colname); + ownedby = new_objtree_VA("OWNED BY %{owner}D", + 2, + "clause", ObjTypeString, "owned", + "owner", ObjTypeObject, tmp); + } + + systable_endscan(scan); + relation_close(depRel, AccessShareLock); + + /* + * If there's no owner column, emit an empty OWNED BY element, set up so + * that it won't print anything. + */ + if (!ownedby) + /* XXX this shouldn't happen ... */ + ownedby = new_objtree_VA("OWNED BY %{owner}D", + 3, + "clause", ObjTypeString, "owned", + "owner", ObjTypeNull, + "present", ObjTypeBool, false); + return new_object_object(ownedby); +} + +/* + * deparse_CreateSeqStmt + * deparse a CreateSeqStmt + * + * Given a sequence OID and the parsetree that created it, return an ObjTree + * representing the creation command. + */ +static ObjTree * +deparse_CreateSeqStmt(Oid objectId, Node *parsetree) +{ + ObjTree *createSeq; + ObjTree *tmp; + Relation relation = relation_open(objectId, AccessShareLock); + Form_pg_sequence seqdata; + List *elems = NIL; + + seqdata = get_sequence_values(objectId); + + createSeq = + new_objtree_VA("CREATE %{persistence}s SEQUENCE %{identity}D " + "%{definition: }s", + 1, + "persistence", ObjTypeString, + get_persistence_str(relation->rd_rel->relpersistence)); + + tmp = new_objtree_for_qualname(relation->rd_rel->relnamespace, + RelationGetRelationName(relation)); + append_object_object(createSeq, "identity", tmp); + + /* definition elements */ + elems = lappend(elems, deparse_Seq_Cache(createSeq, seqdata)); + elems = lappend(elems, deparse_Seq_Cycle(createSeq, seqdata)); + elems = lappend(elems, deparse_Seq_IncrementBy(createSeq, seqdata)); + elems = lappend(elems, deparse_Seq_Minvalue(createSeq, seqdata)); + elems = lappend(elems, deparse_Seq_Maxvalue(createSeq, seqdata)); + elems = lappend(elems, deparse_Seq_Startwith(createSeq, seqdata)); + elems = lappend(elems, deparse_Seq_Restart(createSeq, seqdata)); + /* we purposefully do not emit OWNED BY here */ + + append_array_object(createSeq, "definition", elems); + + relation_close(relation, AccessShareLock); + + return createSeq; +} + +/* + * deparse_AlterSeqStmt + * deparse an AlterSeqStmt + * + * Given a sequence OID and a parsetree that modified it, return an ObjTree + * representing the alter command. + */ +static ObjTree * +deparse_AlterSeqStmt(Oid objectId, Node *parsetree) +{ + ObjTree *alterSeq; + ObjTree *tmp; + Relation relation = relation_open(objectId, AccessShareLock); + Form_pg_sequence seqdata; + List *elems = NIL; + ListCell *cell; + + seqdata = get_sequence_values(objectId); + + alterSeq = + new_objtree_VA("ALTER SEQUENCE %{identity}D %{definition: }s", 0); + tmp = new_objtree_for_qualname(relation->rd_rel->relnamespace, + RelationGetRelationName(relation)); + append_object_object(alterSeq, "identity", tmp); + + foreach(cell, ((AlterSeqStmt *) parsetree)->options) + { + DefElem *elem = (DefElem *) lfirst(cell); + ObjElem *newelm; + + if (strcmp(elem->defname, "cache") == 0) + newelm = deparse_Seq_Cache(alterSeq, seqdata); + else if (strcmp(elem->defname, "cycle") == 0) + newelm = deparse_Seq_Cycle(alterSeq, seqdata); + else if (strcmp(elem->defname, "increment") == 0) + newelm = deparse_Seq_IncrementBy(alterSeq, seqdata); + else if (strcmp(elem->defname, "minvalue") == 0) + newelm = deparse_Seq_Minvalue(alterSeq, seqdata); + else if (strcmp(elem->defname, "maxvalue") == 0) + newelm = deparse_Seq_Maxvalue(alterSeq, seqdata); + else if (strcmp(elem->defname, "start") == 0) + newelm = deparse_Seq_Startwith(alterSeq, seqdata); + else if (strcmp(elem->defname, "restart") == 0) + newelm = deparse_Seq_Restart(alterSeq, seqdata); + else if (strcmp(elem->defname, "owned_by") == 0) + newelm = deparse_Seq_OwnedBy(alterSeq, objectId); + else + elog(ERROR, "invalid sequence option %s", elem->defname); + + elems = lappend(elems, newelm); + } + + append_array_object(alterSeq, "definition", elems); + + relation_close(relation, AccessShareLock); + + return alterSeq; +} + +/* + * deparse_IndexStmt + * deparse an IndexStmt + * + * Given an index OID and the parsetree that created it, return an ObjTree + * representing the creation command. + * + * If the index corresponds to a constraint, NULL is returned. + */ +static ObjTree * +deparse_IndexStmt(Oid objectId, Node *parsetree) +{ + IndexStmt *node = (IndexStmt *) parsetree; + ObjTree *indexStmt; + ObjTree *tmp; + Relation idxrel; + Relation heaprel; + char *index_am; + char *definition; + char *reloptions; + char *tablespace; + char *whereClause; + + if (node->primary || node->isconstraint) + { + /* + * indexes for PRIMARY KEY and other constraints are output + * separately; return empty here. + */ + return NULL; + } + + idxrel = relation_open(objectId, AccessShareLock); + heaprel = relation_open(idxrel->rd_index->indrelid, AccessShareLock); + + pg_get_indexdef_detailed(objectId, + &index_am, &definition, &reloptions, + &tablespace, &whereClause); + + indexStmt = + new_objtree_VA("CREATE %{unique}s INDEX %{concurrently}s %{if_not_exists}s %{name}I " + "ON %{table}D USING %{index_am}s (%{definition}s) " + "%{with}s %{tablespace}s %{where_clause}s", + 6, + "unique", ObjTypeString, node->unique ? "UNIQUE" : "", + "concurrently", ObjTypeString, + node->concurrent ? "CONCURRENTLY" : "", + "if_not_exists", ObjTypeString, node->if_not_exists ? "IF NOT EXISTS" : "", + "name", ObjTypeString, RelationGetRelationName(idxrel), + "definition", ObjTypeString, definition, + "index_am", ObjTypeString, index_am); + + tmp = new_objtree_for_qualname(heaprel->rd_rel->relnamespace, + RelationGetRelationName(heaprel)); + append_object_object(indexStmt, "table", tmp); + + /* reloptions */ + tmp = new_objtree_VA("WITH (%{opts}s)", 0); + if (reloptions) + append_string_object(tmp, "opts", reloptions); + else + append_bool_object(tmp, "present", false); + append_object_object(indexStmt, "with", tmp); + + /* tablespace */ + tmp = new_objtree_VA("TABLESPACE %{tablespace}s", 0); + if (tablespace) + append_string_object(tmp, "tablespace", tablespace); + else + append_bool_object(tmp, "present", false); + append_object_object(indexStmt, "tablespace", tmp); + + /* WHERE clause */ + tmp = new_objtree_VA("WHERE %{where}s", 0); + if (whereClause) + append_string_object(tmp, "where", whereClause); + else + append_bool_object(tmp, "present", false); + append_object_object(indexStmt, "where_clause", tmp); + + heap_close(idxrel, AccessShareLock); + heap_close(heaprel, AccessShareLock); + + return indexStmt; +} + +/* + * deparse_CreateSchemaStmt + * deparse a CreateSchemaStmt + * + * Given a schema OID and the parsetree that created it, return an ObjTree + * representing the creation command. + * + * Note we don't output the schema elements given in the creation command. + * They must be output separately. (In the current implementation, + * CreateSchemaCommand passes them back to ProcessUtility, which will lead to + * this file if appropriate.) + */ +static ObjTree * +deparse_CreateSchemaStmt(Oid objectId, Node *parsetree) +{ + CreateSchemaStmt *node = (CreateSchemaStmt *) parsetree; + ObjTree *createSchema; + ObjTree *auth; + + createSchema = + new_objtree_VA("CREATE SCHEMA %{if_not_exists}s %{name}I %{authorization}s", + 2, + "name", ObjTypeString, node->schemaname, + "if_not_exists", ObjTypeString, + node->if_not_exists ? "IF NOT EXISTS" : ""); + + auth = new_objtree_VA("AUTHORIZATION %{authorization_role}I", 0); + if (node->authid) + append_string_object(auth, "authorization_role", node->authid); + else + { + append_null_object(auth, "authorization_role"); + append_bool_object(auth, "present", false); + } + append_object_object(createSchema, "authorization", auth); + + return createSchema; +} + /* * Handle deparsing of simple commands. * @@ -954,11 +1898,11 @@ deparse_simple_command(StashedCommand *cmd) switch (nodeTag(parsetree)) { case T_CreateSchemaStmt: - command = NULL; + command = deparse_CreateSchemaStmt(objectId, parsetree); break; case T_CreateStmt: - command = NULL; + command = deparse_CreateStmt(objectId, parsetree); break; case T_CreateForeignTableStmt: @@ -981,7 +1925,7 @@ deparse_simple_command(StashedCommand *cmd) break; case T_IndexStmt: - command = NULL; + command = deparse_IndexStmt(objectId, parsetree); break; case T_CreateExtensionStmt: @@ -1063,11 +2007,11 @@ deparse_simple_command(StashedCommand *cmd) break; case T_CreateSeqStmt: - command = NULL; + command = deparse_CreateSeqStmt(objectId, parsetree); break; case T_AlterSeqStmt: - command = NULL; + command = deparse_AlterSeqStmt(objectId, parsetree); break; case T_CreateTableAsStmt: @@ -1080,7 +2024,7 @@ deparse_simple_command(StashedCommand *cmd) break; case T_CreateTrigStmt: - command = NULL; + command = deparse_CreateTrigStmt(objectId, parsetree); break; case T_CreatePLangStmt: diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index bbbe3ac..541e076 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -821,59 +821,12 @@ pg_get_triggerdef_worker(Oid trigid, bool pretty) if (!isnull) { Node *qual; - char relkind; - deparse_context context; - deparse_namespace dpns; - RangeTblEntry *oldrte; - RangeTblEntry *newrte; - - appendStringInfoString(&buf, "WHEN ("); + char *qualstr; qual = stringToNode(TextDatumGetCString(value)); + qualstr = pg_get_trigger_whenclause(trigrec, qual, pretty); - relkind = get_rel_relkind(trigrec->tgrelid); - - /* Build minimal OLD and NEW RTEs for the rel */ - oldrte = makeNode(RangeTblEntry); - oldrte->rtekind = RTE_RELATION; - oldrte->relid = trigrec->tgrelid; - oldrte->relkind = relkind; - oldrte->alias = makeAlias("old", NIL); - oldrte->eref = oldrte->alias; - oldrte->lateral = false; - oldrte->inh = false; - oldrte->inFromCl = true; - - newrte = makeNode(RangeTblEntry); - newrte->rtekind = RTE_RELATION; - newrte->relid = trigrec->tgrelid; - newrte->relkind = relkind; - newrte->alias = makeAlias("new", NIL); - newrte->eref = newrte->alias; - newrte->lateral = false; - newrte->inh = false; - newrte->inFromCl = true; - - /* Build two-element rtable */ - memset(&dpns, 0, sizeof(dpns)); - dpns.rtable = list_make2(oldrte, newrte); - dpns.ctes = NIL; - set_rtable_names(&dpns, NIL, NULL); - set_simple_column_names(&dpns); - - /* Set up context with one-deep namespace stack */ - context.buf = &buf; - context.namespaces = list_make1(&dpns); - context.windowClause = NIL; - context.windowTList = NIL; - context.varprefix = true; - context.prettyFlags = pretty ? PRETTYFLAG_PAREN | PRETTYFLAG_INDENT : PRETTYFLAG_INDENT; - context.wrapColumn = WRAP_COLUMN_DEFAULT; - context.indentLevel = PRETTYINDENT_STD; - - get_rule_expr(qual, &context, false); - - appendStringInfoString(&buf, ") "); + appendStringInfo(&buf, "WHEN (%s) ", qualstr); } appendStringInfo(&buf, "EXECUTE PROCEDURE %s(", @@ -914,6 +867,63 @@ pg_get_triggerdef_worker(Oid trigid, bool pretty) return buf.data; } +char * +pg_get_trigger_whenclause(Form_pg_trigger trigrec, Node *whenClause, bool pretty) +{ + StringInfoData buf; + char relkind; + deparse_context context; + deparse_namespace dpns; + RangeTblEntry *oldrte; + RangeTblEntry *newrte; + + initStringInfo(&buf); + + relkind = get_rel_relkind(trigrec->tgrelid); + + /* Build minimal OLD and NEW RTEs for the rel */ + oldrte = makeNode(RangeTblEntry); + oldrte->rtekind = RTE_RELATION; + oldrte->relid = trigrec->tgrelid; + oldrte->relkind = relkind; + oldrte->alias = makeAlias("old", NIL); + oldrte->eref = oldrte->alias; + oldrte->lateral = false; + oldrte->inh = false; + oldrte->inFromCl = true; + + newrte = makeNode(RangeTblEntry); + newrte->rtekind = RTE_RELATION; + newrte->relid = trigrec->tgrelid; + newrte->relkind = relkind; + newrte->alias = makeAlias("new", NIL); + newrte->eref = newrte->alias; + newrte->lateral = false; + newrte->inh = false; + newrte->inFromCl = true; + + /* Build two-element rtable */ + memset(&dpns, 0, sizeof(dpns)); + dpns.rtable = list_make2(oldrte, newrte); + dpns.ctes = NIL; + set_rtable_names(&dpns, NIL, NULL); + set_simple_column_names(&dpns); + + /* Set up context with one-deep namespace stack */ + context.buf = &buf; + context.namespaces = list_make1(&dpns); + context.windowClause = NIL; + context.windowTList = NIL; + context.varprefix = true; + context.prettyFlags = pretty ? PRETTYFLAG_PAREN | PRETTYFLAG_INDENT : PRETTYFLAG_INDENT; + context.wrapColumn = WRAP_COLUMN_DEFAULT; + context.indentLevel = PRETTYINDENT_STD; + + get_rule_expr(whenClause, &context, false); + + return buf.data; +} + /* ---------- * get_indexdef - Get the definition of an index * @@ -977,6 +987,8 @@ pg_get_indexdef_columns(Oid indexrelid, bool pretty) * * This is now used for exclusion constraints as well: if excludeOps is not * NULL then it points to an array of exclusion operator OIDs. + * + * XXX if you change this function, see pg_get_indexdef_detailed too. */ static char * pg_get_indexdef_worker(Oid indexrelid, int colno, @@ -1256,6 +1268,245 @@ pg_get_indexdef_worker(Oid indexrelid, int colno, return buf.data; } +/* + * Return an index definition, split in several pieces. + * + * There is a huge lot of code that's a dupe of pg_get_indexdef_worker, but + * control flow is different enough that it doesn't seem worth keeping them + * together. + */ +void +pg_get_indexdef_detailed(Oid indexrelid, + char **index_am, + char **definition, + char **reloptions, + char **tablespace, + char **whereClause) +{ + HeapTuple ht_idx; + HeapTuple ht_idxrel; + HeapTuple ht_am; + Form_pg_index idxrec; + Form_pg_class idxrelrec; + Form_pg_am amrec; + List *indexprs; + ListCell *indexpr_item; + List *context; + Oid indrelid; + int keyno; + Datum indcollDatum; + Datum indclassDatum; + Datum indoptionDatum; + bool isnull; + oidvector *indcollation; + oidvector *indclass; + int2vector *indoption; + StringInfoData definitionBuf; + char *sep; + + /* + * Fetch the pg_index tuple by the Oid of the index + */ + ht_idx = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(indexrelid)); + if (!HeapTupleIsValid(ht_idx)) + elog(ERROR, "cache lookup failed for index %u", indexrelid); + idxrec = (Form_pg_index) GETSTRUCT(ht_idx); + + indrelid = idxrec->indrelid; + Assert(indexrelid == idxrec->indexrelid); + + /* Must get indcollation, indclass, and indoption the hard way */ + indcollDatum = SysCacheGetAttr(INDEXRELID, ht_idx, + Anum_pg_index_indcollation, &isnull); + Assert(!isnull); + indcollation = (oidvector *) DatumGetPointer(indcollDatum); + + indclassDatum = SysCacheGetAttr(INDEXRELID, ht_idx, + Anum_pg_index_indclass, &isnull); + Assert(!isnull); + indclass = (oidvector *) DatumGetPointer(indclassDatum); + + indoptionDatum = SysCacheGetAttr(INDEXRELID, ht_idx, + Anum_pg_index_indoption, &isnull); + Assert(!isnull); + indoption = (int2vector *) DatumGetPointer(indoptionDatum); + + /* + * Fetch the pg_class tuple of the index relation + */ + ht_idxrel = SearchSysCache1(RELOID, ObjectIdGetDatum(indexrelid)); + if (!HeapTupleIsValid(ht_idxrel)) + elog(ERROR, "cache lookup failed for relation %u", indexrelid); + idxrelrec = (Form_pg_class) GETSTRUCT(ht_idxrel); + + /* + * Fetch the pg_am tuple of the index' access method + */ + ht_am = SearchSysCache1(AMOID, ObjectIdGetDatum(idxrelrec->relam)); + if (!HeapTupleIsValid(ht_am)) + elog(ERROR, "cache lookup failed for access method %u", + idxrelrec->relam); + amrec = (Form_pg_am) GETSTRUCT(ht_am); + + /* + * Get the index expressions, if any. (NOTE: we do not use the relcache + * versions of the expressions and predicate, because we want to display + * non-const-folded expressions.) + */ + if (!heap_attisnull(ht_idx, Anum_pg_index_indexprs)) + { + Datum exprsDatum; + bool isnull; + char *exprsString; + + exprsDatum = SysCacheGetAttr(INDEXRELID, ht_idx, + Anum_pg_index_indexprs, &isnull); + Assert(!isnull); + exprsString = TextDatumGetCString(exprsDatum); + indexprs = (List *) stringToNode(exprsString); + pfree(exprsString); + } + else + indexprs = NIL; + + indexpr_item = list_head(indexprs); + + context = deparse_context_for(get_relation_name(indrelid), indrelid); + + initStringInfo(&definitionBuf); + + /* output index AM */ + *index_am = pstrdup(quote_identifier(NameStr(amrec->amname))); + + /* + * Output index definition. Note the outer parens must be supplied by + * caller. + */ + sep = ""; + for (keyno = 0; keyno < idxrec->indnatts; keyno++) + { + AttrNumber attnum = idxrec->indkey.values[keyno]; + int16 opt = indoption->values[keyno]; + Oid keycoltype; + Oid keycolcollation; + Oid indcoll; + + appendStringInfoString(&definitionBuf, sep); + sep = ", "; + + if (attnum != 0) + { + /* Simple index column */ + char *attname; + int32 keycoltypmod; + + attname = get_relid_attribute_name(indrelid, attnum); + appendStringInfoString(&definitionBuf, quote_identifier(attname)); + get_atttypetypmodcoll(indrelid, attnum, + &keycoltype, &keycoltypmod, + &keycolcollation); + } + else + { + /* expressional index */ + Node *indexkey; + char *str; + + if (indexpr_item == NULL) + elog(ERROR, "too few entries in indexprs list"); + indexkey = (Node *) lfirst(indexpr_item); + indexpr_item = lnext(indexpr_item); + /* Deparse */ + str = deparse_expression_pretty(indexkey, context, false, false, + 0, 0); + + /* Need parens if it's not a bare function call */ + if (indexkey && IsA(indexkey, FuncExpr) && + ((FuncExpr *) indexkey)->funcformat == COERCE_EXPLICIT_CALL) + appendStringInfoString(&definitionBuf, str); + else + appendStringInfo(&definitionBuf, "(%s)", str); + + keycoltype = exprType(indexkey); + keycolcollation = exprCollation(indexkey); + } + + /* Add collation, even if default */ + indcoll = indcollation->values[keyno]; + if (OidIsValid(indcoll)) + appendStringInfo(&definitionBuf, " COLLATE %s", + generate_collation_name((indcoll))); + + /* Add the operator class name, even if default */ + get_opclass_name(indclass->values[keyno], InvalidOid, &definitionBuf); + + /* Add options if relevant */ + if (amrec->amcanorder) + { + /* if it supports sort ordering, report DESC and NULLS opts */ + if (opt & INDOPTION_DESC) + { + appendStringInfoString(&definitionBuf, " DESC"); + /* NULLS FIRST is the default in this case */ + if (!(opt & INDOPTION_NULLS_FIRST)) + appendStringInfoString(&definitionBuf, " NULLS LAST"); + } + else + { + if (opt & INDOPTION_NULLS_FIRST) + appendStringInfoString(&definitionBuf, " NULLS FIRST"); + } + } + + /* XXX excludeOps thingy was here; do we need anything? */ + } + *definition = definitionBuf.data; + + /* output reloptions */ + *reloptions = flatten_reloptions(indexrelid); + + /* output tablespace */ + { + Oid tblspc; + + tblspc = get_rel_tablespace(indexrelid); + if (OidIsValid(tblspc)) + *tablespace = pstrdup(quote_identifier(get_tablespace_name(tblspc))); + else + *tablespace = NULL; + } + + /* report index predicate, if any */ + if (!heap_attisnull(ht_idx, Anum_pg_index_indpred)) + { + Node *node; + Datum predDatum; + bool isnull; + char *predString; + + /* Convert text string to node tree */ + predDatum = SysCacheGetAttr(INDEXRELID, ht_idx, + Anum_pg_index_indpred, &isnull); + Assert(!isnull); + predString = TextDatumGetCString(predDatum); + node = (Node *) stringToNode(predString); + pfree(predString); + + /* Deparse */ + *whereClause = + deparse_expression_pretty(node, context, false, false, + 0, 0); + } + else + *whereClause = NULL; + + /* Clean up */ + ReleaseSysCache(ht_idx); + ReleaseSysCache(ht_idxrel); + ReleaseSysCache(ht_am); + + /* all done */ +} /* * pg_get_constraintdef @@ -1290,9 +1541,9 @@ pg_get_constraintdef_ext(PG_FUNCTION_ARGS) /* Internal version that returns a palloc'd C string; no pretty-printing */ char * -pg_get_constraintdef_string(Oid constraintId) +pg_get_constraintdef_string(Oid constraintId, bool fullCommand) { - return pg_get_constraintdef_worker(constraintId, true, 0); + return pg_get_constraintdef_worker(constraintId, fullCommand, 0); } /* diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h index 1baf43d..1896036 100644 --- a/src/include/commands/sequence.h +++ b/src/include/commands/sequence.h @@ -71,6 +71,7 @@ extern Datum setval3_oid(PG_FUNCTION_ARGS); extern Datum lastval(PG_FUNCTION_ARGS); extern Datum pg_sequence_parameters(PG_FUNCTION_ARGS); +extern Form_pg_sequence get_sequence_values(Oid sequenceId); extern Oid DefineSequence(CreateSeqStmt *stmt); extern Oid AlterSequence(AlterSeqStmt *stmt); diff --git a/src/include/utils/ruleutils.h b/src/include/utils/ruleutils.h index 1673e3e..4447af9 100644 --- a/src/include/utils/ruleutils.h +++ b/src/include/utils/ruleutils.h @@ -13,6 +13,7 @@ #ifndef RULEUTILS_H #define RULEUTILS_H +#include "catalog/pg_trigger.h" #include "nodes/nodes.h" #include "nodes/parsenodes.h" #include "nodes/pg_list.h" @@ -20,8 +21,16 @@ extern char *pg_get_indexdef_string(Oid indexrelid); extern char *pg_get_indexdef_columns(Oid indexrelid, bool pretty); +extern void pg_get_indexdef_detailed(Oid indexrelid, + char **index_am, + char **definition, + char **reloptions, + char **tablespace, + char **whereClause); +extern char *pg_get_trigger_whenclause(Form_pg_trigger trigrec, + Node *whenClause, bool pretty); +extern char *pg_get_constraintdef_string(Oid constraintId, bool fullCommand); -extern char *pg_get_constraintdef_string(Oid constraintId); extern char *deparse_expression(Node *expr, List *dpcontext, bool forceprefix, bool showimplicit); extern List *deparse_context_for(const char *aliasname, Oid relid); -- 2.1.4 --gatW/ieO32f1wygP Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="0012-deparse-Support-CREATE-TYPE-AS-RANGE.patch" From 4e35ba6557a6d04539d69d75821c568f9efb09b5 Mon Sep 17 00:00:00 2001 From: Alvaro Herrera Date: Fri, 14 Feb 2014 19:04:08 -0300 Subject: [PATCH 12/42] deparse: Support CREATE TYPE AS RANGE --- src/backend/tcop/deparse_utility.c | 102 ++++++++++++++++++++++++++++++++++++- 1 file changed, 101 insertions(+), 1 deletion(-) diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c index 59ef0a1..0ba669d 100644 --- a/src/backend/tcop/deparse_utility.c +++ b/src/backend/tcop/deparse_utility.c @@ -37,7 +37,9 @@ #include "catalog/pg_depend.h" #include "catalog/pg_inherits.h" #include "catalog/pg_operator.h" +#include "catalog/pg_opclass.h" #include "catalog/pg_proc.h" +#include "catalog/pg_range.h" #include "catalog/pg_trigger.h" #include "catalog/pg_type.h" #include "commands/defrem.h" @@ -1477,6 +1479,104 @@ deparse_CreateEnumStmt(Oid objectId, Node *parsetree) return enumtype; } +static ObjTree * +deparse_CreateRangeStmt(Oid objectId, Node *parsetree) +{ + ObjTree *range; + ObjTree *tmp; + List *definition = NIL; + Relation pg_range; + HeapTuple rangeTup; + Form_pg_range rangeForm; + ScanKeyData key[1]; + SysScanDesc scan; + + pg_range = heap_open(RangeRelationId, RowExclusiveLock); + + ScanKeyInit(&key[0], + Anum_pg_range_rngtypid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(objectId)); + + scan = systable_beginscan(pg_range, RangeTypidIndexId, true, + NULL, 1, key); + + rangeTup = systable_getnext(scan); + if (!HeapTupleIsValid(rangeTup)) + elog(ERROR, "cache lookup failed for range with type oid %u", + objectId); + + rangeForm = (Form_pg_range) GETSTRUCT(rangeTup); + + range = new_objtree_VA("CREATE TYPE %{identity}D AS RANGE (%{definition:, }s)", 0); + tmp = new_objtree_for_qualname_id(TypeRelationId, objectId); + append_object_object(range, "identity", tmp); + + /* SUBTYPE */ + tmp = new_objtree_for_qualname_id(TypeRelationId, + rangeForm->rngsubtype); + tmp = new_objtree_VA("SUBTYPE = %{type}D", + 2, + "clause", ObjTypeString, "subtype", + "type", ObjTypeObject, tmp); + definition = lappend(definition, new_object_object(tmp)); + + /* SUBTYPE_OPCLASS */ + if (OidIsValid(rangeForm->rngsubopc)) + { + tmp = new_objtree_for_qualname_id(OperatorClassRelationId, + rangeForm->rngsubopc); + tmp = new_objtree_VA("SUBTYPE_OPCLASS = %{opclass}D", + 2, + "clause", ObjTypeString, "opclass", + "opclass", ObjTypeObject, tmp); + definition = lappend(definition, new_object_object(tmp)); + } + + /* COLLATION */ + if (OidIsValid(rangeForm->rngcollation)) + { + tmp = new_objtree_for_qualname_id(CollationRelationId, + rangeForm->rngcollation); + tmp = new_objtree_VA("COLLATION = %{collation}D", + 2, + "clause", ObjTypeString, "collation", + "collation", ObjTypeObject, tmp); + definition = lappend(definition, new_object_object(tmp)); + } + + /* CANONICAL */ + if (OidIsValid(rangeForm->rngcanonical)) + { + tmp = new_objtree_for_qualname_id(ProcedureRelationId, + rangeForm->rngcanonical); + tmp = new_objtree_VA("CANONICAL = %{canonical}D", + 2, + "clause", ObjTypeString, "canonical", + "canonical", ObjTypeObject, tmp); + definition = lappend(definition, new_object_object(tmp)); + } + + /* SUBTYPE_DIFF */ + if (OidIsValid(rangeForm->rngsubdiff)) + { + tmp = new_objtree_for_qualname_id(ProcedureRelationId, + rangeForm->rngsubdiff); + tmp = new_objtree_VA("SUBTYPE_DIFF = %{subtype_diff}D", + 2, + "clause", ObjTypeString, "subtype_diff", + "subtype_diff", ObjTypeObject, tmp); + definition = lappend(definition, new_object_object(tmp)); + } + + append_array_object(range, "definition", definition); + + systable_endscan(scan); + heap_close(pg_range, RowExclusiveLock); + + return range; +} + static inline ObjElem * deparse_Seq_Cache(ObjTree *parent, Form_pg_sequence seqdata) { @@ -1983,7 +2083,7 @@ deparse_simple_command(StashedCommand *cmd) break; case T_CreateRangeStmt: /* CREATE TYPE AS RANGE */ - command = NULL; + command = deparse_CreateRangeStmt(objectId, parsetree); break; case T_AlterEnumStmt: -- 2.1.4 --gatW/ieO32f1wygP Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="0013-deparse-Support-CREATE-EXTENSION.patch" From 04dc40db971dc51c7e8c646cb5d822488da306f7 Mon Sep 17 00:00:00 2001 From: Alvaro Herrera Date: Fri, 21 Feb 2014 18:11:35 -0300 Subject: [PATCH 13/42] deparse: Support CREATE EXTENSION --- src/backend/tcop/deparse_utility.c | 78 +++++++++++++++++++++++++++++++++++++- 1 file changed, 77 insertions(+), 1 deletion(-) diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c index 0ba669d..eef6bc1 100644 --- a/src/backend/tcop/deparse_utility.c +++ b/src/backend/tcop/deparse_utility.c @@ -35,6 +35,7 @@ #include "catalog/pg_collation.h" #include "catalog/pg_constraint.h" #include "catalog/pg_depend.h" +#include "catalog/pg_extension.h" #include "catalog/pg_inherits.h" #include "catalog/pg_operator.h" #include "catalog/pg_opclass.h" @@ -660,6 +661,81 @@ get_persistence_str(char persistence) } /* + * deparse_CreateExtensionStmt + * deparse a CreateExtensionStmt + * + * Given an extension OID and the parsetree that created it, return the JSON + * blob representing the creation command. + * + * XXX the current representation makes the output command dependant on the + * installed versions of the extension. Is this a problem? + */ +static ObjTree * +deparse_CreateExtensionStmt(Oid objectId, Node *parsetree) +{ + CreateExtensionStmt *node = (CreateExtensionStmt *) parsetree; + Relation pg_extension; + HeapTuple extTup; + Form_pg_extension extForm; + ObjTree *extStmt; + ObjTree *tmp; + List *list; + ListCell *cell; + + pg_extension = heap_open(ExtensionRelationId, AccessShareLock); + extTup = get_catalog_object_by_oid(pg_extension, objectId); + if (!HeapTupleIsValid(extTup)) + elog(ERROR, "cache lookup failed for extension with OID %u", + objectId); + extForm = (Form_pg_extension) GETSTRUCT(extTup); + + extStmt = new_objtree_VA("CREATE EXTENSION %{if_not_exists}s %{identity}I " + "%{options: }s", + 1, "identity", ObjTypeString, node->extname); + append_string_object(extStmt, "if_not_exists", + node->if_not_exists ? "IF NOT EXISTS" : ""); + list = NIL; + foreach(cell, node->options) + { + DefElem *opt = (DefElem *) lfirst(cell); + + if (strcmp(opt->defname, "schema") == 0) + { + /* skip this one; we add one unconditionally below */ + continue; + } + else if (strcmp(opt->defname, "new_version") == 0) + { + tmp = new_objtree_VA("VERSION %{version}L", 2, + "type", ObjTypeString, "version", + "version", ObjTypeString, defGetString(opt)); + list = lappend(list, new_object_object(tmp)); + } + else if (strcmp(opt->defname, "old_version") == 0) + { + tmp = new_objtree_VA("FROM %{version}L", 2, + "type", ObjTypeString, "from", + "version", ObjTypeString, defGetString(opt)); + list = lappend(list, new_object_object(tmp)); + } + else + elog(ERROR, "unsupported option %s", opt->defname); + } + + tmp = new_objtree_VA("SCHEMA %{schema}I", + 2, "type", ObjTypeString, "schema", + "schema", ObjTypeString, + get_namespace_name(extForm->extnamespace)); + list = lappend(list, new_object_object(tmp)); + + append_array_object(extStmt, "options", list); + + heap_close(pg_extension, AccessShareLock); + + return extStmt; +} + +/* * deparse_CreateTrigStmt * Deparse a CreateTrigStmt (CREATE TRIGGER) * @@ -2029,7 +2105,7 @@ deparse_simple_command(StashedCommand *cmd) break; case T_CreateExtensionStmt: - command = NULL; + command = deparse_CreateExtensionStmt(objectId, parsetree); break; case T_AlterExtensionStmt: -- 2.1.4 --gatW/ieO32f1wygP Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="0014-deparse-Support-CREATE-RULE.patch" From f45a9141905e93b83d28809e357f95c7fa713fe7 Mon Sep 17 00:00:00 2001 From: Alvaro Herrera Date: Wed, 26 Feb 2014 17:26:55 -0300 Subject: [PATCH 14/42] deparse: Support CREATE RULE --- src/backend/tcop/deparse_utility.c | 93 +++++++++++++++++++++++++++++++++++++- src/backend/utils/adt/ruleutils.c | 71 +++++++++++++++++++++++++++++ src/include/utils/ruleutils.h | 2 + 3 files changed, 165 insertions(+), 1 deletion(-) diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c index eef6bc1..34d3392 100644 --- a/src/backend/tcop/deparse_utility.c +++ b/src/backend/tcop/deparse_utility.c @@ -27,6 +27,7 @@ #include "postgres.h" #include "access/htup_details.h" +#include "access/sysattr.h" #include "catalog/heap.h" #include "catalog/index.h" #include "catalog/namespace.h" @@ -41,6 +42,7 @@ #include "catalog/pg_opclass.h" #include "catalog/pg_proc.h" #include "catalog/pg_range.h" +#include "catalog/pg_rewrite.h" #include "catalog/pg_trigger.h" #include "catalog/pg_type.h" #include "commands/defrem.h" @@ -2013,6 +2015,95 @@ deparse_IndexStmt(Oid objectId, Node *parsetree) return indexStmt; } +static ObjTree * +deparse_RuleStmt(Oid objectId, Node *parsetree) +{ + RuleStmt *node = (RuleStmt *) parsetree; + ObjTree *ruleStmt; + ObjTree *tmp; + Relation pg_rewrite; + Form_pg_rewrite rewrForm; + HeapTuple rewrTup; + SysScanDesc scan; + ScanKeyData key; + Datum ev_qual; + Datum ev_actions; + bool isnull; + char *qual; + List *actions; + List *list; + ListCell *cell; + + pg_rewrite = heap_open(RewriteRelationId, AccessShareLock); + ScanKeyInit(&key, + ObjectIdAttributeNumber, + BTEqualStrategyNumber, + F_OIDEQ, ObjectIdGetDatum(objectId)); + + scan = systable_beginscan(pg_rewrite, RewriteOidIndexId, true, + NULL, 1, &key); + rewrTup = systable_getnext(scan); + if (!HeapTupleIsValid(rewrTup)) + elog(ERROR, "cache lookup failed for rewrite rule with oid %u", + objectId); + + rewrForm = (Form_pg_rewrite) GETSTRUCT(rewrTup); + + ruleStmt = + new_objtree_VA("CREATE %{or_replace}s RULE %{identity}I " + "AS ON %{event}s TO %{table}D %{where_clause}s " + "DO %{instead}s (%{actions:; }s)", 2, + "identity", ObjTypeString, node->rulename, + "or_replace", ObjTypeString, + node->replace ? "OR REPLACE" : ""); + append_string_object(ruleStmt, "event", + node->event == CMD_SELECT ? "SELECT" : + node->event == CMD_UPDATE ? "UPDATE" : + node->event == CMD_DELETE ? "DELETE" : + node->event == CMD_INSERT ? "INSERT" : "XXX"); + append_object_object(ruleStmt, "table", + new_objtree_for_qualname_id(RelationRelationId, + rewrForm->ev_class)); + + append_string_object(ruleStmt, "instead", + node->instead ? "INSTEAD" : "ALSO"); + + ev_qual = heap_getattr(rewrTup, Anum_pg_rewrite_ev_qual, + RelationGetDescr(pg_rewrite), &isnull); + ev_actions = heap_getattr(rewrTup, Anum_pg_rewrite_ev_action, + RelationGetDescr(pg_rewrite), &isnull); + + pg_get_ruledef_details(ev_qual, ev_actions, &qual, &actions); + + tmp = new_objtree_VA("WHERE %{clause}s", 0); + + if (qual) + append_string_object(tmp, "clause", qual); + else + { + append_null_object(tmp, "clause"); + append_bool_object(tmp, "present", false); + } + + append_object_object(ruleStmt, "where_clause", tmp); + + list = NIL; + foreach(cell, actions) + { + char *action = lfirst(cell); + + list = lappend(list, new_string_object(action)); + } + append_array_object(ruleStmt, "actions", list); + + systable_endscan(scan); + heap_close(pg_rewrite, AccessShareLock); + + return ruleStmt; +} + + + /* * deparse_CreateSchemaStmt * deparse a CreateSchemaStmt @@ -2179,7 +2270,7 @@ deparse_simple_command(StashedCommand *cmd) break; case T_RuleStmt: - command = NULL; + command = deparse_RuleStmt(objectId, parsetree); break; case T_CreateSeqStmt: diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 541e076..8e66b96 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -448,6 +448,77 @@ pg_get_ruledef_ext(PG_FUNCTION_ARGS) PG_RETURN_TEXT_P(string_to_text(pg_get_ruledef_worker(ruleoid, prettyFlags))); } +/* + * Given a pair of Datum corresponding to a rule's pg_rewrite.ev_qual and + * ev_action columns, return their text representation; ev_qual as a single + * string in whereClause and ev_action as a List of strings (which might be + * NIL, signalling NOTHING) in actions. + */ +void +pg_get_ruledef_details(Datum ev_qual, Datum ev_action, + char **whereClause, List **actions) +{ + int prettyFlags = 0; + char *qualstr = TextDatumGetCString(ev_qual); + char *actionstr = TextDatumGetCString(ev_action); + List *actionNodeList = (List *) stringToNode(actionstr); + StringInfoData buf; + + initStringInfo(&buf); + if (strlen(qualstr) > 0 && strcmp(qualstr, "<>") != 0) + { + Node *qual; + Query *query; + deparse_context context; + deparse_namespace dpns; + + qual = stringToNode(qualstr); + + query = (Query *) linitial(actionNodeList); + query = getInsertSelectQuery(query, NULL); + + AcquireRewriteLocks(query, false, false); + + context.buf = &buf; + context.namespaces = list_make1(&dpns); + context.windowClause = NIL; + context.windowTList = NIL; + context.varprefix = (list_length(query->rtable) != 1); + context.prettyFlags = prettyFlags; + context.wrapColumn = WRAP_COLUMN_DEFAULT; + context.indentLevel = PRETTYINDENT_STD; + + set_deparse_for_query(&dpns, query, NIL); + + get_rule_expr(qual, &context, false); + + *whereClause = pstrdup(buf.data); + } + else + *whereClause = NULL; + + if (list_length(actionNodeList) == 0) + *actions = NIL; + else + { + ListCell *cell; + List *output = NIL; + + foreach(cell, actionNodeList) + { + Query *query = (Query *) lfirst(cell); + + if (query->commandType == CMD_NOTHING) + continue; + + resetStringInfo(&buf); + get_query_def(query, &buf, NIL, NULL, + prettyFlags, WRAP_COLUMN_DEFAULT, 0); + output = lappend(output, pstrdup(buf.data)); + } + *actions = output; + } +} static char * pg_get_ruledef_worker(Oid ruleoid, int prettyFlags) diff --git a/src/include/utils/ruleutils.h b/src/include/utils/ruleutils.h index 4447af9..9989cdd 100644 --- a/src/include/utils/ruleutils.h +++ b/src/include/utils/ruleutils.h @@ -30,6 +30,8 @@ extern void pg_get_indexdef_detailed(Oid indexrelid, extern char *pg_get_trigger_whenclause(Form_pg_trigger trigrec, Node *whenClause, bool pretty); extern char *pg_get_constraintdef_string(Oid constraintId, bool fullCommand); +extern void pg_get_ruledef_details(Datum ev_qual, Datum ev_action, + char **whereClause, List **actions); extern char *deparse_expression(Node *expr, List *dpcontext, bool forceprefix, bool showimplicit); -- 2.1.4 --gatW/ieO32f1wygP Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="0015-deparse-Support-ALTER-TYPE-ADD-VALUE-enums.patch" From 80236c8702d3a553d103fe70070c638ae3013235 Mon Sep 17 00:00:00 2001 From: Alvaro Herrera Date: Fri, 21 Mar 2014 16:33:14 -0300 Subject: [PATCH 15/42] deparse: Support ALTER TYPE / ADD VALUE (enums) --- src/backend/tcop/deparse_utility.c | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c index 34d3392..37292e4 100644 --- a/src/backend/tcop/deparse_utility.c +++ b/src/backend/tcop/deparse_utility.c @@ -2143,6 +2143,39 @@ deparse_CreateSchemaStmt(Oid objectId, Node *parsetree) return createSchema; } +static ObjTree * +deparse_AlterEnumStmt(Oid objectId, Node *parsetree) +{ + AlterEnumStmt *node = (AlterEnumStmt *) parsetree; + ObjTree *alterEnum; + ObjTree *tmp; + + alterEnum = + new_objtree_VA("ALTER TYPE %{identity}D ADD VALUE %{if_not_exists}s %{value}L %{position}s", + 0); + + append_string_object(alterEnum, "if_not_exists", + node->skipIfExists ? "IF NOT EXISTS" : ""); + append_object_object(alterEnum, "identity", + new_objtree_for_qualname_id(TypeRelationId, + objectId)); + append_string_object(alterEnum, "value", node->newVal); + tmp = new_objtree_VA("%{after_or_before}s %{neighbour}L", 0); + if (node->newValNeighbor) + { + append_string_object(tmp, "after_or_before", + node->newValIsAfter ? "AFTER" : "BEFORE"); + append_string_object(tmp, "neighbour", node->newValNeighbor); + } + else + { + append_bool_object(tmp, "present", false); + } + append_object_object(alterEnum, "position", tmp); + + return alterEnum; +} + /* * Handle deparsing of simple commands. * @@ -2254,7 +2287,7 @@ deparse_simple_command(StashedCommand *cmd) break; case T_AlterEnumStmt: - command = NULL; + command = deparse_AlterEnumStmt(objectId, parsetree); break; case T_ViewStmt: /* CREATE VIEW */ -- 2.1.4 --gatW/ieO32f1wygP Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="0016-deparse-Support-for-ALTER-OBJECT-RENAME.patch" From 67ff742875bfadd182ae28e40e54476c9e4d220e Mon Sep 17 00:00:00 2001 From: Alvaro Herrera Date: Fri, 25 Apr 2014 16:43:53 -0300 Subject: [PATCH 16/42] deparse: Support for ALTER RENAME It supports everything but functions, aggregates, operator classes and operator families. --- src/backend/catalog/dependency.c | 6 +- src/backend/commands/alter.c | 4 +- src/backend/commands/tablecmds.c | 22 ++ src/backend/tcop/deparse_utility.c | 422 ++++++++++++++++++++++++++++++++++++- src/include/commands/tablecmds.h | 2 + 5 files changed, 452 insertions(+), 4 deletions(-) diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c index bacb242..25ff326 100644 --- a/src/backend/catalog/dependency.c +++ b/src/backend/catalog/dependency.c @@ -2269,8 +2269,9 @@ free_object_addresses(ObjectAddresses *addrs) ObjectClass getObjectClass(const ObjectAddress *object) { - /* only pg_class entries can have nonzero objectSubId */ - if (object->classId != RelationRelationId && + /* only pg_class and pg_type entries can have nonzero objectSubId */ + if ((object->classId != RelationRelationId && + object->classId != TypeRelationId) && object->objectSubId != 0) elog(ERROR, "invalid non-zero objectSubId for object class %u", object->classId); @@ -2285,6 +2286,7 @@ getObjectClass(const ObjectAddress *object) return OCLASS_PROC; case TypeRelationId: + /* caller must check objectSubId */ return OCLASS_TYPE; case CastRelationId: diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c index dfd57e0..4150104 100644 --- a/src/backend/commands/alter.c +++ b/src/backend/commands/alter.c @@ -333,9 +333,11 @@ ExecRenameStmt(RenameStmt *stmt, int *objsubid) return RenameRelation(stmt); case OBJECT_COLUMN: - case OBJECT_ATTRIBUTE: return renameatt(stmt, objsubid); + case OBJECT_ATTRIBUTE: + return renameatt_type(stmt, objsubid); + case OBJECT_RULE: return RenameRewriteRule(stmt->relation, stmt->subname, stmt->newname); diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 31fd7ba..ca672ab 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -2361,6 +2361,28 @@ renameatt(RenameStmt *stmt, int *objsubid) return relid; } +/* + * As above, for composite types; returned OID is that of the pg_type tuple, + * not the pg_class entry. + */ +Oid +renameatt_type(RenameStmt *stmt, int *objsubid) +{ + Oid relid; + Oid typid; + HeapTuple classTup; + + relid = renameatt(stmt, objsubid); + + classTup = SearchSysCache1(RELOID, relid); + if (!HeapTupleIsValid(classTup)) + elog(ERROR, "cache lookup failed for relation %u", relid); + + typid = ((Form_pg_class) GETSTRUCT(classTup))->reltype; + ReleaseSysCache(classTup); + + return typid; +} /* * same logic as renameatt_internal diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c index 37292e4..6fe9c9b 100644 --- a/src/backend/tcop/deparse_utility.c +++ b/src/backend/tcop/deparse_utility.c @@ -126,6 +126,7 @@ static void append_integer_object(ObjTree *tree, char *name, int64 value); static void append_float_object(ObjTree *tree, char *name, float8 value); static inline void append_premade_object(ObjTree *tree, ObjElem *elem); static JsonbValue *objtree_to_jsonb_rec(ObjTree *tree, JsonbParseState *state); +static const char *stringify_objtype(ObjectType objtype); /* * Allocate a new object tree to store parameter values. @@ -1655,6 +1656,425 @@ deparse_CreateRangeStmt(Oid objectId, Node *parsetree) return range; } +/* + * Return the given object type as a string. + */ +static const char * +stringify_objtype(ObjectType objtype) +{ + switch (objtype) + { + case OBJECT_AGGREGATE: + return "AGGREGATE"; + case OBJECT_ATTRIBUTE: + return "ATTRIBUTE"; + case OBJECT_CAST: + return "CAST"; + case OBJECT_COLUMN: + return "COLUMN"; + case OBJECT_COLLATION: + return "COLLATION"; + case OBJECT_CONVERSION: + return "CONVERSION"; + case OBJECT_DATABASE: + return "DATABASE"; + case OBJECT_DOMAIN: + return "DOMAIN"; + case OBJECT_EVENT_TRIGGER: + return "EVENT TRIGGER"; + case OBJECT_EXTENSION: + return "EXTENSION"; + case OBJECT_FDW: + return "FOREIGN DATA WRAPPER"; + case OBJECT_FOREIGN_SERVER: + return "SERVER"; + case OBJECT_FOREIGN_TABLE: + return "FOREIGN TABLE"; + case OBJECT_FUNCTION: + return "FUNCTION"; + case OBJECT_INDEX: + return "INDEX"; + case OBJECT_LANGUAGE: + return "LANGUAGE"; + case OBJECT_LARGEOBJECT: + return "LARGE OBJECT"; + case OBJECT_MATVIEW: + return "MATERIALIZED VIEW"; + case OBJECT_OPCLASS: + return "OPERATOR CLASS"; + case OBJECT_OPERATOR: + return "OPERATOR"; + case OBJECT_OPFAMILY: + return "OPERATOR FAMILY"; + case OBJECT_ROLE: + return "ROLE"; + case OBJECT_RULE: + return "RULE"; + case OBJECT_SCHEMA: + return "SCHEMA"; + case OBJECT_SEQUENCE: + return "SEQUENCE"; + case OBJECT_TABLE: + return "TABLE"; + case OBJECT_TABLESPACE: + return "TABLESPACE"; + case OBJECT_TRIGGER: + return "TRIGGER"; + case OBJECT_TSCONFIGURATION: + return "TEXT SEARCH CONFIGURATION"; + case OBJECT_TSDICTIONARY: + return "TEXT SEARCH DICTIONARY"; + case OBJECT_TSPARSER: + return "TEXT SEARCH PARSER"; + case OBJECT_TSTEMPLATE: + return "TEXT SEARCH TEMPLATE"; + case OBJECT_TYPE: + return "TYPE"; + case OBJECT_USER_MAPPING: + return "USER MAPPING"; + case OBJECT_VIEW: + return "VIEW"; + + default: + elog(ERROR, "unsupported objtype %d", objtype); + } +} + +static ObjTree * +deparse_RenameStmt(Oid objectId, Node *parsetree) +{ + RenameStmt *node = (RenameStmt *) parsetree; + ObjTree *renameStmt; + char *fmtstr; + Relation relation; + Oid schemaId; + + /* + * FIXME --- this code is missing support for inheritance behavioral flags, + * i.e. the "*" and ONLY elements. + */ + + /* + * In a ALTER .. RENAME command, we don't have the original name of the + * object in system catalogs: since we inspect them after the command has + * executed, the old name is already gone. Therefore, we extract it from + * the parse node. Note we still extract the schema name from the catalog + * (it might not be present in the parse node); it cannot possibly have + * changed anyway. + * + * XXX what if there's another event trigger running concurrently that + * renames the schema or moves the object to another schema? Seems + * pretty far-fetched, but possible nonetheless. + */ + switch (node->renameType) + { + case OBJECT_TABLE: + case OBJECT_SEQUENCE: + case OBJECT_VIEW: + case OBJECT_MATVIEW: + case OBJECT_INDEX: + case OBJECT_FOREIGN_TABLE: + fmtstr = psprintf("ALTER %s %%{if_exists}s %%{identity}D RENAME TO %%{newname}I", + stringify_objtype(node->renameType)); + relation = relation_open(objectId, AccessShareLock); + schemaId = RelationGetNamespace(relation); + renameStmt = new_objtree_VA(fmtstr, 0); + append_object_object(renameStmt, "identity", + new_objtree_for_qualname(schemaId, + node->relation->relname)); + append_string_object(renameStmt, "if_exists", + node->missing_ok ? "IF EXISTS" : ""); + relation_close(relation, AccessShareLock); + break; + + case OBJECT_COLUMN: + relation = relation_open(objectId, AccessShareLock); + schemaId = RelationGetNamespace(relation); + + fmtstr = psprintf("ALTER %s %%{if_exists}s %%{identity}D RENAME COLUMN %%{colname}I TO %%{newname}I", + stringify_objtype(node->relationType)); + renameStmt = new_objtree_VA(fmtstr, 0); + append_object_object(renameStmt, "identity", + new_objtree_for_qualname(schemaId, + node->relation->relname)); + append_string_object(renameStmt, "colname", node->subname); + append_string_object(renameStmt, "if_exists", + node->missing_ok ? "IF EXISTS" : ""); + relation_close(relation, AccessShareLock); + break; + + case OBJECT_ATTRIBUTE: + { + HeapTuple typeTup; + Oid schemaId; + char *typname; + + typeTup = SearchSysCache1(TYPEOID, objectId); + if (!HeapTupleIsValid(typeTup)) + elog(ERROR, "cache lookup failed for type %u", objectId); + + schemaId = ((Form_pg_type) GETSTRUCT(typeTup))->typnamespace; + typname = NameStr(((Form_pg_type) GETSTRUCT(typeTup))->typname); + + renameStmt = + new_objtree_VA("ALTER TYPE %{identity}D RENAME ATTRIBUTE %{attname}I TO %{newname}I", + 0); + append_object_object(renameStmt, "identity", + new_objtree_for_qualname(schemaId, + typname)); + append_string_object(renameStmt, "attname", node->subname); + ReleaseSysCache(typeTup); + } + break; + + case OBJECT_SCHEMA: + { + renameStmt = + new_objtree_VA("ALTER SCHEMA %{identity}I RENAME TO %{newname}I", + 0); + append_string_object(renameStmt, "identity", node->subname); + } + break; + + case OBJECT_FDW: + case OBJECT_LANGUAGE: + case OBJECT_FOREIGN_SERVER: + { + fmtstr = psprintf("ALTER %s %%{identity}s RENAME TO %%{newname}I", + stringify_objtype(node->renameType)); + renameStmt = new_objtree_VA(fmtstr, 0); + append_string_object(renameStmt, "identity", + strVal(linitial(node->object))); + } + break; + + case OBJECT_COLLATION: + case OBJECT_CONVERSION: + case OBJECT_DOMAIN: + case OBJECT_TSDICTIONARY: + case OBJECT_TSPARSER: + case OBJECT_TSTEMPLATE: + case OBJECT_TSCONFIGURATION: + case OBJECT_TYPE: + { + char *identity; + HeapTuple objTup; + Relation catalog; + Datum objnsp; + bool isnull; + Oid classId = get_objtype_catalog_oid(node->renameType); + AttrNumber Anum_namespace = get_object_attnum_namespace(classId); + + catalog = relation_open(classId, AccessShareLock); + objTup = get_catalog_object_by_oid(catalog, objectId); + objnsp = heap_getattr(objTup, Anum_namespace, + RelationGetDescr(catalog), &isnull); + if (isnull) + elog(ERROR, "invalid NULL namespace"); + + identity = psprintf("%s.%s", get_namespace_name(DatumGetObjectId(objnsp)), + strVal(llast(node->object))); + + fmtstr = psprintf("ALTER %s %%{identity}s RENAME TO %%{newname}I", + stringify_objtype(node->renameType)); + renameStmt = new_objtree_VA(fmtstr, 0); + append_string_object(renameStmt, "identity", identity); + + relation_close(catalog, AccessShareLock); + } + break; + + case OBJECT_OPCLASS: + case OBJECT_OPFAMILY: + { + char *identity; + HeapTuple objTup; + HeapTuple amTup; + Relation catalog; + Datum objnsp; + bool isnull; + Oid classId = get_objtype_catalog_oid(node->renameType); + AttrNumber Anum_namespace = get_object_attnum_namespace(classId); + Oid amoid; + + catalog = relation_open(classId, AccessShareLock); + objTup = get_catalog_object_by_oid(catalog, objectId); + objnsp = heap_getattr(objTup, Anum_namespace, + RelationGetDescr(catalog), &isnull); + if (isnull) + elog(ERROR, "invalid NULL namespace"); + + if (node->renameType == OBJECT_OPCLASS) + amoid = ((Form_pg_opclass) GETSTRUCT(objTup))->opcmethod; + else + amoid = ((Form_pg_opfamily) GETSTRUCT(objTup))->opfmethod; + amTup = SearchSysCache1(AMOID, ObjectIdGetDatum(amoid)); + if (!HeapTupleIsValid(amTup)) + elog(ERROR, "cache lookup failed for access method %u", amoid); + + identity = psprintf("%s.%s", get_namespace_name(DatumGetObjectId(objnsp)), + strVal(llast(node->object))); + + fmtstr = psprintf("ALTER %s %%{identity}s USING %%{amname}I RENAME TO %%{newname}I", + stringify_objtype(node->renameType)); + renameStmt = new_objtree_VA(fmtstr, 0); + append_string_object(renameStmt, "identity", identity); + append_string_object(renameStmt, "amname", + pstrdup(NameStr(((Form_pg_am) GETSTRUCT(amTup))->amname))); + + ReleaseSysCache(amTup); + relation_close(catalog, AccessShareLock); + } + + case OBJECT_AGGREGATE: + case OBJECT_FUNCTION: + { + char *newident; + ObjectAddress objaddr; + const char *quoted_newname; + StringInfoData old_ident; + char *start; + + /* + * Generating a function/aggregate identity is altogether too + * messy, so instead of doing it ourselves, we generate one for + * the renamed object, then strip out the name and replace it + * with the original name from the parse node. This is so ugly + * that we don't dare do it for any other object kind. + */ + + objaddr.classId = get_objtype_catalog_oid(node->renameType); + objaddr.objectId = objectId; + objaddr.objectSubId = 0; + newident = getObjectIdentity(&objaddr); + + quoted_newname = quote_identifier(node->newname); + start = strstr(newident, quoted_newname); + if (!start) + elog(ERROR, "could not find %s in %s", start, newident); + initStringInfo(&old_ident); + appendBinaryStringInfo(&old_ident, newident, start - newident); + appendStringInfoString(&old_ident, + quote_identifier(strVal(llast(node->object)))); + appendStringInfoString(&old_ident, start + strlen(quoted_newname)); + + fmtstr = psprintf("ALTER %s %%{identity}s RENAME TO %%{newname}I", + stringify_objtype(node->renameType)); + renameStmt = new_objtree_VA(fmtstr, 1, + "identity", ObjTypeString, old_ident.data); + } + break; + + case OBJECT_TABCONSTRAINT: + case OBJECT_DOMCONSTRAINT: + { + HeapTuple conTup; + Form_pg_constraint constrForm; + ObjTree *ident; + + conTup = SearchSysCache1(CONSTROID, objectId); + constrForm = (Form_pg_constraint) GETSTRUCT(conTup); + + if (node->renameType == OBJECT_TABCONSTRAINT) + { + fmtstr = "ALTER TABLE %{identity}D RENAME CONSTRAINT %{conname}I TO %{newname}I"; + ident = new_objtree_for_qualname_id(RelationRelationId, + constrForm->conrelid); + } + else + { + fmtstr = "ALTER DOMAIN %{identity}D RENAME CONSTRAINT %{conname}I TO %{newname}I"; + ident = new_objtree_for_qualname_id(TypeRelationId, + constrForm->contypid); + } + renameStmt = new_objtree_VA(fmtstr, 2, + "conname", ObjTypeString, node->subname, + "identity", ObjTypeObject, ident); + ReleaseSysCache(conTup); + } + break; + + case OBJECT_RULE: + { + HeapTuple rewrTup; + Form_pg_rewrite rewrForm; + Relation pg_rewrite; + + pg_rewrite = relation_open(RewriteRelationId, AccessShareLock); + rewrTup = get_catalog_object_by_oid(pg_rewrite, objectId); + rewrForm = (Form_pg_rewrite) GETSTRUCT(rewrTup); + + renameStmt = new_objtree_VA("ALTER RULE %{rulename}I ON %{identity}D RENAME TO %{newname}I", + 0); + append_string_object(renameStmt, "rulename", node->subname); + append_object_object(renameStmt, "identity", + new_objtree_for_qualname_id(RelationRelationId, + rewrForm->ev_class)); + relation_close(pg_rewrite, AccessShareLock); + } + break; + + case OBJECT_TRIGGER: + { + HeapTuple trigTup; + Form_pg_trigger trigForm; + Relation pg_trigger; + + pg_trigger = relation_open(TriggerRelationId, AccessShareLock); + trigTup = get_catalog_object_by_oid(pg_trigger, objectId); + trigForm = (Form_pg_trigger) GETSTRUCT(trigTup); + + renameStmt = new_objtree_VA("ALTER TRIGGER %{triggername}I ON %{identity}D RENAME TO %{newname}I", + 0); + append_string_object(renameStmt, "triggername", node->subname); + append_object_object(renameStmt, "identity", + new_objtree_for_qualname_id(RelationRelationId, + trigForm->tgrelid)); + relation_close(pg_trigger, AccessShareLock); + } + break; + + case OBJECT_POLICY: + { + HeapTuple polTup; + Form_pg_policy polForm; + Relation pg_policy; + ScanKeyData key; + SysScanDesc scan; + + pg_policy = relation_open(PolicyRelationId, AccessShareLock); + ScanKeyInit(&key, ObjectIdAttributeNumber, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(objectId)); + scan = systable_beginscan(pg_policy, PolicyOidIndexId, true, + NULL, 1, &key); + polTup = systable_getnext(scan); + if (!HeapTupleIsValid(polTup)) + elog(ERROR, "cache lookup failed for policy %u", objectId); + polForm = (Form_pg_policy) GETSTRUCT(polTup); + + renameStmt = new_objtree_VA("ALTER POLICY %{if_exists}s %{policyname}I on %{identity}D RENAME TO %{newname}I", + 0); + append_string_object(renameStmt, "policyname", node->subname); + append_object_object(renameStmt, "identity", + new_objtree_for_qualname_id(RelationRelationId, + polForm->polrelid)); + append_string_object(renameStmt, "if_exists", + node->missing_ok ? "IF EXISTS" : ""); + systable_endscan(scan); + relation_close(pg_policy, AccessShareLock); + } + break; + + default: + elog(ERROR, "unsupported object type %d", node->renameType); + } + + append_string_object(renameStmt, "newname", node->newname); + + return renameStmt; +} + static inline ObjElem * deparse_Seq_Cache(ObjTree *parent, Form_pg_sequence seqdata) { @@ -2369,7 +2789,7 @@ deparse_simple_command(StashedCommand *cmd) break; case T_RenameStmt: - command = NULL; + command = deparse_RenameStmt(objectId, parsetree); break; case T_AlterObjectSchemaStmt: diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h index c6e75e4..d56f393 100644 --- a/src/include/commands/tablecmds.h +++ b/src/include/commands/tablecmds.h @@ -55,6 +55,8 @@ extern void SetRelationHasSubclass(Oid relationId, bool relhassubclass); extern Oid renameatt(RenameStmt *stmt, int *attnum); +extern Oid renameatt_type(RenameStmt *stmt, int *attnum); + extern Oid RenameConstraint(RenameStmt *stmt); extern Oid RenameRelation(RenameStmt *stmt); -- 2.1.4 --gatW/ieO32f1wygP Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="0017-deparse-Support-CREATE-DOMAIN.patch" From 668850e62c18e23c09f52c676829984b094dfd4b Mon Sep 17 00:00:00 2001 From: Alvaro Herrera Date: Fri, 25 Apr 2014 17:54:19 -0300 Subject: [PATCH 17/42] deparse: Support CREATE DOMAIN --- src/backend/tcop/deparse_utility.c | 55 +++++++++++++++++++++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c index 6fe9c9b..a90ab3b 100644 --- a/src/backend/tcop/deparse_utility.c +++ b/src/backend/tcop/deparse_utility.c @@ -1656,6 +1656,59 @@ deparse_CreateRangeStmt(Oid objectId, Node *parsetree) return range; } +static ObjTree * +deparse_CreateDomain(Oid objectId, Node *parsetree) +{ + ObjTree *createDomain; + ObjTree *tmp; + HeapTuple typTup; + Form_pg_type typForm; + List *constraints; + + typTup = SearchSysCache1(TYPEOID, + objectId); + if (!HeapTupleIsValid(typTup)) + elog(ERROR, "cache lookup failed for domain with OID %u", objectId); + typForm = (Form_pg_type) GETSTRUCT(typTup); + + createDomain = new_objtree_VA("CREATE DOMAIN %{identity}D AS %{type}T %{not_null}s %{constraints}s %{collation}s", + 0); + + append_object_object(createDomain, + "identity", + new_objtree_for_qualname_id(TypeRelationId, + objectId)); + append_object_object(createDomain, + "type", + new_objtree_for_type(typForm->typbasetype, typForm->typtypmod)); + + if (typForm->typnotnull) + append_string_object(createDomain, "not_null", "NOT NULL"); + else + append_string_object(createDomain, "not_null", ""); + + constraints = obtainConstraints(NIL, InvalidOid, objectId); + tmp = new_objtree_VA("%{elements: }s", 0); + if (constraints == NIL) + append_bool_object(tmp, "present", false); + else + append_array_object(tmp, "elements", constraints); + append_object_object(createDomain, "constraints", tmp); + + tmp = new_objtree_VA("COLLATE %{collation}D", 0); + if (OidIsValid(typForm->typcollation)) + append_object_object(tmp, "collation", + new_objtree_for_qualname_id(CollationRelationId, + typForm->typcollation)); + else + append_bool_object(tmp, "present", false); + append_object_object(createDomain, "collation", tmp); + + ReleaseSysCache(typTup); + + return createDomain; +} + /* * Return the given object type as a string. */ @@ -2752,7 +2805,7 @@ deparse_simple_command(StashedCommand *cmd) break; case T_CreateDomainStmt: - command = NULL; + command = deparse_CreateDomain(objectId, parsetree); break; case T_CreateConversionStmt: -- 2.1.4 --gatW/ieO32f1wygP Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="0018-deparse-Support-CREATE-FUNCTION.patch" From bc1fea9c92ca23197a7a081299838848038e6502 Mon Sep 17 00:00:00 2001 From: Alvaro Herrera Date: Tue, 15 Apr 2014 16:45:03 -0300 Subject: [PATCH 18/42] deparse: Support CREATE FUNCTION --- src/backend/tcop/deparse_utility.c | 309 ++++++++++++++++++++++++++++++++++++- src/backend/utils/adt/ruleutils.c | 25 +++ src/include/utils/ruleutils.h | 1 + 3 files changed, 334 insertions(+), 1 deletion(-) diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c index a90ab3b..607607e 100644 --- a/src/backend/tcop/deparse_utility.c +++ b/src/backend/tcop/deparse_utility.c @@ -38,6 +38,7 @@ #include "catalog/pg_depend.h" #include "catalog/pg_extension.h" #include "catalog/pg_inherits.h" +#include "catalog/pg_language.h" #include "catalog/pg_operator.h" #include "catalog/pg_opclass.h" #include "catalog/pg_proc.h" @@ -1710,6 +1711,312 @@ deparse_CreateDomain(Oid objectId, Node *parsetree) } /* + * deparse_CreateFunctionStmt + * Deparse a CreateFunctionStmt (CREATE FUNCTION) + * + * Given a function OID and the parsetree that created it, return the JSON + * blob representing the creation command. + * + * XXX this is missing the per-function custom-GUC thing. + */ +static ObjTree * +deparse_CreateFunction(Oid objectId, Node *parsetree) +{ + CreateFunctionStmt *node = (CreateFunctionStmt *) parsetree; + ObjTree *createFunc; + ObjTree *sign; + ObjTree *tmp; + Datum tmpdatum; + char *fmt; + char *definition; + char *source; + char *probin; + List *params; + List *defaults; + ListCell *cell; + ListCell *curdef; + ListCell *table_params = NULL; + HeapTuple procTup; + Form_pg_proc procForm; + HeapTuple langTup; + Oid *typarray; + Form_pg_language langForm; + int i; + int typnum; + bool isnull; + + /* get the pg_proc tuple */ + procTup = SearchSysCache1(PROCOID, objectId); + if (!HeapTupleIsValid(procTup)) + elog(ERROR, "cache lookup failure for function with OID %u", + objectId); + procForm = (Form_pg_proc) GETSTRUCT(procTup); + + /* get the corresponding pg_language tuple */ + langTup = SearchSysCache1(LANGOID, procForm->prolang); + if (!HeapTupleIsValid(langTup)) + elog(ERROR, "cache lookup failure for language with OID %u", + procForm->prolang); + langForm = (Form_pg_language) GETSTRUCT(langTup); + + /* + * Determine useful values for prosrc and probin. We cope with probin + * being either NULL or "-", but prosrc must have a valid value. + */ + tmpdatum = SysCacheGetAttr(PROCOID, procTup, + Anum_pg_proc_prosrc, &isnull); + if (isnull) + elog(ERROR, "null prosrc in function with OID %u", objectId); + source = TextDatumGetCString(tmpdatum); + + /* Determine a useful value for probin */ + tmpdatum = SysCacheGetAttr(PROCOID, procTup, + Anum_pg_proc_probin, &isnull); + if (isnull) + probin = NULL; + else + { + probin = TextDatumGetCString(tmpdatum); + if (probin[0] == '\0' || strcmp(probin, "-") == 0) + probin = NULL; + } + + if (probin == NULL) + definition = "%{definition}L"; + else + definition = "%{objfile}L, %{symbol}L"; + + fmt = psprintf("CREATE %%{or_replace}s FUNCTION %%{signature}s " + "RETURNS %%{return_type}s LANGUAGE %%{language}I " + "%%{window}s %%{volatility}s %%{leakproof}s " + "%%{strict}s %%{security_definer}s %%{cost}s %%{rows}s " + "%%{set_options: }s " + "AS %s", definition); + + createFunc = new_objtree_VA(fmt, 1, + "or_replace", ObjTypeString, + node->replace ? "OR REPLACE" : ""); + + sign = new_objtree_VA("%{identity}D(%{arguments:, }s)", 0); + + /* + * To construct the arguments array, extract the type OIDs from the + * function's pg_proc entry. If pronargs equals the parameter list length, + * there are no OUT parameters and thus we can extract the type OID from + * proargtypes; otherwise we need to decode proallargtypes, which is + * a bit more involved. + */ + typarray = palloc(list_length(node->parameters) * sizeof(Oid)); + if (list_length(node->parameters) > procForm->pronargs) + { + bool isnull; + Datum alltypes; + Datum *values; + bool *nulls; + int nelems; + + alltypes = SysCacheGetAttr(PROCOID, procTup, + Anum_pg_proc_proallargtypes, &isnull); + if (isnull) + elog(ERROR, "NULL proallargtypes, but more parameters than args"); + deconstruct_array(DatumGetArrayTypeP(alltypes), + OIDOID, 4, 't', 'i', + &values, &nulls, &nelems); + if (nelems != list_length(node->parameters)) + elog(ERROR, "mismatched proallargatypes"); + for (i = 0; i < list_length(node->parameters); i++) + typarray[i] = values[i]; + } + else + { + for (i = 0; i < list_length(node->parameters); i++) + typarray[i] = procForm->proargtypes.values[i]; + } + + /* + * If there are any default expressions, we read the deparsed expression as + * a list so that we can attach them to each argument. + */ + tmpdatum = SysCacheGetAttr(PROCOID, procTup, + Anum_pg_proc_proargdefaults, &isnull); + if (!isnull) + { + defaults = FunctionGetDefaults(DatumGetTextP(tmpdatum)); + curdef = list_head(defaults); + } + else + { + defaults = NIL; + curdef = NULL; + } + + /* + * Now iterate over each parameter in the parsetree to create the + * parameters array. + */ + params = NIL; + typnum = 0; + foreach(cell, node->parameters) + { + FunctionParameter *param = (FunctionParameter *) lfirst(cell); + ObjTree *tmp2; + ObjTree *tmp3; + + /* + * A PARAM_TABLE parameter indicates end of input arguments; the + * following parameters are part of the return type. We ignore them + * here, but keep track of the current position in the list so that + * we can easily produce the return type below. + */ + if (param->mode == FUNC_PARAM_TABLE) + { + table_params = cell; + break; + } + + /* + * Note that %{name}s is a string here, not an identifier; the reason + * for this is that an absent parameter name must produce an empty + * string, not "", which is what would happen if we were to use + * %{name}I here. So we add another level of indirection to allow us + * to inject a "present" parameter. + */ + tmp2 = new_objtree_VA("%{mode}s %{name}s %{type}T %{default}s", 0); + append_string_object(tmp2, "mode", + param->mode == FUNC_PARAM_IN ? "IN" : + param->mode == FUNC_PARAM_OUT ? "OUT" : + param->mode == FUNC_PARAM_INOUT ? "INOUT" : + param->mode == FUNC_PARAM_VARIADIC ? "VARIADIC" : + "INVALID MODE"); + + /* optional wholesale suppression of "name" occurs here */ + append_object_object(tmp2, "name", + new_objtree_VA("%{name}I", 2, + "name", ObjTypeString, + param->name ? param->name : "NULL", + "present", ObjTypeBool, + param->name ? true : false)); + + tmp3 = new_objtree_VA("DEFAULT %{value}s", 0); + if (PointerIsValid(param->defexpr)) + { + char *expr; + + if (curdef == NULL) + elog(ERROR, "proargdefaults list too short"); + expr = lfirst(curdef); + + append_string_object(tmp3, "value", expr); + curdef = lnext(curdef); + } + else + append_bool_object(tmp3, "present", false); + append_object_object(tmp2, "default", tmp3); + + append_object_object(tmp2, "type", + new_objtree_for_type(typarray[typnum++], -1)); + + params = lappend(params, new_object_object(tmp2)); + } + append_array_object(sign, "arguments", params); + append_object_object(sign, "identity", + new_objtree_for_qualname_id(ProcedureRelationId, + objectId)); + append_object_object(createFunc, "signature", sign); + + /* + * A return type can adopt one of two forms: either a [SETOF] some_type, or + * a TABLE(list-of-types). We can tell the second form because we saw a + * table param above while scanning the argument list. + */ + if (table_params == NULL) + { + tmp = new_objtree_VA("%{setof}s %{rettype}T", 0); + append_string_object(tmp, "setof", + procForm->proretset ? "SETOF" : ""); + append_object_object(tmp, "rettype", + new_objtree_for_type(procForm->prorettype, -1)); + append_string_object(tmp, "return_form", "plain"); + } + else + { + List *rettypes = NIL; + ObjTree *tmp2; + + tmp = new_objtree_VA("TABLE (%{rettypes:, }s)", 0); + for (; table_params != NULL; table_params = lnext(table_params)) + { + FunctionParameter *param = lfirst(table_params); + + tmp2 = new_objtree_VA("%{name}I %{type}T", 0); + append_string_object(tmp2, "name", param->name); + append_object_object(tmp2, "type", + new_objtree_for_type(typarray[typnum++], -1)); + rettypes = lappend(rettypes, new_object_object(tmp2)); + } + + append_array_object(tmp, "rettypes", rettypes); + append_string_object(tmp, "return_form", "table"); + } + + append_object_object(createFunc, "return_type", tmp); + + append_string_object(createFunc, "language", + NameStr(langForm->lanname)); + + append_string_object(createFunc, "window", + procForm->proiswindow ? "WINDOW" : ""); + append_string_object(createFunc, "volatility", + procForm->provolatile == PROVOLATILE_VOLATILE ? + "VOLATILE" : + procForm->provolatile == PROVOLATILE_STABLE ? + "STABLE" : + procForm->provolatile == PROVOLATILE_IMMUTABLE ? + "IMMUTABLE" : "INVALID VOLATILITY"); + + append_string_object(createFunc, "leakproof", + procForm->proleakproof ? "LEAKPROOF" : ""); + append_string_object(createFunc, "strict", + procForm->proisstrict ? + "RETURNS NULL ON NULL INPUT" : + "CALLED ON NULL INPUT"); + + append_string_object(createFunc, "security_definer", + procForm->prosecdef ? + "SECURITY DEFINER" : "SECURITY INVOKER"); + + append_object_object(createFunc, "cost", + new_objtree_VA("COST %{cost}n", 1, + "cost", ObjTypeFloat, + procForm->procost)); + + tmp = new_objtree_VA("ROWS %{rows}n", 0); + if (procForm->prorows == 0) + append_bool_object(tmp, "present", false); + else + append_float_object(tmp, "rows", procForm->prorows); + append_object_object(createFunc, "rows", tmp); + + append_array_object(createFunc, "set_options", NIL); + + if (probin == NULL) + { + append_string_object(createFunc, "definition", + source); + } + else + { + append_string_object(createFunc, "objfile", probin); + append_string_object(createFunc, "symbol", source); + } + + ReleaseSysCache(langTup); + ReleaseSysCache(procTup); + + return createFunc; +} + +/* * Return the given object type as a string. */ static const char * @@ -2768,7 +3075,7 @@ deparse_simple_command(StashedCommand *cmd) break; case T_CreateFunctionStmt: - command = NULL; + command = deparse_CreateFunction(objectId, parsetree); break; case T_AlterFunctionStmt: diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 8e66b96..886dc4f 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -9692,3 +9692,28 @@ RelationGetColumnDefault(Relation rel, AttrNumber attno, List *dpcontext) return defstr; } + +/* + * Return the defaults values of arguments to a function, as a list of + * deparsed expressions. + */ +List * +FunctionGetDefaults(text *proargdefaults) +{ + List *nodedefs; + List *strdefs = NIL; + ListCell *cell; + + nodedefs = (List *) stringToNode(TextDatumGetCString(proargdefaults)); + if (!IsA(nodedefs, List)) + elog(ERROR, "proargdefaults is not a list"); + + foreach(cell, nodedefs) + { + Node *onedef = lfirst(cell); + + strdefs = lappend(strdefs, deparse_expression_pretty(onedef, NIL, false, false, 0, 0)); + } + + return strdefs; +} diff --git a/src/include/utils/ruleutils.h b/src/include/utils/ruleutils.h index 9989cdd..c3757de 100644 --- a/src/include/utils/ruleutils.h +++ b/src/include/utils/ruleutils.h @@ -42,6 +42,7 @@ extern List *set_deparse_context_planstate(List *dpcontext, extern List *select_rtable_names_for_explain(List *rtable, Bitmapset *rels_used); extern char *generate_collation_name(Oid collid); +extern List *FunctionGetDefaults(text *proargdefaults); extern char *RelationGetColumnDefault(Relation rel, AttrNumber attno, List *dpcontext); -- 2.1.4 --gatW/ieO32f1wygP Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="0019-deparse-Support-ALTER-TABLE.patch" From 66cefe56b288b956c424f6df7ace13d8b069f459 Mon Sep 17 00:00:00 2001 From: Alvaro Herrera Date: Fri, 25 Apr 2014 16:32:20 -0300 Subject: [PATCH 19/42] deparse: Support ALTER TABLE --- src/backend/commands/event_trigger.c | 137 +++++++++- src/backend/commands/tablecmds.c | 7 + src/backend/tcop/deparse_utility.c | 491 +++++++++++++++++++++++++++++++++++ src/backend/tcop/utility.c | 23 +- src/include/commands/event_trigger.h | 5 + src/include/tcop/deparse_utility.h | 8 + 6 files changed, 669 insertions(+), 2 deletions(-) diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c index b258d72..67094c8 100644 --- a/src/backend/commands/event_trigger.c +++ b/src/backend/commands/event_trigger.c @@ -57,6 +57,7 @@ typedef struct EventTriggerQueryState int table_rewrite_reason; /* AT_REWRITE reason */ MemoryContext cxt; + StashedCommand *curcmd; List *stash; /* list of StashedCommand; see deparse_utility.h */ struct EventTriggerQueryState *previous; } EventTriggerQueryState; @@ -1211,6 +1212,7 @@ EventTriggerBeginCompleteQuery(void) slist_init(&(state->SQLDropList)); state->in_sql_drop = false; state->table_rewrite_oid = InvalidOid; + state->curcmd = NULL; state->stash = NIL; state->previous = currentEventTriggerState; currentEventTriggerState = state; @@ -1567,6 +1569,132 @@ EventTriggerStashCommand(Oid objectId, uint32 objectSubId, ObjectType objtype, MemoryContextSwitchTo(oldcxt); } +/* + * EventTriggerStartRecordingSubcmds + * Prepare to receive data on a complex DDL command about to be executed + * + * Note we don't actually stash the object we create here into the "stashed" + * list; instead we keep it in curcmd, and only when we're done processing the + * subcommands we will add it to the actual stash. + * + * FIXME -- this API isn't considering the possibility of an ALTER TABLE command + * being called reentrantly by an event trigger function. Do we need stackable + * commands at this level? + */ +void +EventTriggerComplexCmdStart(Node *parsetree, ObjectType objtype) +{ + MemoryContext oldcxt; + StashedCommand *stashed; + + oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt); + + stashed = palloc(sizeof(StashedCommand)); + + stashed->type = SCT_AlterTable; + stashed->in_extension = creating_extension; + + stashed->d.alterTable.objectId = InvalidOid; + stashed->d.alterTable.objtype = objtype; + stashed->d.alterTable.subcmds = NIL; + stashed->parsetree = copyObject(parsetree); + + currentEventTriggerState->curcmd = stashed; + + MemoryContextSwitchTo(oldcxt); +} + +void +EventTriggerComplexCmdSetOid(Oid objectId) +{ + /* + * When processing an ALTER TYPE command, the given objectId is the one + * found on pg_class for the composite type; we translate it to the + * corresponding pg_type OID so that it can be properly be considered the + * object's canonical objectId. + */ + if (currentEventTriggerState->curcmd->d.alterTable.objtype == OBJECT_TYPE) + { + HeapTuple relTup; + Form_pg_class relForm; + + relTup = SearchSysCache1(RELOID, objectId); + if (!HeapTupleIsValid(relTup)) + elog(ERROR, "cache lookup failed for relation %u", objectId); + relForm = (Form_pg_class) GETSTRUCT(relTup); + Assert(relForm->relkind == RELKIND_COMPOSITE_TYPE); + objectId = relForm->reltype; + ReleaseSysCache(relTup); + } + + currentEventTriggerState->curcmd->d.alterTable.objectId = objectId; +} + +/* + * EventTriggerRecordSubcmd + * Save data about a single part of a complex DDL command + * + * Right now we only support ALTER TABLE; there are no other DDL commands that + * require this. (ALTER TYPE can also generate multiple subcommands, but it's + * actually parsed as ALTER TABLE, so there is no difference at this level.) + */ +void +EventTriggerRecordSubcmd(Node *subcmd, Oid relid, AttrNumber attnum, + Oid newoid) +{ + MemoryContext oldcxt; + StashedATSubcmd *newsub; + + Assert(IsA(subcmd, AlterTableCmd)); + Assert(OidIsValid(currentEventTriggerState->curcmd->d.alterTable.objectId)); + + /* + * If we receive a subcommand intended for a relation other than the one + * we've started the complex command for, ignore it. This is chiefly + * concerned with inheritance situations: in such cases, alter table + * would dispatch multiple copies of the same command for various things, + * but we're only concerned with the one for the main table. + */ + if (relid != currentEventTriggerState->curcmd->d.alterTable.objectId) + return; + + oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt); + + newsub = palloc(sizeof(StashedATSubcmd)); + newsub->attnum = attnum; + newsub->oid = newoid; + newsub->parsetree = copyObject(subcmd); + + currentEventTriggerState->curcmd->d.alterTable.subcmds = + lappend(currentEventTriggerState->curcmd->d.alterTable.subcmds, newsub); + + MemoryContextSwitchTo(oldcxt); +} + +/* + * EventTriggerEndRecordingSubcmds + * Finish up saving a complex DDL command + * + * FIXME this API isn't considering the possibility that a xact/subxact is + * aborted partway through. Probably it's best to add an + * AtEOSubXact_EventTriggers() to fix this. + */ +void +EventTriggerComplexCmdEnd(void) +{ + /* If no subcommands, don't stash anything */ + if (list_length(currentEventTriggerState->curcmd->d.alterTable.subcmds) != 0) + { + currentEventTriggerState->stash = + lappend(currentEventTriggerState->stash, + currentEventTriggerState->curcmd); + } + else + pfree(currentEventTriggerState->curcmd); + + currentEventTriggerState->curcmd = NULL; +} + Datum pg_event_trigger_get_creation_commands(PG_FUNCTION_ARGS) { @@ -1646,7 +1774,8 @@ pg_event_trigger_get_creation_commands(PG_FUNCTION_ARGS) MemSet(nulls, 0, sizeof(nulls)); - if (cmd->type == SCT_Simple) + if (cmd->type == SCT_Simple || + cmd->type == SCT_AlterTable) { Oid classId; Oid objId; @@ -1662,6 +1791,12 @@ pg_event_trigger_get_creation_commands(PG_FUNCTION_ARGS) objId = cmd->d.simple.objectId; objSubId = cmd->d.simple.objectSubId; } + else if (cmd->type == SCT_AlterTable) + { + classId = get_objtype_catalog_oid(cmd->d.alterTable.objtype); + objId = cmd->d.alterTable.objectId; + objSubId = 0; + } tag = CreateCommandTag(cmd->parsetree); addr.classId = classId; diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index ca672ab..377e5dd 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -2779,6 +2779,8 @@ AlterTableInternal(Oid relid, List *cmds, bool recurse) rel = relation_open(relid, lockmode); + EventTriggerComplexCmdSetOid(relid); + ATController(NULL, rel, cmds, recurse, lockmode); } @@ -3655,6 +3657,9 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel, break; } + EventTriggerRecordSubcmd((Node *) cmd, RelationGetRelid(rel), + colno, newoid); + /* * Bump the command counter to ensure the next subcommand in the sequence * can see the changes so far @@ -9624,7 +9629,9 @@ AlterTableMoveAll(AlterTableMoveAllStmt *stmt) cmds = lappend(cmds, cmd); + EventTriggerComplexCmdStart((Node *) stmt, OBJECT_TABLE); AlterTableInternal(lfirst_oid(l), cmds, false); + EventTriggerComplexCmdEnd(); } return new_tablespaceoid; diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c index 607607e..ff3f684 100644 --- a/src/backend/tcop/deparse_utility.c +++ b/src/backend/tcop/deparse_utility.c @@ -2956,6 +2956,494 @@ deparse_AlterEnumStmt(Oid objectId, Node *parsetree) return alterEnum; } +static ObjTree * +deparse_AlterTableStmt(StashedCommand *cmd) +{ + ObjTree *alterTableStmt; + ObjTree *tmp; + ObjTree *tmp2; + List *dpcontext; + Relation rel; + List *subcmds = NIL; + ListCell *cell; + char *fmtstr; + + Assert(cmd->type == SCT_AlterTable); + + rel = relation_open(cmd->d.alterTable.objectId, AccessShareLock); + dpcontext = deparse_context_for(RelationGetRelationName(rel), + cmd->d.alterTable.objectId); + + fmtstr = psprintf("ALTER %s %%{identity}D %%{subcmds:, }s", + stringify_objtype(cmd->d.alterTable.objtype)); + alterTableStmt = new_objtree_VA(fmtstr, 0); + + tmp = new_objtree_for_qualname(rel->rd_rel->relnamespace, + RelationGetRelationName(rel)); + append_object_object(alterTableStmt, "identity", tmp); + + foreach(cell, cmd->d.alterTable.subcmds) + { + StashedATSubcmd *substashed = (StashedATSubcmd *) lfirst(cell); + AlterTableCmd *subcmd = (AlterTableCmd *) substashed->parsetree; + ObjTree *tree; + + Assert(IsA(subcmd, AlterTableCmd)); + + switch (subcmd->subtype) + { + case AT_AddColumn: + case AT_AddColumnRecurse: + /* XXX need to set the "recurse" bit somewhere? */ + Assert(IsA(subcmd->def, ColumnDef)); + tree = deparse_ColumnDef(rel, dpcontext, false, + (ColumnDef *) subcmd->def); + tmp = new_objtree_VA("ADD COLUMN %{definition}s", + 2, "type", ObjTypeString, "add column", + "definition", ObjTypeObject, tree); + subcmds = lappend(subcmds, new_object_object(tmp)); + break; + + case AT_DropColumnRecurse: + case AT_ValidateConstraintRecurse: + case AT_DropConstraintRecurse: + case AT_AddOidsRecurse: + case AT_AddIndexConstraint: + case AT_ReAddIndex: + case AT_ReAddConstraint: + case AT_ProcessedConstraint: + case AT_ReplaceRelOptions: + /* Subtypes used for internal operations; nothing to do here */ + break; + + case AT_AddColumnToView: + /* CREATE OR REPLACE VIEW -- nothing to do here */ + break; + + case AT_ColumnDefault: + if (subcmd->def == NULL) + { + tmp = new_objtree_VA("ALTER COLUMN %{column}I DROP DEFAULT", + 1, "type", ObjTypeString, "drop default"); + } + else + { + List *dpcontext; + HeapTuple attrtup; + AttrNumber attno; + + tmp = new_objtree_VA("ALTER COLUMN %{column}I SET DEFAULT %{definition}s", + 1, "type", ObjTypeString, "set default"); + + dpcontext = deparse_context_for(RelationGetRelationName(rel), + RelationGetRelid(rel)); + attrtup = SearchSysCacheAttName(RelationGetRelid(rel), subcmd->name); + attno = ((Form_pg_attribute) GETSTRUCT(attrtup))->attnum; + append_string_object(tmp, "definition", + RelationGetColumnDefault(rel, attno, dpcontext)); + ReleaseSysCache(attrtup); + } + append_string_object(tmp, "column", subcmd->name); + + subcmds = lappend(subcmds, new_object_object(tmp)); + break; + + case AT_DropNotNull: + tmp = new_objtree_VA("ALTER COLUMN %{column}I DROP NOT NULL", + 1, "type", ObjTypeString, "drop not null"); + append_string_object(tmp, "column", subcmd->name); + subcmds = lappend(subcmds, new_object_object(tmp)); + break; + + case AT_SetNotNull: + tmp = new_objtree_VA("ALTER COLUMN %{column}I SET NOT NULL", + 1, "type", ObjTypeString, "set not null"); + append_string_object(tmp, "column", subcmd->name); + subcmds = lappend(subcmds, new_object_object(tmp)); + break; + + case AT_SetStatistics: + { + Assert(IsA(subcmd->def, Integer)); + tmp = new_objtree_VA("ALTER COLUMN %{column}I SET STATISTICS %{statistics}n", + 3, "type", ObjTypeString, "set statistics", + "column", ObjTypeString, subcmd->name, + "statistics", ObjTypeInteger, + intVal((Value *) subcmd->def)); + subcmds = lappend(subcmds, new_object_object(tmp)); + } + break; + + case AT_SetOptions: + elog(ERROR, "unimplemented deparse of ALTER TABLE SET OPTIONS"); + break; + + case AT_ResetOptions: + elog(ERROR, "unimplemented deparse of ALTER TABLE RESET OPTIONS"); + break; + + case AT_SetStorage: + Assert(IsA(subcmd->def, String)); + tmp = new_objtree_VA("ALTER COLUMN %{column}I SET STORAGE %{storage}s", + 3, "type", ObjTypeString, "set storage", + "column", ObjTypeString, subcmd->name, + "storage", ObjTypeString, + strVal((Value *) subcmd->def)); + subcmds = lappend(subcmds, new_object_object(tmp)); + break; + + case AT_DropColumn: + tmp = new_objtree_VA("DROP COLUMN %{column}I", + 2, "type", ObjTypeString, "drop column", + "column", ObjTypeString, subcmd->name); + subcmds = lappend(subcmds, new_object_object(tmp)); + break; + + case AT_AddIndex: + { + Oid idxOid = substashed->oid; + IndexStmt *istmt; + Relation idx; + const char *idxname; + Oid constrOid; + + Assert(IsA(subcmd->def, IndexStmt)); + istmt = (IndexStmt *) subcmd->def; + + if (!istmt->isconstraint) + break; + + idx = relation_open(idxOid, AccessShareLock); + idxname = RelationGetRelationName(idx); + + constrOid = get_relation_constraint_oid( + cmd->d.alterTable.objectId, idxname, false); + + tmp = new_objtree_VA("ADD CONSTRAINT %{name}I %{definition}s", + 3, "type", ObjTypeString, "add constraint", + "name", ObjTypeString, idxname, + "definition", ObjTypeString, + pg_get_constraintdef_string(constrOid, false)); + subcmds = lappend(subcmds, new_object_object(tmp)); + + relation_close(idx, AccessShareLock); + } + break; + + case AT_AddConstraint: + case AT_AddConstraintRecurse: + { + /* XXX need to set the "recurse" bit somewhere? */ + Oid constrOid = substashed->oid; + + tmp = new_objtree_VA("ADD CONSTRAINT %{name}I %{definition}s", + 3, "type", ObjTypeString, "add constraint", + "name", ObjTypeString, get_constraint_name(constrOid), + "definition", ObjTypeString, + pg_get_constraintdef_string(constrOid, false)); + subcmds = lappend(subcmds, new_object_object(tmp)); + } + break; + + case AT_AlterConstraint: + elog(ERROR, "unimplemented deparse of ALTER TABLE ALTER CONSTRAINT"); + break; + + case AT_ValidateConstraint: + tmp = new_objtree_VA("VALIDATE CONSTRAINT %{constraint}I", 2, + "type", ObjTypeString, "validate constraint", + "constraint", ObjTypeString, subcmd->name); + subcmds = lappend(subcmds, new_object_object(tmp)); + break; + + case AT_DropConstraint: + tmp = new_objtree_VA("DROP CONSTRAINT %{constraint}I", 2, + "type", ObjTypeString, "drop constraint", + "constraint", ObjTypeString, subcmd->name); + subcmds = lappend(subcmds, new_object_object(tmp)); + break; + + case AT_AlterColumnType: + { + TupleDesc tupdesc = RelationGetDescr(rel); + Form_pg_attribute att = tupdesc->attrs[substashed->attnum - 1]; + ColumnDef *def; + + def = (ColumnDef *) subcmd->def; + Assert(IsA(def, ColumnDef)); + + tmp = new_objtree_VA("ALTER COLUMN %{column}I SET DATA TYPE %{datatype}T %{collation}s %{using}s", + 2, "type", ObjTypeString, "alter column type", + "column", ObjTypeString, subcmd->name); + /* add the TYPE clause */ + append_object_object(tmp, "datatype", + new_objtree_for_type(att->atttypid, + att->atttypmod)); + + /* add a COLLATE clause, if needed */ + tmp2 = new_objtree_VA("COLLATE %{name}D", 0); + if (OidIsValid(att->attcollation)) + { + ObjTree *collname; + + collname = new_objtree_for_qualname_id(CollationRelationId, + att->attcollation); + append_object_object(tmp2, "name", collname); + } + else + append_bool_object(tmp2, "present", false); + append_object_object(tmp, "collation", tmp2); + + /* + * Error out if the USING clause was used. We cannot use + * it directly here, because it needs to run through + * transformExpr() before being usable for ruleutils.c, and + * we're not in a position to transform it ourselves. To + * fix this problem, tablecmds.c needs to be modified to store + * the transformed expression somewhere in the StashedATSubcmd. + */ + tmp2 = new_objtree_VA("USING %{expression}s", 0); + if (def->raw_default) + elog(ERROR, "unimplemented deparse of ALTER TABLE TYPE USING"); + else + append_bool_object(tmp2, "present", false); + append_object_object(tmp, "using", tmp2); + + subcmds = lappend(subcmds, new_object_object(tmp)); + } + break; + + case AT_AlterColumnGenericOptions: + elog(ERROR, "unimplemented deparse of ALTER TABLE ALTER COLUMN OPTIONS"); + break; + + case AT_ChangeOwner: + tmp = new_objtree_VA("OWNER TO %{owner}I", + 2, "type", ObjTypeString, "change owner", + "owner", ObjTypeString, subcmd->name); + subcmds = lappend(subcmds, new_object_object(tmp)); + break; + + case AT_ClusterOn: + tmp = new_objtree_VA("CLUSTER ON %{index}I", 2, + "type", ObjTypeString, "cluster on", + "index", ObjTypeString, subcmd->name); + subcmds = lappend(subcmds, new_object_object(tmp)); + break; + + case AT_DropCluster: + tmp = new_objtree_VA("SET WITHOUT CLUSTER", 1, + "type", ObjTypeString, "set without cluster"); + subcmds = lappend(subcmds, new_object_object(tmp)); + break; + + case AT_SetLogged: + tmp = new_objtree_VA("SET LOGGED", 1, + "type", ObjTypeString, "set logged"); + subcmds = lappend(subcmds, new_object_object(tmp)); + break; + + case AT_SetUnLogged: + tmp = new_objtree_VA("SET UNLOGGED", 1, + "type", ObjTypeString, "set unlogged"); + subcmds = lappend(subcmds, new_object_object(tmp)); + break; + + case AT_AddOids: + tmp = new_objtree_VA("SET WITH OIDS", 1, + "type", ObjTypeString, "set with oids"); + subcmds = lappend(subcmds, new_object_object(tmp)); + break; + + case AT_DropOids: + tmp = new_objtree_VA("SET WITHOUT OIDS", 1, + "type", ObjTypeString, "set without oids"); + subcmds = lappend(subcmds, new_object_object(tmp)); + break; + + case AT_SetTableSpace: + tmp = new_objtree_VA("SET TABLESPACE %{tablespace}I", 2, + "type", ObjTypeString, "set tablespace", + "tablespace", ObjTypeString, subcmd->name); + subcmds = lappend(subcmds, new_object_object(tmp)); + break; + + case AT_SetRelOptions: + elog(ERROR, "unimplemented deparse of ALTER TABLE SET"); + break; + + case AT_ResetRelOptions: + elog(ERROR, "unimplemented deparse of ALTER TABLE RESET"); + break; + + case AT_EnableTrig: + tmp = new_objtree_VA("ENABLE TRIGGER %{trigger}I", 2, + "type", ObjTypeString, "enable trigger", + "trigger", ObjTypeString, subcmd->name); + subcmds = lappend(subcmds, new_object_object(tmp)); + break; + + case AT_EnableAlwaysTrig: + tmp = new_objtree_VA("ENABLE ALWAYS TRIGGER %{trigger}I", 2, + "type", ObjTypeString, "enable always trigger", + "trigger", ObjTypeString, subcmd->name); + subcmds = lappend(subcmds, new_object_object(tmp)); + break; + + case AT_EnableReplicaTrig: + tmp = new_objtree_VA("ENABLE REPLICA TRIGGER %{trigger}I", 2, + "type", ObjTypeString, "enable replica trigger", + "trigger", ObjTypeString, subcmd->name); + subcmds = lappend(subcmds, new_object_object(tmp)); + break; + + case AT_DisableTrig: + tmp = new_objtree_VA("DISABLE TRIGGER %{trigger}I", 2, + "type", ObjTypeString, "disable trigger", + "trigger", ObjTypeString, subcmd->name); + subcmds = lappend(subcmds, new_object_object(tmp)); + break; + + case AT_EnableTrigAll: + tmp = new_objtree_VA("ENABLE TRIGGER ALL", 1, + "type", ObjTypeString, "enable trigger all"); + subcmds = lappend(subcmds, new_object_object(tmp)); + break; + + case AT_DisableTrigAll: + tmp = new_objtree_VA("DISABLE TRIGGER ALL", 1, + "type", ObjTypeString, "disable trigger all"); + subcmds = lappend(subcmds, new_object_object(tmp)); + break; + + case AT_EnableTrigUser: + tmp = new_objtree_VA("ENABLE TRIGGER USER", 1, + "type", ObjTypeString, "enable trigger user"); + subcmds = lappend(subcmds, new_object_object(tmp)); + break; + + case AT_DisableTrigUser: + tmp = new_objtree_VA("DISABLE TRIGGER USER", 1, + "type", ObjTypeString, "disable trigger user"); + subcmds = lappend(subcmds, new_object_object(tmp)); + break; + + case AT_EnableRule: + tmp = new_objtree_VA("ENABLE RULE %{rule}I", 2, + "type", ObjTypeString, "enable rule", + "rule", ObjTypeString, subcmd->name); + subcmds = lappend(subcmds, new_object_object(tmp)); + break; + + case AT_EnableAlwaysRule: + tmp = new_objtree_VA("ENABLE ALWAYS RULE %{rule}I", 2, + "type", ObjTypeString, "enable always rule", + "rule", ObjTypeString, subcmd->name); + subcmds = lappend(subcmds, new_object_object(tmp)); + break; + + case AT_EnableReplicaRule: + tmp = new_objtree_VA("ENABLE REPLICA RULE %{rule}I", 2, + "type", ObjTypeString, "enable replica rule", + "rule", ObjTypeString, subcmd->name); + subcmds = lappend(subcmds, new_object_object(tmp)); + break; + + case AT_DisableRule: + tmp = new_objtree_VA("DISABLE RULE %{rule}I", 2, + "type", ObjTypeString, "disable rule", + "rule", ObjTypeString, subcmd->name); + subcmds = lappend(subcmds, new_object_object(tmp)); + break; + + case AT_AddInherit: + tmp = new_objtree_VA("ADD INHERIT %{parent}D", + 2, "type", ObjTypeString, "add inherit", + "parent", ObjTypeObject, + new_objtree_for_qualname_id(RelationRelationId, + substashed->oid)); + subcmds = lappend(subcmds, new_object_object(tmp)); + break; + + case AT_DropInherit: + tmp = new_objtree_VA("NO INHERIT %{parent}D", + 2, "type", ObjTypeString, "drop inherit", + "parent", ObjTypeObject, + new_objtree_for_qualname_id(RelationRelationId, + substashed->oid)); + subcmds = lappend(subcmds, new_object_object(tmp)); + break; + + case AT_AddOf: + tmp = new_objtree_VA("ADD OF %{type_of}T", + 2, "type", ObjTypeString, "add of", + "type_of", ObjTypeObject, + new_objtree_for_type(substashed->oid, -1)); + subcmds = lappend(subcmds, new_object_object(tmp)); + break; + + case AT_DropOf: + tmp = new_objtree_VA("NOT OF", + 1, "type", ObjTypeString, "not of"); + subcmds = lappend(subcmds, new_object_object(tmp)); + break; + + case AT_ReplicaIdentity: + tmp = new_objtree_VA("REPLICA IDENTITY %{ident}s", 1, + "type", ObjTypeString, "replica identity"); + switch (((ReplicaIdentityStmt *) subcmd->def)->identity_type) + { + case REPLICA_IDENTITY_DEFAULT: + append_string_object(tmp, "ident", "DEFAULT"); + break; + case REPLICA_IDENTITY_FULL: + append_string_object(tmp, "ident", "FULL"); + break; + case REPLICA_IDENTITY_NOTHING: + append_string_object(tmp, "ident", "NOTHING"); + break; + case REPLICA_IDENTITY_INDEX: + tmp2 = new_objtree_VA("USING INDEX %{index}I", 1, + "index", ObjTypeString, + ((ReplicaIdentityStmt *) subcmd->def)->name); + append_object_object(tmp, "ident", tmp2); + break; + } + subcmds = lappend(subcmds, new_object_object(tmp)); + break; + + case AT_EnableRowSecurity: + tmp = new_objtree_VA("ENABLE ROW LEVEL SECURITY", 1, + "type", ObjTypeString, "enable row security"); + subcmds = lappend(subcmds, new_object_object(tmp)); + break; + + case AT_DisableRowSecurity: + tmp = new_objtree_VA("DISABLE ROW LEVEL SECURITY", 1, + "type", ObjTypeString, "disable row security"); + subcmds = lappend(subcmds, new_object_object(tmp)); + break; + + case AT_GenericOptions: + elog(ERROR, "unimplemented deparse of ALTER TABLE OPTIONS (...)"); + break; + + default: + elog(WARNING, "unsupported alter table subtype %d", + subcmd->subtype); + break; + } + } + + heap_close(rel, AccessShareLock); + + if (list_length(subcmds) == 0) + return NULL; + + append_array_object(alterTableStmt, "subcmds", subcmds); + return alterTableStmt; +} + /* * Handle deparsing of simple commands. * @@ -3248,6 +3736,9 @@ deparse_utility_command(StashedCommand *cmd) case SCT_Simple: tree = deparse_simple_command(cmd); break; + case SCT_AlterTable: + tree = deparse_AlterTableStmt(cmd); + break; default: elog(ERROR, "unexpected deparse node type %d", cmd->type); } diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 2e21659..c09915d 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1019,6 +1019,10 @@ ProcessUtilitySlow(Node *parsetree, stmts = transformAlterTableStmt(relid, atstmt, queryString); + /* ... ensure we have an event trigger context ... */ + EventTriggerComplexCmdStart(parsetree, atstmt->relkind); + EventTriggerComplexCmdSetOid(relid); + /* ... and do it */ foreach(l, stmts) { @@ -1032,19 +1036,32 @@ ProcessUtilitySlow(Node *parsetree, } else { - /* Recurse for anything else */ + /* + * Recurse for anything else. If we need to do + * so, "close" the current complex-command set, + * and start a new one at the bottom; this is + * needed to ensure the ordering of queued + * commands is consistent with the way they are + * executed here. + */ + EventTriggerComplexCmdEnd(); ProcessUtility(stmt, queryString, PROCESS_UTILITY_SUBCOMMAND, params, None_Receiver, NULL); + EventTriggerComplexCmdStart(parsetree, atstmt->relkind); + EventTriggerComplexCmdSetOid(relid); } /* Need CCI between commands */ if (lnext(l) != NULL) CommandCounterIncrement(); } + + /* done */ + EventTriggerComplexCmdEnd(); } else ereport(NOTICE, @@ -1201,6 +1218,7 @@ ProcessUtilitySlow(Node *parsetree, stmt = transformIndexStmt(relid, stmt, queryString); /* ... and do it */ + EventTriggerComplexCmdStart(parsetree, OBJECT_INDEX); /* relkind? */ objectId = DefineIndex(relid, /* OID of heap relation */ stmt, @@ -1211,6 +1229,7 @@ ProcessUtilitySlow(Node *parsetree, false); /* quiet */ EventTriggerStashCommand(objectId, 0, OBJECT_INDEX, InvalidOid, parsetree); + EventTriggerComplexCmdEnd(); } break; @@ -1299,8 +1318,10 @@ ProcessUtilitySlow(Node *parsetree, break; case T_ViewStmt: /* CREATE VIEW */ + EventTriggerComplexCmdStart(parsetree, OBJECT_VIEW); /* XXX relkind? */ objectId = DefineView((ViewStmt *) parsetree, queryString); EventTriggerStashCommand(objectId, 0, OBJECT_VIEW, InvalidOid, parsetree); + EventTriggerComplexCmdEnd(); break; case T_CreateFunctionStmt: /* CREATE FUNCTION */ diff --git a/src/include/commands/event_trigger.h b/src/include/commands/event_trigger.h index c07f04c..cdce9e1 100644 --- a/src/include/commands/event_trigger.h +++ b/src/include/commands/event_trigger.h @@ -62,5 +62,10 @@ extern void EventTriggerSQLDropAddObject(const ObjectAddress *object, extern void EventTriggerStashCommand(Oid objectId, uint32 objectSubId, ObjectType objtype, Oid secondaryOid, Node *parsetree); +extern void EventTriggerComplexCmdStart(Node *parsetree, ObjectType objtype); +extern void EventTriggerComplexCmdSetOid(Oid objectId); +extern void EventTriggerRecordSubcmd(Node *subcmd, Oid relid, + AttrNumber attnum, Oid newoid); +extern void EventTriggerComplexCmdEnd(void); #endif /* EVENT_TRIGGER_H */ diff --git a/src/include/tcop/deparse_utility.h b/src/include/tcop/deparse_utility.h index bcc8611..8ebbf34 100644 --- a/src/include/tcop/deparse_utility.h +++ b/src/include/tcop/deparse_utility.h @@ -26,6 +26,7 @@ typedef enum StashedCommandType { SCT_Simple, + SCT_AlterTable } StashedCommandType; /* @@ -53,6 +54,13 @@ typedef struct StashedCommand ObjectType objtype; Oid secondaryOid; } simple; + + struct AlterTableCommand + { + Oid objectId; + ObjectType objtype; + List *subcmds; + } alterTable; } d; } StashedCommand; -- 2.1.4 --gatW/ieO32f1wygP Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="0020-deparse-Support-CREATE-VIEW.patch" From ecfd258aa07a38bed9d4396d785975330efeaba0 Mon Sep 17 00:00:00 2001 From: Alvaro Herrera Date: Thu, 25 Sep 2014 14:34:00 -0300 Subject: [PATCH 20/42] deparse: Support CREATE VIEW --- src/backend/tcop/deparse_utility.c | 38 +++++++++++++++++++++++++++++++++++++- src/backend/utils/adt/ruleutils.c | 7 +++++++ src/include/utils/ruleutils.h | 1 + 3 files changed, 45 insertions(+), 1 deletion(-) diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c index ff3f684..49a9ddd 100644 --- a/src/backend/tcop/deparse_utility.c +++ b/src/backend/tcop/deparse_utility.c @@ -740,6 +740,42 @@ deparse_CreateExtensionStmt(Oid objectId, Node *parsetree) } /* + * deparse_ViewStmt + * deparse a ViewStmt + * + * Given a view OID and the parsetree that created it, return an ObjTree + * representing the creation command. + */ +static ObjTree * +deparse_ViewStmt(Oid objectId, Node *parsetree) +{ + ViewStmt *node = (ViewStmt *) parsetree; + ObjTree *viewStmt; + ObjTree *tmp; + Relation relation; + + relation = relation_open(objectId, AccessShareLock); + + viewStmt = new_objtree_VA("CREATE %{or_replace}s %{persistence}s VIEW %{identity}D AS %{query}s", + 2, + "or_replace", ObjTypeString, + node->replace ? "OR REPLACE" : "", + "persistence", ObjTypeString, + get_persistence_str(relation->rd_rel->relpersistence)); + + tmp = new_objtree_for_qualname(relation->rd_rel->relnamespace, + RelationGetRelationName(relation)); + append_object_object(viewStmt, "identity", tmp); + + append_string_object(viewStmt, "query", + pg_get_viewdef_internal(objectId)); + + relation_close(relation, AccessShareLock); + + return viewStmt; +} + +/* * deparse_CreateTrigStmt * Deparse a CreateTrigStmt (CREATE TRIGGER) * @@ -3559,7 +3595,7 @@ deparse_simple_command(StashedCommand *cmd) break; case T_ViewStmt: /* CREATE VIEW */ - command = NULL; + command = deparse_ViewStmt(objectId, parsetree); break; case T_CreateFunctionStmt: diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 886dc4f..c281c38 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -669,6 +669,13 @@ pg_get_viewdef_name_ext(PG_FUNCTION_ARGS) PG_RETURN_TEXT_P(string_to_text(pg_get_viewdef_worker(viewoid, prettyFlags, WRAP_COLUMN_DEFAULT))); } +char * +pg_get_viewdef_internal(Oid viewoid) +{ + return pg_get_viewdef_worker(viewoid, 0, WRAP_COLUMN_DEFAULT); +} + + /* * Common code for by-OID and by-name variants of pg_get_viewdef */ diff --git a/src/include/utils/ruleutils.h b/src/include/utils/ruleutils.h index c3757de..91b61f9 100644 --- a/src/include/utils/ruleutils.h +++ b/src/include/utils/ruleutils.h @@ -32,6 +32,7 @@ extern char *pg_get_trigger_whenclause(Form_pg_trigger trigrec, extern char *pg_get_constraintdef_string(Oid constraintId, bool fullCommand); extern void pg_get_ruledef_details(Datum ev_qual, Datum ev_action, char **whereClause, List **actions); +extern char *pg_get_viewdef_internal(Oid viewoid); extern char *deparse_expression(Node *expr, List *dpcontext, bool forceprefix, bool showimplicit); -- 2.1.4 --gatW/ieO32f1wygP Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="0021-deparse-Support-CREATE-OPERATOR-FAMILY.patch" From 28098adc265a25aa627cb7b3185835c49241371c Mon Sep 17 00:00:00 2001 From: Alvaro Herrera Date: Thu, 25 Sep 2014 14:34:29 -0300 Subject: [PATCH 21/42] deparse: Support CREATE OPERATOR FAMILY --- src/backend/tcop/deparse_utility.c | 38 +++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c index 49a9ddd..5586fc3 100644 --- a/src/backend/tcop/deparse_utility.c +++ b/src/backend/tcop/deparse_utility.c @@ -41,6 +41,7 @@ #include "catalog/pg_language.h" #include "catalog/pg_operator.h" #include "catalog/pg_opclass.h" +#include "catalog/pg_opfamily.h" #include "catalog/pg_proc.h" #include "catalog/pg_range.h" #include "catalog/pg_rewrite.h" @@ -2993,6 +2994,41 @@ deparse_AlterEnumStmt(Oid objectId, Node *parsetree) } static ObjTree * +deparse_CreateOpFamily(Oid objectId, Node *parsetree) +{ + HeapTuple opfTup; + HeapTuple amTup; + Form_pg_opfamily opfForm; + Form_pg_am amForm; + ObjTree *copfStmt; + ObjTree *tmp; + + opfTup = SearchSysCache1(OPFAMILYOID, ObjectIdGetDatum(objectId)); + if (!HeapTupleIsValid(opfTup)) + elog(ERROR, "cache lookup failed for operator family with OID %u", objectId); + opfForm = (Form_pg_opfamily) GETSTRUCT(opfTup); + + amTup = SearchSysCache1(AMOID, ObjectIdGetDatum(opfForm->opfmethod)); + if (!HeapTupleIsValid(amTup)) + elog(ERROR, "cache lookup failed for access method %u", + opfForm->opfmethod); + amForm = (Form_pg_am) GETSTRUCT(amTup); + + copfStmt = new_objtree_VA("CREATE OPERATOR FAMILY %{identity}D USING %{amname}s", + 0); + + tmp = new_objtree_for_qualname(opfForm->opfnamespace, + NameStr(opfForm->opfname)); + append_object_object(copfStmt, "identity", tmp); + append_string_object(copfStmt, "amname", NameStr(amForm->amname)); + + ReleaseSysCache(amTup); + ReleaseSysCache(opfTup); + + return copfStmt; +} + +static ObjTree * deparse_AlterTableStmt(StashedCommand *cmd) { ObjTree *alterTableStmt; @@ -3652,7 +3688,7 @@ deparse_simple_command(StashedCommand *cmd) break; case T_CreateOpFamilyStmt: - command = NULL; + command = deparse_CreateOpFamily(objectId, parsetree); break; case T_AlterOpFamilyStmt: -- 2.1.4 --gatW/ieO32f1wygP Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="0022-deparse-Support-CREATE-CONVERSION.patch" From 8987530c8fc3b5bdd87e9e152f7097e7b8a1a192 Mon Sep 17 00:00:00 2001 From: Abhijit Menon-Sen Date: Wed, 30 Apr 2014 17:30:07 +0530 Subject: [PATCH 22/42] deparse: Support CREATE CONVERSION --- src/backend/tcop/deparse_utility.c | 39 +++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c index 5586fc3..faba3f7 100644 --- a/src/backend/tcop/deparse_utility.c +++ b/src/backend/tcop/deparse_utility.c @@ -35,6 +35,7 @@ #include "catalog/pg_class.h" #include "catalog/pg_collation.h" #include "catalog/pg_constraint.h" +#include "catalog/pg_conversion.h" #include "catalog/pg_depend.h" #include "catalog/pg_extension.h" #include "catalog/pg_inherits.h" @@ -52,6 +53,7 @@ #include "funcapi.h" #include "lib/ilist.h" #include "lib/stringinfo.h" +#include "mb/pg_wchar.h" #include "nodes/makefuncs.h" #include "nodes/parsenodes.h" #include "parser/analyze.h" @@ -2994,6 +2996,41 @@ deparse_AlterEnumStmt(Oid objectId, Node *parsetree) } static ObjTree * +deparse_CreateConversion(Oid objectId, Node *parsetree) +{ + HeapTuple conTup; + Relation convrel; + Form_pg_conversion conForm; + ObjTree *ccStmt; + + convrel = heap_open(ConversionRelationId, AccessShareLock); + conTup = get_catalog_object_by_oid(convrel, objectId); + if (!HeapTupleIsValid(conTup)) + elog(ERROR, "cache lookup failed for conversion with OID %u", objectId); + conForm = (Form_pg_conversion) GETSTRUCT(conTup); + + ccStmt = new_objtree_VA("CREATE %{default}s CONVERSION %{identity}D FOR " + "%{source}L TO %{dest}L FROM %{function}D", 0); + + append_string_object(ccStmt, "default", + conForm->condefault ? "DEFAULT" : ""); + append_object_object(ccStmt, "identity", + new_objtree_for_qualname(conForm->connamespace, + NameStr(conForm->conname))); + append_string_object(ccStmt, "source", (char *) + pg_encoding_to_char(conForm->conforencoding)); + append_string_object(ccStmt, "dest", (char *) + pg_encoding_to_char(conForm->contoencoding)); + append_object_object(ccStmt, "function", + new_objtree_for_qualname_id(ProcedureRelationId, + conForm->conproc)); + + heap_close(convrel, AccessShareLock); + + return ccStmt; +} + +static ObjTree * deparse_CreateOpFamily(Oid objectId, Node *parsetree) { HeapTuple opfTup; @@ -3676,7 +3713,7 @@ deparse_simple_command(StashedCommand *cmd) break; case T_CreateConversionStmt: - command = NULL; + command = deparse_CreateConversion(objectId, parsetree); break; case T_CreateCastStmt: -- 2.1.4 --gatW/ieO32f1wygP Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0023-deparse-Support-DefineStmt-commands.patch" Content-Transfer-Encoding: 8bit From 965af28caaaa7401943e7672600f37d6697a9cca Mon Sep 17 00:00:00 2001 From: Abhijit Menon-Sen Date: Mon, 5 May 2014 12:20:58 +0530 Subject: [PATCH 23/42] deparse: Support DefineStmt commands CREATE AGGREGATE CREATE COLLATION CREATE OPERATOR CREATE TEXT SEARCH CONFIGURATION CREATE TEXT SEARCH PARSER CREATE TEXT SEARCH DICTIONARY CREATE TEXT SEARCH TEMPLATE CREATE TYPE --- src/backend/commands/typecmds.c | 2 +- src/backend/tcop/deparse_utility.c | 855 ++++++++++++++++++++++++++++++++++++- 2 files changed, 855 insertions(+), 2 deletions(-) diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c index b77e1b4..0564535 100644 --- a/src/backend/commands/typecmds.c +++ b/src/backend/commands/typecmds.c @@ -229,7 +229,7 @@ DefineType(List *names, List *parameters) * creating the shell type was all we're supposed to do. */ if (parameters == NIL) - return InvalidOid; + return typoid; } else { diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c index faba3f7..4008db2 100644 --- a/src/backend/tcop/deparse_utility.c +++ b/src/backend/tcop/deparse_utility.c @@ -47,6 +47,11 @@ #include "catalog/pg_range.h" #include "catalog/pg_rewrite.h" #include "catalog/pg_trigger.h" +#include "catalog/pg_ts_config.h" +#include "catalog/pg_ts_config_map.h" +#include "catalog/pg_ts_dict.h" +#include "catalog/pg_ts_parser.h" +#include "catalog/pg_ts_template.h" #include "catalog/pg_type.h" #include "commands/defrem.h" #include "commands/sequence.h" @@ -667,6 +672,854 @@ get_persistence_str(char persistence) } } +static ObjTree * +deparse_DefineStmt_Aggregate(Oid objectId, DefineStmt *define) +{ + HeapTuple aggTup; + HeapTuple procTup; + ObjTree *stmt; + ObjTree *tmp; + List *list; + Datum initval; + bool isnull; + Form_pg_aggregate agg; + Form_pg_proc proc; + + aggTup = SearchSysCache1(AGGFNOID, ObjectIdGetDatum(objectId)); + if (!HeapTupleIsValid(aggTup)) + elog(ERROR, "cache lookup failed for aggregate with OID %u", objectId); + agg = (Form_pg_aggregate) GETSTRUCT(aggTup); + + procTup = SearchSysCache1(PROCOID, ObjectIdGetDatum(agg->aggfnoid)); + if (!HeapTupleIsValid(procTup)) + elog(ERROR, "cache lookup failed for procedure with OID %u", + agg->aggfnoid); + proc = (Form_pg_proc) GETSTRUCT(procTup); + + stmt = new_objtree_VA("CREATE AGGREGATE %{identity}D (%{types:, }s) " + "(%{elems:, }s)", 0); + + append_object_object(stmt, "identity", + new_objtree_for_qualname(proc->pronamespace, + NameStr(proc->proname))); + + list = NIL; + + /* + * An aggregate may have no arguments, in which case its signature + * is (*), to match count(*). If it's not an ordered-set aggregate, + * it may have a non-zero number of arguments. Otherwise it may have + * zero or more direct arguments and zero or more ordered arguments. + * There are no defaults or table parameters, and the only mode that + * we need to consider is VARIADIC. + */ + + if (proc->pronargs == 0) + list = lappend(list, new_object_object(new_objtree_VA("*", 0))); + else + { + int i; + int nargs; + Oid *types; + char *modes; + char **names; + int insertorderbyat = -1; + + nargs = get_func_arg_info(procTup, &types, &names, &modes); + + if (AGGKIND_IS_ORDERED_SET(agg->aggkind)) + insertorderbyat = agg->aggnumdirectargs; + + for (i = 0; i < nargs; i++) + { + tmp = new_objtree_VA("%{order}s %{mode}s %{name}s %{type}T", 0); + + if (i == insertorderbyat) + append_string_object(tmp, "order", "ORDER BY"); + else + append_string_object(tmp, "order", ""); + + if (modes) + append_string_object(tmp, "mode", + modes[i] == 'v' ? "VARIADIC" : ""); + else + append_string_object(tmp, "mode", ""); + + if (names) + append_string_object(tmp, "name", names[i]); + else + append_string_object(tmp, "name", ""); + + append_object_object(tmp, "type", + new_objtree_for_type(types[i], -1)); + + list = lappend(list, new_object_object(tmp)); + + /* + * For variadic ordered-set aggregates, we have to repeat + * the last argument. This nasty hack is copied from + * print_function_arguments in ruleutils.c + */ + if (i == insertorderbyat && i == nargs-1) + list = lappend(list, new_object_object(tmp)); + } + } + + append_array_object(stmt, "types", list); + + list = NIL; + + tmp = new_objtree_VA("SFUNC=%{procedure}D", 0); + append_object_object(tmp, "procedure", + new_objtree_for_qualname_id(ProcedureRelationId, + agg->aggtransfn)); + list = lappend(list, new_object_object(tmp)); + + tmp = new_objtree_VA("STYPE=%{type}T", 0); + append_object_object(tmp, "type", + new_objtree_for_type(agg->aggtranstype, -1)); + list = lappend(list, new_object_object(tmp)); + + if (agg->aggtransspace != 0) + { + tmp = new_objtree_VA("SSPACE=%{space}n", 1, + "space", ObjTypeInteger, + agg->aggtransspace); + list = lappend(list, new_object_object(tmp)); + } + + if (OidIsValid(agg->aggfinalfn)) + { + tmp = new_objtree_VA("FINALFUNC=%{procedure}D", 0); + append_object_object(tmp, "procedure", + new_objtree_for_qualname_id(ProcedureRelationId, + agg->aggfinalfn)); + list = lappend(list, new_object_object(tmp)); + } + + if (agg->aggfinalextra) + { + tmp = new_objtree_VA("FINALFUNC_EXTRA=true", 0); + list = lappend(list, new_object_object(tmp)); + } + + initval = SysCacheGetAttr(AGGFNOID, aggTup, + Anum_pg_aggregate_agginitval, + &isnull); + if (!isnull) + { + tmp = new_objtree_VA("INITCOND=%{initval}L", + 1, "initval", ObjTypeString, + TextDatumGetCString(initval)); + list = lappend(list, new_object_object(tmp)); + } + + if (OidIsValid(agg->aggmtransfn)) + { + tmp = new_objtree_VA("MSFUNC=%{procedure}D", 0); + append_object_object(tmp, "procedure", + new_objtree_for_qualname_id(ProcedureRelationId, + agg->aggmtransfn)); + list = lappend(list, new_object_object(tmp)); + } + + if (OidIsValid(agg->aggmtranstype)) + { + tmp = new_objtree_VA("MSTYPE=%{type}T", 0); + append_object_object(tmp, "type", + new_objtree_for_type(agg->aggmtranstype, -1)); + list = lappend(list, new_object_object(tmp)); + } + + if (agg->aggmtransspace != 0) + { + tmp = new_objtree_VA("MSSPACE=%{space}n", 1, + "space", ObjTypeInteger, + agg->aggmtransspace); + list = lappend(list, new_object_object(tmp)); + } + + if (OidIsValid(agg->aggminvtransfn)) + { + tmp = new_objtree_VA("MINVFUNC=%{procedure}D", 0); + append_object_object(tmp, "procedure", + new_objtree_for_qualname_id(ProcedureRelationId, + agg->aggminvtransfn)); + list = lappend(list, new_object_object(tmp)); + } + + if (OidIsValid(agg->aggmfinalfn)) + { + tmp = new_objtree_VA("MFINALFUNC=%{procedure}D", 0); + append_object_object(tmp, "procedure", + new_objtree_for_qualname_id(ProcedureRelationId, + agg->aggmfinalfn)); + list = lappend(list, new_object_object(tmp)); + } + + if (agg->aggmfinalextra) + { + tmp = new_objtree_VA("MFINALFUNC_EXTRA=true", 0); + list = lappend(list, new_object_object(tmp)); + } + + initval = SysCacheGetAttr(AGGFNOID, aggTup, + Anum_pg_aggregate_aggminitval, + &isnull); + if (!isnull) + { + tmp = new_objtree_VA("MINITCOND=%{initval}L", + 1, "initval", ObjTypeString, + TextDatumGetCString(initval)); + list = lappend(list, new_object_object(tmp)); + } + + if (agg->aggkind == AGGKIND_HYPOTHETICAL) + { + tmp = new_objtree_VA("HYPOTHETICAL=true", 0); + list = lappend(list, new_object_object(tmp)); + } + + if (OidIsValid(agg->aggsortop)) + { + Oid sortop = agg->aggsortop; + Form_pg_operator op; + HeapTuple tup; + + tup = SearchSysCache1(OPEROID, ObjectIdGetDatum(sortop)); + if (!HeapTupleIsValid(tup)) + elog(ERROR, "cache lookup failed for operator with OID %u", sortop); + op = (Form_pg_operator) GETSTRUCT(tup); + + tmp = new_objtree_VA("SORTOP=%{operator}D", 0); + append_object_object(tmp, "operator", + new_objtree_for_qualname(op->oprnamespace, + NameStr(op->oprname))); + list = lappend(list, new_object_object(tmp)); + + ReleaseSysCache(tup); + } + + append_array_object(stmt, "elems", list); + + ReleaseSysCache(procTup); + ReleaseSysCache(aggTup); + + return stmt; +} + +static ObjTree * +deparse_DefineStmt_Collation(Oid objectId, DefineStmt *define) +{ + HeapTuple colTup; + ObjTree *stmt; + Form_pg_collation colForm; + + colTup = SearchSysCache1(COLLOID, ObjectIdGetDatum(objectId)); + if (!HeapTupleIsValid(colTup)) + elog(ERROR, "cache lookup failed for collation with OID %u", objectId); + colForm = (Form_pg_collation) GETSTRUCT(colTup); + + stmt = new_objtree_VA("CREATE COLLATION %{identity}O " + "(LC_COLLATE = %{collate}L," + " LC_CTYPE = %{ctype}L)", 0); + + append_object_object(stmt, "identity", + new_objtree_for_qualname(colForm->collnamespace, + NameStr(colForm->collname))); + append_string_object(stmt, "collate", NameStr(colForm->collcollate)); + append_string_object(stmt, "ctype", NameStr(colForm->collctype)); + + ReleaseSysCache(colTup); + + return stmt; +} + +static ObjTree * +deparse_DefineStmt_Operator(Oid objectId, DefineStmt *define) +{ + HeapTuple oprTup; + ObjTree *stmt; + ObjTree *tmp; + List *list; + Form_pg_operator oprForm; + + oprTup = SearchSysCache1(OPEROID, ObjectIdGetDatum(objectId)); + if (!HeapTupleIsValid(oprTup)) + elog(ERROR, "cache lookup failed for operator with OID %u", objectId); + oprForm = (Form_pg_operator) GETSTRUCT(oprTup); + + stmt = new_objtree_VA("CREATE OPERATOR %{identity}O (%{elems:, }s)", 0); + + append_object_object(stmt, "identity", + new_objtree_for_qualname(oprForm->oprnamespace, + NameStr(oprForm->oprname))); + + list = NIL; + + tmp = new_objtree_VA("PROCEDURE=%{procedure}D", 0); + append_object_object(tmp, "procedure", + new_objtree_for_qualname_id(ProcedureRelationId, + oprForm->oprcode)); + list = lappend(list, new_object_object(tmp)); + + if (OidIsValid(oprForm->oprleft)) + { + tmp = new_objtree_VA("LEFTARG=%{type}T", 0); + append_object_object(tmp, "type", + new_objtree_for_type(oprForm->oprleft, -1)); + list = lappend(list, new_object_object(tmp)); + } + + if (OidIsValid(oprForm->oprright)) + { + tmp = new_objtree_VA("RIGHTARG=%{type}T", 0); + append_object_object(tmp, "type", + new_objtree_for_type(oprForm->oprright, -1)); + list = lappend(list, new_object_object(tmp)); + } + + if (OidIsValid(oprForm->oprcom)) + { + tmp = new_objtree_VA("COMMUTATOR=%{oper}D", 0); + append_object_object(tmp, "oper", + new_objtree_for_qualname_id(OperatorRelationId, + oprForm->oprcom)); + list = lappend(list, new_object_object(tmp)); + } + + if (OidIsValid(oprForm->oprnegate)) + { + tmp = new_objtree_VA("NEGATOR=%{oper}D", 0); + append_object_object(tmp, "oper", + new_objtree_for_qualname_id(OperatorRelationId, + oprForm->oprnegate)); + list = lappend(list, new_object_object(tmp)); + } + + if (OidIsValid(oprForm->oprrest)) + { + tmp = new_objtree_VA("RESTRICT=%{procedure}D", 0); + append_object_object(tmp, "procedure", + new_objtree_for_qualname_id(ProcedureRelationId, + oprForm->oprrest)); + list = lappend(list, new_object_object(tmp)); + } + + if (OidIsValid(oprForm->oprjoin)) + { + tmp = new_objtree_VA("JOIN=%{procedure}D", 0); + append_object_object(tmp, "procedure", + new_objtree_for_qualname_id(ProcedureRelationId, + oprForm->oprjoin)); + list = lappend(list, new_object_object(tmp)); + } + + if (oprForm->oprcanmerge) + list = lappend(list, new_object_object(new_objtree_VA("MERGES", 0))); + if (oprForm->oprcanhash) + list = lappend(list, new_object_object(new_objtree_VA("HASHES", 0))); + + append_array_object(stmt, "elems", list); + + ReleaseSysCache(oprTup); + + return stmt; +} + +static ObjTree * +deparse_DefineStmt_TSConfig(Oid objectId, DefineStmt *define) +{ + HeapTuple tscTup; + HeapTuple tspTup; + ObjTree *stmt; + Form_pg_ts_config tscForm; + Form_pg_ts_parser tspForm; + + tscTup = SearchSysCache1(TSCONFIGOID, ObjectIdGetDatum(objectId)); + if (!HeapTupleIsValid(tscTup)) + elog(ERROR, "cache lookup failed for text search configuration " + "with OID %u", objectId); + tscForm = (Form_pg_ts_config) GETSTRUCT(tscTup); + + tspTup = SearchSysCache1(TSPARSEROID, ObjectIdGetDatum(tscForm->cfgparser)); + if (!HeapTupleIsValid(tspTup)) + elog(ERROR, "cache lookup failed for text search parser with OID %u", + tscForm->cfgparser); + tspForm = (Form_pg_ts_parser) GETSTRUCT(tspTup); + + stmt = new_objtree_VA("CREATE TEXT SEARCH CONFIGURATION %{identity}D " + "(PARSER=%{parser}s)", 0); + + append_object_object(stmt, "identity", + new_objtree_for_qualname(tscForm->cfgnamespace, + NameStr(tscForm->cfgname))); + append_object_object(stmt, "parser", + new_objtree_for_qualname(tspForm->prsnamespace, + NameStr(tspForm->prsname))); + + /* + * If this text search configuration was created by copying another + * one with "CREATE TEXT SEARCH CONFIGURATION x (COPY=y)", then y's + * PARSER selection is copied along with its mappings of tokens to + * dictionaries (created with ALTER … ADD MAPPING …). + * + * Unfortunately, there's no way to define these mappings in the + * CREATE command, so if they exist for the configuration we're + * deparsing, we must detect them and fail. + */ + + { + ScanKeyData skey; + SysScanDesc scan; + HeapTuple tup; + Relation map; + bool has_mapping; + + map = heap_open(TSConfigMapRelationId, AccessShareLock); + + ScanKeyInit(&skey, + Anum_pg_ts_config_map_mapcfg, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(objectId)); + + scan = systable_beginscan(map, TSConfigMapIndexId, true, + NULL, 1, &skey); + + while (HeapTupleIsValid((tup = systable_getnext(scan)))) + { + has_mapping = true; + break; + } + + systable_endscan(scan); + heap_close(map, AccessShareLock); + + if (has_mapping) + ereport(ERROR, + (errmsg("can't recreate text search configuration with mappings"))); + } + + ReleaseSysCache(tspTup); + ReleaseSysCache(tscTup); + + return stmt; +} + +static ObjTree * +deparse_DefineStmt_TSParser(Oid objectId, DefineStmt *define) +{ + HeapTuple tspTup; + ObjTree *stmt; + ObjTree *tmp; + List *list; + Form_pg_ts_parser tspForm; + + tspTup = SearchSysCache1(TSPARSEROID, ObjectIdGetDatum(objectId)); + if (!HeapTupleIsValid(tspTup)) + elog(ERROR, "cache lookup failed for text search parser with OID %u", + objectId); + tspForm = (Form_pg_ts_parser) GETSTRUCT(tspTup); + + stmt = new_objtree_VA("CREATE TEXT SEARCH PARSER %{identity}D " + "(%{elems:, }s)", 0); + + append_object_object(stmt, "identity", + new_objtree_for_qualname(tspForm->prsnamespace, + NameStr(tspForm->prsname))); + + list = NIL; + + tmp = new_objtree_VA("START=%{procedure}D", 0); + append_object_object(tmp, "procedure", + new_objtree_for_qualname_id(ProcedureRelationId, + tspForm->prsstart)); + list = lappend(list, new_object_object(tmp)); + + tmp = new_objtree_VA("GETTOKEN=%{procedure}D", 0); + append_object_object(tmp, "procedure", + new_objtree_for_qualname_id(ProcedureRelationId, + tspForm->prstoken)); + list = lappend(list, new_object_object(tmp)); + + tmp = new_objtree_VA("END=%{procedure}D", 0); + append_object_object(tmp, "procedure", + new_objtree_for_qualname_id(ProcedureRelationId, + tspForm->prsend)); + list = lappend(list, new_object_object(tmp)); + + tmp = new_objtree_VA("LEXTYPES=%{procedure}D", 0); + append_object_object(tmp, "procedure", + new_objtree_for_qualname_id(ProcedureRelationId, + tspForm->prslextype)); + list = lappend(list, new_object_object(tmp)); + + if (OidIsValid(tspForm->prsheadline)) + { + tmp = new_objtree_VA("HEADLINE=%{procedure}D", 0); + append_object_object(tmp, "procedure", + new_objtree_for_qualname_id(ProcedureRelationId, + tspForm->prsheadline)); + list = lappend(list, new_object_object(tmp)); + } + + append_array_object(stmt, "elems", list); + + ReleaseSysCache(tspTup); + + return stmt; +} + +static ObjTree * +deparse_DefineStmt_TSDictionary(Oid objectId, DefineStmt *define) +{ + HeapTuple tsdTup; + ObjTree *stmt; + ObjTree *tmp; + List *list; + Datum options; + bool isnull; + Form_pg_ts_dict tsdForm; + + tsdTup = SearchSysCache1(TSDICTOID, ObjectIdGetDatum(objectId)); + if (!HeapTupleIsValid(tsdTup)) + elog(ERROR, "cache lookup failed for text search dictionary " + "with OID %u", objectId); + tsdForm = (Form_pg_ts_dict) GETSTRUCT(tsdTup); + + stmt = new_objtree_VA("CREATE TEXT SEARCH DICTIONARY %{identity}D " + "(%{elems:, }s)", 0); + + append_object_object(stmt, "identity", + new_objtree_for_qualname(tsdForm->dictnamespace, + NameStr(tsdForm->dictname))); + + list = NIL; + + tmp = new_objtree_VA("TEMPLATE=%{template}D", 0); + append_object_object(tmp, "template", + new_objtree_for_qualname_id(TSTemplateRelationId, + tsdForm->dicttemplate)); + list = lappend(list, new_object_object(tmp)); + + options = SysCacheGetAttr(TSDICTOID, tsdTup, + Anum_pg_ts_dict_dictinitoption, + &isnull); + if (!isnull) + { + tmp = new_objtree_VA("%{options}s", 0); + append_string_object(tmp, "options", TextDatumGetCString(options)); + list = lappend(list, new_object_object(tmp)); + } + + append_array_object(stmt, "elems", list); + + ReleaseSysCache(tsdTup); + + return stmt; +} + +static ObjTree * +deparse_DefineStmt_TSTemplate(Oid objectId, DefineStmt *define) +{ + HeapTuple tstTup; + ObjTree *stmt; + ObjTree *tmp; + List *list; + Form_pg_ts_template tstForm; + + tstTup = SearchSysCache1(TSTEMPLATEOID, ObjectIdGetDatum(objectId)); + if (!HeapTupleIsValid(tstTup)) + elog(ERROR, "cache lookup failed for text search template with OID %u", + objectId); + tstForm = (Form_pg_ts_template) GETSTRUCT(tstTup); + + stmt = new_objtree_VA("CREATE TEXT SEARCH TEMPLATE %{identity}D " + "(%{elems:, }s)", 0); + + append_object_object(stmt, "identity", + new_objtree_for_qualname(tstForm->tmplnamespace, + NameStr(tstForm->tmplname))); + + list = NIL; + + if (OidIsValid(tstForm->tmplinit)) + { + tmp = new_objtree_VA("INIT=%{procedure}D", 0); + append_object_object(tmp, "procedure", + new_objtree_for_qualname_id(ProcedureRelationId, + tstForm->tmplinit)); + list = lappend(list, new_object_object(tmp)); + } + + tmp = new_objtree_VA("LEXIZE=%{procedure}D", 0); + append_object_object(tmp, "procedure", + new_objtree_for_qualname_id(ProcedureRelationId, + tstForm->tmpllexize)); + list = lappend(list, new_object_object(tmp)); + + append_array_object(stmt, "elems", list); + + ReleaseSysCache(tstTup); + + return stmt; +} + +static ObjTree * +deparse_DefineStmt_Type(Oid objectId, DefineStmt *define) +{ + HeapTuple typTup; + ObjTree *stmt; + ObjTree *tmp; + List *list; + char *str; + Datum dflt; + bool isnull; + Form_pg_type typForm; + + typTup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(objectId)); + if (!HeapTupleIsValid(typTup)) + elog(ERROR, "cache lookup failed for type with OID %u", objectId); + typForm = (Form_pg_type) GETSTRUCT(typTup); + + /* Shortcut processing for shell types. */ + if (!typForm->typisdefined) + { + stmt = new_objtree_VA("CREATE TYPE %{identity}D", 0); + append_object_object(stmt, "identity", + new_objtree_for_qualname(typForm->typnamespace, + NameStr(typForm->typname))); + ReleaseSysCache(typTup); + return stmt; + } + + stmt = new_objtree_VA("CREATE TYPE %{identity}D (%{elems:, }s)", 0); + + append_object_object(stmt, "identity", + new_objtree_for_qualname(typForm->typnamespace, + NameStr(typForm->typname))); + + list = NIL; + + /* INPUT */ + tmp = new_objtree_VA("INPUT=%{procedure}D", 0); + append_object_object(tmp, "procedure", + new_objtree_for_qualname_id(ProcedureRelationId, + typForm->typinput)); + list = lappend(list, new_object_object(tmp)); + + /* OUTPUT */ + tmp = new_objtree_VA("OUTPUT=%{procedure}D", 0); + append_object_object(tmp, "procedure", + new_objtree_for_qualname_id(ProcedureRelationId, + typForm->typoutput)); + list = lappend(list, new_object_object(tmp)); + + /* RECEIVE */ + if (OidIsValid(typForm->typreceive)) + { + tmp = new_objtree_VA("RECEIVE=%{procedure}D", 0); + append_object_object(tmp, "procedure", + new_objtree_for_qualname_id(ProcedureRelationId, + typForm->typreceive)); + list = lappend(list, new_object_object(tmp)); + } + + /* SEND */ + if (OidIsValid(typForm->typsend)) + { + tmp = new_objtree_VA("SEND=%{procedure}D", 0); + append_object_object(tmp, "procedure", + new_objtree_for_qualname_id(ProcedureRelationId, + typForm->typsend)); + list = lappend(list, new_object_object(tmp)); + } + + /* TYPMOD_IN */ + if (OidIsValid(typForm->typmodin)) + { + tmp = new_objtree_VA("TYPMOD_IN=%{procedure}D", 0); + append_object_object(tmp, "procedure", + new_objtree_for_qualname_id(ProcedureRelationId, + typForm->typmodin)); + list = lappend(list, new_object_object(tmp)); + } + + /* TYPMOD_OUT */ + if (OidIsValid(typForm->typmodout)) + { + tmp = new_objtree_VA("TYPMOD_OUT=%{procedure}D", 0); + append_object_object(tmp, "procedure", + new_objtree_for_qualname_id(ProcedureRelationId, + typForm->typmodout)); + list = lappend(list, new_object_object(tmp)); + } + + /* ANALYZE */ + if (OidIsValid(typForm->typanalyze)) + { + tmp = new_objtree_VA("ANALYZE=%{procedure}D", 0); + append_object_object(tmp, "procedure", + new_objtree_for_qualname_id(ProcedureRelationId, + typForm->typanalyze)); + list = lappend(list, new_object_object(tmp)); + } + + /* INTERNALLENGTH */ + if (typForm->typlen == -1) + { + tmp = new_objtree_VA("INTERNALLENGTH=VARIABLE", 0); + } + else + { + tmp = new_objtree_VA("INTERNALLENGTH=%{typlen}n", 1, + "typlen", ObjTypeInteger, typForm->typlen); + } + list = lappend(list, new_object_object(tmp)); + + /* PASSEDBYVALUE */ + if (typForm->typbyval) + list = lappend(list, new_object_object(new_objtree_VA("PASSEDBYVALUE", 0))); + + /* ALIGNMENT */ + tmp = new_objtree_VA("ALIGNMENT=%{align}s", 0); + switch (typForm->typalign) + { + case 'd': + str = "pg_catalog.float8"; + break; + case 'i': + str = "pg_catalog.int4"; + break; + case 's': + str = "pg_catalog.int2"; + break; + case 'c': + str = "pg_catalog.bpchar"; + break; + default: + elog(ERROR, "invalid alignment %c", typForm->typalign); + } + append_string_object(tmp, "align", str); + list = lappend(list, new_object_object(tmp)); + + tmp = new_objtree_VA("STORAGE=%{storage}s", 0); + switch (typForm->typstorage) + { + case 'p': + str = "plain"; + break; + case 'e': + str = "external"; + break; + case 'x': + str = "extended"; + break; + case 'm': + str = "main"; + break; + default: + elog(ERROR, "invalid storage specifier %c", typForm->typstorage); + } + append_string_object(tmp, "storage", str); + list = lappend(list, new_object_object(tmp)); + + /* CATEGORY */ + tmp = new_objtree_VA("CATEGORY=%{category}L", 0); + append_string_object(tmp, "category", + psprintf("%c", typForm->typcategory)); + list = lappend(list, new_object_object(tmp)); + + /* PREFERRED */ + if (typForm->typispreferred) + list = lappend(list, new_object_object(new_objtree_VA("PREFERRED=true", 0))); + + /* DEFAULT */ + dflt = SysCacheGetAttr(TYPEOID, typTup, + Anum_pg_type_typdefault, + &isnull); + if (!isnull) + { + tmp = new_objtree_VA("DEFAULT=%{default}L", 0); + append_string_object(tmp, "default", TextDatumGetCString(dflt)); + list = lappend(list, new_object_object(tmp)); + } + + /* ELEMENT */ + if (OidIsValid(typForm->typelem)) + { + tmp = new_objtree_VA("ELEMENT=%{elem}T", 0); + append_object_object(tmp, "elem", + new_objtree_for_type(typForm->typelem, -1)); + list = lappend(list, new_object_object(tmp)); + } + + /* DELIMITER */ + tmp = new_objtree_VA("DELIMITER=%{delim}L", 0); + append_string_object(tmp, "delim", + psprintf("%c", typForm->typdelim)); + list = lappend(list, new_object_object(tmp)); + + /* COLLATABLE */ + if (OidIsValid(typForm->typcollation)) + list = lappend(list, + new_object_object(new_objtree_VA("COLLATABLE=true", 0))); + + append_array_object(stmt, "elems", list); + + ReleaseSysCache(typTup); + + return stmt; +} + +static ObjTree * +deparse_DefineStmt(Oid objectId, Node *parsetree) +{ + DefineStmt *define = (DefineStmt *) parsetree; + ObjTree *defStmt; + + switch (define->kind) + { + case OBJECT_AGGREGATE: + defStmt = deparse_DefineStmt_Aggregate(objectId, define); + break; + + case OBJECT_COLLATION: + defStmt = deparse_DefineStmt_Collation(objectId, define); + break; + + case OBJECT_OPERATOR: + defStmt = deparse_DefineStmt_Operator(objectId, define); + break; + + case OBJECT_TSCONFIGURATION: + defStmt = deparse_DefineStmt_TSConfig(objectId, define); + break; + + case OBJECT_TSPARSER: + defStmt = deparse_DefineStmt_TSParser(objectId, define); + break; + + case OBJECT_TSDICTIONARY: + defStmt = deparse_DefineStmt_TSDictionary(objectId, define); + break; + + case OBJECT_TSTEMPLATE: + defStmt = deparse_DefineStmt_TSTemplate(objectId, define); + break; + + case OBJECT_TYPE: + defStmt = deparse_DefineStmt_Type(objectId, define); + break; + + default: + elog(ERROR, "unsupported object kind"); + return NULL; + } + + return defStmt; +} + /* * deparse_CreateExtensionStmt * deparse a CreateExtensionStmt @@ -3598,7 +4451,7 @@ deparse_simple_command(StashedCommand *cmd) /* other local objects */ case T_DefineStmt: - command = NULL; + command = deparse_DefineStmt(objectId, parsetree); break; case T_IndexStmt: -- 2.1.4 --gatW/ieO32f1wygP Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="0024-deparse-support-ALTER-THING-OWNER-TO.patch" From 4de9050c55183b6c7f12a0d399b332ae14a97a35 Mon Sep 17 00:00:00 2001 From: Alvaro Herrera Date: Tue, 6 May 2014 17:22:13 -0400 Subject: [PATCH 24/42] deparse: support ALTER THING OWNER TO --- src/backend/tcop/deparse_utility.c | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c index 4008db2..002c65b 100644 --- a/src/backend/tcop/deparse_utility.c +++ b/src/backend/tcop/deparse_utility.c @@ -3849,6 +3849,29 @@ deparse_AlterEnumStmt(Oid objectId, Node *parsetree) } static ObjTree * +deparse_AlterOwnerStmt(Oid objectId, Node *parsetree) +{ + AlterOwnerStmt *node = (AlterOwnerStmt *) parsetree; + ObjTree *ownerStmt; + ObjectAddress addr; + char *fmt; + + fmt = psprintf("ALTER %s %%{identity}s OWNER TO %%{newname}I", + stringify_objtype(node->objectType)); + ownerStmt = new_objtree_VA(fmt, 0); + append_string_object(ownerStmt, "newname", node->newowner); + + addr.classId = get_objtype_catalog_oid(node->objectType); + addr.objectId = objectId; + addr.objectSubId = 0; + + append_string_object(ownerStmt, "identity", + getObjectIdentity(&addr)); + + return ownerStmt; +} + +static ObjTree * deparse_CreateConversion(Oid objectId, Node *parsetree) { HeapTuple conTup; @@ -4607,7 +4630,7 @@ deparse_simple_command(StashedCommand *cmd) break; case T_AlterOwnerStmt: - command = NULL; + command = deparse_AlterOwnerStmt(objectId, parsetree); break; case T_CommentStmt: -- 2.1.4 --gatW/ieO32f1wygP Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="0025-deparse-Support-ALTER-EXTENSION-UPDATE-TO.patch" From 65b9e8703180f157cebdf64e186b7bbcec3c4388 Mon Sep 17 00:00:00 2001 From: Abhijit Menon-Sen Date: Thu, 8 May 2014 15:35:58 +0530 Subject: [PATCH 25/42] deparse: Support ALTER EXTENSION / UPDATE TO --- src/backend/tcop/deparse_utility.c | 44 +++++++++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c index 002c65b..422b11c 100644 --- a/src/backend/tcop/deparse_utility.c +++ b/src/backend/tcop/deparse_utility.c @@ -1595,6 +1595,48 @@ deparse_CreateExtensionStmt(Oid objectId, Node *parsetree) return extStmt; } +static ObjTree * +deparse_AlterExtensionStmt(Oid objectId, Node *parsetree) +{ + AlterExtensionStmt *node = (AlterExtensionStmt *) parsetree; + Relation pg_extension; + HeapTuple extTup; + Form_pg_extension extForm; + ObjTree *stmt; + char *version = NULL; + ListCell *cell; + + pg_extension = heap_open(ExtensionRelationId, AccessShareLock); + extTup = get_catalog_object_by_oid(pg_extension, objectId); + if (!HeapTupleIsValid(extTup)) + elog(ERROR, "cache lookup failed for extension with OID %u", + objectId); + extForm = (Form_pg_extension) GETSTRUCT(extTup); + + stmt = new_objtree_VA("ALTER EXTENSION %{identity}I UPDATE%{to}s", 1, + "identity", ObjTypeString, + NameStr(extForm->extname)); + + foreach(cell, node->options) + { + DefElem *opt = (DefElem *) lfirst(cell); + + if (strcmp(opt->defname, "new_version") == 0) + version = defGetString(opt); + else + elog(ERROR, "unsupported option %s", opt->defname); + } + + if (version) + append_string_object(stmt, "to", psprintf(" TO '%s'", version)); + else + append_string_object(stmt, "to", ""); + + heap_close(pg_extension, AccessShareLock); + + return stmt; +} + /* * deparse_ViewStmt * deparse a ViewStmt @@ -4486,7 +4528,7 @@ deparse_simple_command(StashedCommand *cmd) break; case T_AlterExtensionStmt: - command = NULL; + command = deparse_AlterExtensionStmt(objectId, parsetree); break; case T_AlterExtensionContentsStmt: -- 2.1.4 --gatW/ieO32f1wygP Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="0026-deparse-Support-GRANT-REVOKE.patch" From d2b665e39d3dd5607611fbb4444d52367f543328 Mon Sep 17 00:00:00 2001 From: Alvaro Herrera Date: Thu, 12 Jun 2014 18:34:53 -0400 Subject: [PATCH 26/42] deparse: Support GRANT/REVOKE --- src/backend/catalog/aclchk.c | 37 ++---- src/backend/commands/event_trigger.c | 103 +++++++++++++++++ src/backend/tcop/deparse_utility.c | 216 +++++++++++++++++++++++++++++++++++ src/backend/tcop/utility.c | 1 + src/include/commands/event_trigger.h | 2 + src/include/tcop/deparse_utility.h | 11 +- src/include/utils/aclchk.h | 45 ++++++++ 7 files changed, 388 insertions(+), 27 deletions(-) create mode 100644 src/include/utils/aclchk.h diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c index 1e3888e..9e79654 100644 --- a/src/backend/catalog/aclchk.c +++ b/src/backend/catalog/aclchk.c @@ -48,6 +48,7 @@ #include "catalog/pg_ts_config.h" #include "catalog/pg_ts_dict.h" #include "commands/dbcommands.h" +#include "commands/event_trigger.h" #include "commands/proclang.h" #include "commands/tablespace.h" #include "foreign/foreign.h" @@ -56,6 +57,7 @@ #include "parser/parse_func.h" #include "parser/parse_type.h" #include "utils/acl.h" +#include "utils/aclchk.h" #include "utils/builtins.h" #include "utils/fmgroids.h" #include "utils/lsyscache.h" @@ -65,32 +67,6 @@ /* - * The information about one Grant/Revoke statement, in internal format: object - * and grantees names have been turned into Oids, the privilege list is an - * AclMode bitmask. If 'privileges' is ACL_NO_RIGHTS (the 0 value) and - * all_privs is true, 'privileges' will be internally set to the right kind of - * ACL_ALL_RIGHTS_*, depending on the object type (NB - this will modify the - * InternalGrant struct!) - * - * Note: 'all_privs' and 'privileges' represent object-level privileges only. - * There might also be column-level privilege specifications, which are - * represented in col_privs (this is a list of untransformed AccessPriv nodes). - * Column privileges are only valid for objtype ACL_OBJECT_RELATION. - */ -typedef struct -{ - bool is_grant; - GrantObjectType objtype; - List *objects; - bool all_privs; - AclMode privileges; - List *col_privs; - List *grantees; - bool grant_option; - DropBehavior behavior; -} InternalGrant; - -/* * Internal format used by ALTER DEFAULT PRIVILEGES. */ typedef struct @@ -602,6 +578,15 @@ ExecGrantStmt_oids(InternalGrant *istmt) elog(ERROR, "unrecognized GrantStmt.objtype: %d", (int) istmt->objtype); } + + /* + * Pass the info to event triggers about the just-executed GRANT. Note + * that we prefer to do it after actually executing it, because that gives + * the functions a chance to adjust the istmt with privileges actually + * granted. + */ + if (EventTriggerSupportsGrantObjectType(istmt->objtype)) + EventTriggerStashGrant(istmt); } /* diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c index 67094c8..d44cda2 100644 --- a/src/backend/commands/event_trigger.c +++ b/src/backend/commands/event_trigger.c @@ -1695,6 +1695,85 @@ EventTriggerComplexCmdEnd(void) currentEventTriggerState->curcmd = NULL; } +static const char * +stringify_grantobjtype(GrantObjectType objtype) +{ + switch (objtype) + { + case ACL_OBJECT_COLUMN: + return "COLUMN"; + case ACL_OBJECT_RELATION: + return "TABLE"; + case ACL_OBJECT_SEQUENCE: + return "SEQUENCE"; + case ACL_OBJECT_DATABASE: + return "DATABASE"; + case ACL_OBJECT_DOMAIN: + return "DOMAIN"; + case ACL_OBJECT_FDW: + return "FOREIGN DATA WRAPPER"; + case ACL_OBJECT_FOREIGN_SERVER: + return "FOREIGN SERVER"; + case ACL_OBJECT_FUNCTION: + return "FUNCTION"; + case ACL_OBJECT_LANGUAGE: + return "LANGUAGE"; + case ACL_OBJECT_LARGEOBJECT: + return "LARGE OBJECT"; + case ACL_OBJECT_NAMESPACE: + return "SCHEMA"; + case ACL_OBJECT_TABLESPACE: + return "TABLESPACE"; + case ACL_OBJECT_TYPE: + return "TYPE"; + default: + elog(ERROR, "unrecognized type %d", objtype); + return "???"; /* keep compiler quiet */ + } +} + +/* + * EventTriggerStashGrant + * Save data about a GRANT/REVOKE command being executed + * + * This function creates a copy of the InternalGrant, as the original might + * not have the right lifetime. + */ +void +EventTriggerStashGrant(InternalGrant *istmt) +{ + MemoryContext oldcxt; + StashedCommand *stashed; + InternalGrant *icopy; + ListCell *cell; + + oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt); + + /* + * copying the node is moderately challenging ... should we consider + * changing InternalGrant into a full-fledged node instead? + */ + icopy = palloc(sizeof(InternalGrant)); + memcpy(icopy, istmt, sizeof(InternalGrant)); + icopy->objects = list_copy(istmt->objects); + icopy->grantees = list_copy(istmt->grantees); + icopy->col_privs = NIL; + foreach(cell, istmt->col_privs) + icopy->col_privs = lappend(icopy->col_privs, copyObject(lfirst(cell))); + + stashed = palloc(sizeof(StashedCommand)); + stashed->type = SCT_Grant; + stashed->in_extension = creating_extension; + stashed->d.grant.type = stringify_grantobjtype(istmt->objtype); + stashed->d.grant.istmt = icopy; + stashed->parsetree = NULL; + + currentEventTriggerState->stash = lappend(currentEventTriggerState->stash, + stashed); + + MemoryContextSwitchTo(oldcxt); +} + Datum pg_event_trigger_get_creation_commands(PG_FUNCTION_ARGS) { @@ -1863,6 +1942,30 @@ pg_event_trigger_get_creation_commands(PG_FUNCTION_ARGS) /* command */ values[i++] = CStringGetTextDatum(command); } + else + { + Assert(cmd->type == SCT_Grant); + + /* classid */ + nulls[i++] = true; + /* objid */ + nulls[i++] = true; + /* objsubid */ + nulls[i++] = true; + /* command tag */ + values[i++] = CStringGetTextDatum(cmd->d.grant.istmt->is_grant ? + "GRANT" : "REVOKE"); + /* object_type */ + values[i++] = CStringGetTextDatum(cmd->d.grant.type); + /* schema */ + nulls[i++] = true; + /* identity */ + nulls[i++] = true; + /* in_extension */ + values[i++] = BoolGetDatum(cmd->in_extension); + /* command */ + values[i++] = CStringGetTextDatum(command); + } tuplestore_putvalues(tupstore, tupdesc, values, nulls); } diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c index 422b11c..a432a38 100644 --- a/src/backend/tcop/deparse_utility.c +++ b/src/backend/tcop/deparse_utility.c @@ -32,17 +32,23 @@ #include "catalog/index.h" #include "catalog/namespace.h" #include "catalog/pg_aggregate.h" +#include "catalog/pg_authid.h" #include "catalog/pg_class.h" #include "catalog/pg_collation.h" #include "catalog/pg_constraint.h" #include "catalog/pg_conversion.h" #include "catalog/pg_depend.h" #include "catalog/pg_extension.h" +#include "catalog/pg_foreign_data_wrapper.h" +#include "catalog/pg_foreign_server.h" #include "catalog/pg_inherits.h" #include "catalog/pg_language.h" +#include "catalog/pg_largeobject.h" +#include "catalog/pg_namespace.h" #include "catalog/pg_operator.h" #include "catalog/pg_opclass.h" #include "catalog/pg_opfamily.h" +#include "catalog/pg_policy.h" #include "catalog/pg_proc.h" #include "catalog/pg_range.h" #include "catalog/pg_rewrite.h" @@ -3984,6 +3990,213 @@ deparse_CreateOpFamily(Oid objectId, Node *parsetree) } static ObjTree * +deparse_GrantStmt(StashedCommand *cmd) +{ + InternalGrant *istmt; + ObjTree *grantStmt; + char *fmt; + char *objtype; + List *list; + ListCell *cell; + Oid classId; + ObjTree *tmp; + + istmt = cmd->d.grant.istmt; + + switch (istmt->objtype) + { + case ACL_OBJECT_COLUMN: + case ACL_OBJECT_RELATION: + objtype = "TABLE"; + classId = RelationRelationId; + break; + case ACL_OBJECT_SEQUENCE: + objtype = "SEQUENCE"; + classId = RelationRelationId; + break; + case ACL_OBJECT_DOMAIN: + objtype = "DOMAIN"; + classId = TypeRelationId; + break; + case ACL_OBJECT_FDW: + objtype = "FOREIGN DATA WRAPPER"; + classId = ForeignDataWrapperRelationId; + break; + case ACL_OBJECT_FOREIGN_SERVER: + objtype = "FOREIGN SERVER"; + classId = ForeignServerRelationId; + break; + case ACL_OBJECT_FUNCTION: + objtype = "FUNCTION"; + classId = ProcedureRelationId; + break; + case ACL_OBJECT_LANGUAGE: + objtype = "LANGUAGE"; + classId = LanguageRelationId; + break; + case ACL_OBJECT_LARGEOBJECT: + objtype = "LARGE OBJECT"; + classId = LargeObjectRelationId; + break; + case ACL_OBJECT_NAMESPACE: + objtype = "SCHEMA"; + classId = NamespaceRelationId; + break; + case ACL_OBJECT_TYPE: + objtype = "TYPE"; + classId = TypeRelationId; + break; + case ACL_OBJECT_DATABASE: + case ACL_OBJECT_TABLESPACE: + objtype = ""; + classId = InvalidOid; + elog(ERROR, "global objects not supported"); + default: + elog(ERROR, "invalid ACL_OBJECT value %d", istmt->objtype); + } + + /* GRANT TO or REVOKE FROM */ + if (istmt->is_grant) + fmt = psprintf("GRANT %%{privileges:, }s ON %s %%{privtarget:, }s " + "TO %%{grantees:, }s %%{grant_option}s", + objtype); + else + fmt = psprintf("REVOKE %%{grant_option}s %%{privileges:, }s ON %s %%{privtarget:, }s " + "FROM %%{grantees:, }s %%{cascade}s", + objtype); + + grantStmt = new_objtree_VA(fmt, 0); + + /* build list of privileges to grant/revoke */ + if (istmt->all_privs) + { + tmp = new_objtree_VA("ALL PRIVILEGES", 0); + list = list_make1(new_object_object(tmp)); + } + else + { + list = NIL; + + if (istmt->privileges & ACL_INSERT) + list = lappend(list, new_string_object("INSERT")); + if (istmt->privileges & ACL_SELECT) + list = lappend(list, new_string_object("SELECT")); + if (istmt->privileges & ACL_UPDATE) + list = lappend(list, new_string_object("UPDATE")); + if (istmt->privileges & ACL_DELETE) + list = lappend(list, new_string_object("DELETE")); + if (istmt->privileges & ACL_TRUNCATE) + list = lappend(list, new_string_object("TRUNCATE")); + if (istmt->privileges & ACL_REFERENCES) + list = lappend(list, new_string_object("REFERENCES")); + if (istmt->privileges & ACL_TRIGGER) + list = lappend(list, new_string_object("TRIGGER")); + if (istmt->privileges & ACL_EXECUTE) + list = lappend(list, new_string_object("EXECUTE")); + if (istmt->privileges & ACL_USAGE) + list = lappend(list, new_string_object("USAGE")); + if (istmt->privileges & ACL_CREATE) + list = lappend(list, new_string_object("CREATE")); + if (istmt->privileges & ACL_CREATE_TEMP) + list = lappend(list, new_string_object("TEMPORARY")); + if (istmt->privileges & ACL_CONNECT) + list = lappend(list, new_string_object("CONNECT")); + + if (istmt->col_privs != NIL) + { + ListCell *ocell; + + foreach(ocell, istmt->col_privs) + { + AccessPriv *priv = lfirst(ocell); + List *cols = NIL; + + tmp = new_objtree_VA("%{priv}s (%{cols:, }I)", 0); + foreach(cell, priv->cols) + { + Value *colname = lfirst(cell); + + cols = lappend(cols, + new_string_object(strVal(colname))); + } + append_array_object(tmp, "cols", cols); + if (priv->priv_name == NULL) + append_string_object(tmp, "priv", "ALL PRIVILEGES"); + else + append_string_object(tmp, "priv", priv->priv_name); + + list = lappend(list, new_object_object(tmp)); + } + } + } + append_array_object(grantStmt, "privileges", list); + + /* target objects. We use object identities here */ + list = NIL; + foreach(cell, istmt->objects) + { + Oid objid = lfirst_oid(cell); + ObjectAddress addr; + + addr.classId = classId; + addr.objectId = objid; + addr.objectSubId = 0; + + tmp = new_objtree_VA("%{identity}s", 0); + append_string_object(tmp, "identity", + getObjectIdentity(&addr)); + list = lappend(list, new_object_object(tmp)); + } + append_array_object(grantStmt, "privtarget", list); + + /* list of grantees */ + list = NIL; + foreach(cell, istmt->grantees) + { + Oid grantee = lfirst_oid(cell); + + if (grantee == ACL_ID_PUBLIC) + tmp = new_objtree_VA("PUBLIC", 0); + else + { + HeapTuple roltup; + char *rolname; + + roltup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(grantee)); + if (!HeapTupleIsValid(roltup)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("role with OID %u does not exist", grantee))); + + tmp = new_objtree_VA("%{name}I", 0); + rolname = NameStr(((Form_pg_authid) GETSTRUCT(roltup))->rolname); + append_string_object(tmp, "name", pstrdup(rolname)); + ReleaseSysCache(roltup); + } + list = lappend(list, new_object_object(tmp)); + } + append_array_object(grantStmt, "grantees", list); + + /* the wording of the grant option is variable ... */ + if (istmt->is_grant) + append_string_object(grantStmt, "grant_option", + istmt->grant_option ? "WITH GRANT OPTION" : ""); + else + append_string_object(grantStmt, "grant_option", + istmt->grant_option ? "GRANT OPTION FOR" : ""); + + if (!istmt->is_grant) + { + if (istmt->behavior == DROP_CASCADE) + append_string_object(grantStmt, "cascade", "CASCADE"); + else + append_string_object(grantStmt, "cascade", ""); + } + + return grantStmt; +} + +static ObjTree * deparse_AlterTableStmt(StashedCommand *cmd) { ObjTree *alterTableStmt; @@ -4766,6 +4979,9 @@ deparse_utility_command(StashedCommand *cmd) case SCT_AlterTable: tree = deparse_AlterTableStmt(cmd); break; + case SCT_Grant: + tree = deparse_GrantStmt(cmd); + break; default: elog(ERROR, "unexpected deparse node type %d", cmd->type); } diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index c09915d..92bccf5 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -781,6 +781,7 @@ standard_ProcessUtility(Node *parsetree, else ExecuteGrantStmt((GrantStmt *) parsetree); } + break; case T_DropStmt: { diff --git a/src/include/commands/event_trigger.h b/src/include/commands/event_trigger.h index cdce9e1..60b590b 100644 --- a/src/include/commands/event_trigger.h +++ b/src/include/commands/event_trigger.h @@ -17,6 +17,7 @@ #include "catalog/objectaddress.h" #include "catalog/pg_event_trigger.h" #include "nodes/parsenodes.h" +#include "utils/aclchk.h" typedef struct EventTriggerData { @@ -62,6 +63,7 @@ extern void EventTriggerSQLDropAddObject(const ObjectAddress *object, extern void EventTriggerStashCommand(Oid objectId, uint32 objectSubId, ObjectType objtype, Oid secondaryOid, Node *parsetree); +extern void EventTriggerStashGrant(InternalGrant *istmt); extern void EventTriggerComplexCmdStart(Node *parsetree, ObjectType objtype); extern void EventTriggerComplexCmdSetOid(Oid objectId); extern void EventTriggerRecordSubcmd(Node *subcmd, Oid relid, diff --git a/src/include/tcop/deparse_utility.h b/src/include/tcop/deparse_utility.h index 8ebbf34..c364603 100644 --- a/src/include/tcop/deparse_utility.h +++ b/src/include/tcop/deparse_utility.h @@ -14,6 +14,8 @@ #include "access/attnum.h" #include "nodes/nodes.h" +#include "utils/aclchk.h" + /* * Support for keeping track of a command to deparse. @@ -26,7 +28,8 @@ typedef enum StashedCommandType { SCT_Simple, - SCT_AlterTable + SCT_AlterTable, + SCT_Grant } StashedCommandType; /* @@ -61,6 +64,12 @@ typedef struct StashedCommand ObjectType objtype; List *subcmds; } alterTable; + + struct GrantCommand + { + InternalGrant *istmt; + const char *type; + } grant; } d; } StashedCommand; diff --git a/src/include/utils/aclchk.h b/src/include/utils/aclchk.h new file mode 100644 index 0000000..1ca7095 --- /dev/null +++ b/src/include/utils/aclchk.h @@ -0,0 +1,45 @@ +/*------------------------------------------------------------------------- + * + * aclchk.h + * + * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/utils/aclchk.h + * + *------------------------------------------------------------------------- + */ +#ifndef ACLCHK_H +#define ACLCHK_H + +#include "nodes/parsenodes.h" +#include "nodes/pg_list.h" + +/* + * The information about one Grant/Revoke statement, in internal format: object + * and grantees names have been turned into Oids, the privilege list is an + * AclMode bitmask. If 'privileges' is ACL_NO_RIGHTS (the 0 value) and + * all_privs is true, 'privileges' will be internally set to the right kind of + * ACL_ALL_RIGHTS_*, depending on the object type (NB - this will modify the + * InternalGrant struct!) + * + * Note: 'all_privs' and 'privileges' represent object-level privileges only. + * There might also be column-level privilege specifications, which are + * represented in col_privs (this is a list of untransformed AccessPriv nodes). + * Column privileges are only valid for objtype ACL_OBJECT_RELATION. + */ +typedef struct +{ + bool is_grant; + GrantObjectType objtype; + List *objects; + bool all_privs; + AclMode privileges; + List *col_privs; + List *grantees; + bool grant_option; + DropBehavior behavior; +} InternalGrant; + + +#endif /* ACLCHK_H */ -- 2.1.4 --gatW/ieO32f1wygP Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="0027-deparse-Support-ALTER-FUNCTION.patch" From 5e6d387d583c9f434ed8e58107043b5f1e2f9e89 Mon Sep 17 00:00:00 2001 From: Petr Jelinek Date: Fri, 8 Aug 2014 14:55:47 +0200 Subject: [PATCH 27/42] deparse: Support ALTER FUNCTION --- src/backend/tcop/deparse_utility.c | 109 ++++++++++++++++++++++++++++++++++++- 1 file changed, 108 insertions(+), 1 deletion(-) diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c index a432a38..6110827 100644 --- a/src/backend/tcop/deparse_utility.c +++ b/src/backend/tcop/deparse_utility.c @@ -2957,6 +2957,113 @@ deparse_CreateFunction(Oid objectId, Node *parsetree) } /* + * deparse_AlterFunctionStmt + * Deparse a AlterFunctionStmt (ALTER FUNCTION) + * + * Given a function OID and the parsetree that created it, return the JSON + * blob representing the alter command. + * + * XXX this is missing the per-function custom-GUC thing. + */ +static ObjTree * +deparse_AlterFunction(Oid objectId, Node *parsetree) +{ + AlterFunctionStmt *node = (AlterFunctionStmt *) parsetree; + ObjTree *alterFunc; + ObjTree *sign; + HeapTuple procTup; + Form_pg_proc procForm; + List *params; + List *elems = NIL; + ListCell *cell; + int i; + + /* get the pg_proc tuple */ + procTup = SearchSysCache1(PROCOID, objectId); + if (!HeapTupleIsValid(procTup)) + elog(ERROR, "cache lookup failure for function with OID %u", + objectId); + procForm = (Form_pg_proc) GETSTRUCT(procTup); + + alterFunc = new_objtree_VA("ALTER FUNCTION %{signature}s %{definition: }s", 0); + + sign = new_objtree_VA("%{identity}D(%{arguments:, }s)", 0); + + params = NIL; + + /* + * ALTER FUNCTION does not change signature so we can use catalog + * to get input type Oids. + */ + for (i = 0; i < procForm->pronargs; i++) + { + ObjTree *tmp = new_objtree_VA("%{type}T", 0); + + append_object_object(tmp, "type", + new_objtree_for_type(procForm->proargtypes.values[i], -1)); + params = lappend(params, new_object_object(tmp)); + } + + append_array_object(sign, "arguments", params); + append_object_object(sign, "identity", + new_objtree_for_qualname_id(ProcedureRelationId, + objectId)); + append_object_object(alterFunc, "signature", sign); + + foreach(cell, node->actions) + { + DefElem *defel = (DefElem *) lfirst(cell); + ObjTree *tmp = NULL; + + if (strcmp(defel->defname, "volatility") == 0) + { + tmp = new_objtree_VA(strVal(defel->arg), 0); + } + else if (strcmp(defel->defname, "strict") == 0) + { + tmp = new_objtree_VA(intVal(defel->arg) ? + "RETURNS NULL ON NULL INPUT" : + "CALLED ON NULL INPUT", 0); + } + else if (strcmp(defel->defname, "security") == 0) + { + tmp = new_objtree_VA(intVal(defel->arg) ? + "SECURITY DEFINER" : "SECURITY INVOKER", 0); + } + else if (strcmp(defel->defname, "leakproof") == 0) + { + tmp = new_objtree_VA(intVal(defel->arg) ? + "LEAKPROOF" : "", 0); + } + else if (strcmp(defel->defname, "cost") == 0) + { + tmp = new_objtree_VA("COST %{cost}n", 1, + "cost", ObjTypeFloat, + defGetNumeric(defel)); + } + else if (strcmp(defel->defname, "rows") == 0) + { + tmp = new_objtree_VA("ROWS %{rows}n", 0); + if (defGetNumeric(defel) == 0) + append_bool_object(tmp, "present", false); + else + append_float_object(tmp, "rows", + defGetNumeric(defel)); + } + else if (strcmp(defel->defname, "set") == 0) + elog(ERROR, "unimplemented deparse of ALTER FUNCTION SET"); + + elems = lappend(elems, new_object_object(tmp)); + } + + append_array_object(alterFunc, "definition", elems); + + ReleaseSysCache(procTup); + + return alterFunc; +} + +/* * Return the given object type as a string. */ static const char * @@ -4807,7 +4914,7 @@ deparse_simple_command(StashedCommand *cmd) break; case T_AlterFunctionStmt: - command = NULL; + command = deparse_AlterFunction(objectId, parsetree); break; case T_RuleStmt: -- 2.1.4 --gatW/ieO32f1wygP Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="0028-deparse-support-COMMENT-ON.patch" From 7332f3e927b82748398ae4563d23751d0b229499 Mon Sep 17 00:00:00 2001 From: Alvaro Herrera Date: Thu, 25 Sep 2014 15:45:04 -0300 Subject: [PATCH 28/42] deparse: support COMMENT ON --- src/backend/tcop/deparse_utility.c | 93 +++++++++++++++++++++++++++++++++++++- 1 file changed, 92 insertions(+), 1 deletion(-) diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c index 6110827..9d3ebba 100644 --- a/src/backend/tcop/deparse_utility.c +++ b/src/backend/tcop/deparse_utility.c @@ -4026,6 +4026,96 @@ deparse_AlterOwnerStmt(Oid objectId, Node *parsetree) return ownerStmt; } +/* + * Deparse a CommentStmt when it pertains to a constraint. + */ +static ObjTree * +deparse_CommentOnConstraintSmt(Oid objectId, Node *parsetree) +{ + CommentStmt *node = (CommentStmt *) parsetree; + ObjTree *comment; + HeapTuple constrTup; + Form_pg_constraint constrForm; + char *fmt; + ObjectAddress addr; + + Assert(node->objtype == OBJECT_TABCONSTRAINT || node->objtype == OBJECT_DOMCONSTRAINT); + + if (node->comment) + fmt = psprintf("COMMENT ON CONSTRAINT %%{identity}s ON %s%%{parentobj}s IS %%{comment}L", + node->objtype == OBJECT_TABCONSTRAINT ? "" : "DOMAIN "); + else + fmt = psprintf("COMMENT ON CONSTRAINT %%{identity}s ON %s%%{parentobj}s IS NULL", + node->objtype == OBJECT_TABCONSTRAINT ? "" : "DOMAIN "); + comment = new_objtree_VA(fmt, 0); + if (node->comment) + append_string_object(comment, "comment", node->comment); + + constrTup = SearchSysCache1(CONSTROID, objectId); + if (!HeapTupleIsValid(constrTup)) + elog(ERROR, "cache lookup failed for constraint %u", objectId); + constrForm = (Form_pg_constraint) GETSTRUCT(constrTup); + + append_string_object(comment, "identity", pstrdup(NameStr(constrForm->conname))); + + if (OidIsValid(constrForm->conrelid)) + { + addr.classId = RelationRelationId; + addr.objectId = constrForm->conrelid; + addr.objectSubId = 0; + } + else + { + addr.classId = TypeRelationId; + addr.objectId = constrForm->contypid; + addr.objectSubId = 0; + } + + append_string_object(comment, "parentobj", + getObjectIdentity(&addr)); + + ReleaseSysCache(constrTup); + + return comment; +} + +static ObjTree * +deparse_CommentStmt(Oid objectId, Oid objectSubId, Node *parsetree) +{ + CommentStmt *node = (CommentStmt *) parsetree; + ObjTree *comment; + ObjectAddress addr; + char *fmt; + + /* + * Constraints are sufficiently different that it is easier to handle them + * separately. + */ + if (node->objtype == OBJECT_DOMCONSTRAINT || + node->objtype == OBJECT_TABCONSTRAINT) + return deparse_CommentOnConstraintSmt(objectId, parsetree); + + if (node->comment) + fmt = psprintf("COMMENT ON %s %%{identity}s IS %%{comment}L", + stringify_objtype(node->objtype)); + else + fmt = psprintf("COMMENT ON %s %%{identity}s IS NULL", + stringify_objtype(node->objtype)); + + comment = new_objtree_VA(fmt, 0); + if (node->comment) + append_string_object(comment, "comment", node->comment); + + addr.classId = get_objtype_catalog_oid(node->objtype); + addr.objectId = objectId; + addr.objectSubId = objectSubId; + + append_string_object(comment, "identity", + getObjectIdentity(&addr)); + + return comment; +} + static ObjTree * deparse_CreateConversion(Oid objectId, Node *parsetree) { @@ -4996,7 +5086,8 @@ deparse_simple_command(StashedCommand *cmd) break; case T_CommentStmt: - command = NULL; + command = deparse_CommentStmt(objectId, cmd->d.simple.objectSubId, + parsetree); break; case T_GrantStmt: -- 2.1.4 --gatW/ieO32f1wygP Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="0029-deparse-support-SECURITY-LABEL.patch" From b8a3640af3f06abb68a1e0b5a286a9ff7368a782 Mon Sep 17 00:00:00 2001 From: Alvaro Herrera Date: Tue, 10 Feb 2015 17:35:51 -0300 Subject: [PATCH 29/42] deparse: support SECURITY LABEL --- src/backend/tcop/deparse_utility.c | 38 +++++++++++++++++++++++++++++++++++++- src/backend/tcop/utility.c | 3 ++- 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c index 9d3ebba..bd0a4df 100644 --- a/src/backend/tcop/deparse_utility.c +++ b/src/backend/tcop/deparse_utility.c @@ -4117,6 +4117,41 @@ deparse_CommentStmt(Oid objectId, Oid objectSubId, Node *parsetree) } static ObjTree * +deparse_SecLabelStmt(Oid objectId, Oid objectSubId, Node *parsetree) +{ + SecLabelStmt *node = (SecLabelStmt *) parsetree; + ObjTree *label; + ObjectAddress addr; + char *fmt; + + if (node->label) + { + fmt = psprintf("SECURITY LABEL FOR %%{provider}s ON %s %%{identity}s IS %%{label}L", + stringify_objtype(node->objtype)); + label = new_objtree_VA(fmt, 0); + + append_string_object(label, "label", node->label); + } + else + { + fmt = psprintf("SECURITY LABEL FOR %%{provider}s ON %s %%{identity}s IS NULL", + stringify_objtype(node->objtype)); + label = new_objtree_VA(fmt, 0); + } + + append_string_object(label, "provider", node->provider); + + addr.classId = get_objtype_catalog_oid(node->objtype); + addr.objectId = objectId; + addr.objectSubId = objectSubId; + + append_string_object(label, "identity", + getObjectIdentity(&addr)); + + return label; +} + +static ObjTree * deparse_CreateConversion(Oid objectId, Node *parsetree) { HeapTuple conTup; @@ -5113,7 +5148,8 @@ deparse_simple_command(StashedCommand *cmd) break; case T_SecLabelStmt: - elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree)); + command = deparse_SecLabelStmt(objectId, cmd->d.simple.objectSubId, + parsetree); break; default: diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 92bccf5..bec5231 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1486,7 +1486,8 @@ ProcessUtilitySlow(Node *parsetree, { SecLabelStmt *stmt = (SecLabelStmt *) parsetree; - ExecSecLabelStmt(stmt); + objectId = ExecSecLabelStmt(stmt); + EventTriggerStashCommand(objectId, 0, stmt->objtype, InvalidOid, parsetree); } break; -- 2.1.4 --gatW/ieO32f1wygP Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="0030-deparse-support-ALTER-DOMAIN.patch" From d29332d1e91f9016278abfda90372e8118e47056 Mon Sep 17 00:00:00 2001 From: Alvaro Herrera Date: Mon, 9 Feb 2015 15:38:07 -0300 Subject: [PATCH 30/42] deparse: support ALTER DOMAIN --- src/backend/commands/typecmds.c | 70 +++++++++++++++++--------------- src/backend/tcop/deparse_utility.c | 83 +++++++++++++++++++++++++++++++++++++- src/backend/tcop/utility.c | 5 ++- src/backend/utils/adt/ruleutils.c | 20 +++++++++ src/include/commands/typecmds.h | 2 +- src/include/utils/ruleutils.h | 2 + 6 files changed, 146 insertions(+), 36 deletions(-) diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c index 0564535..0fd225c 100644 --- a/src/backend/commands/typecmds.c +++ b/src/backend/commands/typecmds.c @@ -108,7 +108,7 @@ static void checkEnumOwner(HeapTuple tup); static char *domainAddConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid, int typMod, Constraint *constr, - char *domainName); + char *domainName, Oid *constrOid); /* @@ -1075,7 +1075,7 @@ DefineDomain(CreateDomainStmt *stmt) case CONSTR_CHECK: domainAddConstraint(domainoid, domainNamespace, basetypeoid, basetypeMod, - constr, domainName); + constr, domainName, NULL); break; /* Other constraint types were fully processed above */ @@ -2462,7 +2462,7 @@ AlterDomainDropConstraint(List *names, const char *constrName, * Implements the ALTER DOMAIN .. ADD CONSTRAINT statement. */ Oid -AlterDomainAddConstraint(List *names, Node *newConstraint) +AlterDomainAddConstraint(List *names, Node *newConstraint, Oid *constrOid) { TypeName *typename; Oid domainoid; @@ -2546,7 +2546,7 @@ AlterDomainAddConstraint(List *names, Node *newConstraint) ccbin = domainAddConstraint(domainoid, typTup->typnamespace, typTup->typbasetype, typTup->typtypmod, - constr, NameStr(typTup->typname)); + constr, NameStr(typTup->typname), constrOid); /* * If requested to validate the constraint, test all values stored in the @@ -2960,13 +2960,14 @@ checkDomainOwner(HeapTuple tup) static char * domainAddConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid, int typMod, Constraint *constr, - char *domainName) + char *domainName, Oid *constrOid) { Node *expr; char *ccsrc; char *ccbin; ParseState *pstate; CoerceToDomainValue *domVal; + Oid ccoid; /* * Assign or validate constraint name @@ -3045,34 +3046,37 @@ domainAddConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid, /* * Store the constraint in pg_constraint */ - CreateConstraintEntry(constr->conname, /* Constraint Name */ - domainNamespace, /* namespace */ - CONSTRAINT_CHECK, /* Constraint Type */ - false, /* Is Deferrable */ - false, /* Is Deferred */ - !constr->skip_validation, /* Is Validated */ - InvalidOid, /* not a relation constraint */ - NULL, - 0, - domainOid, /* domain constraint */ - InvalidOid, /* no associated index */ - InvalidOid, /* Foreign key fields */ - NULL, - NULL, - NULL, - NULL, - 0, - ' ', - ' ', - ' ', - NULL, /* not an exclusion constraint */ - expr, /* Tree form of check constraint */ - ccbin, /* Binary form of check constraint */ - ccsrc, /* Source form of check constraint */ - true, /* is local */ - 0, /* inhcount */ - false, /* connoinherit */ - false); /* is_internal */ + ccoid = + CreateConstraintEntry(constr->conname, /* Constraint Name */ + domainNamespace, /* namespace */ + CONSTRAINT_CHECK, /* Constraint Type */ + false, /* Is Deferrable */ + false, /* Is Deferred */ + !constr->skip_validation, /* Is Validated */ + InvalidOid, /* not a relation constraint */ + NULL, + 0, + domainOid, /* domain constraint */ + InvalidOid, /* no associated index */ + InvalidOid, /* Foreign key fields */ + NULL, + NULL, + NULL, + NULL, + 0, + ' ', + ' ', + ' ', + NULL, /* not an exclusion constraint */ + expr, /* Tree form of check constraint */ + ccbin, /* Binary form of check constraint */ + ccsrc, /* Source form of check constraint */ + true, /* is local */ + 0, /* inhcount */ + false, /* connoinherit */ + false); /* is_internal */ + if (constrOid) + *constrOid = ccoid; /* * Return the compiled constraint expression so the calling routine can diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c index bd0a4df..b5b6727 100644 --- a/src/backend/tcop/deparse_utility.c +++ b/src/backend/tcop/deparse_utility.c @@ -1602,6 +1602,86 @@ deparse_CreateExtensionStmt(Oid objectId, Node *parsetree) } static ObjTree * +deparse_AlterDomainStmt(Oid objectId, Node *parsetree, Oid secondaryOid) +{ + AlterDomainStmt *node = (AlterDomainStmt *) parsetree; + HeapTuple domTup; + Form_pg_type domForm; + ObjTree *alterDom; + char *fmt; + + /* ALTER DOMAIN DROP CONSTRAINT is handled by the DROP support code */ + if (node->subtype == 'X') + return NULL; + + domTup = SearchSysCache1(TYPEOID, objectId); + if (!HeapTupleIsValid(domTup)) + elog(ERROR, "cache lookup failed for domain with OID %u", + objectId); + domForm = (Form_pg_type) GETSTRUCT(domTup); + + switch (node->subtype) + { + case 'T': + /* SET DEFAULT / DROP DEFAULT */ + if (node->def == NULL) + fmt = "ALTER DOMAIN %{identity}D DROP DEFAULT"; + else + fmt = "ALTER DOMAIN %{identity}D SET DEFAULT %{default}s"; + break; + case 'N': + /* DROP NOT NULL */ + fmt = "ALTER DOMAIN %{identity}D DROP NOT NULL"; + break; + case 'O': + /* SET NOT NULL */ + fmt = "ALTER DOMAIN %{identity}D SET NOT NULL"; + break; + case 'C': + /* ADD CONSTRAINT. Only CHECK constraints are supported by domains */ + fmt = "ALTER DOMAIN %{identity}D ADD CONSTRAINT %{constraint_name}I %{definition}s"; + break; + case 'V': + /* VALIDATE CONSTRAINT */ + fmt = "ALTER DOMAIN %{identity}D VALIDATE CONSTRAINT %{constraint_name}I"; + break; + default: + elog(ERROR, "invalid subtype %c", node->subtype); + } + + alterDom = new_objtree_VA(fmt, 0); + append_object_object(alterDom, "identity", + new_objtree_for_qualname(domForm->typnamespace, + NameStr(domForm->typname))); + + /* + * Process subtype-specific options. Validating a constraint + * requires its name ... + */ + if (node->subtype == 'V') + append_string_object(alterDom, "constraint_name", node->name); + + /* ... a new constraint has a name and definition ... */ + if (node->subtype == 'C') + { + append_string_object(alterDom, "definition", + pg_get_constraintdef_string(secondaryOid, false)); + /* can't rely on node->name here; might not be defined */ + append_string_object(alterDom, "constraint_name", + get_constraint_name(secondaryOid)); + } + + /* ... and setting a default has a definition only. */ + if (node->subtype == 'T' && node->def != NULL) + append_string_object(alterDom, "default", DomainGetDefault(domTup)); + + /* done */ + ReleaseSysCache(domTup); + + return alterDom; +} + +static ObjTree * deparse_AlterExtensionStmt(Oid objectId, Node *parsetree) { AlterExtensionStmt *node = (AlterExtensionStmt *) parsetree; @@ -4956,7 +5036,8 @@ deparse_simple_command(StashedCommand *cmd) break; case T_AlterDomainStmt: - elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree)); + command = deparse_AlterDomainStmt(objectId, parsetree, + cmd->d.simple.secondaryOid); break; /* other local objects */ diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index bec5231..20d6565 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1075,6 +1075,8 @@ ProcessUtilitySlow(Node *parsetree, { AlterDomainStmt *stmt = (AlterDomainStmt *) parsetree; + secondaryOid = InvalidOid; + /* * Some or all of these functions are recursive to cover * inherited things, so permission checks are done there. @@ -1104,7 +1106,8 @@ ProcessUtilitySlow(Node *parsetree, case 'C': /* ADD CONSTRAINT */ objectId = AlterDomainAddConstraint(stmt->typeName, - stmt->def); + stmt->def, + &secondaryOid); break; case 'X': /* DROP CONSTRAINT */ objectId = diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index c281c38..2c4786f 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -9700,6 +9700,26 @@ RelationGetColumnDefault(Relation rel, AttrNumber attno, List *dpcontext) return defstr; } +char * +DomainGetDefault(HeapTuple domTup) +{ + Datum def; + Node *defval; + char *defstr; + bool isnull; + + def = SysCacheGetAttr(TYPEOID, domTup, Anum_pg_type_typdefaultbin, + &isnull); + if (isnull) + elog(ERROR, "domain \"%s\" does not have a default value", + NameStr(((Form_pg_type) GETSTRUCT(domTup))->typname)); + defval = stringToNode(TextDatumGetCString(def)); + defstr = deparse_expression_pretty(defval, NULL /* dpcontext? */, + false, false, 0, 0); + + return defstr; +} + /* * Return the defaults values of arguments to a function, as a list of * deparsed expressions. diff --git a/src/include/commands/typecmds.h b/src/include/commands/typecmds.h index e18a714..b38584c 100644 --- a/src/include/commands/typecmds.h +++ b/src/include/commands/typecmds.h @@ -32,7 +32,7 @@ extern Oid AssignTypeArrayOid(void); extern Oid AlterDomainDefault(List *names, Node *defaultRaw); extern Oid AlterDomainNotNull(List *names, bool notNull); -extern Oid AlterDomainAddConstraint(List *names, Node *constr); +extern Oid AlterDomainAddConstraint(List *names, Node *constr, Oid *constrOid); extern Oid AlterDomainValidateConstraint(List *names, char *constrName); extern Oid AlterDomainDropConstraint(List *names, const char *constrName, DropBehavior behavior, bool missing_ok); diff --git a/src/include/utils/ruleutils.h b/src/include/utils/ruleutils.h index 91b61f9..7b22ea0 100644 --- a/src/include/utils/ruleutils.h +++ b/src/include/utils/ruleutils.h @@ -48,4 +48,6 @@ extern List *FunctionGetDefaults(text *proargdefaults); extern char *RelationGetColumnDefault(Relation rel, AttrNumber attno, List *dpcontext); +extern char *DomainGetDefault(HeapTuple domTup); + #endif /* RULEUTILS_H */ -- 2.1.4 --gatW/ieO32f1wygP Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="0031-deparse-support-ALTER-.-SET-SCHEMA.patch" From 69674ea6ef2da763ccd27a3f408c0f5fe489fea1 Mon Sep 17 00:00:00 2001 From: Alvaro Herrera Date: Mon, 9 Feb 2015 19:17:21 -0300 Subject: [PATCH 31/42] deparse: support ALTER ... SET SCHEMA --- src/backend/commands/alter.c | 16 ++++++++------ src/backend/commands/extension.c | 6 +++++- src/backend/commands/tablecmds.c | 5 ++++- src/backend/commands/typecmds.c | 9 ++++++-- src/backend/tcop/deparse_utility.c | 44 +++++++++++++++++++++++++++++++++++++- src/backend/tcop/utility.c | 7 +++--- src/include/commands/alter.h | 3 ++- src/include/commands/extension.h | 3 ++- src/include/commands/tablecmds.h | 2 +- src/include/commands/typecmds.h | 3 ++- 10 files changed, 80 insertions(+), 18 deletions(-) diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c index 4150104..943ddef 100644 --- a/src/backend/commands/alter.c +++ b/src/backend/commands/alter.c @@ -398,24 +398,24 @@ ExecRenameStmt(RenameStmt *stmt, int *objsubid) * type, the function appropriate to that type is executed. */ Oid -ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt) +ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt, Oid *oldschema) { switch (stmt->objectType) { case OBJECT_EXTENSION: - return AlterExtensionNamespace(stmt->object, stmt->newschema); + return AlterExtensionNamespace(stmt->object, stmt->newschema, oldschema); case OBJECT_FOREIGN_TABLE: case OBJECT_SEQUENCE: case OBJECT_TABLE: case OBJECT_VIEW: case OBJECT_MATVIEW: - return AlterTableNamespace(stmt); + return AlterTableNamespace(stmt, oldschema); case OBJECT_DOMAIN: case OBJECT_TYPE: return AlterTypeNamespace(stmt->object, stmt->newschema, - stmt->objectType); + stmt->objectType, oldschema); /* generic code path */ case OBJECT_AGGREGATE: @@ -435,6 +435,7 @@ ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt) Oid classId; Oid nspOid; ObjectAddress address; + Oid oldNspOid; address = get_object_address(stmt->objectType, stmt->object, @@ -447,10 +448,13 @@ ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt) catalog = heap_open(classId, RowExclusiveLock); nspOid = LookupCreationNamespace(stmt->newschema); - AlterObjectNamespace_internal(catalog, address.objectId, - nspOid); + oldNspOid = AlterObjectNamespace_internal(catalog, address.objectId, + nspOid); heap_close(catalog, RowExclusiveLock); + if (oldschema) + *oldschema = oldNspOid; + return address.objectId; } break; diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c index 3b95552..14be4cd 100644 --- a/src/backend/commands/extension.c +++ b/src/backend/commands/extension.c @@ -2400,7 +2400,7 @@ extension_config_remove(Oid extensionoid, Oid tableoid) * Execute ALTER EXTENSION SET SCHEMA */ Oid -AlterExtensionNamespace(List *names, const char *newschema) +AlterExtensionNamespace(List *names, const char *newschema, Oid *oldschema) { char *extensionName; Oid extensionOid; @@ -2557,6 +2557,10 @@ AlterExtensionNamespace(List *names, const char *newschema) get_namespace_name(oldNspOid)))); } + /* report old schema, if caller wants it */ + if (oldschema) + *oldschema = oldNspOid; + systable_endscan(depScan); relation_close(depRel, AccessShareLock); diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 377e5dd..7634a7d 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -11057,7 +11057,7 @@ ATPrepChangePersistence(Relation rel, bool toLogged) * Execute ALTER TABLE SET SCHEMA */ Oid -AlterTableNamespace(AlterObjectSchemaStmt *stmt) +AlterTableNamespace(AlterObjectSchemaStmt *stmt, Oid *oldschema) { Relation rel; Oid relid; @@ -11109,6 +11109,9 @@ AlterTableNamespace(AlterObjectSchemaStmt *stmt) AlterTableNamespaceInternal(rel, oldNspOid, nspOid, objsMoved); free_object_addresses(objsMoved); + if (oldschema) + *oldschema = oldNspOid; + /* close rel, but keep lock until commit */ relation_close(rel, NoLock); diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c index 0fd225c..8a1f6b5 100644 --- a/src/backend/commands/typecmds.c +++ b/src/backend/commands/typecmds.c @@ -3508,11 +3508,13 @@ AlterTypeOwnerInternal(Oid typeOid, Oid newOwnerId, * Execute ALTER TYPE SET SCHEMA */ Oid -AlterTypeNamespace(List *names, const char *newschema, ObjectType objecttype) +AlterTypeNamespace(List *names, const char *newschema, ObjectType objecttype, + Oid *oldschema) { TypeName *typename; Oid typeOid; Oid nspOid; + Oid oldNspOid; ObjectAddresses *objsMoved; /* Make a TypeName so we can use standard type lookup machinery */ @@ -3530,9 +3532,12 @@ AlterTypeNamespace(List *names, const char *newschema, ObjectType objecttype) nspOid = LookupCreationNamespace(newschema); objsMoved = new_object_addresses(); - AlterTypeNamespace_oid(typeOid, nspOid, objsMoved); + oldNspOid = AlterTypeNamespace_oid(typeOid, nspOid, objsMoved); free_object_addresses(objsMoved); + if (oldschema) + *oldschema = oldNspOid; + return typeOid; } diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c index b5b6727..a813f94 100644 --- a/src/backend/tcop/deparse_utility.c +++ b/src/backend/tcop/deparse_utility.c @@ -4084,6 +4084,47 @@ deparse_AlterEnumStmt(Oid objectId, Node *parsetree) } static ObjTree * +deparse_AlterObjectSchemaStmt(Oid objectId, Node *parsetree, Oid oldschema) +{ + AlterObjectSchemaStmt *node = (AlterObjectSchemaStmt *) parsetree; + ObjTree *alterStmt; + ObjectAddress addr; + char *fmt; + char *identity; + char *newschema; + char *oldschname; + char *ident; + + newschema = node->newschema; + + fmt = psprintf("ALTER %s %%{identity}s SET SCHEMA %%{newschema}I", + stringify_objtype(node->objectType)); + alterStmt = new_objtree_VA(fmt, 0); + append_string_object(alterStmt, "newschema", newschema); + + /* + * Since the command has already taken place from the point of view of + * catalogs, getObjectIdentity returns the object name with the already + * changed schema. The output of our deparsing must return the original + * schema name however, so we chop the schema name off the identity string + * and then prepend the quoted schema name. + */ + addr.classId = get_objtype_catalog_oid(node->objectType); + addr.objectId = objectId; + addr.objectSubId = 0; + identity = getObjectIdentity(&addr); + oldschname = get_namespace_name(oldschema); + if (!oldschname) + elog(ERROR, "cache lookup failed for schema with OID %u", oldschema); + ident = psprintf("%s%s", + quote_identifier(oldschname), + identity + strlen(quote_identifier(newschema))); + append_string_object(alterStmt, "identity", ident); + + return alterStmt; +} + +static ObjTree * deparse_AlterOwnerStmt(Oid objectId, Node *parsetree) { AlterOwnerStmt *node = (AlterOwnerStmt *) parsetree; @@ -5194,7 +5235,8 @@ deparse_simple_command(StashedCommand *cmd) break; case T_AlterObjectSchemaStmt: - elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree)); + command = deparse_AlterObjectSchemaStmt(objectId, parsetree, + cmd->d.simple.secondaryOid); break; case T_AlterOwnerStmt: diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 20d6565..438ee9b 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -818,7 +818,7 @@ standard_ProcessUtility(Node *parsetree, context, params, dest, completionTag); else - ExecAlterObjectSchemaStmt(stmt); + ExecAlterObjectSchemaStmt(stmt, NULL); } break; @@ -1438,10 +1438,11 @@ ProcessUtilitySlow(Node *parsetree, break; case T_AlterObjectSchemaStmt: - objectId = ExecAlterObjectSchemaStmt((AlterObjectSchemaStmt *) parsetree); + objectId = ExecAlterObjectSchemaStmt((AlterObjectSchemaStmt *) parsetree, + &secondaryOid); EventTriggerStashCommand(objectId, 0, ((AlterObjectSchemaStmt *) parsetree)->objectType, - parsetree); + secondaryOid, parsetree); break; case T_AlterOwnerStmt: diff --git a/src/include/commands/alter.h b/src/include/commands/alter.h index 0382037..ecba1d7 100644 --- a/src/include/commands/alter.h +++ b/src/include/commands/alter.h @@ -20,7 +20,8 @@ extern Oid ExecRenameStmt(RenameStmt *stmt, int *objsubid); -extern Oid ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt); +extern Oid ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt, + Oid *oldschema); extern Oid AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid, ObjectAddresses *objsMoved); diff --git a/src/include/commands/extension.h b/src/include/commands/extension.h index 4259072..f678950 100644 --- a/src/include/commands/extension.h +++ b/src/include/commands/extension.h @@ -43,7 +43,8 @@ extern Oid ExecAlterExtensionContentsStmt(AlterExtensionContentsStmt *stmt); extern Oid get_extension_oid(const char *extname, bool missing_ok); extern char *get_extension_name(Oid ext_oid); -extern Oid AlterExtensionNamespace(List *names, const char *newschema); +extern Oid AlterExtensionNamespace(List *names, const char *newschema, + Oid *oldschema); extern void AlterExtensionOwner_oid(Oid extensionOid, Oid newOwnerId); diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h index d56f393..fa77235 100644 --- a/src/include/commands/tablecmds.h +++ b/src/include/commands/tablecmds.h @@ -37,7 +37,7 @@ extern void AlterTableInternal(Oid relid, List *cmds, bool recurse); extern Oid AlterTableMoveAll(AlterTableMoveAllStmt *stmt); -extern Oid AlterTableNamespace(AlterObjectSchemaStmt *stmt); +extern Oid AlterTableNamespace(AlterObjectSchemaStmt *stmt, Oid *oldschema); extern void AlterTableNamespaceInternal(Relation rel, Oid oldNspOid, Oid nspOid, ObjectAddresses *objsMoved); diff --git a/src/include/commands/typecmds.h b/src/include/commands/typecmds.h index b38584c..ecc4551 100644 --- a/src/include/commands/typecmds.h +++ b/src/include/commands/typecmds.h @@ -45,7 +45,8 @@ extern Oid RenameType(RenameStmt *stmt); extern Oid AlterTypeOwner(List *names, Oid newOwnerId, ObjectType objecttype); extern void AlterTypeOwnerInternal(Oid typeOid, Oid newOwnerId, bool hasDependEntry); -extern Oid AlterTypeNamespace(List *names, const char *newschema, ObjectType objecttype); +extern Oid AlterTypeNamespace(List *names, const char *newschema, + ObjectType objecttype, Oid *oldschema); extern Oid AlterTypeNamespace_oid(Oid typeOid, Oid nspOid, ObjectAddresses *objsMoved); extern Oid AlterTypeNamespaceInternal(Oid typeOid, Oid nspOid, bool isImplicitArray, -- 2.1.4 --gatW/ieO32f1wygP Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="0032-deparse-support-ALTER-EXTENSION-ADD-DROP.patch" From aefc63f845900445d04c60f4ce73d8f1a92c9d97 Mon Sep 17 00:00:00 2001 From: Alvaro Herrera Date: Mon, 9 Feb 2015 20:41:51 -0300 Subject: [PATCH 32/42] deparse: support ALTER EXTENSION ADD/DROP --- src/backend/commands/extension.c | 6 +++++- src/backend/tcop/deparse_utility.c | 27 ++++++++++++++++++++++++++- src/backend/tcop/utility.c | 5 +++-- src/include/commands/extension.h | 3 ++- 4 files changed, 36 insertions(+), 5 deletions(-) diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c index 14be4cd..4b28a8d 100644 --- a/src/backend/commands/extension.c +++ b/src/backend/commands/extension.c @@ -2885,7 +2885,7 @@ ApplyExtensionUpdates(Oid extensionOid, * Execute ALTER EXTENSION ADD/DROP */ Oid -ExecAlterExtensionContentsStmt(AlterExtensionContentsStmt *stmt) +ExecAlterExtensionContentsStmt(AlterExtensionContentsStmt *stmt, Oid *objectId) { ObjectAddress extension; ObjectAddress object; @@ -2910,6 +2910,10 @@ ExecAlterExtensionContentsStmt(AlterExtensionContentsStmt *stmt) object = get_object_address(stmt->objtype, stmt->objname, stmt->objargs, &relation, ShareUpdateExclusiveLock, false); + Assert(object.objectSubId == 0); + if (objectId) + *objectId = object.objectId; + /* Permission check: must own target object, too */ check_object_ownership(GetUserId(), stmt->objtype, object, stmt->objname, stmt->objargs, relation); diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c index a813f94..cefea83 100644 --- a/src/backend/tcop/deparse_utility.c +++ b/src/backend/tcop/deparse_utility.c @@ -1682,6 +1682,30 @@ deparse_AlterDomainStmt(Oid objectId, Node *parsetree, Oid secondaryOid) } static ObjTree * +deparse_AlterExtensionContentsStmt(Oid objectId, Node *parsetree, Oid secondaryOid) +{ + AlterExtensionContentsStmt *node = (AlterExtensionContentsStmt *) parsetree; + ObjTree *stmt; + char *fmt; + ObjectAddress addr; + + Assert(node->action == +1 || node->action == -1); + + fmt = psprintf("ALTER EXTENSION %%{extidentity}I %s %s %%{objidentity}s", + node->action == +1 ? "ADD" : "DROP", + stringify_objtype(node->objtype)); + addr.classId = get_objtype_catalog_oid(node->objtype); + addr.objectId = secondaryOid; + addr.objectSubId = 0; + + stmt = new_objtree_VA(fmt, 2, "extidentity", ObjTypeString, node->extname, + "objidentity", ObjTypeString, + getObjectIdentity(&addr)); + + return stmt; +} + +static ObjTree * deparse_AlterExtensionStmt(Oid objectId, Node *parsetree) { AlterExtensionStmt *node = (AlterExtensionStmt *) parsetree; @@ -5099,7 +5123,8 @@ deparse_simple_command(StashedCommand *cmd) break; case T_AlterExtensionContentsStmt: - elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree)); + command = deparse_AlterExtensionContentsStmt(objectId, parsetree, + cmd->d.simple.secondaryOid); break; case T_CreateFdwStmt: diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 438ee9b..a06b901 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1248,8 +1248,9 @@ ProcessUtilitySlow(Node *parsetree, break; case T_AlterExtensionContentsStmt: - objectId = ExecAlterExtensionContentsStmt((AlterExtensionContentsStmt *) parsetree); - EventTriggerStashCommand(objectId, 0, OBJECT_EXTENSION, InvalidOid, parsetree); + objectId = ExecAlterExtensionContentsStmt((AlterExtensionContentsStmt *) parsetree, + &secondaryOid); + EventTriggerStashCommand(objectId, 0, OBJECT_EXTENSION, secondaryOid, parsetree); break; case T_CreateFdwStmt: diff --git a/src/include/commands/extension.h b/src/include/commands/extension.h index f678950..b3b73cc 100644 --- a/src/include/commands/extension.h +++ b/src/include/commands/extension.h @@ -38,7 +38,8 @@ extern Oid InsertExtensionTuple(const char *extName, Oid extOwner, extern Oid ExecAlterExtensionStmt(AlterExtensionStmt *stmt); -extern Oid ExecAlterExtensionContentsStmt(AlterExtensionContentsStmt *stmt); +extern Oid ExecAlterExtensionContentsStmt(AlterExtensionContentsStmt *stmt, + Oid *objectId); extern Oid get_extension_oid(const char *extname, bool missing_ok); extern char *get_extension_name(Oid ext_oid); -- 2.1.4 --gatW/ieO32f1wygP Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="0033-deparse-support-CREATE-CAST.patch" From 7ea75c6fc7d52ad3546258238482292e0382be76 Mon Sep 17 00:00:00 2001 From: Alvaro Herrera Date: Tue, 10 Feb 2015 14:09:51 -0300 Subject: [PATCH 33/42] deparse: support CREATE CAST --- src/backend/tcop/deparse_utility.c | 78 +++++++++++++++++++++++++++++++++++++- 1 file changed, 77 insertions(+), 1 deletion(-) diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c index cefea83..93df2da 100644 --- a/src/backend/tcop/deparse_utility.c +++ b/src/backend/tcop/deparse_utility.c @@ -33,6 +33,7 @@ #include "catalog/namespace.h" #include "catalog/pg_aggregate.h" #include "catalog/pg_authid.h" +#include "catalog/pg_cast.h" #include "catalog/pg_class.h" #include "catalog/pg_collation.h" #include "catalog/pg_constraint.h" @@ -4332,6 +4333,81 @@ deparse_CreateConversion(Oid objectId, Node *parsetree) } static ObjTree * +deparse_CreateCastStmt(Oid objectId, Node *parsetree) +{ + CreateCastStmt *node = (CreateCastStmt *) parsetree; + Relation castrel; + HeapTuple castTup; + Form_pg_cast castForm; + ObjTree *createCast; + char *context; + + castrel = heap_open(CastRelationId, AccessShareLock); + castTup = get_catalog_object_by_oid(castrel, objectId); + if (!HeapTupleIsValid(castTup)) + elog(ERROR, "cache lookup failed for cast with OID %u", objectId); + castForm = (Form_pg_cast) GETSTRUCT(castTup); + + createCast = new_objtree_VA("CREATE CAST (%{sourcetype}T AS %{targettype}T) %{mechanism}s %{context}s", + 2, "sourcetype", ObjTypeObject, + new_objtree_for_type(castForm->castsource, -1), + "targettype", ObjTypeObject, + new_objtree_for_type(castForm->casttarget, -1)); + + if (node->inout) + append_string_object(createCast, "mechanism", "WITH INOUT"); + else if (node->func == NULL) + append_string_object(createCast, "mechanism", "WITHOUT FUNCTION"); + else + { + ObjTree *tmp; + StringInfoData func; + HeapTuple funcTup; + Form_pg_proc funcForm; + int i; + + funcTup = SearchSysCache1(PROCOID, castForm->castfunc); + funcForm = (Form_pg_proc) GETSTRUCT(funcTup); + + initStringInfo(&func); + appendStringInfo(&func, "%s(", + quote_qualified_identifier(get_namespace_name(funcForm->pronamespace), + NameStr(funcForm->proname))); + for (i = 0; i < funcForm->pronargs; i++) + appendStringInfoString(&func, + format_type_be_qualified(funcForm->proargtypes.values[i])); + appendStringInfoChar(&func, ')'); + + tmp = new_objtree_VA("WITH FUNCTION %{castfunction}s", 1, + "castfunction", ObjTypeString, func.data); + append_object_object(createCast, "mechanism", tmp); + + ReleaseSysCache(funcTup); + } + + switch (node->context) + { + case COERCION_IMPLICIT: + context = "AS IMPLICIT"; + break; + case COERCION_ASSIGNMENT: + context = "AS ASSIGNMENT"; + break; + case COERCION_EXPLICIT: + context = ""; + break; + default: + elog(ERROR, "invalid coercion code %c", node->context); + return NULL; /* keep compiler quiet */ + } + append_string_object(createCast, "context", context); + + heap_close(castrel, AccessShareLock); + + return createCast; +} + +static ObjTree * deparse_CreateOpFamily(Oid objectId, Node *parsetree) { HeapTuple opfTup; @@ -5227,7 +5303,7 @@ deparse_simple_command(StashedCommand *cmd) break; case T_CreateCastStmt: - elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree)); + command = deparse_CreateCastStmt(objectId, parsetree); break; case T_CreateOpClassStmt: -- 2.1.4 --gatW/ieO32f1wygP Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="0034-deparse-support-CREATE-TABLE-AS.patch" From ad16e4e03443f673fcd946fdd47ad67aa8531542 Mon Sep 17 00:00:00 2001 From: Alvaro Herrera Date: Sat, 14 Feb 2015 15:30:04 -0300 Subject: [PATCH 34/42] deparse: support CREATE TABLE AS --- src/backend/tcop/deparse_utility.c | 142 ++++++++++++++++++++++++++++++++++++- src/backend/utils/adt/ruleutils.c | 15 ++++ src/include/utils/ruleutils.h | 1 + 3 files changed, 156 insertions(+), 2 deletions(-) diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c index 93df2da..2a0a2b9 100644 --- a/src/backend/tcop/deparse_utility.c +++ b/src/backend/tcop/deparse_utility.c @@ -4034,7 +4034,146 @@ deparse_RuleStmt(Oid objectId, Node *parsetree) return ruleStmt; } +static ObjTree * +deparse_CreateTableAsStmt(Oid objectId, Node *parsetree) +{ + CreateTableAsStmt *node = (CreateTableAsStmt *) parsetree; + ObjTree *createStmt; + ObjTree *tmp; + char *fmt; + List *list; + /* + * Reject unsupported case right away. + */ + if (((Query *) (node->query))->commandType == CMD_UTILITY) + elog(ERROR, "unimplemented deparse of CREATE TABLE AS EXECUTE"); + + /* + * Note that INSERT INTO is deparsed as CREATE TABLE AS. They are + * functionally equivalent synonyms so there is no harm from this. + */ + if (node->relkind == OBJECT_MATVIEW) + fmt = "CREATE %{persistence}s MATERIALIZED VIEW %{if_not_exists}s " + "%{identity}D %{columns}s %{with}s %{tablespace}s " + "AS %{query}s %{with_no_data}s"; + else + fmt = "CREATE %{persistence}s TABLE %{if_not_exists}s " + "%{identity}D %{columns}s %{with}s %{on_commit}s %{tablespace}s " + "AS %{query}s %{with_no_data}s"; + + createStmt = + new_objtree_VA(fmt, 2, + "persistence", ObjTypeString, + get_persistence_str(node->into->rel->relpersistence), + "if_not_exists", ObjTypeString, + node->if_not_exists ? "IF NOT EXISTS" : ""); + append_object_object(createStmt, "identity", + new_objtree_for_qualname_id(RelationRelationId, + objectId)); + + /* Add an ON COMMIT clause. CREATE MATERIALIZED VIEW doesn't have one */ + if (node->relkind == OBJECT_TABLE) + { + tmp = new_objtree_VA("ON COMMIT %{on_commit_value}s", 0); + switch (node->into->onCommit) + { + case ONCOMMIT_DROP: + append_string_object(tmp, "on_commit_value", "DROP"); + break; + + case ONCOMMIT_DELETE_ROWS: + append_string_object(tmp, "on_commit_value", "DELETE ROWS"); + break; + + case ONCOMMIT_PRESERVE_ROWS: + append_string_object(tmp, "on_commit_value", "PRESERVE ROWS"); + break; + + case ONCOMMIT_NOOP: + append_null_object(tmp, "on_commit_value"); + append_bool_object(tmp, "present", false); + break; + } + append_object_object(createStmt, "on_commit", tmp); + } + + /* add a TABLESPACE clause */ + tmp = new_objtree_VA("TABLESPACE %{tablespace}I", 0); + if (node->into->tableSpaceName) + append_string_object(tmp, "tablespace", node->into->tableSpaceName); + else + { + append_null_object(tmp, "tablespace"); + append_bool_object(tmp, "present", false); + } + append_object_object(createStmt, "tablespace", tmp); + + /* add a WITH NO DATA clause */ + tmp = new_objtree_VA("WITH NO DATA", 1, + "present", ObjTypeBool, + node->into->skipData ? true : false); + append_object_object(createStmt, "with_no_data", tmp); + + /* add the column list, if any */ + if (node->into->colNames == NIL) + tmp = new_objtree_VA("", 1, + "present", ObjTypeBool, false); + else + { + ListCell *cell; + + list = NIL; + foreach (cell, node->into->colNames) + list = lappend(list, new_string_object(strVal(lfirst(cell)))); + + tmp = new_objtree_VA("(%{columns:, }I)", 1, + "columns", ObjTypeArray, list); + } + append_object_object(createStmt, "columns", tmp); + + /* add the query */ + Assert(IsA(node->query, Query)); + append_string_object(createStmt, "query", + pg_get_createtableas_def((Query *) node->query)); + + /* add a WITH clause, but only if any options are set */ + tmp = new_objtree_VA("WITH (%{with_options:, }s)", 0); + if (node->into->options == NIL) + { + append_null_object(tmp, "with_options"); + append_bool_object(tmp, "present", false); + } + else + { + ListCell *cell; + + list = NIL; + foreach (cell, node->into->options) + { + DefElem *opt = (DefElem *) lfirst(cell); + char *defname; + char *value; + ObjTree *tmp2; + + if (opt->defnamespace) + defname = psprintf("%s.%s", opt->defnamespace, opt->defname); + else + defname = opt->defname; + + value = opt->arg ? defGetString(opt) : + defGetBoolean(opt) ? "TRUE" : "FALSE"; + tmp2 = new_objtree_VA("%{option}s=%{value}s", 2, + "option", ObjTypeString, defname, + "value", ObjTypeString, value); + list = lappend(list, new_object_object(tmp2)); + } + append_array_object(tmp, "with_options", list); + } + append_object_object(createStmt, "with", tmp); + + return createStmt; +} /* * deparse_CreateSchemaStmt @@ -5278,8 +5417,7 @@ deparse_simple_command(StashedCommand *cmd) break; case T_CreateTableAsStmt: - /* XXX handle at least the CREATE MATVIEW case? */ - elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree)); + command = deparse_CreateTableAsStmt(objectId, parsetree); break; case T_RefreshMatViewStmt: diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 2c4786f..ad7153d 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -750,6 +750,21 @@ pg_get_viewdef_worker(Oid viewoid, int prettyFlags, int wrapColumn) return buf.data; } +/* + * get_createtableas_def - Get the accompanying query for a CREATE TABLE AS + */ +char * +pg_get_createtableas_def(Query *query) +{ + StringInfoData buf; + + initStringInfo(&buf); + + get_query_def(query, &buf, NIL, NULL, 0, 0, 0); + + return buf.data; +} + /* ---------- * get_triggerdef - Get the definition of a trigger * ---------- diff --git a/src/include/utils/ruleutils.h b/src/include/utils/ruleutils.h index 7b22ea0..2c020e9 100644 --- a/src/include/utils/ruleutils.h +++ b/src/include/utils/ruleutils.h @@ -33,6 +33,7 @@ extern char *pg_get_constraintdef_string(Oid constraintId, bool fullCommand); extern void pg_get_ruledef_details(Datum ev_qual, Datum ev_action, char **whereClause, List **actions); extern char *pg_get_viewdef_internal(Oid viewoid); +extern char *pg_get_createtableas_def(Query *query); extern char *deparse_expression(Node *expr, List *dpcontext, bool forceprefix, bool showimplicit); -- 2.1.4 --gatW/ieO32f1wygP Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="0035-deparse-support-CREATE-POLICY.patch" From b1e3ac04467589fdc3a9e4136ceff0ba27d2eedb Mon Sep 17 00:00:00 2001 From: Alvaro Herrera Date: Sat, 14 Feb 2015 15:30:49 -0300 Subject: [PATCH 35/42] deparse: support CREATE POLICY --- src/backend/tcop/deparse_utility.c | 87 +++++++++++++++++++++++++++++++++++++- 1 file changed, 86 insertions(+), 1 deletion(-) diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c index 2a0a2b9..cd600ff 100644 --- a/src/backend/tcop/deparse_utility.c +++ b/src/backend/tcop/deparse_utility.c @@ -4402,6 +4402,91 @@ deparse_CommentStmt(Oid objectId, Oid objectSubId, Node *parsetree) } static ObjTree * +deparse_CreatePolicyStmt(Oid objectId, Node *parsetree) +{ + CreatePolicyStmt *node = (CreatePolicyStmt *) parsetree; + ObjTree *policy; + ObjTree *tmp; + Relation polRel = heap_open(PolicyRelationId, AccessShareLock); + HeapTuple polTup = get_catalog_object_by_oid(polRel, objectId); + Form_pg_policy polForm; + ListCell *cell; + List *list; + + if (!HeapTupleIsValid(polTup)) + elog(ERROR, "cache lookup failed for policy %u", objectId); + polForm = (Form_pg_policy) GETSTRUCT(polTup); + + policy = new_objtree_VA("CREATE POLICY %{identity}I ON %{table}D %{for_command}s " + "%{to_role}s %{using}s %{with_check}s", 1, + "identity", ObjTypeString, node->policy_name); + + /* add the "ON table" clause */ + append_object_object(policy, "table", + new_objtree_for_qualname_id(RelationRelationId, + polForm->polrelid)); + /* add "FOR command" clause */ + tmp = new_objtree_VA("FOR %{command}s", 1, + "command", ObjTypeString, node->cmd); + append_object_object(policy, "for_command", tmp); + + /* add the "TO role" clause. Always contains at least PUBLIC */ + tmp = new_objtree_VA("TO %{role:, }I", 0); + list = NIL; + foreach (cell, node->roles) + { + list = lappend(list, + new_string_object(strVal(lfirst(cell)))); + } + append_array_object(tmp, "role", list); + append_object_object(policy, "to_role", tmp); + + /* add the USING clause, if any */ + tmp = new_objtree_VA("USING (%{expression}s)", 0); + if (node->qual) + { + Datum deparsed; + Datum storedexpr; + bool isnull; + + storedexpr = heap_getattr(polTup, Anum_pg_policy_polqual, + RelationGetDescr(polRel), &isnull); + if (isnull) + elog(ERROR, "invalid NULL polqual expression in policy %u", objectId); + deparsed = DirectFunctionCall2(pg_get_expr, storedexpr, polForm->polrelid); + append_string_object(tmp, "expression", + TextDatumGetCString(deparsed)); + } + else + append_bool_object(tmp, "present", false); + append_object_object(policy, "using", tmp); + + /* add the WITH CHECK clause, if any */ + tmp = new_objtree_VA("WITH CHECK (%{expression}s)", 0); + if (node->with_check) + { + Datum deparsed; + Datum storedexpr; + bool isnull; + + storedexpr = heap_getattr(polTup, Anum_pg_policy_polwithcheck, + RelationGetDescr(polRel), &isnull); + if (isnull) + elog(ERROR, "invalid NULL polwithcheck expression in policy %u", objectId); + deparsed = DirectFunctionCall2(pg_get_expr, storedexpr, polForm->polrelid); + append_string_object(tmp, "expression", + TextDatumGetCString(deparsed)); + } + else + append_bool_object(tmp, "present", false); + append_object_object(policy, "with_check", tmp); + + heap_close(polRel, AccessShareLock); + + return policy; +} + +static ObjTree * deparse_SecLabelStmt(Oid objectId, Oid objectSubId, Node *parsetree) { SecLabelStmt *node = (SecLabelStmt *) parsetree; @@ -5502,7 +5587,7 @@ deparse_simple_command(StashedCommand *cmd) break; case T_CreatePolicyStmt: /* CREATE POLICY */ - elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree)); + command = deparse_CreatePolicyStmt(objectId, parsetree); break; case T_AlterPolicyStmt: /* ALTER POLICY */ -- 2.1.4 --gatW/ieO32f1wygP Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="0036-deparse-support-CREATE-SERVER.patch" From 323b7e0a07a5467e7f8a6fe54769392eabbb639b Mon Sep 17 00:00:00 2001 From: Alvaro Herrera Date: Sat, 14 Feb 2015 15:37:57 -0300 Subject: [PATCH 36/42] deparse: support CREATE SERVER --- src/backend/tcop/deparse_utility.c | 41 +++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c index cd600ff..c4ec7c0 100644 --- a/src/backend/tcop/deparse_utility.c +++ b/src/backend/tcop/deparse_utility.c @@ -1748,6 +1748,45 @@ deparse_AlterExtensionStmt(Oid objectId, Node *parsetree) return stmt; } +static ObjTree * +deparse_CreateForeignServerStmt(Oid objectId, Node *parsetree) +{ + CreateForeignServerStmt *node = (CreateForeignServerStmt *) parsetree; + ObjTree *createServer; + ObjTree *tmp; + + createServer = new_objtree_VA("CREATE SERVER %{identity}I %{type}s %{version}s " + "FOREIGN DATA WRAPPER %{fdw}I %{options}s", 2, + "identity", ObjTypeString, node->servername, + "fdw", ObjTypeString, node->fdwname); + + /* add a TYPE clause, if any */ + tmp = new_objtree_VA("TYPE %{type}L", 0); + if (node->servertype) + append_string_object(tmp, "type", node->servertype); + else + append_bool_object(tmp, "present", false); + append_object_object(createServer, "type", tmp); + + /* add a VERSION clause, if any */ + tmp = new_objtree_VA("VERSION %{version}L", 0); + if (node->version) + append_string_object(tmp, "version", node->version); + else + append_bool_object(tmp, "present", false); + append_object_object(createServer, "version", tmp); + + /* add an OPTIONS clause, if any */ + tmp = new_objtree_VA("OPTIONS (%{option:, }s)", 0); + if (node->options) + elog(ERROR, "unimplemented deparse of CREATE SERVER ... OPTIONS"); + else + append_bool_object(tmp, "present", false); + append_object_object(createServer, "options", tmp); + + return createServer; +} + /* * deparse_ViewStmt * deparse a ViewStmt @@ -5436,7 +5475,7 @@ deparse_simple_command(StashedCommand *cmd) break; case T_CreateForeignServerStmt: - elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree)); + command = deparse_CreateForeignServerStmt(objectId, parsetree); break; case T_AlterForeignServerStmt: -- 2.1.4 --gatW/ieO32f1wygP Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="0037-deparse-support-REFRESH-MATERIALIZED-VIEW.patch" From 2ee2dad54fc9489c5ab5371d4e27cb6b23dacf06 Mon Sep 17 00:00:00 2001 From: Alvaro Herrera Date: Sat, 14 Feb 2015 15:38:28 -0300 Subject: [PATCH 37/42] deparse: support REFRESH MATERIALIZED VIEW --- src/backend/tcop/deparse_utility.c | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c index c4ec7c0..71859ec 100644 --- a/src/backend/tcop/deparse_utility.c +++ b/src/backend/tcop/deparse_utility.c @@ -2021,6 +2021,31 @@ deparse_CreateTrigStmt(Oid objectId, Node *parsetree) return trigger; } +static ObjTree * +deparse_RefreshMatViewStmt(Oid objectId, Node *parsetree) +{ + RefreshMatViewStmt *node = (RefreshMatViewStmt *) parsetree; + ObjTree *refreshStmt; + ObjTree *tmp; + + refreshStmt = new_objtree_VA("REFRESH MATERIALIZED VIEW %{concurrently}s %{identity}D " + "%{with_no_data}s", 0); + /* add a CONCURRENTLY clause */ + append_string_object(refreshStmt, "concurrently", + node->concurrent ? "CONCURRENTLY" : ""); + /* add the matview name */ + append_object_object(refreshStmt, "identity", + new_objtree_for_qualname_id(RelationRelationId, + objectId)); + /* add a WITH NO DATA clause */ + tmp = new_objtree_VA("WITH NO DATA", 1, + "present", ObjTypeBool, + node->skipData ? true : false); + append_object_object(refreshStmt, "with_no_data", tmp); + + return refreshStmt; +} + /* * deparse_ColumnDef * Subroutine for CREATE TABLE deparsing @@ -5545,7 +5570,7 @@ deparse_simple_command(StashedCommand *cmd) break; case T_RefreshMatViewStmt: - elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree)); + command = deparse_RefreshMatViewStmt(objectId, parsetree); break; case T_CreateTrigStmt: -- 2.1.4 --gatW/ieO32f1wygP Content-Type: application/x-sql Content-Disposition: attachment; filename="deparse_init.sql" Content-Transfer-Encoding: quoted-printable --=0A= -- DEPARSE_INIT=0A= --=0A= CREATE SCHEMA deparse;=0A= CREATE TABLE deparse.deparse_test_commands (=0A= lsn pg_lsn,=0A= ord integer,=0A= command TEXT=0A= );=0A= CREATE OR REPLACE FUNCTION deparse.deparse_test_ddl_command_end()=0A= RETURNS event_trigger=0A= SECURITY DEFINER=0A= LANGUAGE plpgsql=0A= AS $fn$=0A= BEGIN=0A= BEGIN=0A= INSERT INTO deparse.deparse_test_commands (command, ord, lsn)=0A= SELECT pg_event_trigger_expand_command(command), ordinality, lsn=0A= FROM pg_event_trigger_get_creation_commands() WITH ORDINALITY,=0A= pg_current_xlog_insert_location() lsn;=0A= EXCEPTION WHEN OTHERS THEN=20=0A= RAISE WARNING 'state: % errm: %', sqlstate, sqlerrm;=0A= END;=0A= END;=0A= $fn$;=0A= =0A= CREATE OR REPLACE FUNCTION deparse.deparse_test_sql_drop()=0A= RETURNS event_trigger=0A= SECURITY DEFINER=0A= LANGUAGE plpgsql=0A= AS $fn$=0A= DECLARE=0A= fmt TEXT;=0A= obj RECORD;=0A= i integer =3D 1;=0A= BEGIN=0A= FOR obj IN SELECT * FROM pg_event_trigger_dropped_objects() LOOP=0A= IF NOT obj.original THEN=0A= CONTINUE;=0A= END IF;=0A= =0A= IF obj.object_type =3D 'table column' OR obj.object_type =3D 'foreign tab= le column' THEN=0A= fmt =3D format('ALTER TABLE %I.%I DROP COLUMN %I CASCADE',=0A= obj.address_names[1],=0A= obj.address_names[2],=0A= obj.address_names[3]);=0A= ELSIF obj.object_type =3D 'composite type column' THEN=0A= fmt =3D format('ALTER TYPE %I.%I DROP ATTRIBUTE %I CASCADE',=0A= obj.address_names[1],=0A= obj.address_names[2],=0A= obj.address_names[3]);=0A= ELSIF obj.object_type =3D 'table constraint' THEN=0A= fmt =3D format('ALTER TABLE %I.%I DROP CONSTRAINT %I CASCADE',=0A= obj.address_names[1],=0A= obj.address_names[2],=0A= obj.address_names[3]);=0A= ELSIF obj.object_type =3D 'domain constraint' THEN=0A= fmt =3D format('ALTER DOMAIN %s DROP CONSTRAINT %I CASCADE',=0A= obj.address_names[1],=0A= obj.address_args[1]);=0A= ELSIF obj.object_type =3D 'default value' THEN=0A= fmt =3D format('ALTER TABLE %I.%I ALTER COLUMN %I DROP DEFAULT',=0A= obj.address_names[1],=0A= obj.address_names[2],=0A= obj.address_names[3]);=0A= ELSIF obj.object_type =3D 'foreign-data wrapper' THEN=0A= fmt =3D format('DROP FOREIGN DATA WRAPPER %s CASCADE',=0A= obj.object_identity);=0A= ELSE=0A= fmt =3D format('DROP %s %s CASCADE',=0A= obj.object_type, obj.object_identity);=0A= END IF;=0A= =0A= INSERT INTO deparse.deparse_test_commands (lsn, ord, command)=0A= VALUES (pg_current_xlog_insert_location(), i, fmt);=0A= i :=3D i + 1;=0A= END LOOP;=0A= END;=0A= $fn$;=0A= =0A= CREATE EVENT TRIGGER deparse_test_trg_sql_drop=0A= ON sql_drop=0A= EXECUTE PROCEDURE deparse.deparse_test_sql_drop();=0A= =0A= CREATE EVENT TRIGGER deparse_test_trg_ddl_command_end=0A= ON ddl_command_end=0A= EXECUTE PROCEDURE deparse.deparse_test_ddl_command_end();=0A= --gatW/ieO32f1wygP Content-Type: text/plain Content-Disposition: inline Content-Transfer-Encoding: 8bit MIME-Version: 1.0 -- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers --gatW/ieO32f1wygP--