pg.ddx.io  pgsql-hackers@postgresql.org mailing list archive  
help / color / mirror / Atom feed
From: Alvaro Herrera <alvherre@2ndquadrant.com>
To: Pg Hackers <pgsql-hackers@postgresql.org>
Subject: Add CREATE support to event triggers
Date: Fri, 8 Nov 2013 12:33:22 -0300
Message-ID: <20131108153322.GU5809@eldon.alvh.no-ip.org> (raw)
List-Unsubscribe: <mailto:majordomo@postgresql.org?body=unsub%20pgsql-hackers>

Hello,

Attached you can find a very-much-WIP patch to add CREATE info support
for event triggers (normalized commands).  This patch builds mainly on
two things:

1. Dimitri's "DDL rewrite" patch he submitted way back, in 
   http://www.postgresql.org/message-id/m2zk1j9c44.fsf@2ndQuadrant.fr

I borrowed the whole ddl_rewrite.c code, and tweaked it a bit.  There
are several things still wrong with it and which will need to be fixed
before a final patch can even be contemplated; but there are some
questions that require a consensus answer before I go and fix it all,
because what it will look like will depend on said answers.


2. The ideas we used to build DROP support.  Mainly, the interesting
   thing here is the fact that we use a SRF to report, at
   ddl_command_end, all the objects that were created during execution
   of that command.  We do this by collecting them in a list in some raw
   form somewhere during ProcessUtility, and then spitting them out if
   the SRF is called.  I think the general idea is sound, although of
   course I admit there might be bugs in the implementation.

Note this patch doesn't try to add any kind of ALTER support.  I think
this is fine in principle, because we agreed that we would attack each
kind of command separately (divide to conquer and all that); but there
is a slight problem for some kind of objects that are represented partly
as ALTER state during creation; for example creating a table with a
sequence uses ALTER SEQ/OWNED BY internally at some point.  There might
be other cases I'm missing, also.  (The REFRESH command is nominally
also supported.)


Now about the questions I mentioned above:

a) It doesn't work to reverse-parse the statement nodes in all cases;
there are several unfixable bugs if we only do that.  In order to create
always-correct statements, we need access to the catalogs for the
created objects.  But if we are doing catalog access, then it seems to
me that we can do away with the statement parse nodes completely and
just reconstruct the objects from catalog information.  Shall we go that
route?

b) What's the best design of the SRF output?  This patch proposes two
columns, object identity and create statement.  Is there use for
anything else?  Class/object OIDs perhaps, schema OIDs for objects types
that have it?  I don't see any immediate need to that info, but perhaps
someone does.

c) The current patch stashes all objects in a list, whenever there's an
event trigger function.  But perhaps some users want to have event
triggers and not have any use for the CREATE statements.  So one idea is
to require users that want the objects reported to call a special
function in a ddl_command_start event trigger which enables collection;
if it's not called, objects are not reported.  This eliminates
performance impact for people not using it, but then maybe it will be
surprising for people that call the SRF and find that it always returns
empty.

d) There's a new routine uncookConstraintOrDefault.  This takes a raw
expression, runs transformExpr() on it, and then deparses it (possibly
setting up a deparse context based on some relation).  This is a
somewhat funny thing to be doing, so if there are other ideas on how to
handle this, I'm all ears.


This patch doesn't include doc changes or regression tests.  Those will
be added in a later version.  For now, you can see this code in action
by running installing an event trigger like this:

CREATE OR REPLACE FUNCTION snitch() RETURNS event_trigger LANGUAGE plpgsql AS $$
DECLARE 
        r RECORD;
BEGIN
        FOR r IN SELECT * FROM pg_event_trigger_get_normalized_commands()
        LOOP
                RAISE NOTICE 'object created: id %, statement %',
                r.identity, r.command;
        END LOOP;
END;
$$;
CREATE EVENT TRIGGER snitch ON ddl_command_end EXECUTE PROCEDURE snitch();

And then running the DDL of your preference.  Be warned that there are
many rough edges, so some objects might be incompletely reported (or
bogusly so, in particular with regards to objects in search_path but not
in the first position of it); but if you see any crashes, please let me
know.  Also, many commands are not supported yet by the ddl_rewrite.c
code and they return "unsupported FOOBAR".  I didn't want to go to the
effort of writing code for them that might end up being ripped out if we
wanted to go the route of using only syscache to build CREATE
statements.  That part is pretty mechanical, and not much code anyway.
I just left whatever Dimitri already had in his patch.


I would have continued working some more on this patch before
submitting, except that I'm going on vacations for a few days starting
tomorrow and we have a rule that no complex patches can go in at CF4
without it having been discussed in previous commitfests.  My intention
is that the patch that arrives at CF4 is free of design surprises and
ready for a final review before commit, so if there's any disagreement
with the direction this is going, please speak up now.

-- 
Álvaro Herrera                http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Attachments:

event-trigger-create.patch (text/x-diff, 81.1K) download *** a/src/backend/catalog/dependency.c --- b/src/backend/catalog/dependency.c *************** *** 2006,2011 **** add_object_address(ObjectClass oclass, Oid objectId, int32 subId, --- 2006,2018 ---- addrs->numrefs++; } + Oid + get_class_catalog(ObjectClass oclass) + { + Assert(oclass < MAX_OCLASS); + return object_classes[oclass]; + } + /* * Add an entry to an ObjectAddresses array. * *** a/src/backend/commands/createas.c --- b/src/backend/commands/createas.c *************** *** 55,60 **** typedef struct --- 55,63 ---- BulkInsertState bistate; /* bulk insert state */ } DR_intorel; + /* the OID of the created table, for ExecCreateTableAs consumption */ + static Oid CreateAsRelid = InvalidOid; + static void intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo); static void intorel_receive(TupleTableSlot *slot, DestReceiver *self); static void intorel_shutdown(DestReceiver *self); *************** *** 64,70 **** static void intorel_destroy(DestReceiver *self); /* * ExecCreateTableAs -- execute a CREATE TABLE AS command */ ! void ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString, ParamListInfo params, char *completionTag) { --- 67,73 ---- /* * ExecCreateTableAs -- execute a CREATE TABLE AS command */ ! Oid ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString, ParamListInfo params, char *completionTag) { *************** *** 75,80 **** ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString, --- 78,84 ---- Oid save_userid = InvalidOid; int save_sec_context = 0; int save_nestlevel = 0; + Oid relOid; List *rewritten; PlannedStmt *plan; QueryDesc *queryDesc; *************** *** 98,104 **** ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString, Assert(!is_matview); /* excluded by syntax */ ExecuteQuery(estmt, into, queryString, params, dest, completionTag); ! return; } Assert(query->commandType == CMD_SELECT); --- 102,110 ---- Assert(!is_matview); /* excluded by syntax */ ExecuteQuery(estmt, into, queryString, params, dest, completionTag); ! relOid = CreateAsRelid; ! CreateAsRelid = InvalidOid; ! return relOid; } Assert(query->commandType == CMD_SELECT); *************** *** 190,195 **** ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString, --- 196,206 ---- /* Restore userid and security context */ SetUserIdAndSecContext(save_userid, save_sec_context); } + + relOid = CreateAsRelid; + CreateAsRelid = InvalidOid; + + return relOid; } /* *************** *** 420,425 **** intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo) --- 431,439 ---- myState->rel = intoRelationDesc; myState->output_cid = GetCurrentCommandId(true); + /* and remember the new relation's OID for ExecCreateTableAs */ + CreateAsRelid = RelationGetRelid(myState->rel); + /* * We can skip WAL-logging the insertions, unless PITR or streaming * replication is in use. We can skip the FSM in any case. *** a/src/backend/commands/event_trigger.c --- b/src/backend/commands/event_trigger.c *************** *** 33,38 **** --- 33,39 ---- #include "miscadmin.h" #include "utils/acl.h" #include "utils/builtins.h" + #include "utils/ddl_rewrite.h" #include "utils/evtcache.h" #include "utils/fmgroids.h" #include "utils/lsyscache.h" *************** *** 48,53 **** typedef struct EventTriggerQueryState --- 49,55 ---- slist_head SQLDropList; bool in_sql_drop; MemoryContext cxt; + List *stash; struct EventTriggerQueryState *previous; } EventTriggerQueryState; *************** *** 488,494 **** AlterEventTriggerOwner(const char *name, Oid newOwnerId) } /* ! * Change extension owner, by OID */ void AlterEventTriggerOwner_oid(Oid trigOid, Oid newOwnerId) --- 490,496 ---- } /* ! * Change event trigger owner, by OID */ void AlterEventTriggerOwner_oid(Oid trigOid, Oid newOwnerId) *************** *** 1019,1031 **** EventTriggerBeginCompleteQuery(void) EventTriggerQueryState *state; MemoryContext cxt; - /* - * Currently, sql_drop 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, --- 1021,1026 ---- *************** *** 1033,1040 **** EventTriggerBeginCompleteQuery(void) ALLOCSET_DEFAULT_MAXSIZE); state = MemoryContextAlloc(cxt, sizeof(EventTriggerQueryState)); state->cxt = cxt; ! slist_init(&(state->SQLDropList)); state->in_sql_drop = false; state->previous = currentEventTriggerState; currentEventTriggerState = state; --- 1028,1037 ---- ALLOCSET_DEFAULT_MAXSIZE); state = MemoryContextAlloc(cxt, sizeof(EventTriggerQueryState)); state->cxt = cxt; ! if (trackDroppedObjectsNeeded()) ! slist_init(&(state->SQLDropList)); state->in_sql_drop = false; + state->stash = NIL; state->previous = currentEventTriggerState; currentEventTriggerState = state; *************** *** 1066,1071 **** EventTriggerEndCompleteQuery(void) --- 1063,1102 ---- currentEventTriggerState = prevstate; } + typedef struct stashedObject + { + Oid objectId; + Oid classId; + Node *parsetree; + } stashedObject; + + static stashedObject * + newStashedObject(Oid objectId, ObjectClass class, Node *parsetree) + { + stashedObject *stashed = palloc(sizeof(stashedObject)); + + stashed->objectId = objectId; + stashed->classId = get_class_catalog(class); + stashed->parsetree = copyObject(parsetree); + + return stashed; + } + + void + EventTriggerStashCreatedObject(Oid objectId, ObjectClass class, + Node *parsetree) + { + MemoryContext oldcxt; + + oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt); + + currentEventTriggerState->stash = + lappend(currentEventTriggerState->stash, + newStashedObject(objectId, class, parsetree)); + + MemoryContextSwitchTo(oldcxt); + } + /* * Do we need to keep close track of objects being dropped? * *************** *** 1291,1293 **** pg_event_trigger_dropped_objects(PG_FUNCTION_ARGS) --- 1322,1403 ---- return (Datum) 0; } + + Datum + pg_event_trigger_get_normalized_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_normalized_command"))); + + /* 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) + { + stashedObject *obj = lfirst(lc); + const char *identity; + const char *command; + Datum values[2]; + bool nulls[2]; + ObjectAddress addr; + int i = 0; + + addr.classId = obj->classId; + addr.objectId = obj->objectId; + addr.objectSubId = 0; + identity = getObjectIdentity(&addr); + + command = rewrite_utility_command(obj->objectId, + identity, + obj->parsetree); + + MemSet(nulls, 0, sizeof(nulls)); + + /* identity */ + values[i++] = CStringGetTextDatum(identity); + /* command */ + values[i++] = CStringGetTextDatum(command); + + tuplestore_putvalues(tupstore, tupdesc, values, nulls); + } + + /* clean up and return the tuplestore */ + tuplestore_donestoring(tupstore); + + PG_RETURN_VOID(); + } *** a/src/backend/commands/matview.c --- b/src/backend/commands/matview.c *************** *** 131,137 **** SetMatViewPopulatedState(Relation relation, bool newstate) * The matview's "populated" state is changed based on whether the contents * reflect the result set of the materialized view's query. */ ! void ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString, ParamListInfo params, char *completionTag) { --- 131,137 ---- * The matview's "populated" state is changed based on whether the contents * reflect the result set of the materialized view's query. */ ! Oid ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString, ParamListInfo params, char *completionTag) { *************** *** 268,273 **** ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString, --- 268,275 ---- } else refresh_by_heap_swap(matviewOid, OIDNewHeap); + + return matviewOid; } /* *** a/src/backend/commands/schemacmds.c --- b/src/backend/commands/schemacmds.c *************** *** 24,29 **** --- 24,30 ---- #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,135 **** CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString) --- 131,145 ---- 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. + */ + EventTriggerStashCreatedObject(namespaceId, OCLASS_SCHEMA, + (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 --- *** a/src/backend/commands/tablecmds.c --- b/src/backend/commands/tablecmds.c *************** *** 43,48 **** --- 43,49 ---- #include "catalog/pg_type_fn.h" #include "catalog/storage.h" #include "catalog/toasting.h" + #include "commands/alter_table.h" #include "commands/cluster.h" #include "commands/comment.h" #include "commands/defrem.h" *************** *** 112,165 **** typedef struct OnCommitItem static List *on_commits = NIL; - /* - * State information for ALTER TABLE - * - * The pending-work queue for an ALTER TABLE is a List of AlteredTableInfo - * structs, one for each table modified by the operation (the named table - * plus any child tables that are affected). We save lists of subcommands - * to apply to this table (possibly modified by parse transformation steps); - * these lists will be executed in Phase 2. If a Phase 3 step is needed, - * necessary information is stored in the constraints and newvals lists. - * - * Phase 2 is divided into multiple passes; subcommands are executed in - * a pass determined by subcommand type. - */ - - #define AT_PASS_UNSET -1 /* UNSET will cause ERROR */ - #define AT_PASS_DROP 0 /* DROP (all flavors) */ - #define AT_PASS_ALTER_TYPE 1 /* ALTER COLUMN TYPE */ - #define AT_PASS_OLD_INDEX 2 /* re-add existing indexes */ - #define AT_PASS_OLD_CONSTR 3 /* re-add existing constraints */ - #define AT_PASS_COL_ATTRS 4 /* set other column attributes */ - /* We could support a RENAME COLUMN pass here, but not currently used */ - #define AT_PASS_ADD_COL 5 /* ADD COLUMN */ - #define AT_PASS_ADD_INDEX 6 /* ADD indexes */ - #define AT_PASS_ADD_CONSTR 7 /* ADD constraints, defaults */ - #define AT_PASS_MISC 8 /* other stuff */ - #define AT_NUM_PASSES 9 - - typedef struct AlteredTableInfo - { - /* Information saved before any work commences: */ - Oid relid; /* Relation to work on */ - char relkind; /* Its relkind */ - TupleDesc oldDesc; /* Pre-modification tuple descriptor */ - /* Information saved by Phase 1 for Phase 2: */ - List *subcmds[AT_NUM_PASSES]; /* Lists of AlterTableCmd */ - /* Information saved by Phases 1/2 for Phase 3: */ - List *constraints; /* List of NewConstraint */ - List *newvals; /* List of NewColumnValue */ - bool new_notnull; /* T if we added new NOT NULL constraints */ - bool rewrite; /* T if a rewrite is forced */ - Oid newTableSpace; /* new tablespace; 0 means no change */ - /* Objects to rebuild after completing ALTER TYPE operations */ - List *changedConstraintOids; /* OIDs of constraints to rebuild */ - List *changedConstraintDefs; /* string definitions of same */ - List *changedIndexOids; /* OIDs of indexes to rebuild */ - List *changedIndexDefs; /* string definitions of same */ - } AlteredTableInfo; - /* Struct describing one new constraint to check in Phase 3 scan */ /* Note: new NOT NULL constraints are handled elsewhere */ typedef struct NewConstraint --- 113,118 ---- *** a/src/backend/tcop/utility.c --- b/src/backend/tcop/utility.c *************** *** 893,898 **** ProcessUtilitySlow(Node *parsetree, --- 893,899 ---- bool isTopLevel = (context == PROCESS_UTILITY_TOPLEVEL); bool isCompleteQuery = (context <= PROCESS_UTILITY_QUERY); bool needCleanup; + Oid objectId; /* All event trigger calls are done only when isCompleteQuery is true */ needCleanup = isCompleteQuery && EventTriggerBeginCompleteQuery(); *************** *** 911,916 **** ProcessUtilitySlow(Node *parsetree, --- 912,921 ---- case T_CreateSchemaStmt: CreateSchemaCommand((CreateSchemaStmt *) parsetree, queryString); + /* + * CreateSchemaCommand calls EventTriggerStashCreatedObject + * internally, for reasons explained there. + */ break; case T_CreateStmt: *************** *** 918,924 **** ProcessUtilitySlow(Node *parsetree, { List *stmts; ListCell *l; - Oid relOid; /* Run parse analysis ... */ stmts = transformCreateStmt((CreateStmt *) parsetree, --- 923,928 ---- *************** *** 935,943 **** ProcessUtilitySlow(Node *parsetree, static char *validnsps[] = HEAP_RELOPT_NAMESPACES; /* Create the table itself */ ! relOid = DefineRelation((CreateStmt *) stmt, ! RELKIND_RELATION, ! InvalidOid); /* * Let AlterTableCreateToastTable decide if this --- 939,950 ---- static char *validnsps[] = HEAP_RELOPT_NAMESPACES; /* Create the table itself */ ! objectId = DefineRelation((CreateStmt *) stmt, ! RELKIND_RELATION, ! InvalidOid); ! EventTriggerStashCreatedObject(objectId, ! OCLASS_CLASS, ! stmt); /* * Let AlterTableCreateToastTable decide if this *************** *** 959,978 **** ProcessUtilitySlow(Node *parsetree, toast_options, true); ! AlterTableCreateToastTable(relOid, toast_options); } else if (IsA(stmt, CreateForeignTableStmt)) { /* Create the table itself */ ! relOid = DefineRelation((CreateStmt *) stmt, ! RELKIND_FOREIGN_TABLE, ! InvalidOid); CreateForeignTable((CreateForeignTableStmt *) stmt, ! relOid); } else { ! /* Recurse for anything else */ ProcessUtility(stmt, queryString, PROCESS_UTILITY_SUBCOMMAND, --- 966,992 ---- toast_options, true); ! AlterTableCreateToastTable(objectId, toast_options); } else if (IsA(stmt, CreateForeignTableStmt)) { /* Create the table itself */ ! objectId = DefineRelation((CreateStmt *) stmt, ! RELKIND_FOREIGN_TABLE, ! InvalidOid); CreateForeignTable((CreateForeignTableStmt *) stmt, ! objectId); ! EventTriggerStashCreatedObject(objectId, ! OCLASS_CLASS, ! stmt); } 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, *************** *** 1099,1148 **** ProcessUtilitySlow(Node *parsetree, case T_DefineStmt: { DefineStmt *stmt = (DefineStmt *) parsetree; switch (stmt->kind) { case OBJECT_AGGREGATE: ! DefineAggregate(stmt->defnames, stmt->args, ! stmt->oldstyle, stmt->definition, ! queryString); break; case OBJECT_OPERATOR: Assert(stmt->args == NIL); ! DefineOperator(stmt->defnames, stmt->definition); break; case OBJECT_TYPE: Assert(stmt->args == NIL); ! DefineType(stmt->defnames, stmt->definition); break; case OBJECT_TSPARSER: Assert(stmt->args == NIL); ! DefineTSParser(stmt->defnames, stmt->definition); break; case OBJECT_TSDICTIONARY: Assert(stmt->args == NIL); ! DefineTSDictionary(stmt->defnames, ! stmt->definition); break; case OBJECT_TSTEMPLATE: Assert(stmt->args == NIL); ! DefineTSTemplate(stmt->defnames, ! stmt->definition); break; case OBJECT_TSCONFIGURATION: Assert(stmt->args == NIL); ! DefineTSConfiguration(stmt->defnames, ! stmt->definition); break; case OBJECT_COLLATION: Assert(stmt->args == NIL); ! DefineCollation(stmt->defnames, stmt->definition); break; default: elog(ERROR, "unrecognized define stmt type: %d", (int) stmt->kind); break; } } break; --- 1113,1178 ---- case T_DefineStmt: { DefineStmt *stmt = (DefineStmt *) parsetree; + ObjectClass class; switch (stmt->kind) { case OBJECT_AGGREGATE: ! objectId = ! DefineAggregate(stmt->defnames, stmt->args, ! stmt->oldstyle, ! stmt->definition, queryString); ! class = OCLASS_PROC; break; case OBJECT_OPERATOR: Assert(stmt->args == NIL); ! objectId = DefineOperator(stmt->defnames, ! stmt->definition); ! class = OCLASS_OPERATOR; break; case OBJECT_TYPE: Assert(stmt->args == NIL); ! objectId = DefineType(stmt->defnames, ! stmt->definition); ! class = OCLASS_TYPE; break; case OBJECT_TSPARSER: Assert(stmt->args == NIL); ! objectId = DefineTSParser(stmt->defnames, ! stmt->definition); ! class = OCLASS_TSPARSER; break; case OBJECT_TSDICTIONARY: Assert(stmt->args == NIL); ! objectId = DefineTSDictionary(stmt->defnames, ! stmt->definition); ! class = OCLASS_TSDICT; break; case OBJECT_TSTEMPLATE: Assert(stmt->args == NIL); ! objectId = DefineTSTemplate(stmt->defnames, ! stmt->definition); ! class = OCLASS_TSTEMPLATE; break; case OBJECT_TSCONFIGURATION: Assert(stmt->args == NIL); ! objectId = DefineTSConfiguration(stmt->defnames, ! stmt->definition); ! class = OCLASS_TSCONFIG; break; case OBJECT_COLLATION: Assert(stmt->args == NIL); ! objectId = DefineCollation(stmt->defnames, ! stmt->definition); ! class = OCLASS_COLLATION; break; default: elog(ERROR, "unrecognized define stmt type: %d", (int) stmt->kind); break; } + + EventTriggerStashCreatedObject(objectId, class, parsetree); } break; *************** *** 1160,1176 **** ProcessUtilitySlow(Node *parsetree, stmt = transformIndexStmt(stmt, queryString); /* ... and do it */ ! DefineIndex(stmt, ! InvalidOid, /* no predefined OID */ ! false, /* is_alter_table */ ! true, /* check_rights */ ! false, /* skip_build */ ! false); /* quiet */ } break; case T_CreateExtensionStmt: ! CreateExtension((CreateExtensionStmt *) parsetree); break; case T_AlterExtensionStmt: --- 1190,1209 ---- stmt = transformIndexStmt(stmt, queryString); /* ... and do it */ ! objectId = DefineIndex(stmt, ! InvalidOid, /* no predefined OID */ ! false, /* is_alter_table */ ! true, /* check_rights */ ! false, /* skip_build */ ! false); /* quiet */ ! EventTriggerStashCreatedObject(objectId, OCLASS_CLASS, ! parsetree); } break; case T_CreateExtensionStmt: ! objectId = CreateExtension((CreateExtensionStmt *) parsetree); ! EventTriggerStashCreatedObject(objectId, OCLASS_EXTENSION, parsetree); break; case T_AlterExtensionStmt: *************** *** 1182,1188 **** ProcessUtilitySlow(Node *parsetree, break; case T_CreateFdwStmt: ! CreateForeignDataWrapper((CreateFdwStmt *) parsetree); break; case T_AlterFdwStmt: --- 1215,1222 ---- break; case T_CreateFdwStmt: ! objectId = CreateForeignDataWrapper((CreateFdwStmt *) parsetree); ! EventTriggerStashCreatedObject(objectId, OCLASS_FDW, parsetree); break; case T_AlterFdwStmt: *************** *** 1190,1196 **** ProcessUtilitySlow(Node *parsetree, break; case T_CreateForeignServerStmt: ! CreateForeignServer((CreateForeignServerStmt *) parsetree); break; case T_AlterForeignServerStmt: --- 1224,1232 ---- break; case T_CreateForeignServerStmt: ! objectId = CreateForeignServer((CreateForeignServerStmt *) parsetree); ! EventTriggerStashCreatedObject(objectId, OCLASS_FOREIGN_SERVER, ! parsetree); break; case T_AlterForeignServerStmt: *************** *** 1198,1204 **** ProcessUtilitySlow(Node *parsetree, break; case T_CreateUserMappingStmt: ! CreateUserMapping((CreateUserMappingStmt *) parsetree); break; case T_AlterUserMappingStmt: --- 1234,1242 ---- break; case T_CreateUserMappingStmt: ! objectId = CreateUserMapping((CreateUserMappingStmt *) parsetree); ! EventTriggerStashCreatedObject(objectId, OCLASS_USER_MAPPING, ! parsetree); break; case T_AlterUserMappingStmt: *************** *** 1213,1228 **** ProcessUtilitySlow(Node *parsetree, { CompositeTypeStmt *stmt = (CompositeTypeStmt *) parsetree; ! DefineCompositeType(stmt->typevar, stmt->coldeflist); } break; case T_CreateEnumStmt: /* CREATE TYPE AS ENUM */ ! DefineEnum((CreateEnumStmt *) parsetree); break; case T_CreateRangeStmt: /* CREATE TYPE AS RANGE */ ! DefineRange((CreateRangeStmt *) parsetree); break; case T_AlterEnumStmt: /* ALTER TYPE (enum) */ --- 1251,1269 ---- { CompositeTypeStmt *stmt = (CompositeTypeStmt *) parsetree; ! objectId = DefineCompositeType(stmt->typevar, stmt->coldeflist); ! EventTriggerStashCreatedObject(objectId, OCLASS_TYPE, parsetree); } break; case T_CreateEnumStmt: /* CREATE TYPE AS ENUM */ ! objectId = DefineEnum((CreateEnumStmt *) parsetree); ! EventTriggerStashCreatedObject(objectId, OCLASS_TYPE, parsetree); break; case T_CreateRangeStmt: /* CREATE TYPE AS RANGE */ ! objectId = DefineRange((CreateRangeStmt *) parsetree); ! EventTriggerStashCreatedObject(objectId, OCLASS_TYPE, parsetree); break; case T_AlterEnumStmt: /* ALTER TYPE (enum) */ *************** *** 1230,1252 **** ProcessUtilitySlow(Node *parsetree, break; case T_ViewStmt: /* CREATE VIEW */ ! DefineView((ViewStmt *) parsetree, queryString); break; case T_CreateFunctionStmt: /* CREATE FUNCTION */ ! CreateFunction((CreateFunctionStmt *) parsetree, queryString); break; case T_AlterFunctionStmt: /* ALTER FUNCTION */ ! AlterFunction((AlterFunctionStmt *) parsetree); break; case T_RuleStmt: /* CREATE RULE */ ! DefineRule((RuleStmt *) parsetree, queryString); break; case T_CreateSeqStmt: ! DefineSequence((CreateSeqStmt *) parsetree); break; case T_AlterSeqStmt: --- 1271,1297 ---- break; case T_ViewStmt: /* CREATE VIEW */ ! objectId = DefineView((ViewStmt *) parsetree, queryString); ! EventTriggerStashCreatedObject(objectId, OCLASS_CLASS, parsetree); break; case T_CreateFunctionStmt: /* CREATE FUNCTION */ ! objectId = CreateFunction((CreateFunctionStmt *) parsetree, queryString); ! EventTriggerStashCreatedObject(objectId, OCLASS_PROC, parsetree); break; case T_AlterFunctionStmt: /* ALTER FUNCTION */ ! objectId = AlterFunction((AlterFunctionStmt *) parsetree); break; case T_RuleStmt: /* CREATE RULE */ ! objectId = DefineRule((RuleStmt *) parsetree, queryString); ! EventTriggerStashCreatedObject(objectId, OCLASS_REWRITE, parsetree); break; case T_CreateSeqStmt: ! objectId = DefineSequence((CreateSeqStmt *) parsetree); ! EventTriggerStashCreatedObject(objectId, OCLASS_CLASS, parsetree); break; case T_AlterSeqStmt: *************** *** 1254,1295 **** ProcessUtilitySlow(Node *parsetree, break; case T_CreateTableAsStmt: ! ExecCreateTableAs((CreateTableAsStmt *) parsetree, queryString, params, completionTag); break; case T_RefreshMatViewStmt: ! ExecRefreshMatView((RefreshMatViewStmt *) parsetree, queryString, params, completionTag); break; case T_CreateTrigStmt: ! (void) CreateTrigger((CreateTrigStmt *) parsetree, queryString, InvalidOid, InvalidOid, false); break; case T_CreatePLangStmt: ! CreateProceduralLanguage((CreatePLangStmt *) parsetree); break; case T_CreateDomainStmt: ! DefineDomain((CreateDomainStmt *) parsetree); break; case T_CreateConversionStmt: ! CreateConversionCommand((CreateConversionStmt *) parsetree); break; case T_CreateCastStmt: ! CreateCast((CreateCastStmt *) parsetree); break; case T_CreateOpClassStmt: ! DefineOpClass((CreateOpClassStmt *) parsetree); break; case T_CreateOpFamilyStmt: ! DefineOpFamily((CreateOpFamilyStmt *) parsetree); break; case T_AlterOpFamilyStmt: --- 1299,1349 ---- break; case T_CreateTableAsStmt: ! objectId = ExecCreateTableAs((CreateTableAsStmt *) parsetree, queryString, params, completionTag); + EventTriggerStashCreatedObject(objectId, OCLASS_CLASS, parsetree); break; case T_RefreshMatViewStmt: ! objectId = ExecRefreshMatView((RefreshMatViewStmt *) parsetree, queryString, params, completionTag); + EventTriggerStashCreatedObject(objectId, OCLASS_CLASS, parsetree); break; case T_CreateTrigStmt: ! objectId = CreateTrigger((CreateTrigStmt *) parsetree, queryString, InvalidOid, InvalidOid, false); + EventTriggerStashCreatedObject(objectId, OCLASS_TRIGGER, parsetree); break; case T_CreatePLangStmt: ! objectId = CreateProceduralLanguage((CreatePLangStmt *) parsetree); ! EventTriggerStashCreatedObject(objectId, OCLASS_LANGUAGE, parsetree); break; case T_CreateDomainStmt: ! objectId = DefineDomain((CreateDomainStmt *) parsetree); ! EventTriggerStashCreatedObject(objectId, OCLASS_TYPE, parsetree); break; case T_CreateConversionStmt: ! objectId = CreateConversionCommand((CreateConversionStmt *) parsetree); ! EventTriggerStashCreatedObject(objectId, OCLASS_CONVERSION, parsetree); break; case T_CreateCastStmt: ! objectId = CreateCast((CreateCastStmt *) parsetree); ! EventTriggerStashCreatedObject(objectId, OCLASS_CAST, parsetree); break; case T_CreateOpClassStmt: ! objectId = DefineOpClass((CreateOpClassStmt *) parsetree); ! EventTriggerStashCreatedObject(objectId, OCLASS_OPCLASS, parsetree); break; case T_CreateOpFamilyStmt: ! objectId = DefineOpFamily((CreateOpFamilyStmt *) parsetree); ! EventTriggerStashCreatedObject(objectId, OCLASS_OPFAMILY, parsetree); break; case T_AlterOpFamilyStmt: *** a/src/backend/utils/adt/Makefile --- b/src/backend/utils/adt/Makefile *************** *** 17,23 **** endif OBJS = acl.o arrayfuncs.o array_selfuncs.o array_typanalyze.o \ array_userfuncs.o arrayutils.o bool.o \ ! cash.o char.o date.o datetime.o datum.o domains.o \ enum.o float.o format_type.o \ geo_ops.o geo_selfuncs.o int.o int8.o json.o jsonfuncs.o like.o \ lockfuncs.o misc.o nabstime.o name.o numeric.o numutils.o \ --- 17,23 ---- OBJS = acl.o arrayfuncs.o array_selfuncs.o array_typanalyze.o \ array_userfuncs.o arrayutils.o bool.o \ ! cash.o char.o date.o datetime.o datum.o domains.o ddl_rewrite.o \ enum.o float.o format_type.o \ geo_ops.o geo_selfuncs.o int.o int8.o json.o jsonfuncs.o like.o \ lockfuncs.o misc.o nabstime.o name.o numeric.o numutils.o \ *** /dev/null --- b/src/backend/utils/adt/ddl_rewrite.c *************** *** 0 **** --- 1,1800 ---- + /*------------------------------------------------------------------------- + * + * ddl_rewrite.c + * Functions to convert a utility command parsetree back to a command string + * + * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/adt/ddl_rewrite.c + * + *------------------------------------------------------------------------- + */ + #include "postgres.h" + + #include "access/htup_details.h" + #include "catalog/index.h" + #include "catalog/namespace.h" + #include "catalog/pg_class.h" + #include "catalog/pg_proc.h" + #include "catalog/heap.h" + #include "commands/defrem.h" + #include "lib/stringinfo.h" + #include "nodes/parsenodes.h" + #include "parser/parse_collate.h" + #include "parser/parse_expr.h" + #include "parser/parse_relation.h" + #include "parser/parse_type.h" + #include "parser/analyze.h" + #include "utils/builtins.h" + #include "utils/ddl_rewrite.h" + #include "utils/lsyscache.h" + #include "utils/memutils.h" + #include "utils/syscache.h" + + /* + * Given a RangeVar, return the namespace name to use as schemaname. When + * r->schemaname is NULL, returns the first schema name of the current + * search_path. + * + * It can be so that the resulting object (schema.name) does not exists, that + * check didn't happen yet when at the "ddl_command_start" event. All we ca do + * is play by the system's rule. The other option would be to capture and + * expose the search_path to the event trigger functions. Then any trigger + * function would have to duplicate the code here to extract the first schema + * in the search_path anyway. + */ + static char * + RangeVarGetNamespace(RangeVar *r) + { + char *schemaname; + List *search_path; + + if (r->schemaname) + return r->schemaname; + + search_path = fetch_search_path(false); + if (search_path == NIL) /* probably can't happen */ + schemaname = NULL; + else + schemaname = get_namespace_name(linitial_oid(search_path)); + + list_free(search_path); + + return schemaname; + } + + static char * + RangeVarToString(RangeVar *r) + { + char *schemaname = RangeVarGetNamespace(r); + StringInfoData string; + initStringInfo(&string); + + if (r->catalogname != NULL) + { + appendStringInfoString(&string, quote_identifier(r->catalogname)); + appendStringInfoChar(&string, '.'); + } + if (schemaname != NULL) + { + appendStringInfoString(&string, quote_identifier(schemaname)); + appendStringInfoChar(&string, '.'); + } + appendStringInfoString(&string, quote_identifier(r->relname)); + + return string.data; + } + + static const char * + objTypeToString(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_CONSTRAINT: + return "CONSTRAINT"; + 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 "FDW"; + case OBJECT_FOREIGN_SERVER: + return "FOREIGN 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_VIEW: + return "VIEW"; + default: + elog(ERROR, "unrecognized object type: %d", objtype); + return NULL; /* silence compiler */ + } + } + + static void + maybeAddSeparator(StringInfo buf, const char *sep, bool *first) + { + if (*first) + *first = false; + else appendStringInfoString(buf, sep); + } + + /* + * Given a raw expression used for a DEFAULT or a CHECK constraint, turn it + * back into source code. + * + * Note we don't apply sanity checks such as the return type of the expression. + */ + static char * + uncookConstraintOrDefault(Node *raw_expr, ParseExprKind kind, + RangeVar *relation, Oid relOid) + { + Node *expr; + char *src; + List *dpcontext = NULL; + ParseState *pstate; + MemoryContext cxt; + MemoryContext oldcxt; + + cxt = AllocSetContextCreate(CurrentMemoryContext, + "uncook context", + ALLOCSET_SMALL_MINSIZE, + ALLOCSET_SMALL_INITSIZE, + ALLOCSET_SMALL_MAXSIZE); + oldcxt = MemoryContextSwitchTo(cxt); + + pstate = make_parsestate(NULL); + if (relation) + { + RangeTblEntry *rte; + + rte = addRangeTableEntry(pstate, relation, NULL, false, true); + addRTEtoQuery(pstate, rte, true, true, true); + dpcontext = deparse_context_for(relation->relname, relOid); + } + + /* + * Transform raw parsetree to executable expression. + */ + expr = transformExpr(pstate, raw_expr, kind); + + /* + * Take care of collations. + */ + assign_expr_collations(pstate, expr); + + src = deparse_expression(expr, dpcontext, false, false); + + free_parsestate(pstate); + + MemoryContextSwitchTo(oldcxt); + + /* copy to destination context before destroying our own */ + /* XXX maybe we should switch just before deparse_expression? */ + src = pstrdup(src); + MemoryContextDelete(cxt); + + return src; + } + + /* + * rewrite any_name parser production + */ + static void + _rwAnyName(StringInfo buf, List *name) + { + bool first = true; + ListCell *lc; + + foreach(lc, name) + { + Value *member = (Value *) lfirst(lc); + + maybeAddSeparator(buf, ".", &first); + appendStringInfo(buf, "%s", member->val.str); + } + } + + static char * + _rwCreateExtensionStmt(Oid objectId, const char *identity, Node *parsetree) + { + CreateExtensionStmt *node = (CreateExtensionStmt *) parsetree; + StringInfoData buf; + ListCell *lc; + + initStringInfo(&buf); + appendStringInfo(&buf, "CREATE EXTENSION%s %s", + node->if_not_exists ? " IF NOT EXISTS" : "", + identity); + + foreach(lc, node->options) + { + DefElem *defel = (DefElem *) lfirst(lc); + + if (strcmp(defel->defname, "schema") == 0) + appendStringInfo(&buf, " SCHEMA %s", strVal(defel->arg)); + + else if (strcmp(defel->defname, "new_version") == 0) + appendStringInfo(&buf, " VERSION %s", strVal(defel->arg)); + + else if (strcmp(defel->defname, "old_version") == 0) + appendStringInfo(&buf, " FROM %s", strVal(defel->arg)); + } + + return buf.data; + } + + static char * + _rwViewStmt(Oid objectId, const char *identity, Node *parsetree) + { + ViewStmt *node = (ViewStmt *) parsetree; + StringInfoData buf; + Query *viewParse; + char *viewsrc; + + initStringInfo(&buf); + viewParse = parse_analyze(node->query, + "(unavailable source text)", NULL, 0); + + appendStringInfo(&buf, "CREATE %sVIEW %s AS ", + node->replace? "OR REPLACE": "", + identity); + + viewsrc = pg_get_viewstmt_definition(viewParse); + appendStringInfoString(&buf, viewsrc); + pfree(viewsrc); + + return buf.data; + } + + /* + * Rewrite a OptTableSpace: grammar production + */ + static void + _rwOptTableSpace(StringInfo buf, const char *name) + { + if (name != NULL) + appendStringInfo(buf, " TABLESPACE %s", name); + } + + /* + * Rewrite a OptConsTableSpace: grammar production + */ + static void + _rwOptConsTableSpace(StringInfo buf, const char *name) + { + if (name != NULL) + appendStringInfo(buf, " USING INDEX TABLESPACE %s", name); + } + + /* + * Rewrite a generic def_arg: grammar production + */ + static void + _rwDefArg(StringInfo buf, Node *arg) + { + appendStringInfoChar(buf, '?'); + } + + /* + * Rewrite a generic definition grammar production + */ + static void + _rwDefinition(StringInfo buf, List *definitions) + { + /* FIXME: needs an option to print () when empty? */ + if (definitions != NULL) + { + ListCell *k; + bool first = true; + + appendStringInfoChar(buf, '('); + foreach(k, definitions) + { + DefElem *def = (DefElem *) lfirst(k); + + elog(LOG, "dumping definition element: %s", + nodeToString(def)); + + maybeAddSeparator(buf, ", ", &first); + + /* ColLabel */ + appendStringInfo(buf, "%s", def->defname); + /* [ '=' def_arg ] */ + if (def->arg) + { + appendStringInfoChar(buf, '='); + _rwDefArg(buf, def->arg); + } + + } + appendStringInfoChar(buf, ')'); + } + } + + /* + * Rewrite the opt_column_list: grammar production + */ + static void + _rwOptColumnList(StringInfo buf, List *clist) + { + if (clist != NIL) + { + ListCell *c; + bool first = true; + + appendStringInfoChar(buf, '('); + foreach(c, clist) + { + maybeAddSeparator(buf, ",", &first); + appendStringInfo(buf, "%s", strVal(lfirst(c))); + } + appendStringInfoChar(buf, ')'); + } + } + + /* + * Rewrite the opt_column_list: grammar production + */ + static void + _rwColumnList(StringInfo buf, List *clist) + { + if (clist == NIL) + appendStringInfo(buf, "()"); + else + _rwOptColumnList(buf, clist); + } + + /* + * Rewrite the key_match: grammar production + */ + static void + _rwKeyMatch(StringInfo buf, int matchtype) + { + switch (matchtype) + { + case FKCONSTR_MATCH_FULL: + appendStringInfo(buf, " MATCH FULL"); + break; + + case FKCONSTR_MATCH_PARTIAL: + /* should not happen, not yet implemented */ + appendStringInfo(buf, " MATCH PARTIAL"); + break; + + case FKCONSTR_MATCH_SIMPLE: + default: + appendStringInfo(buf, " MATCH SIMPLE"); + break; + } + } + + /* + * Rewrite the key_action: grammar production + */ + static void + _rwKeyAction(StringInfo buf, int action) + { + switch (action) + { + case FKCONSTR_ACTION_NOACTION: + appendStringInfo(buf, " NO ACTION"); + break; + + case FKCONSTR_ACTION_RESTRICT: + appendStringInfo(buf, " RESTRICT"); + break; + + case FKCONSTR_ACTION_CASCADE: + appendStringInfo(buf, " CASCADE"); + break; + + case FKCONSTR_ACTION_SETNULL: + appendStringInfo(buf, " SET NULL"); + break; + + case FKCONSTR_ACTION_SETDEFAULT: + appendStringInfo(buf, " SET DEFAULT"); + break; + + default: + elog(ERROR, "unexpected foreign key action: %d", action); + break; + } + } + + static void + _rwKeyActions(StringInfo buf, int upd_action, int del_action) + { + appendStringInfo(buf, " ON UPDATE"); + _rwKeyAction(buf, upd_action); + + appendStringInfo(buf, " ON DELETE"); + _rwKeyAction(buf, del_action); + } + + /* + * Rewrite the ConstraintAttributeSpec parser production + */ + static void + _rwConstraintAttributeSpec(StringInfo buf, + bool deferrable, bool initdeferred) + { + if (deferrable) + appendStringInfo(buf, " DEFERRABLE"); + else + appendStringInfo(buf, " NOT DEFERRABLE"); + + if (initdeferred) + appendStringInfo(buf, " INITIALLY DEFERRED"); + else + appendStringInfo(buf, " INITIALLY IMMEDIATE"); + } + + static void + _rwRelPersistence(StringInfo buf, int relpersistence) + { + switch (relpersistence) + { + case RELPERSISTENCE_TEMP: + appendStringInfo(buf, " TEMPORARY"); + break; + + case RELPERSISTENCE_UNLOGGED: + appendStringInfo(buf, " UNLOGGED"); + break; + + case RELPERSISTENCE_PERMANENT: + default: + break; + } + } + + /* + * rewrite the ColConstraintElem grammar production + * + * Not all constraint types can be expected here, and some of them can be found + * with other grammars as table level constraint attributes. + */ + static void + _rwColConstraintElem(StringInfo buf, Oid objectId, List *constraints, + RangeVar *relation) + { + ListCell *lc; + + foreach(lc, constraints) + { + Constraint *c = (Constraint *) lfirst(lc); + Assert(IsA(c, Constraint)); + + if (c->conname != NULL) + appendStringInfo(buf, " CONSTRAINT %s", c->conname); + + switch (c->contype) + { + case CONSTR_NOTNULL: + appendStringInfo(buf, " NOT NULL"); + break; + + case CONSTR_NULL: + appendStringInfo(buf, " NULL"); + break; + + case CONSTR_UNIQUE: + appendStringInfo(buf, " UNIQUE"); + _rwOptConsTableSpace(buf, c->indexspace); + break; + + case CONSTR_PRIMARY: + appendStringInfo(buf, " PRIMARY KEY"); + _rwDefinition(buf, c->options); + _rwOptConsTableSpace(buf, c->indexspace); + break; + + case CONSTR_CHECK: + { + char *src; + + src = uncookConstraintOrDefault(c->raw_expr, + EXPR_KIND_CHECK_CONSTRAINT, + relation, + objectId); + appendStringInfo(buf, " CHECK (%s)", src); + pfree(src); + break; + } + + case CONSTR_DEFAULT: + { + if (c->cooked_expr) + { + List *dpcontext; + Node *expr = (Node *) stringToNode(c->cooked_expr); + char *consrc; + + dpcontext = deparse_context_for(relation->relname, + objectId); + consrc = deparse_expression(expr, dpcontext, false, false); + + appendStringInfo(buf, " DEFAULT %s", consrc); + } + else if (c->raw_expr) + { + char *src; + + /* deparse the default expression */ + src = uncookConstraintOrDefault(c->raw_expr, + EXPR_KIND_COLUMN_DEFAULT, + relation, objectId); + + appendStringInfo(buf, " DEFAULT %s", src); + } + break; + } + + case CONSTR_FOREIGN: + { + appendStringInfo(buf, " REFERENCES %s ", + RangeVarToString(c->pktable)); + _rwOptColumnList(buf, c->pk_attrs); + _rwKeyMatch(buf, c->fk_matchtype); + _rwKeyActions(buf, c->fk_upd_action, c->fk_del_action); + break; + } + + default: + /* unexpected case, WARNING? */ + elog(WARNING, "constraint %d is not a column constraint", + c->contype); + break; + } + } + } + + /* + * rewrite a list of TableConstraint: grammar production + */ + static void + _rwTableConstraint(StringInfo buf, Oid objectId, List *constraints, + RangeVar *relation) + { + ListCell *lc; + List *context = NIL; + + foreach(lc, constraints) + { + Constraint *c = (Constraint *) lfirst(lc); + Assert(IsA(c, Constraint)); + + if (c->conname != NULL) + appendStringInfo(buf, " CONSTRAINT %s", c->conname); + + switch (c->contype) + { + case CONSTR_CHECK: + { + char *src; + + src = uncookConstraintOrDefault(c->raw_expr, + EXPR_KIND_CHECK_CONSTRAINT, + relation, + objectId); + appendStringInfo(buf, " CHECK (%s)", src); + pfree(src); + } + break; + + case CONSTR_UNIQUE: + appendStringInfo(buf, " UNIQUE"); + + if (c->keys) + { + /* unique (column, list) */ + _rwColumnList(buf, c->keys); + _rwDefinition(buf, c->options); + _rwConstraintAttributeSpec(buf, + c->deferrable, c->initdeferred); + _rwOptConsTableSpace(buf, c->indexspace); + } + else + { + /* unique using index */ + appendStringInfo(buf, " USING INDEX %s", c->indexname); + _rwConstraintAttributeSpec(buf, + c->deferrable, c->initdeferred); + } + break; + + case CONSTR_PRIMARY: + appendStringInfo(buf, " PRIMARY KEY"); + + if (c->keys) + { + /* primary key (column, list) */ + _rwColumnList(buf, c->keys); + _rwDefinition(buf, c->options); + _rwOptConsTableSpace(buf, c->indexspace); + _rwConstraintAttributeSpec(buf, + c->deferrable, c->initdeferred); + } + else + { + /* primary key using index */ + appendStringInfo(buf, " USING INDEX %s", c->indexname); + _rwConstraintAttributeSpec(buf, + c->deferrable, c->initdeferred); + } + break; + + case CONSTR_EXCLUSION: + appendStringInfo(buf, " EXCLUDE %s ", c->access_method); + + if (c->exclusions == NULL) + appendStringInfo(buf, "()"); + else + { + /* ExclusionConstraintList */ + ListCell *e; + bool first = true; + + appendStringInfoChar(buf, '('); + foreach(e, c->exclusions) + { + List *ec = (List *)lfirst(e); + + maybeAddSeparator(buf, ",", &first); + + /* ExclustionConstraintElem */ + appendStringInfo(buf, "%s WITH OPERATOR(%s)", + strVal(linitial(ec)), + strVal(lsecond(ec))); + } + appendStringInfoChar(buf, ')'); + } + _rwDefinition(buf, c->options); + _rwOptConsTableSpace(buf, c->indexspace); + + /* ExclusionWhereClause: */ + if (c->where_clause) + { + char *str; + + if (context == NIL) + context = deparse_context_for(relation->relname, + objectId); + + str = deparse_expression(c->where_clause, context, + false, false); + + appendStringInfo(buf, " WHERE (%s)", str); + } + + _rwConstraintAttributeSpec(buf, + c->deferrable, c->initdeferred); + break; + + case CONSTR_FOREIGN: + appendStringInfoString(buf, " FOREIGN KEY"); + + _rwColumnList(buf, c->fk_attrs); + + appendStringInfo(buf, " REFERENCES %s", + RangeVarToString(c->pktable)); + + _rwOptColumnList(buf, c->pk_attrs); + + _rwKeyMatch(buf, c->fk_matchtype); + _rwKeyActions(buf, c->fk_upd_action, c->fk_del_action); + + _rwConstraintAttributeSpec(buf, + c->deferrable, c->initdeferred); + + if (c->skip_validation) + appendStringInfoString(buf, " NOT VALID"); + break; + + default: + /* unexpected case, WARNING? */ + elog(WARNING, "constraint %d is not a column constraint", + c->contype); + break; + } + } + } + + /* + * rewrite TableLikeOptionList parser production + */ + static void + _rwTableLikeOptionList(StringInfo buf, bits32 options) + { + if (options == CREATE_TABLE_LIKE_ALL) + appendStringInfo(buf, " INCLUDING ALL"); + else + { + if (options & CREATE_TABLE_LIKE_DEFAULTS) + appendStringInfo(buf, " INCLUDING DEFAULTS"); + + if (options & CREATE_TABLE_LIKE_CONSTRAINTS) + appendStringInfo(buf, " INCLUDING CONSTRAINTS"); + + if (options & CREATE_TABLE_LIKE_INDEXES) + appendStringInfo(buf, " INCLUDING INDEXES"); + + if (options & CREATE_TABLE_LIKE_STORAGE) + appendStringInfo(buf, " INCLUDING STORAGE"); + + if (options & CREATE_TABLE_LIKE_COMMENTS) + appendStringInfo(buf, " INCLUDING COMMENTS"); + } + } + + /* + * rewrite OptTableElementList parser production + */ + static void + _rwOptTableElementList(StringInfo buf, Oid objectId, List *tableElts, + RangeVar *relation) + { + bool first = true; + ListCell *e; + + appendStringInfoChar(buf, '('); + + foreach(e, tableElts) + { + Node *elt = (Node *) lfirst(e); + + maybeAddSeparator(buf, ", ", &first); + + switch (nodeTag(elt)) + { + case T_ColumnDef: + { + ColumnDef *c = (ColumnDef *) elt; + + appendStringInfo(buf, "%s %s", + c->colname, + TypeNameToString(c->typeName)); + /* XXX this might need conditional supression in some cases */ + _rwColConstraintElem(buf, objectId, c->constraints, relation); + break; + } + case T_TableLikeClause: + { + TableLikeClause *like = (TableLikeClause *) elt; + appendStringInfo(buf, "LIKE %s", + RangeVarToString(like->relation)); + _rwTableLikeOptionList(buf, like->options); + break; + } + case T_Constraint: + { + Constraint *c = (Constraint *) elt; + _rwTableConstraint(buf, objectId, list_make1(c), relation); + break; + } + default: + break; + } + } + appendStringInfoChar(buf, ')'); + } + + /* + * rewrite OptTypedTableElementList parser production + */ + static void + _rwOptTypedTableElementList(StringInfo buf, Oid objectId, List *tableElts, + RangeVar *relation) + { + bool first = true, + parens = false; + ListCell *e; + + foreach(e, tableElts) + { + Node *elt = (Node *) lfirst(e); + + switch (nodeTag(elt)) + { + case T_ColumnDef: + { + ColumnDef *c = (ColumnDef *) elt; + + if (c->constraints) + { + maybeAddSeparator(buf, ",", &first); + + /* only add parens if we have columns with options */ + if (!parens) + { + appendStringInfoChar(buf, '('); + parens = true; + } + appendStringInfo(buf, " %s WITH OPTIONS", c->colname); + _rwColConstraintElem(buf, objectId, c->constraints, + relation); + } + break; + } + case T_Constraint: + { + Constraint *c = (Constraint *) elt; + + _rwTableConstraint(buf, objectId, list_make1(c), relation); + break; + } + default: + break; + } + } + + if (parens) + appendStringInfoChar(buf, ')'); + } + + /* + * rewrite OptInherit parser production + */ + static void + _rwOptInherit(StringInfo buf, List *inhRelations) + { + if (inhRelations) + { + ListCell *lc; + bool first = true; + + appendStringInfo(buf, " INHERITS ("); + foreach(lc, inhRelations) + { + RangeVar *inh = (RangeVar *) lfirst(lc); + + maybeAddSeparator(buf, ",", &first); + appendStringInfo(buf, "%s", RangeVarToString(inh)); + } + appendStringInfoChar(buf, ')'); + } + } + + /* + * rewrite reloptions parser production + */ + static void + _rwRelOptions(StringInfo buf, List *options, bool null_is_true) + { + bool first = true; + ListCell *lc; + + foreach(lc, options) + { + DefElem *def = (DefElem *) lfirst(lc); + const char *value; + + maybeAddSeparator(buf, ", ", &first); + + if (def->arg != NULL) + { + /* ... defname = value */ + value = defGetString(def); + appendStringInfo(buf, "%s=%s", def->defname, value); + } + else + { + if (null_is_true) + { + /* ... defname = true */ + appendStringInfo(buf, "%s=true", def->defname); + } + else + { + /* ... defname */ + appendStringInfo(buf, "%s", def->defname); + } + } + } + } + + /* + * rewrite OptWith parser production + */ + static void + _rwOptWith(StringInfo buf, List *options) + { + if (options) + { + appendStringInfoString(buf, " WITH ("); + _rwRelOptions(buf, options, true); + appendStringInfoChar(buf, ')'); + } + } + + /* + * rewrite OptCommitOption parser production + */ + static void + _rwOnCommitOption(StringInfo buf, int oncommit) + { + switch (oncommit) + { + case ONCOMMIT_DROP: + appendStringInfo(buf, " ON COMMIT DROP"); + break; + + case ONCOMMIT_DELETE_ROWS: + appendStringInfo(buf, " ON COMMIT DELETE ROWS"); + break; + + case ONCOMMIT_PRESERVE_ROWS: + appendStringInfo(buf, " ON COMMIT PRESERVE ROWS"); + break; + + case ONCOMMIT_NOOP: + /* EMPTY */ + break; + + } + } + + /* + * rewrite CreateStmt parser production + */ + static char * + _rwCreateStmt(Oid objectId, const char *identity, Node *parsetree) + { + CreateStmt *node = (CreateStmt *) parsetree; + StringInfoData buf; + + initStringInfo(&buf); + appendStringInfoString(&buf, "CREATE"); + _rwRelPersistence(&buf, node->relation->relpersistence); + appendStringInfo(&buf, " TABLE %s %s", identity, + node->if_not_exists ? " IF NOT EXISTS" : ""); + + if (node->ofTypename) + { + appendStringInfo(&buf, "OF %s", + TypeNameToString(node->ofTypename)); + _rwOptTypedTableElementList(&buf, objectId, node->tableElts, + node->relation); + } + else + { + List *elts = list_concat(node->tableElts, node->constraints); + + _rwOptTableElementList(&buf, objectId, elts, node->relation); + _rwOptInherit(&buf, node->inhRelations); + } + + _rwOptWith(&buf, node->options); + _rwOnCommitOption(&buf, node->oncommit); + _rwOptTableSpace(&buf, node->tablespacename); + + return buf.data; + } + + /* + * rewrite OptSeqOptList parser production + */ + static void + _rwOptSeqOptList(StringInfo buf, List *options) + { + ListCell *opt; + + foreach(opt, options) + { + DefElem *defel = (DefElem *) lfirst(opt); + + if (strcmp(defel->defname, "cache") == 0) + { + char num[100]; + + snprintf(num, sizeof(num), INT64_FORMAT, defGetInt64(defel)); + appendStringInfo(buf, " CACHE %s", num); + } + else if (strcmp(defel->defname, "cycle") == 0) + { + if (intVal(defel)) + appendStringInfo(buf, " CYCLE"); + else + appendStringInfo(buf, " NO CYCLE"); + } + else if (strcmp(defel->defname, "increment") == 0) + { + char num[100]; + + snprintf(num, sizeof(num), INT64_FORMAT, defGetInt64(defel)); + appendStringInfo(buf, " INCREMENT BY %s", num); + } + else if (strcmp(defel->defname, "maxvalue") == 0) + { + if (defel->arg) + { + char num[100]; + + snprintf(num, sizeof(num), INT64_FORMAT, defGetInt64(defel)); + appendStringInfo(buf, " MAXVALUE %s", num); + } + else + appendStringInfo(buf, " NO MAXVALUE"); + } + else if (strcmp(defel->defname, "minvalue") == 0) + { + if (defel->arg) + { + char num[100]; + + snprintf(num, sizeof(num), INT64_FORMAT, defGetInt64(defel)); + appendStringInfo(buf, " MINVALUE %s", num); + } + else + appendStringInfo(buf, " NO MINVALUE"); + } + else if (strcmp(defel->defname, "owned_by") == 0) + { + List *owned_by = defGetQualifiedName(defel); + int nnames = list_length(owned_by); + List *relname; + char *attrname; + RangeVar *rel; + + relname = list_truncate(list_copy(owned_by), nnames - 1); + attrname = strVal(lfirst(list_tail(owned_by))); + rel = makeRangeVarFromNameList(relname); + + appendStringInfo(buf, " OWNED BY %s.%s", + RangeVarToString(rel), attrname); + } + else if (strcmp(defel->defname, "start") == 0) + { + char num[100]; + + snprintf(num, sizeof(num), INT64_FORMAT, defGetInt64(defel)); + appendStringInfo(buf, " START WITH %s", num); + } + else if (strcmp(defel->defname, "restart") == 0) + { + if (defel->arg) + { + char num[100]; + + snprintf(num, sizeof(num), INT64_FORMAT, defGetInt64(defel)); + appendStringInfo(buf, " RESTART WITH %s", num); + } + else + appendStringInfo(buf, " RESTART"); + } + } + } + + /* + * rewrite CreateSeqStmt parser production + */ + static char * + _rwCreateSeqStmt(Oid objectId, const char *identity, Node *parsetree) + { + CreateSeqStmt *node = (CreateSeqStmt *) parsetree; + StringInfoData buf; + + initStringInfo(&buf); + appendStringInfo(&buf, "CREATE"); + _rwRelPersistence(&buf, node->sequence->relpersistence); + appendStringInfo(&buf, " SEQUENCE %s ", identity); + _rwOptSeqOptList(&buf, node->options); + + return buf.data; + } + + /* + * rewrite index_elem parser production + */ + static void + _rwIndexElem(StringInfo buf, IndexElem *e, List *context) + { + if (e->name) + appendStringInfo(buf, "%s", e->name); + else + { + char *str; + + str = deparse_expression(e->expr, context, false, false); + + /* Need parens if it's not a bare function call */ + if (IsA(e->expr, FuncExpr) && + ((FuncExpr *) e->expr)->funcformat == COERCE_EXPLICIT_CALL) + appendStringInfo(buf, "%s", str); + else + appendStringInfo(buf, "(%s)", str); + } + + if (e->collation) + { + appendStringInfo(buf, " COLLATE"); + _rwAnyName(buf, e->collation); + } + + if (e->opclass) + { + appendStringInfo(buf, " USING "); + _rwAnyName(buf, e->opclass); + } + + /* defensive coding, so that the compiler hints us into updating those bits + * if needs be */ + switch (e->ordering) + { + /* using unexpected in create index */ + case SORTBY_DEFAULT: + case SORTBY_USING: + break; + + case SORTBY_ASC: + appendStringInfo(buf, " ASC"); + break; + + case SORTBY_DESC: + appendStringInfo(buf, " DESC"); + break; + } + switch (e->nulls_ordering) + { + case SORTBY_NULLS_DEFAULT: + break; + + case SORTBY_NULLS_FIRST: + appendStringInfo(buf, " NULLS FIRST"); + break; + + case SORTBY_NULLS_LAST: + appendStringInfo(buf, " NULLS LAST"); + break; + } + } + + /* + * rewrite IndexStmt parser production + */ + static char * + _rwCreateIndexStmt(Oid objectId, const char *identity, Node *parsetree) + { + IndexStmt *node = (IndexStmt *) parsetree; + StringInfoData buf; + Oid relId; + bool first = true; + ListCell *lc; + List *context; + + initStringInfo(&buf); + appendStringInfo(&buf, "CREATE%s INDEX", node->unique ? " UNIQUE" : ""); + + if (node->concurrent) + appendStringInfo(&buf, " CONCURRENTLY"); + + appendStringInfo(&buf, " %s ON %s USING %s (", + identity, + RangeVarToString(node->relation), + node->accessMethod); + + relId = IndexGetRelation(objectId, false); + context = deparse_context_for("", relId); /* alias is not important */ + + foreach(lc, node->indexParams) + { + IndexElem *e = (IndexElem *) lfirst(lc); + + maybeAddSeparator(&buf, ", ", &first); + _rwIndexElem(&buf, e, context); + } + appendStringInfoChar(&buf, ')'); + + _rwOptWith(&buf, node->options); + _rwOptConsTableSpace(&buf, node->tableSpace); + + if (node->whereClause) + { + Node *transformed; + ParseState *pstate; + char *str; + RangeTblEntry *rte; + + pstate = make_parsestate(NULL); + rte = addRangeTableEntry(pstate, node->relation, + NULL, false, true); + addRTEtoQuery(pstate, rte, true, true, true); + + transformed = transformExpr(pstate, node->whereClause, + EXPR_KIND_INDEX_PREDICATE); + + str = deparse_expression(transformed, context, false, false); + /* deparse_expression adds outer parens */ + appendStringInfo(&buf, " WHERE %s", str); + } + + + return buf.data; + } + + /* + * rewrite func_arg parser production + */ + static void + _rwFuncArg(StringInfo buf, FunctionParameter *fp) + { + /* Parameter's mode */ + switch (fp->mode) + { + case FUNC_PARAM_INOUT: + appendStringInfoString(buf, "IN OUT"); + break; + + case FUNC_PARAM_IN: + appendStringInfoString(buf, "IN"); + break; + + case FUNC_PARAM_OUT: + appendStringInfoString(buf, "OUT"); + break; + + case FUNC_PARAM_VARIADIC: + appendStringInfoString(buf, "VARIADIC"); + break; + + case FUNC_PARAM_TABLE: + elog(ERROR, "FUNC_PARAM_TABLE not expected in argument list"); + break; + } + + /* Parameter's name is optional */ + if (fp->name) + appendStringInfo(buf, " %s", fp->name); + + /* Parameter's type name is not */ + appendStringInfo(buf, " %s", TypeNameToString(fp->argType)); + + if (fp->defexpr) + { + char *src; + + src = uncookConstraintOrDefault(fp->defexpr, + EXPR_KIND_FUNCTION_DEFAULT, + NULL, InvalidOid); + + appendStringInfo(buf, " DEFAULT %s", src); + pfree(src); + } + } + + /* + * rewrite CreateFunctionStmt parser production + */ + static char * + _rwCreateFunctionStmt(Oid objectId, const char *identity, Node *parsetree) + { + CreateFunctionStmt *node = (CreateFunctionStmt *) parsetree; + StringInfoData buf; + HeapTuple funcTup; + Form_pg_proc func; + char *schema; + char *qualifiedName; + + #if 0 + char *fname, *nspname; + Oid namespaceId, languageOid; + ListCell *x; + Oid prorettype; + bool returnsSet; + List *as_clause; + char *language; + bool isWindowFunc, isStrict, security, isLeakProof; + char volatility = PROVOLATILE_VOLATILE; + ArrayType *proconfig; + float4 procost = -1; + float4 prorows = -1; + char *procost_str, *prorows_str; + char *probin_str; + char *prosrc_str; + HeapTuple languageTuple; + #endif + + initStringInfo(&buf); + + /* + * We cannot use the passed identity here: it includes only IN parameters, + * but that's not sufficient to write a CREATE FUNCTION command. So + * fetch the name on our own. + */ + funcTup = SearchSysCache1(PROCOID, objectId); + func = (Form_pg_proc) GETSTRUCT(funcTup); + + schema = get_namespace_name(func->pronamespace); + qualifiedName = quote_qualified_identifier(schema, + NameStr(func->proname)); + appendStringInfo(&buf, "CREATE FUNCTION %s ", + qualifiedName); + pfree(schema); + pfree(qualifiedName); + + /* Emit complete parameter list */ + if (node->parameters) + { + ListCell *lc; + bool first = true; + + appendStringInfoChar(&buf, '('); + + foreach(lc, node->parameters) + { + FunctionParameter *fp = (FunctionParameter *) lfirst(lc); + + maybeAddSeparator(&buf, ", ", &first); + /* direction, name, type, default */ + _rwFuncArg(&buf, fp); + } + appendStringInfoChar(&buf, ')'); + } + + #if 0 + + /* return type */ + compute_return_type(node->returnType, languageOid, &prorettype, &returnsSet); + appendStringInfo(&buf, " RETURNS %s", TypeNameToString(node->returnType)); + + /* get options and attributes */ + compute_attributes_sql_style(node->options, + &as_clause, &language, + &isWindowFunc, &volatility, + &isStrict, &security, &isLeakProof, + &proconfig, &procost, &prorows); + + languageTuple = SearchSysCache1(LANGNAME, PointerGetDatum(language)); + languageOid = HeapTupleGetOid(languageTuple); + ReleaseSysCache(languageTuple); + + /* also get the function's body */ + interpret_AS_clause(languageOid, language, fname, as_clause, + &prosrc_str, &probin_str, + returnsSet, &procost, &prorows); + + /* language */ + appendStringInfo(&buf, " LANGUAGE %s", language); + + /* options */ + if (isWindowFunc) + appendStringInfo(&buf, " WINDOW"); + + switch (volatility) + { + case PROVOLATILE_IMMUTABLE: + appendStringInfo(&buf, " IMMUTABLE"); + break; + case PROVOLATILE_STABLE: + appendStringInfo(&buf, " STABLE"); + break; + case PROVOLATILE_VOLATILE: + appendStringInfo(&buf, " VOLATILE"); + break; + } + appendStringInfo(&buf, " %sLEAKPROOF", isLeakProof ? "" : "NOT "); + + if (isStrict) + appendStringInfo(&buf, " RETURNS NULL ON NULL INPUT"); + else + appendStringInfo(&buf, " CALLED ON NULL INPUT"); + + /* friendly output for cost and rows */ + procost_str = DatumGetCString( + DirectFunctionCall1(float4out, Float4GetDatum(procost))); + prorows_str = DatumGetCString( + DirectFunctionCall1(float4out, Float4GetDatum(prorows))); + + appendStringInfo(&buf, " COST %s", procost_str); + appendStringInfo(&buf, " ROWS %s", prorows_str); + + /* body */ + appendStringInfo(&buf, " AS $%s$ %s $%s$;", fname, prosrc_str, fname); + #endif + + ReleaseSysCache(funcTup); + + return buf.data; + } + + /* + * rewrite CreateSchemaStmt parser production + * + * We don't bother with OptSchemaEltList, those will get back each separately + * as new ProcessUtility queries with a SUBCOMMAND context. + */ + static char * + _rwCreateSchemaStmt(Oid objectId, const char *identity, Node *parsetree) + { + CreateSchemaStmt *node = (CreateSchemaStmt *) parsetree; + StringInfoData buf; + + initStringInfo(&buf); + appendStringInfo(&buf, "CREATE SCHEMA%s %s", + node->if_not_exists ? " IF NOT EXISTS" : "", + identity); + + if (node->authid) + appendStringInfo(&buf, " AUTHORIZATION %s", node->authid); + + return buf.data; + } + + /* + * rewrite CreateConversionStmt parser production + */ + static char * + _rwCreateConversionStmt(Oid objectId, const char *identity, Node *parsetree) + { + CreateConversionStmt *node = (CreateConversionStmt *) parsetree; + StringInfoData buf; + StringInfoData fnamebuf; + + initStringInfo(&buf); + initStringInfo(&fnamebuf); + + _rwAnyName(&fnamebuf, node->func_name); + + appendStringInfo(&buf, "CREATE%s CONVERSION %s FOR %s TO %s FROM %s;", + node->def ? " DEFAULT" : "", + identity, + node->for_encoding_name, + node->to_encoding_name, + fnamebuf.data); + pfree(fnamebuf.data); + + return buf.data; + } + + /* + * rewrite DefineStmt parser productions + */ + static char * + _rwDefineStmt(Oid objectId, const char *identity, Node *parsetree) + { + DefineStmt *node = (DefineStmt *) parsetree; + StringInfoData buf; + ListCell *opt; + bool first = true; + + initStringInfo(&buf); + + appendStringInfo(&buf, "CREATE %s %s ", objTypeToString(node->kind), + identity); + + if (node->definition) + { + appendStringInfoChar(&buf, '('); + + /* definition: grammar production */ + foreach(opt, node->definition) + { + DefElem *defel = (DefElem *) lfirst(opt); + + maybeAddSeparator(&buf, ", ", &first); + + if (defel->arg != NULL) + { + Node *arg = (Node *)defel->arg; + + appendStringInfo(&buf, "%s=", defel->defname); + + /* def_arg: grammar production */ + switch (nodeTag(arg)) + { + /* func_type */ + case T_TypeName: + appendStringInfoString(&buf, + TypeNameToString((TypeName *)arg)); + break; + + /* reserved_keyword or Sconst */ + case T_String: + appendStringInfo(&buf, "'%s'", defGetString(defel)); + break; + + /* qual_all_Op */ + case T_List: + appendStringInfo(&buf, "OPERATOR (%s)", + strVal((Value *)linitial((List *)arg))); + break; + + /* NumericOnly */ + case T_Float: + appendStringInfo(&buf, "%g", defGetNumeric(defel)); + break; + + /* NumericOnly */ + case T_Integer: + { + char num[100]; + + snprintf(num, sizeof(num), INT64_FORMAT, defGetInt64(defel)); + appendStringInfo(&buf, "%s", num); + break; + } + + default: + elog(DEBUG1, "unrecognized node type: %d", + (int) nodeTag(arg)); + } + } + else + { + appendStringInfo(&buf, "%s", defel->defname); + } + } + appendStringInfoChar(&buf, ')'); + } + + return buf.data; + } + + /* + * rewrite ColQualList parser production + * + * FIXME this code assumes the ColQualList comes from a domain, but is this + * always the case? + */ + static void + _rwColQualList(StringInfo buf, List *constraints, + const char *domainName, TypeName *typeName) + { + ListCell *lc; + HeapTuple typeTup = NULL; + Oid basetypeoid = InvalidOid; + int32 basetypeMod; + + foreach(lc, constraints) + { + Constraint *c = (Constraint *) lfirst(lc); + Assert(IsA(c, Constraint)); + + if (c->conname != NULL) + appendStringInfo(buf, " CONSTRAINT %s", c->conname); + + switch (c->contype) + { + case CONSTR_NOTNULL: + appendStringInfo(buf, " NOT NULL"); + break; + + case CONSTR_NULL: + appendStringInfo(buf, " NULL"); + break; + + case CONSTR_DEFAULT: + { + char *src; + + src = uncookConstraintOrDefault(c->raw_expr, + EXPR_KIND_COLUMN_DEFAULT, + NULL, InvalidOid); + + appendStringInfo(buf, " DEFAULT %s", src); + break; + } + + case CONSTR_CHECK: + { + Node *expr; + char *ccsrc; + ParseState *pstate; + CoerceToDomainValue *domVal; + + if (typeTup == NULL) + { + typeTup = typenameType(NULL, typeName, &basetypeMod); + basetypeoid = HeapTupleGetOid(typeTup); + } + + domVal = makeNode(CoerceToDomainValue); + domVal->typeId = basetypeoid; + domVal->typeMod = basetypeMod; + domVal->collation = get_typcollation(basetypeoid); + domVal->location = -1; + + pstate = make_parsestate(NULL); + pstate->p_value_substitute = (Node *) domVal; + + expr = transformExpr(pstate, + c->raw_expr, + EXPR_KIND_DOMAIN_CHECK); + + assign_expr_collations(pstate, expr); + + ccsrc = deparse_expression(expr, NIL, false, false); + + appendStringInfo(buf, " CHECK (%s)", ccsrc); + break; + } + + default: + /* not a domain constraint */ + break; + } + } + + if (typeTup) + ReleaseSysCache(typeTup); + } + + /* + * rewrite CreateDomainStmt parser production + */ + static char * + _rwCreateDomainStmt(Oid objectId, const char *identity, Node *parsetree) + { + CreateDomainStmt *node = (CreateDomainStmt *) parsetree; + StringInfoData buf; + + initStringInfo(&buf); + + appendStringInfo(&buf, "CREATE DOMAIN %s AS %s ", + identity, + TypeNameToString(node->typeName)); + + if (node->collClause) + { + Oid collNspId; + char *collname; + + collNspId = + QualifiedNameGetCreationNamespace(node->collClause->collname, + &collname); + + appendStringInfo(&buf, "COLLATE %s.%s ", + get_namespace_name(collNspId), collname); + } + _rwColQualList(&buf, node->constraints, identity, node->typeName); + + return buf.data; + } + + /* + * Given a utility command parsetree and the OID of the corresponding object, + * return a textual representation of the command. + * + * The command is expanded fully, so that there are no ambiguities even in the + * face of search_path changes. + * + * Note we currently only support commands for which ProcessUtilitySlow saves + * objects to create; currently this excludes all forms of ALTER and DROP. + */ + char * + rewrite_utility_command(Oid objectId, const char *identity, Node *parsetree) + { + + switch (nodeTag(parsetree)) + { + case T_CreateSchemaStmt: + return _rwCreateSchemaStmt(objectId, identity, parsetree); + + case T_CreateStmt: + case T_CreateForeignTableStmt: + return _rwCreateStmt(objectId, identity, parsetree); + + case T_DefineStmt: + return _rwDefineStmt(objectId, identity, parsetree); + + case T_IndexStmt: + return _rwCreateIndexStmt(objectId, identity, parsetree); + + case T_CreateExtensionStmt: + return _rwCreateExtensionStmt(objectId, identity, parsetree); + + case T_CreateFdwStmt: + /* XXX these "unsupported" cases need to be filled in for + * final submission */ + return "unsupported CREATE FDW"; + case T_CreateForeignServerStmt: + return "unsupported CREATE FOREIGN SERVER"; + + case T_CreateUserMappingStmt: + return "unsupported CREATE USER MAPPING"; + case T_CompositeTypeStmt: /* CREATE TYPE (composite) */ + case T_CreateEnumStmt: /* CREATE TYPE AS ENUM */ + case T_CreateRangeStmt: /* CREATE TYPE AS RANGE */ + return "unsupported CREATE TYPE"; + + case T_ViewStmt: + return _rwViewStmt(objectId, identity, parsetree); + + case T_CreateFunctionStmt: + return _rwCreateFunctionStmt(objectId, identity, parsetree); + + case T_RuleStmt: + return "unsupported CREATE RULE"; + + case T_CreateSeqStmt: + return _rwCreateSeqStmt(objectId, identity, parsetree); + + case T_CreateTableAsStmt: + return "unsupported CREATE TABLE AS"; + case T_RefreshMatViewStmt: + return "unsupported REFRESH MATERIALIZED VIEW"; + case T_CreateTrigStmt: + return "unsupported CREATE TRIGGER"; + case T_CreatePLangStmt: + return "unsupported CREATE PROCEDURAL LANGUAGE"; + + case T_CreateDomainStmt: + return _rwCreateDomainStmt(objectId, identity, parsetree); + + case T_CreateConversionStmt: + return _rwCreateConversionStmt(objectId, identity, parsetree); + + case T_CreateCastStmt: + return "unsupported CREATE CAST"; + case T_CreateOpClassStmt: + return "unsupported CREATE OPERATOR CLASS"; + case T_CreateOpFamilyStmt: + return "unsupported CREATE OPERATOR FAMILY"; + + default: + elog(LOG, "unrecognized node type: %d", + (int) nodeTag(parsetree)); + } + + return NULL; + } *** a/src/backend/utils/adt/ruleutils.c --- b/src/backend/utils/adt/ruleutils.c *************** *** 4036,4041 **** get_query_def(Query *query, StringInfo buf, List *parentnamespace, --- 4036,4055 ---- } } + char * + pg_get_viewstmt_definition(Query *viewParse) + { + StringInfoData buf; + + initStringInfo(&buf); + + get_query_def(viewParse, &buf, NIL, NULL, 0, + WRAP_COLUMN_DEFAULT, 1); + + return buf.data; + } + + /* ---------- * get_values_def - Parse back a VALUES list * ---------- *** a/src/include/catalog/dependency.h --- b/src/include/catalog/dependency.h *************** *** 176,181 **** extern void recordDependencyOnSingleRelExpr(const ObjectAddress *depender, --- 176,183 ---- extern ObjectClass getObjectClass(const ObjectAddress *object); + extern Oid get_class_catalog(ObjectClass oclass); + extern ObjectAddresses *new_object_addresses(void); extern void add_exact_object_address(const ObjectAddress *object, *** a/src/include/catalog/pg_proc.h --- b/src/include/catalog/pg_proc.h *************** *** 4750,4755 **** DESCR("SP-GiST support for quad tree over range"); --- 4750,4758 ---- /* 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,25,25,25,25}" "{o,o,o,o,o,o,o}" "{classid, objid, objsubid, object_type, schema_name, object_name, object_identity}" _null_ pg_event_trigger_dropped_objects _null_ _null_ _null_ )); DESCR("list objects dropped by the current command"); + DATA(insert OID = 3567 ( pg_event_trigger_get_normalized_commands PGNSP PGUID 12 10 100 0 0 f f f f t t s 0 0 2249 "" "{25,25}" "{o,o}" "{identity,command}" _null_ pg_event_trigger_get_normalized_commands _null_ _null_ _null_ )); + + /* * Symbolic values for provolatile column: these indicate whether the result * of a function is dependent *only* on the values of its explicit arguments, *** /dev/null --- b/src/include/commands/alter_table.h *************** *** 0 **** --- 1,53 ---- + #ifndef ALTER_TABLE_H + #define ALTER_TABLE_H + + /* + * State information for ALTER TABLE + * + * The pending-work queue for an ALTER TABLE is a List of AlteredTableInfo + * structs, one for each table modified by the operation (the named table + * plus any child tables that are affected). We save lists of subcommands + * to apply to this table (possibly modified by parse transformation steps); + * these lists will be executed in Phase 2. If a Phase 3 step is needed, + * necessary information is stored in the constraints and newvals lists. + * + * Phase 2 is divided into multiple passes; subcommands are executed in + * a pass determined by subcommand type. + */ + + #define AT_PASS_UNSET -1 /* UNSET will cause ERROR */ + #define AT_PASS_DROP 0 /* DROP (all flavors) */ + #define AT_PASS_ALTER_TYPE 1 /* ALTER COLUMN TYPE */ + #define AT_PASS_OLD_INDEX 2 /* re-add existing indexes */ + #define AT_PASS_OLD_CONSTR 3 /* re-add existing constraints */ + #define AT_PASS_COL_ATTRS 4 /* set other column attributes */ + /* We could support a RENAME COLUMN pass here, but not currently used */ + #define AT_PASS_ADD_COL 5 /* ADD COLUMN */ + #define AT_PASS_ADD_INDEX 6 /* ADD indexes */ + #define AT_PASS_ADD_CONSTR 7 /* ADD constraints, defaults */ + #define AT_PASS_MISC 8 /* other stuff */ + #define AT_NUM_PASSES 9 + + typedef struct AlteredTableInfo + { + /* Information saved before any work commences: */ + Oid relid; /* Relation to work on */ + char relkind; /* Its relkind */ + TupleDesc oldDesc; /* Pre-modification tuple descriptor */ + /* Information saved by Phase 1 for Phase 2: */ + List *subcmds[AT_NUM_PASSES]; /* Lists of AlterTableCmd */ + /* Information saved by Phases 1/2 for Phase 3: */ + List *constraints; /* List of NewConstraint */ + List *newvals; /* List of NewColumnValue */ + bool new_notnull; /* T if we added new NOT NULL constraints */ + bool rewrite; /* T if a rewrite is forced */ + Oid newTableSpace; /* new tablespace; 0 means no change */ + /* Objects to rebuild after completing ALTER TYPE operations */ + List *changedConstraintOids; /* OIDs of constraints to rebuild */ + List *changedConstraintDefs; /* string definitions of same */ + List *changedIndexOids; /* OIDs of indexes to rebuild */ + List *changedIndexDefs; /* string definitions of same */ + } AlteredTableInfo; + + + #endif /* ALTER_TABLE_H */ *** a/src/include/commands/createas.h --- b/src/include/commands/createas.h *************** *** 19,25 **** #include "tcop/dest.h" ! extern void ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString, ParamListInfo params, char *completionTag); extern int GetIntoRelEFlags(IntoClause *intoClause); --- 19,25 ---- #include "tcop/dest.h" ! extern Oid ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString, ParamListInfo params, char *completionTag); extern int GetIntoRelEFlags(IntoClause *intoClause); *** a/src/include/commands/event_trigger.h --- b/src/include/commands/event_trigger.h *************** *** 49,54 **** extern void EventTriggerSQLDrop(Node *parsetree); --- 49,56 ---- extern bool EventTriggerBeginCompleteQuery(void); extern void EventTriggerEndCompleteQuery(void); + extern void EventTriggerStashCreatedObject(Oid objectId, Oid classId, + Node *parsetree); extern bool trackDroppedObjectsNeeded(void); extern void EventTriggerSQLDropAddObject(ObjectAddress *object); *** a/src/include/commands/matview.h --- b/src/include/commands/matview.h *************** *** 22,28 **** extern void SetMatViewPopulatedState(Relation relation, bool newstate); ! extern void ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString, ParamListInfo params, char *completionTag); extern DestReceiver *CreateTransientRelDestReceiver(Oid oid); --- 22,28 ---- extern void SetMatViewPopulatedState(Relation relation, bool newstate); ! extern Oid ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString, ParamListInfo params, char *completionTag); extern DestReceiver *CreateTransientRelDestReceiver(Oid oid); *** a/src/include/utils/builtins.h --- b/src/include/utils/builtins.h *************** *** 657,662 **** extern Datum pg_get_triggerdef_ext(PG_FUNCTION_ARGS); --- 657,663 ---- extern Datum pg_get_constraintdef(PG_FUNCTION_ARGS); extern Datum pg_get_constraintdef_ext(PG_FUNCTION_ARGS); extern char *pg_get_constraintdef_string(Oid constraintId); + extern char *pg_get_viewstmt_definition(Query *viewParse); extern Datum pg_get_expr(PG_FUNCTION_ARGS); extern Datum pg_get_expr_ext(PG_FUNCTION_ARGS); extern Datum pg_get_userbyid(PG_FUNCTION_ARGS); *************** *** 1160,1165 **** extern Datum unique_key_recheck(PG_FUNCTION_ARGS); --- 1161,1167 ---- /* commands/event_trigger.c */ extern Datum pg_event_trigger_dropped_objects(PG_FUNCTION_ARGS); + extern Datum pg_event_trigger_get_normalized_commands(PG_FUNCTION_ARGS); /* commands/extension.c */ extern Datum pg_available_extensions(PG_FUNCTION_ARGS); *** /dev/null --- b/src/include/utils/ddl_rewrite.h *************** *** 0 **** --- 1,18 ---- + /*------------------------------------------------------------------------- + * + * ddl_rewrite.h + * + * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/utils/ddl_rewrite.h + * + *------------------------------------------------------------------------- + */ + #ifndef DDL_REWRITE_H + #define DDL_REWRITE_H + + extern char *rewrite_utility_command(Oid objectId, const char *identity, + Node *parsetree); + + #endif /* DDL_REWRITE_H */

view thread (105+ messages)  latest in thread

Message-ID: <20131108153322.GU5809@eldon.alvh.no-ip.org>
Permalink:  ../20131108153322.GU5809@eldon.alvh.no-ip.org/
Also on:    postgresql.org/message-id/20131108153322.GU5809@eldon.alvh.no-ip.org

 ·  · 

reply

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Reply to all the recipients using the --to and --cc options:
  reply via email

  To: pgsql-hackers@postgresql.org
  Cc: alvherre@2ndquadrant.com
  Subject: Re: Add CREATE support to event triggers
  In-Reply-To: <20131108153322.GU5809@eldon.alvh.no-ip.org>

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

This inbox is served by DDX for PostgreSQL; see mirroring instructions
for how to clone and mirror all data and code used for this inbox