>From 9848b3c6b27b5b7cf592643950804662cc89e3bb Mon Sep 17 00:00:00 2001 From: Alvaro Herrera Date: Wed, 24 Sep 2014 15:53:31 -0300 Subject: [PATCH 05/30] deparse: initial set of supported commands CREATE SCHEMA CREATE TABLE CREATE SEQUENCE ALTER SEQUENCE CREATE INDEX CREATE TRIGGER CREATE TYPE AS CREATE TYPE AS ENUM --- src/backend/commands/schemacmds.c | 9 + src/backend/commands/sequence.c | 34 + src/backend/commands/tablecmds.c | 3 +- src/backend/tcop/deparse_utility.c | 1262 ++++++++++++++++++++++++++++++++++- src/backend/tcop/utility.c | 156 +++-- src/backend/utils/adt/format_type.c | 4 +- src/backend/utils/adt/ruleutils.c | 373 +++++++++-- src/include/commands/sequence.h | 1 + src/include/utils/ruleutils.h | 14 +- 9 files changed, 1738 insertions(+), 118 deletions(-) diff --git a/src/backend/commands/schemacmds.c b/src/backend/commands/schemacmds.c index 03f5514..4548dfd 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, (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/commands/sequence.c b/src/backend/commands/sequence.c index 3b89dd0..0c5d5d7 100644 --- a/src/backend/commands/sequence.c +++ b/src/backend/commands/sequence.c @@ -1492,6 +1492,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 add3ab9..9587217 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -8010,7 +8010,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 bea12ac..11d7894 100644 --- a/src/backend/tcop/deparse_utility.c +++ b/src/backend/tcop/deparse_utility.c @@ -587,6 +587,1252 @@ new_objtree_for_qualname_id(Oid classId, Oid objectId) return qualified; } +/* + * 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 the JSON blob + * representing the creation command. + */ +static char * +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; + char *command; + + 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(NULL, "INSERT")); + if (node->events & TRIGGER_TYPE_DELETE) + events = lappend(events, new_string_object(NULL, "DELETE")); + if (node->events & TRIGGER_TYPE_TRUNCATE) + events = lappend(events, new_string_object(NULL, "TRUNCATE")); + if (node->events & TRIGGER_TYPE_UPDATE) + { + if (node->columns == NIL) + { + events = lappend(events, new_string_object(NULL, "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(NULL, colname)); + } + + append_array_object(update, "columns", cols); + + events = lappend(events, + new_object_object(NULL, 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(NULL, "DEFERRABLE")); + if (node->initdeferred) + list = lappend(list, + new_string_object(NULL, "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(NULL, p)); + + p += tlen + 1; + } + } + + append_array_object(tmp, "args", list); /* might be NIL */ + + append_object_object(trigger, "function", tmp); + + heap_close(pg_trigger, AccessShareLock); + + command = jsonize_objtree(trigger); + free_objtree(trigger); + + return command; +} + +/* + * 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(NULL, tree); + elements = lappend(elements, column); + } + } + break; + case T_Constraint: + break; + default: + elog(ERROR, "invalid node type %d", nodeTag(elt)); + } + } + + return elements; +} + +/* + * 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(NULL, 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 the JSON blob + * representing the creation command. + */ +static char * +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 *command; + 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 " + "%{on_commit}s WITH (%{with:, }s) %{tablespace}s"; + else + fmtstr = "CREATE %{persistence}s TABLE %{if_not_exists}s %{identity}D " + "(%{table_elements:, }s) %{inherits}s " + "%{on_commit}s WITH (%{with:, }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(NULL, 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(NULL, tmp)); + foreach(cell, node->options) + { + DefElem *opt = (DefElem *) lfirst(cell); + char *fmt; + char *value; + + /* already handled above */ + if (strcmp(opt->defname, "oids") == 0) + continue; + + if (opt->defnamespace) + fmt = psprintf("%s.%s=%%{value}s", opt->defnamespace, opt->defname); + else + fmt = psprintf("%s=%%{value}s", opt->defname); + value = opt->arg ? defGetString(opt) : + defGetBoolean(opt) ? "TRUE" : "FALSE"; + tmp = new_objtree_VA(fmt, 2, + "option", ObjTypeString, opt->defname, + "value", ObjTypeString, value); + list = lappend(list, new_object_object(NULL, tmp)); + } + append_array_object(createStmt, "with", list); + + command = jsonize_objtree(createStmt); + + free_objtree(createStmt); + relation_close(relation, AccessShareLock); + + return command; +} + +static char * +deparse_CompositeTypeStmt(Oid objectId, Node *parsetree) +{ + CompositeTypeStmt *node = (CompositeTypeStmt *) parsetree; + ObjTree *composite; + Relation typerel = relation_open(objectId, AccessShareLock); + List *dpcontext; + List *tableelts = NIL; + char *command; + + 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); + + command = jsonize_objtree(composite); + free_objtree(composite); + heap_close(typerel, AccessShareLock); + + return command; +} + +static char * +deparse_CreateEnumStmt(Oid objectId, Node *parsetree) +{ + CreateEnumStmt *node = (CreateEnumStmt *) parsetree; + ObjTree *enumtype; + char *command; + 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(NULL, strVal(val))); + } + append_array_object(enumtype, "values", values); + + command = jsonize_objtree(enumtype); + free_objtree(enumtype); + + return command; +} + +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(NULL, 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(NULL, 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(NULL, 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(NULL, 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(NULL, 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(NULL, 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(NULL, 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(NULL, ownedby); +} + +/* + * deparse_CreateSeqStmt + * deparse a CreateSeqStmt + * + * Given a sequence OID and the parsetree that created it, return the JSON blob + * representing the creation command. + */ +static char * +deparse_CreateSeqStmt(Oid objectId, Node *parsetree) +{ + ObjTree *createSeq; + ObjTree *tmp; + Relation relation = relation_open(objectId, AccessShareLock); + char *command; + 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); + + command = jsonize_objtree(createSeq); + + free_objtree(createSeq); + relation_close(relation, AccessShareLock); + + return command; +} + +static char * +deparse_AlterSeqStmt(Oid objectId, Node *parsetree) +{ + ObjTree *alterSeq; + ObjTree *tmp; + Relation relation = relation_open(objectId, AccessShareLock); + char *command; + 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); + + command = jsonize_objtree(alterSeq); + + free_objtree(alterSeq); + relation_close(relation, AccessShareLock); + + return command; +} + +/* + * deparse_IndexStmt + * deparse an IndexStmt + * + * Given an index OID and the parsetree that created it, return the JSON blob + * representing the creation command. + * + * If the index corresponds to a constraint, NULL is returned. + */ +static char * +deparse_IndexStmt(Oid objectId, Node *parsetree) +{ + IndexStmt *node = (IndexStmt *) parsetree; + ObjTree *indexStmt; + ObjTree *tmp; + Relation idxrel; + Relation heaprel; + char *command; + 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 %{name}I " + "ON %{table}D USING %{index_am}s (%{definition}s) " + "%{with}s %{tablespace}s %{where_clause}s", + 5, + "unique", ObjTypeString, node->unique ? "UNIQUE" : "", + "concurrently", ObjTypeString, + node->concurrent ? "CONCURRENTLY" : "", + "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); + + command = jsonize_objtree(indexStmt); + free_objtree(indexStmt); + + heap_close(idxrel, AccessShareLock); + heap_close(heaprel, AccessShareLock); + + return command; +} + +/* + * deparse_CreateSchemaStmt + * deparse a CreateSchemaStmt + * + * Given a schema OID and the parsetree that created it, return the JSON blob + * 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 char * +deparse_CreateSchemaStmt(Oid objectId, Node *parsetree) +{ + CreateSchemaStmt *node = (CreateSchemaStmt *) parsetree; + ObjTree *createSchema; + ObjTree *auth; + char *command; + + 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); + + command = jsonize_objtree(createSchema); + free_objtree(createSchema); + + return command; +} + static char * deparse_parsenode_cmd(StashedCommand *cmd) { @@ -611,11 +1857,11 @@ deparse_parsenode_cmd(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; /* other local objects */ @@ -624,7 +1870,7 @@ deparse_parsenode_cmd(StashedCommand *cmd) break; case T_IndexStmt: - command = NULL; + command = deparse_IndexStmt(objectId, parsetree); break; case T_CreateExtensionStmt: @@ -649,11 +1895,11 @@ deparse_parsenode_cmd(StashedCommand *cmd) break; case T_CompositeTypeStmt: /* CREATE TYPE (composite) */ - command = NULL; + command = deparse_CompositeTypeStmt(objectId, parsetree); break; case T_CreateEnumStmt: /* CREATE TYPE AS ENUM */ - command = NULL; + command = deparse_CreateEnumStmt(objectId, parsetree); break; case T_CreateRangeStmt: /* CREATE TYPE AS RANGE */ @@ -673,11 +1919,11 @@ deparse_parsenode_cmd(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: @@ -689,7 +1935,7 @@ deparse_parsenode_cmd(StashedCommand *cmd) break; case T_CreateTrigStmt: - command = NULL; + command = deparse_CreateTrigStmt(objectId, parsetree); break; case T_CreatePLangStmt: diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index d4be750..c98c7dc 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -853,6 +853,7 @@ ProcessUtilitySlow(Node *parsetree, 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(); @@ -871,6 +872,10 @@ ProcessUtilitySlow(Node *parsetree, case T_CreateSchemaStmt: CreateSchemaCommand((CreateSchemaStmt *) parsetree, queryString); + /* + * CreateSchemaCommand calls EventTriggerStashCommand + * internally, for reasons explained there. + */ break; case T_CreateStmt: @@ -878,7 +883,6 @@ ProcessUtilitySlow(Node *parsetree, { List *stmts; ListCell *l; - Oid relOid; /* Run parse analysis ... */ stmts = transformCreateStmt((CreateStmt *) parsetree, @@ -895,9 +899,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, + stmt); /* * Let NewRelationCreateToastTable decide if this @@ -919,20 +925,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, + 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, @@ -1064,46 +1077,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, parsetree); } break; @@ -1138,18 +1158,22 @@ 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, + parsetree); } break; case T_CreateExtensionStmt: - CreateExtension((CreateExtensionStmt *) parsetree); + objectId = CreateExtension((CreateExtensionStmt *) parsetree); + EventTriggerStashCommand(objectId, 0, OBJECT_EXTENSION, parsetree); break; case T_AlterExtensionStmt: @@ -1161,7 +1185,8 @@ ProcessUtilitySlow(Node *parsetree, break; case T_CreateFdwStmt: - CreateForeignDataWrapper((CreateFdwStmt *) parsetree); + objectId = CreateForeignDataWrapper((CreateFdwStmt *) parsetree); + EventTriggerStashCommand(objectId, 0, OBJECT_FDW, parsetree); break; case T_AlterFdwStmt: @@ -1169,7 +1194,9 @@ ProcessUtilitySlow(Node *parsetree, break; case T_CreateForeignServerStmt: - CreateForeignServer((CreateForeignServerStmt *) parsetree); + objectId = CreateForeignServer((CreateForeignServerStmt *) parsetree); + EventTriggerStashCommand(objectId, 0, OBJECT_FOREIGN_SERVER, + parsetree); break; case T_AlterForeignServerStmt: @@ -1177,7 +1204,9 @@ ProcessUtilitySlow(Node *parsetree, break; case T_CreateUserMappingStmt: - CreateUserMapping((CreateUserMappingStmt *) parsetree); + objectId = CreateUserMapping((CreateUserMappingStmt *) parsetree); + EventTriggerStashCommand(objectId, 0, OBJECT_USER_MAPPING, + parsetree); break; case T_AlterUserMappingStmt: @@ -1196,16 +1225,21 @@ ProcessUtilitySlow(Node *parsetree, { CompositeTypeStmt *stmt = (CompositeTypeStmt *) parsetree; - DefineCompositeType(stmt->typevar, stmt->coldeflist); + objectId = DefineCompositeType(stmt->typevar, + stmt->coldeflist); + EventTriggerStashCommand(objectId, 0, OBJECT_COMPOSITE, + parsetree); } break; case T_CreateEnumStmt: /* CREATE TYPE AS ENUM */ - DefineEnum((CreateEnumStmt *) parsetree); + objectId = DefineEnum((CreateEnumStmt *) parsetree); + EventTriggerStashCommand(objectId, 0, OBJECT_TYPE, parsetree); break; case T_CreateRangeStmt: /* CREATE TYPE AS RANGE */ - DefineRange((CreateRangeStmt *) parsetree); + objectId = DefineRange((CreateRangeStmt *) parsetree); + EventTriggerStashCommand(objectId, 0, OBJECT_TYPE, parsetree); break; case T_AlterEnumStmt: /* ALTER TYPE (enum) */ @@ -1213,67 +1247,81 @@ ProcessUtilitySlow(Node *parsetree, break; case T_ViewStmt: /* CREATE VIEW */ - DefineView((ViewStmt *) parsetree, queryString); + objectId = DefineView((ViewStmt *) parsetree, queryString); + EventTriggerStashCommand(objectId, 0, OBJECT_VIEW, parsetree); break; case T_CreateFunctionStmt: /* CREATE FUNCTION */ - CreateFunction((CreateFunctionStmt *) parsetree, queryString); + objectId = CreateFunction((CreateFunctionStmt *) parsetree, queryString); + EventTriggerStashCommand(objectId, 0, OBJECT_FUNCTION, parsetree); break; case T_AlterFunctionStmt: /* ALTER FUNCTION */ - AlterFunction((AlterFunctionStmt *) parsetree); + objectId = AlterFunction((AlterFunctionStmt *) parsetree); break; case T_RuleStmt: /* CREATE RULE */ - DefineRule((RuleStmt *) parsetree, queryString); + objectId = DefineRule((RuleStmt *) parsetree, queryString); + EventTriggerStashCommand(objectId, 0, OBJECT_RULE, parsetree); break; case T_CreateSeqStmt: - DefineSequence((CreateSeqStmt *) parsetree); + objectId = DefineSequence((CreateSeqStmt *) parsetree); + EventTriggerStashCommand(objectId, 0, OBJECT_SEQUENCE, parsetree); break; case T_AlterSeqStmt: - AlterSequence((AlterSeqStmt *) parsetree); + objectId = AlterSequence((AlterSeqStmt *) parsetree); + EventTriggerStashCommand(objectId, 0, OBJECT_SEQUENCE, parsetree); break; case T_CreateTableAsStmt: - ExecCreateTableAs((CreateTableAsStmt *) parsetree, + objectId = ExecCreateTableAs((CreateTableAsStmt *) parsetree, queryString, params, completionTag); + EventTriggerStashCommand(objectId, 0, OBJECT_TABLE, parsetree); break; case T_RefreshMatViewStmt: - ExecRefreshMatView((RefreshMatViewStmt *) parsetree, + objectId = ExecRefreshMatView((RefreshMatViewStmt *) parsetree, queryString, params, completionTag); + EventTriggerStashCommand(objectId, 0, OBJECT_MATVIEW, 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, parsetree); break; case T_CreatePLangStmt: - CreateProceduralLanguage((CreatePLangStmt *) parsetree); + objectId = CreateProceduralLanguage((CreatePLangStmt *) parsetree); + EventTriggerStashCommand(objectId, 0, OBJECT_LANGUAGE, parsetree); break; case T_CreateDomainStmt: - DefineDomain((CreateDomainStmt *) parsetree); + objectId = DefineDomain((CreateDomainStmt *) parsetree); + EventTriggerStashCommand(objectId, 0, OBJECT_DOMAIN, parsetree); break; case T_CreateConversionStmt: - CreateConversionCommand((CreateConversionStmt *) parsetree); + objectId = CreateConversionCommand((CreateConversionStmt *) parsetree); + EventTriggerStashCommand(objectId, 0, OBJECT_CONVERSION, parsetree); break; case T_CreateCastStmt: - CreateCast((CreateCastStmt *) parsetree); + objectId = CreateCast((CreateCastStmt *) parsetree); + EventTriggerStashCommand(objectId, 0, OBJECT_CAST, parsetree); break; case T_CreateOpClassStmt: - DefineOpClass((CreateOpClassStmt *) parsetree); + objectId = DefineOpClass((CreateOpClassStmt *) parsetree); + EventTriggerStashCommand(objectId, 0, OBJECT_OPCLASS, parsetree); break; case T_CreateOpFamilyStmt: - DefineOpFamily((CreateOpFamilyStmt *) parsetree); + objectId = DefineOpFamily((CreateOpFamilyStmt *) parsetree); + EventTriggerStashCommand(objectId, 0, OBJECT_OPFAMILY, parsetree); break; case T_AlterOpFamilyStmt: diff --git a/src/backend/utils/adt/format_type.c b/src/backend/utils/adt/format_type.c index d9958d6..65d5887 100644 --- a/src/backend/utils/adt/format_type.c +++ b/src/backend/utils/adt/format_type.c @@ -288,7 +288,8 @@ format_type_internal(Oid type_oid, int32 typemod, case VARCHAROID: if (with_typemod) - buf = printTypmod("character varying", typemod, typeform->typmodout); + buf = printTypmod("character varying", typemod, + typeform->typmodout); else buf = pstrdup("character varying"); break; @@ -462,7 +463,6 @@ printTypmod(const char *typname, int32 typmod, Oid typmodout) return res; } - /* * type_maximum_size --- determine maximum width of a variable-width column * diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 18d9b63..56e74b7 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); } /* @@ -9323,3 +9574,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/commands/sequence.h b/src/include/commands/sequence.h index 914d155..984631a 100644 --- a/src/include/commands/sequence.h +++ b/src/include/commands/sequence.h @@ -70,6 +70,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 520b066..0503816 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); @@ -31,4 +40,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 */ -- 1.9.1