diff --git a/src/backend/catalog/pg_inherits.c b/src/backend/catalog/pg_inherits.c index 4b9802aafcc..6dfded98b1e 100644 --- a/src/backend/catalog/pg_inherits.c +++ b/src/backend/catalog/pg_inherits.c @@ -655,3 +655,39 @@ PartitionHasPendingDetach(Oid partoid) elog(ERROR, "relation %u is not a partition", partoid); return false; /* keep compiler quiet */ } + +/* + * Given a table OID, return a schema-qualified table list representing + * the parent tables. + */ +List * +relation_get_inh_parents(Oid objectId) +{ + List *parents = NIL; + Relation inhRel; + SysScanDesc scan; + ScanKeyData key; + HeapTuple tuple; + + inhRel = table_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))) + { + Form_pg_inherits formInh = (Form_pg_inherits) GETSTRUCT(tuple); + + parents = lappend_oid(parents, formInh->inhparent); + } + + systable_endscan(scan); + table_close(inhRel, RowExclusiveLock); + + return parents; +} diff --git a/src/backend/commands/Makefile b/src/backend/commands/Makefile index 5b9d084977e..5e012bc35b5 100644 --- a/src/backend/commands/Makefile +++ b/src/backend/commands/Makefile @@ -28,6 +28,8 @@ OBJS = \ copyto.o \ createas.o \ dbcommands.o \ + ddldeparse.o \ + ddljson.o \ define.o \ discard.o \ dropcmds.o \ diff --git a/src/backend/commands/ddldeparse.c b/src/backend/commands/ddldeparse.c new file mode 100644 index 00000000000..25166bb5978 --- /dev/null +++ b/src/backend/commands/ddldeparse.c @@ -0,0 +1,3513 @@ +/*------------------------------------------------------------------------- + * + * ddldeparse.c + * Functions to convert utility commands to machine-parseable + * representation + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * NOTES + * + * This is intended to provide JSON blobs representing DDL commands, which can + * later be re-processed into plain strings by well-defined sprintf-like + * expansion. These JSON objects are intended to allow for machine-editing of + * the commands, by replacing certain nodes within the objects. + * + * Much of the information in the output blob actually comes from system + * catalogs, not from the command parse node, as it is impossible to reliably + * construct a fully-specified command (i.e. one not dependent on search_path + * etc) looking only at the parse node. + * + * Deparsed JsonbValue is created by using: + * new_jsonb_VA where the key-value pairs composing an jsonb object can be + * derived using the passed variable arguments. In order to successfully + * construct one key:value pair, a set of three arguments consisting of a name + * (string), type (from the jbvType enum) and value must be supplied. It can + * take multiple such sets and construct multiple key-value pairs and append + * those to output parse-state. + * + * IDENTIFICATION + * src/backend/commands/ddldeparse.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/amapi.h" +#include "access/relation.h" +#include "access/table.h" +#include "access/htup_details.h" +#include "catalog/namespace.h" +#include "catalog/pg_collation.h" +#include "catalog/pg_constraint.h" +#include "catalog/pg_depend.h" +#include "catalog/pg_inherits.h" +#include "catalog/pg_namespace.h" +#include "catalog/pg_proc.h" +#include "catalog/pg_type.h" +#include "commands/defrem.h" +#include "commands/sequence.h" +#include "commands/tablespace.h" +#include "commands/tablecmds.h" +#include "funcapi.h" +#include "partitioning/partbounds.h" +#include "tcop/ddldeparse.h" +#include "tcop/utility.h" +#include "utils/builtins.h" +#include "utils/fmgroids.h" +#include "utils/jsonb.h" +#include "utils/lsyscache.h" +#include "utils/rel.h" +#include "utils/ruleutils.h" +#include "utils/syscache.h" + +/* Estimated length of the generated jsonb string */ +#define JSONB_ESTIMATED_LEN 128 + +/* + * 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 NULL; + default: + elog(ERROR, "unexpected persistence marking %c", + persistence); + return NULL; /* make compiler happy */ + } +} + +/* + * Insert JsonbValue key to the output parse state. + */ +static void +insert_jsonb_key(JsonbInState *state, char *name) +{ + JsonbValue key; + + /* Push the key */ + key.type = jbvString; + key.val.string.val = name; + key.val.string.len = strlen(name); + pushJsonbValue(state, WJB_KEY, &key); +} + +/* + * Append new jsonb key:value pairs to the output parse state -- varargs + * function + * + * Arguments: + * "state": the output jsonb state where each key-value pair is pushed. + * + * "numobjs": the number of key:value pairs to be pushed to JsonbInState; + * for each one, a name (string), type (from the jbvType enum) and value must + * be supplied. The value must match the type given; for instance, jbvBool + * requires an bool, jbvString requires a char * and so on. + * Each element type must match the conversion specifier given in the format + * string, as described in ddl_deparse_expand_command. + * + * Notes: + * a) The caller can pass "fmt":"fmtstr" as a regular key:value pair to this, + * no special handling needed for that. + * b) The caller need to carefully pass sets of arguments, we don't have the + * luxury of sprintf-like compiler warnings for malformed argument lists. + */ +static void +new_jsonb_VA(JsonbInState *state, int numobjs,...) +{ + va_list args; + int i; + JsonbValue val; + + /* Process the given varargs */ + va_start(args, numobjs); + + for (i = 0; i < numobjs; i++) + { + char *name; + enum jbvType type; + + name = va_arg(args, char *); + type = va_arg(args, enum jbvType); + + /* Push the key first */ + insert_jsonb_key(state, name); + + /* + * For all param types other than jbvNull, there must be a value in + * the varargs. Fetch it and add the fully formed subobject into the + * main object. + */ + switch (type) + { + case jbvNull: + /* Null params don't have a value (obviously) */ + val.type = jbvNull; + pushJsonbValue(state, WJB_VALUE, &val); + break; + + case jbvBool: + /* Push the bool value */ + val.type = jbvBool; + val.val.boolean = va_arg(args, int); + pushJsonbValue(state, WJB_VALUE, &val); + break; + + case jbvString: + /* Push the string value */ + val.type = jbvString; + val.val.string.val = pstrdup(va_arg(args, char *)); + val.val.string.len = strlen(val.val.string.val); + pushJsonbValue(state, WJB_VALUE, &val); + break; + + case jbvNumeric: + /* Push the numeric value */ + val.type = jbvNumeric; + val.val.numeric = (Numeric) + DatumGetNumeric(DirectFunctionCall1( + int8_numeric, + va_arg(args, int))); + + pushJsonbValue(state, WJB_VALUE, &val); + break; + + default: + elog(ERROR, "unrecognized jbvType %d", type); + } + } + + va_end(args); +} + +/* + * A helper routine to insert jsonb for typId to the output parse state. + */ +static void +new_jsonb_for_type(JsonbInState *state, char *parentKey, + Oid typId, int32 typmod) +{ + Oid typnspid; + char *type_nsp; + char *type_name = NULL; + char *typmodstr; + bool type_array; + + Assert(parentKey); + + format_type_detailed(typId, typmod, &typnspid, &type_name, &typmodstr, + &type_array); + + if (OidIsValid(typnspid)) + type_nsp = get_namespace_name_or_temp(typnspid); + else + type_nsp = pstrdup(""); + + insert_jsonb_key(state, parentKey); + pushJsonbValue(state, WJB_BEGIN_OBJECT, NULL); + new_jsonb_VA(state, 4, + "schemaname", jbvString, type_nsp, + "typename", jbvString, type_name, + "typmod", jbvString, typmodstr, + "typarray", jbvBool, type_array); + pushJsonbValue(state, WJB_END_OBJECT, NULL); +} + +/* + * A helper routine to set up name: schemaname, objname + * + * Elements "schema_name" and "obj_name" are set. If the namespace OID + * corresponds to a temp schema, that's set to "pg_temp". + * + * The difference between those two element types is whether the obj_name will + * be quoted as an identifier or not, which is not something that this routine + * concerns itself with; that will be up to the expand function. + */ +static void +new_jsonb_for_qualname(JsonbInState *state, Oid nspid, char *objName, + char *keyName, bool createObject) +{ + char *namespace; + + if (isAnyTempNamespace(nspid)) + namespace = pstrdup("pg_temp"); + else + namespace = get_namespace_name(nspid); + + /* Push the key first */ + if (keyName) + insert_jsonb_key(state, keyName); + + if (createObject) + pushJsonbValue(state, WJB_BEGIN_OBJECT, NULL); + + new_jsonb_VA(state, 2, + "schemaname", jbvString, namespace, + "objname", jbvString, objName); + + if (createObject) + pushJsonbValue(state, WJB_END_OBJECT, NULL); +} + +/* + * A helper routine to set up name: 'schemaname, objname' where the object is + * specified by classId and objId. + */ +static void +new_jsonb_for_qualname_id(JsonbInState *state, Oid classId, Oid objectId, + char *keyName, bool createObject) +{ + Relation catalog; + HeapTuple catobj; + Datum obj_nsp; + Datum obj_name; + AttrNumber Anum_name; + AttrNumber Anum_namespace; + AttrNumber Anum_oid = get_object_attnum_oid(classId); + bool isnull; + + catalog = table_open(classId, AccessShareLock); + + catobj = get_catalog_object_by_oid(catalog, Anum_oid, objectId); + if (!catobj) + elog(ERROR, "cache lookup failed for object with OID %u of catalog \"%s\"", + objectId, RelationGetRelationName(catalog)); + Anum_name = get_object_attnum_name(classId); + Anum_namespace = get_object_attnum_namespace(classId); + + obj_nsp = heap_getattr(catobj, Anum_namespace, RelationGetDescr(catalog), + &isnull); + if (isnull) + elog(ERROR, "null namespace for object %u", objectId); + + obj_name = heap_getattr(catobj, Anum_name, RelationGetDescr(catalog), + &isnull); + if (isnull) + elog(ERROR, "null attribute name for object %u", objectId); + + new_jsonb_for_qualname(state, DatumGetObjectId(obj_nsp), + NameStr(*DatumGetName(obj_name)), + keyName, createObject); + table_close(catalog, AccessShareLock); +} + +/* + * A helper routine to insert key:value where value is array of qualname to + * the output parse state. + */ +static void +new_jsonbArray_for_qualname_id(JsonbInState *state, + char *keyname, List *array) +{ + ListCell *lc; + + /* Push the key first */ + insert_jsonb_key(state, keyname); + + pushJsonbValue(state, WJB_BEGIN_ARRAY, NULL); + + /* Push the array elements now */ + foreach(lc, array) + new_jsonb_for_qualname_id(state, RelationRelationId, lfirst_oid(lc), + NULL, true); + + pushJsonbValue(state, WJB_END_ARRAY, NULL); +} + +/* + * A helper routine to insert collate object for column + * definition to the output parse state. + */ +static void +insert_collate_object(JsonbInState *state, char *parentKey, char *fmt, + Oid classId, Oid objectId, char *key) +{ + /* + * Insert parent key for which we are going to create value object here. + */ + if (parentKey) + insert_jsonb_key(state, parentKey); + + pushJsonbValue(state, WJB_BEGIN_OBJECT, NULL); + + new_jsonb_VA(state, 1, "fmt", jbvString, fmt); + + /* push object now */ + new_jsonb_for_qualname_id(state, classId, objectId, key, true); + + pushJsonbValue(state, WJB_END_OBJECT, NULL); +} + +/* + * A helper routine to insert identity object for the table definition + * to the output parse state. + */ +static void +insert_identity_object(JsonbInState *state, Oid nspid, char *relname) +{ + new_jsonb_for_qualname(state, nspid, relname, "identity", true); +} + +/* + * Deparse the sequence CACHE option to Jsonb + * + * Verbose syntax + * CACHE %{value} + */ +static inline void +deparse_Seq_Cache(JsonbInState *state, Form_pg_sequence seqdata, + bool alter_table) +{ + char *fmt; + + fmt = alter_table ? "SET CACHE %{value}s" : "CACHE %{value}s"; + + pushJsonbValue(state, WJB_BEGIN_OBJECT, NULL); + + new_jsonb_VA(state, 3, + "fmt", jbvString, fmt, + "clause", jbvString, "cache", + "value", jbvString, + psprintf(INT64_FORMAT, seqdata->seqcache)); + pushJsonbValue(state, WJB_END_OBJECT, NULL); +} + +/* + * Deparse the sequence CYCLE option to Jsonb. + * + * Verbose syntax + * SET %{no}s CYCLE + * OR + * %{no}s CYCLE + */ +static inline void +deparse_Seq_Cycle(JsonbInState *state, Form_pg_sequence seqdata, + bool alter_table) +{ + StringInfoData fmtStr; + + initStringInfo(&fmtStr); + + pushJsonbValue(state, WJB_BEGIN_OBJECT, NULL); + + if (alter_table) + appendStringInfoString(&fmtStr, "SET "); + + if (!seqdata->seqcycle) + { + appendStringInfoString(&fmtStr, "%{no}s "); + new_jsonb_VA(state, 1, "no", jbvString, "NO"); + } + + appendStringInfoString(&fmtStr, "CYCLE"); + new_jsonb_VA(state, 2, + "fmt", jbvString, fmtStr.data, + "clause", jbvString, "cycle"); + + pfree(fmtStr.data); + pushJsonbValue(state, WJB_END_OBJECT, NULL); +} + +/* + * Deparse the sequence INCREMENT BY option to Jsonb + * + * Verbose syntax + * SET INCREMENT BY %{value}s + * OR + * INCREMENT BY %{value}s + */ +static inline void +deparse_Seq_IncrementBy(JsonbInState *state, Form_pg_sequence seqdata, + bool alter_table) +{ + char *fmt; + + fmt = alter_table ? "SET INCREMENT BY %{value}s" + : "INCREMENT BY %{value}s"; + + pushJsonbValue(state, WJB_BEGIN_OBJECT, NULL); + new_jsonb_VA(state, 3, + "fmt", jbvString, fmt, + "clause", jbvString, "seqincrement", + "value", jbvString, + psprintf(INT64_FORMAT, seqdata->seqincrement)); + pushJsonbValue(state, WJB_END_OBJECT, NULL); +} + +/* + * Deparse the sequence MAXVALUE option to Jsonb. + * + * Verbose syntax + * SET MAXVALUE %{value}s + * OR + * MAXVALUE %{value}s + */ +static inline void +deparse_Seq_Maxvalue(JsonbInState *state, Form_pg_sequence seqdata, + bool alter_table) +{ + char *fmt; + + fmt = alter_table ? "SET MAXVALUE %{value}s" : "MAXVALUE %{value}s"; + + pushJsonbValue(state, WJB_BEGIN_OBJECT, NULL); + + new_jsonb_VA(state, 3, + "fmt", jbvString, fmt, + "clause", jbvString, "maxvalue", + "value", jbvString, + psprintf(INT64_FORMAT, seqdata->seqmax)); + pushJsonbValue(state, WJB_END_OBJECT, NULL); +} + +/* + * Deparse the sequence MINVALUE option to Jsonb + * + * Verbose syntax + * SET MINVALUE %{value}s + * OR + * MINVALUE %{value}s + */ +static inline void +deparse_Seq_Minvalue(JsonbInState *state, Form_pg_sequence seqdata, + bool alter_table) +{ + char *fmt; + + fmt = alter_table ? "SET MINVALUE %{value}s" : "MINVALUE %{value}s"; + + pushJsonbValue(state, WJB_BEGIN_OBJECT, NULL); + + new_jsonb_VA(state, 3, + "fmt", jbvString, fmt, + "clause", jbvString, "minvalue", + "value", jbvString, + psprintf(INT64_FORMAT, seqdata->seqmin)); + pushJsonbValue(state, WJB_END_OBJECT, NULL); +} + +/* + * Deparse the sequence OWNED BY command. + * + * Verbose syntax + * OWNED BY %{owner}D + */ +static void +deparse_Seq_OwnedBy(JsonbInState *state, Oid sequenceId, + bool alter_table) +{ + Relation depRel; + SysScanDesc scan; + ScanKeyData keys[3]; + HeapTuple tuple; + bool elem_found PG_USED_FOR_ASSERTS_ONLY = false; + + depRel = table_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; + 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, false); + + /* mark the begin of owner's definition object */ + pushJsonbValue(state, WJB_BEGIN_OBJECT, NULL); + + new_jsonb_VA(state, 2, + "fmt", jbvString, "OWNED BY %{owner}D", + "clause", jbvString, "owned"); + + /* owner key */ + insert_jsonb_key(state, "owner"); + + /* owner value begin */ + pushJsonbValue(state, WJB_BEGIN_OBJECT, NULL); + + new_jsonb_for_qualname_id(state, RelationRelationId, + ownerId, NULL, false); + new_jsonb_VA(state, 1, "attrname", jbvString, colname); + + /* owner value end */ + pushJsonbValue(state, WJB_END_OBJECT, NULL); + + /* mark the end of owner's definition object */ + pushJsonbValue(state, WJB_END_OBJECT, NULL); + +#ifdef USE_ASSERT_CHECKING + elem_found = true; +#endif + } + + systable_endscan(scan); + relation_close(depRel, AccessShareLock); + + /* + * If there's no owner column, assert. The caller must have checked + * presence of owned_by element before invoking this. + */ + Assert(elem_found); +} + +/* + * Deparse the sequence START WITH option to Jsonb. + * + * Verbose syntax + * SET START WITH %{value}s + * OR + * START WITH %{value}s + */ +static inline void +deparse_Seq_Startwith(JsonbInState *state, + Form_pg_sequence seqdata, bool alter_table) +{ + char *fmt; + + fmt = alter_table ? "SET START WITH %{value}s" : "START WITH %{value}s"; + + pushJsonbValue(state, WJB_BEGIN_OBJECT, NULL); + new_jsonb_VA(state, 3, + "fmt", jbvString, fmt, + "clause", jbvString, "start", + "value", jbvString, + psprintf(INT64_FORMAT, seqdata->seqstart)); + pushJsonbValue(state, WJB_END_OBJECT, NULL); +} + +/* + * Deparse the sequence RESTART option to Jsonb + * + * Verbose syntax + * RESTART %{value}s + */ +static inline void +deparse_Seq_Restart(JsonbInState *state, int64 last_value) +{ + pushJsonbValue(state, WJB_BEGIN_OBJECT, NULL); + new_jsonb_VA(state, 3, + "fmt", jbvString, "RESTART %{value}s", + "clause", jbvString, "restart", + "value", jbvString, + psprintf(INT64_FORMAT, last_value)); + pushJsonbValue(state, WJB_END_OBJECT, NULL); +} + +/* + * Deparse the sequence AS option. + * + * Verbose syntax + * AS %{seqtype}T + */ +static inline void +deparse_Seq_As(JsonbInState *state, Form_pg_sequence seqdata) +{ + pushJsonbValue(state, WJB_BEGIN_OBJECT, NULL); + new_jsonb_VA(state, 1, "fmt", jbvString, "AS %{seqtype}T"); + new_jsonb_for_type(state, "seqtype", seqdata->seqtypid, -1); + pushJsonbValue(state, WJB_END_OBJECT, NULL); +} + +/* + * Deparse the definition of column identity to Jsonb. + * + * Verbose syntax + * SET GENERATED %{option}s %{seq_definition: }s + * OR + * GENERATED %{option}s AS IDENTITY ( %{seq_definition: }s ) + */ +static void +deparse_ColumnIdentity(JsonbInState *state, char *parentKey, + Oid seqrelid, char identity, bool alter_table) +{ + Form_pg_sequence seqform; + Sequence_values *seqvalues; + char *identfmt; + StringInfoData fmtStr; + + initStringInfo(&fmtStr); + + /* + * Insert parent key for which we are going to create value object here. + */ + if (parentKey) + insert_jsonb_key(state, parentKey); + + /* create object now for value of identity_column */ + pushJsonbValue(state, WJB_BEGIN_OBJECT, NULL); + + /* identity_type object creation */ + if (identity == ATTRIBUTE_IDENTITY_ALWAYS || + identity == ATTRIBUTE_IDENTITY_BY_DEFAULT) + { + appendStringInfoString(&fmtStr, "%{identity_type}s"); + insert_jsonb_key(state, "identity_type"); + + pushJsonbValue(state, WJB_BEGIN_OBJECT, NULL); + identfmt = alter_table ? "SET GENERATED %{option}s" : + "GENERATED %{option}s AS IDENTITY"; + + new_jsonb_VA(state, 2, + "fmt", jbvString, identfmt, + "option", jbvString, + (identity == ATTRIBUTE_IDENTITY_ALWAYS ? + "ALWAYS" : "BY DEFAULT")); + pushJsonbValue(state, WJB_END_OBJECT, NULL); + } + + /* seq_definition array object creation */ + insert_jsonb_key(state, "seq_definition"); + + appendStringInfoString(&fmtStr, alter_table ? " %{seq_definition: }s" : + " ( %{seq_definition: }s )"); + + pushJsonbValue(state, WJB_BEGIN_ARRAY, NULL); + + seqvalues = get_sequence_values(seqrelid); + seqform = seqvalues->seqform; + + /* Definition elements */ + deparse_Seq_Cache(state, seqform, alter_table); + deparse_Seq_Cycle(state, seqform, alter_table); + deparse_Seq_IncrementBy(state, seqform, alter_table); + deparse_Seq_Minvalue(state, seqform, alter_table); + deparse_Seq_Maxvalue(state, seqform, alter_table); + deparse_Seq_Startwith(state, seqform, alter_table); + deparse_Seq_Restart(state, seqvalues->last_value); + /* We purposefully do not emit OWNED BY here */ + + pushJsonbValue(state, WJB_END_ARRAY, NULL); + + /* We have full fmt by now, so add jsonb element for that */ + new_jsonb_VA(state, 1, "fmt", jbvString, fmtStr.data); + + pfree(fmtStr.data); + + /* end of idendity_column object */ + pushJsonbValue(state, WJB_END_OBJECT, NULL); +} + +/* + * 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). + * + * Verbose syntax + * "%{name}I %{coltype}T STORAGE %{colstorage}s %{compression}s %{collation}s + * %{not_null}s %{default}s %{identity_column}s %{generated_column}s" + */ +static void +deparse_ColumnDef(JsonbInState *state, Relation relation, + List *dpcontext, bool composite, ColumnDef *coldef, + bool is_alter) +{ + Oid relid = RelationGetRelid(relation); + HeapTuple attrTup; + Form_pg_attribute attrForm; + Oid typid; + int32 typmod; + Oid typcollation; + bool saw_notnull; + ListCell *cell; + StringInfoData fmtStr; + + initStringInfo(&fmtStr); + + /* + * Inherited columns without local definitions should be skipped. We don't + * want those to be part of final string. + */ + if (!coldef->is_local) + return; + + 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); + + /* start making column object */ + pushJsonbValue(state, WJB_BEGIN_OBJECT, NULL); + + /* create name and type elements for column */ + appendStringInfoString(&fmtStr, "%{name}I"); + new_jsonb_VA(state, 2, + "name", jbvString, coldef->colname, + "type", jbvString, "column"); + + /* + * create coltype object having 4 elements: schemaname, typename, typemod, + * typearray + */ + appendStringInfoString(&fmtStr, " %{coltype}T"); + new_jsonb_for_type(state, "coltype", typid, typmod); + + /* STORAGE clause */ + if (!composite) + { + appendStringInfoString(&fmtStr, " STORAGE %{colstorage}s"); + new_jsonb_VA(state, 1, + "colstorage", jbvString, + storage_name(attrForm->attstorage)); + } + + /* COMPRESSION clause */ + if (coldef->compression) + { + appendStringInfoString(&fmtStr, " %{compression}s"); + insert_jsonb_key(state, "compression"); + pushJsonbValue(state, WJB_BEGIN_OBJECT, NULL); + new_jsonb_VA(state, 2, + "fmt", jbvString, "COMPRESSION %{compression_method}I", + "compression_method", jbvString, coldef->compression); + pushJsonbValue(state, WJB_END_OBJECT, NULL); + } + + /* COLLATE clause */ + if (OidIsValid(typcollation)) + { + appendStringInfoString(&fmtStr, " %{collation}s"); + insert_collate_object(state, "collation", + "COLLATE %{collation_name}D", + CollationRelationId, typcollation, + "collation_name"); + } + + if (!composite) + { + Oid seqrelid = InvalidOid; + + /* + * 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; we must not emit a NOT NULL + * constraint in that case, unless explicitly 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.) + * + * In the ALTER TABLE cases, we also add a NOT NULL if the colDef is + * marked 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 (is_alter && coldef->is_not_null) + saw_notnull = true; + + /* NOT NULL */ + if (saw_notnull) + { + appendStringInfoString(&fmtStr, " %{not_null}s"); + new_jsonb_VA(state, 1, + "not_null", jbvString, "NOT NULL"); + } + + + /* DEFAULT */ + if (attrForm->atthasdef && + coldef->generated != ATTRIBUTE_GENERATED_STORED) + { + char *defstr; + + appendStringInfoString(&fmtStr, " %{default}s"); + + defstr = relation_get_column_default(relation, attrForm->attnum, + dpcontext); + + insert_jsonb_key(state, "default"); + pushJsonbValue(state, WJB_BEGIN_OBJECT, NULL); + new_jsonb_VA(state, 2, + "fmt", jbvString, "DEFAULT %{default}s", + "default", jbvString, defstr); + + pushJsonbValue(state, WJB_END_OBJECT, NULL); + } + + /* IDENTITY COLUMN */ + if (coldef->identity) + { + /* + * For identity column, find the sequence owned by column in order + * to deparse the column definition. + */ + seqrelid = getIdentitySequence(relation, attrForm->attnum, true); + if (OidIsValid(seqrelid) && coldef->identitySequence) + seqrelid = RangeVarGetRelid(coldef->identitySequence, + NoLock, false); + + if (OidIsValid(seqrelid)) + { + appendStringInfoString(&fmtStr, " %{identity_column}s"); + deparse_ColumnIdentity(state, "identity_column", + seqrelid, + coldef->identity, is_alter); + } + } + + + /* GENERATED COLUMN EXPRESSION */ + if (coldef->generated == ATTRIBUTE_GENERATED_STORED) + { + char *defstr; + + appendStringInfoString(&fmtStr, " %{generated_column}s"); + defstr = relation_get_column_default(relation, attrForm->attnum, + dpcontext); + + insert_jsonb_key(state, "generated_column"); + pushJsonbValue(state, WJB_BEGIN_OBJECT, NULL); + new_jsonb_VA(state, 2, + "fmt", jbvString, "GENERATED ALWAYS AS" + " (%{generation_expr}s) STORED", + "generation_expr", jbvString, defstr); + + pushJsonbValue(state, WJB_END_OBJECT, NULL); + } + } + + ReleaseSysCache(attrTup); + + /* We have full fmt by now, so add jsonb element for that */ + new_jsonb_VA(state, 1, "fmt", jbvString, fmtStr.data); + + pfree(fmtStr.data); + + /* mark the end of one column object */ + pushJsonbValue(state, WJB_END_OBJECT, NULL); +} + +/* + * Helper for deparse_ColumnDef_typed() + * + * Returns true if we need to deparse a ColumnDef node within a typed + * table creation. + */ +static bool +deparse_ColDef_typed_needed(Relation relation, ColumnDef *coldef, + Form_pg_attribute *atFormOut, bool *notnull) +{ + 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); + + if (atFormOut) + *atFormOut = attrForm; + + 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. + * (This routine is never used for ALTER cases.) + */ + saw_notnull = false; + foreach(cell, coldef->constraints) + { + Constraint *constr = (Constraint *) lfirst(cell); + + if (constr->contype == CONSTR_NOTNULL) + { + saw_notnull = true; + break; + } + } + + if (notnull) + *notnull = saw_notnull; + + if (!saw_notnull && !attrForm->atthasdef) + { + ReleaseSysCache(attrTup); + return false; + } + + ReleaseSysCache(attrTup); + return true; +} + +/* + * 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. + * + * Verbose syntax + * %{name}I WITH OPTIONS %{not_null}s %{default}s. + */ +static void +deparse_ColumnDef_typed(JsonbInState *state, Relation relation, + List *dpcontext, ColumnDef *coldef) +{ + bool needed; + Form_pg_attribute attrForm; + bool saw_notnull; + StringInfoData fmtStr; + + initStringInfo(&fmtStr); + + needed = deparse_ColDef_typed_needed(relation, coldef, + &attrForm, &saw_notnull); + if (!needed) + return; + + /* start making column object */ + pushJsonbValue(state, WJB_BEGIN_OBJECT, NULL); + + appendStringInfoString(&fmtStr, "%{name}I WITH OPTIONS"); + + /* TYPE and NAME */ + new_jsonb_VA(state, 2, + "type", jbvString, "column", + "name", jbvString, coldef->colname); + + /* NOT NULL */ + if (saw_notnull) + { + appendStringInfoString(&fmtStr, " %{not_null}s"); + new_jsonb_VA(state, 1, "not_null", jbvString, "NOT NULL"); + } + + /* DEFAULT */ + if (attrForm->atthasdef) + { + char *defstr; + + appendStringInfoString(&fmtStr, " %{default}s"); + defstr = relation_get_column_default(relation, attrForm->attnum, + dpcontext); + + insert_jsonb_key(state, "default"); + pushJsonbValue(state, WJB_BEGIN_OBJECT, NULL); + new_jsonb_VA(state, 2, + "fmt", jbvString, "DEFAULT %{default}s", + "default", jbvString, defstr); + + pushJsonbValue(state, WJB_END_OBJECT, NULL); + } + + /* We have full fmt by now, so add jsonb element for that */ + new_jsonb_VA(state, 1, "fmt", jbvString, fmtStr.data); + + pfree(fmtStr.data); + + /* mark the end of column object */ + pushJsonbValue(state, WJB_END_OBJECT, NULL); + + /* Generated columns are not supported on typed tables, so we are done */ +} + +/* + * 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 void +deparse_TableElems(JsonbInState *state, Relation relation, + List *tableElements, List *dpcontext, + bool typed, bool composite) +{ + ListCell *lc; + + foreach(lc, tableElements) + { + Node *elt = (Node *) lfirst(lc); + + switch (nodeTag(elt)) + { + case T_ColumnDef: + { + if (typed) + deparse_ColumnDef_typed(state, relation, + dpcontext, + (ColumnDef *) elt); + else + deparse_ColumnDef(state, relation, dpcontext, + composite, (ColumnDef *) elt, false); + } + break; + case T_Constraint: + break; + default: + elog(ERROR, "invalid node type %d", nodeTag(elt)); + } + } +} + +/* + * Subroutine for CREATE TABLE deparsing. + * + * Given a table OID, obtain its constraints and append them to the given + * JsonbInState. + * + * This works for typed tables, regular tables. + * + * Note that CONSTRAINT_FOREIGN constraints are always ignored. + */ +static void +deparse_Constraints(JsonbInState *state, Oid relationId) +{ + Relation conRel; + ScanKeyData key; + SysScanDesc scan; + HeapTuple tuple; + + Assert(OidIsValid(relationId)); + + /* + * Scan pg_constraint to fetch all constraints linked to the given + * relation. + */ + conRel = table_open(ConstraintRelationId, AccessShareLock); + ScanKeyInit(&key, Anum_pg_constraint_conrelid, BTEqualStrategyNumber, + F_OIDEQ, ObjectIdGetDatum(relationId)); + scan = systable_beginscan(conRel, ConstraintRelidTypidNameIndexId, 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; + StringInfoData fmtStr; + + constrForm = (Form_pg_constraint) GETSTRUCT(tuple); + + switch (constrForm->contype) + { + case CONSTRAINT_CHECK: + contype = "check"; + break; + case CONSTRAINT_FOREIGN: + continue; /* not here */ + case CONSTRAINT_NOTNULL: + contype = "not null"; + continue; + case CONSTRAINT_PRIMARY: + contype = "primary key"; + break; + case CONSTRAINT_UNIQUE: + contype = "unique"; + break; + case CONSTRAINT_EXCLUSION: + contype = "exclusion"; + break; + default: + elog(ERROR, "unrecognized constraint type \"%c\"", constrForm->contype); + } + + /* No need to deparse constraints inherited from parent table. */ + if (OidIsValid(constrForm->conparentid)) + continue; + + /* + * "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. + */ + initStringInfo(&fmtStr); + appendStringInfoString(&fmtStr, "CONSTRAINT %{name}I %{definition}s"); + pushJsonbValue(state, WJB_BEGIN_OBJECT, NULL); + + new_jsonb_VA(state, 4, + "type", jbvString, "constraint", + "contype", jbvString, contype, + "name", jbvString, NameStr(constrForm->conname), + "definition", jbvString, + pg_get_constraintdef_string(constrForm->oid)); + + if (constrForm->conindid && + (constrForm->contype == CONSTRAINT_PRIMARY || + constrForm->contype == CONSTRAINT_UNIQUE || + constrForm->contype == CONSTRAINT_EXCLUSION)) + { + Oid tblspc = get_rel_tablespace(constrForm->conindid); + + if (OidIsValid(tblspc)) + { + char *tblspcname = get_tablespace_name(tblspc); + + if (!tblspcname) + elog(ERROR, "cache lookup failed for tablespace %u", + tblspc); + + appendStringInfoString(&fmtStr, + " USING INDEX TABLESPACE %{tblspc}s"); + new_jsonb_VA(state, 1, + "tblspc", jbvString, tblspcname); + } + } + + /* We have full fmt by now, so add jsonb element for that */ + new_jsonb_VA(state, 1, "fmt", jbvString, fmtStr.data); + + pfree(fmtStr.data); + + pushJsonbValue(state, WJB_END_OBJECT, NULL); + } + + systable_endscan(scan); + table_close(conRel, AccessShareLock); +} + +/* + * Subroutine for CREATE TABLE deparsing. + * + * Insert columns and constraints elements(if any) in output JsonbInState + */ +static void +add_table_elems(JsonbInState *state, StringInfo fmtStr, + Relation relation, List *tableElts, List *dpcontext, + Oid objectId, bool inherit, bool typed, bool composite) +{ + insert_jsonb_key(state, "table_elements"); + + pushJsonbValue(state, WJB_BEGIN_ARRAY, NULL); + + /* + * 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. + */ + deparse_TableElems(state, relation, tableElts, dpcontext, + typed, /* typed table */ + composite); /* not composite */ + + deparse_Constraints(state, objectId); + + /* + * Decide if we need to put '()' around table_elements. It is needed for + * below cases: + * + * a) where actual table-elements are present, eg: create table t1 (a int) + * + * a) inherit case with no local table-elements present, eg: create table + * t1 () inherits (t2) + * + * OTOH, '()' is not needed for below cases when no table-elements are + * present: + * + * a) 'partition of' case, eg: create table t2 partition of t1 + * + * b) 'of type' case, eg: create table t1 of type1; + */ + if ((state->parseState->contVal.type == jbvArray) && + (inherit || (state->parseState->contVal.val.array.nElems > 0))) + { + appendStringInfoString(fmtStr, " (%{table_elements:, }s)"); + } + else + appendStringInfoString(fmtStr, " %{table_elements:, }s"); + + /* end of table_elements array */ + pushJsonbValue(state, WJB_END_ARRAY, NULL); +} + +/* + * Deparse DefElems, as used by Create Table + * + * Verbose syntax + * %{label}s = %{value}L + * where label is: %{schema}I %{label}I + */ +static void +deparse_DefElem(JsonbInState *state, DefElem *elem, bool is_reset) +{ + StringInfoData fmtStr; + StringInfoData labelfmt; + + initStringInfo(&fmtStr); + + appendStringInfoString(&fmtStr, "%{label}s"); + pushJsonbValue(state, WJB_BEGIN_OBJECT, NULL); + + /* LABEL */ + initStringInfo(&labelfmt); + + insert_jsonb_key(state, "label"); + pushJsonbValue(state, WJB_BEGIN_OBJECT, NULL); + + if (elem->defnamespace != NULL) + { + appendStringInfoString(&labelfmt, "%{schema}I."); + new_jsonb_VA(state, 1, + "schema", jbvString, elem->defnamespace); + } + + appendStringInfoString(&labelfmt, "%{label}I"); + new_jsonb_VA(state, 2, + "label", jbvString, elem->defname, + "fmt", jbvString, labelfmt.data); + pfree(labelfmt.data); + + pushJsonbValue(state, WJB_END_OBJECT, NULL); + + /* VALUE */ + if (!is_reset) + { + appendStringInfoString(&fmtStr, " = %{value}L"); + new_jsonb_VA(state, 1, "value", jbvString, + elem->arg ? defGetString(elem) : + defGetBoolean(elem) ? "true" : "false"); + } + + new_jsonb_VA(state, 1, "fmt", jbvString, fmtStr.data); + pfree(fmtStr.data); + + pushJsonbValue(state, WJB_END_OBJECT, NULL); +} + +/* + * Deparse SET/RESET as used by + * ALTER TABLE ... ALTER COLUMN ... SET/RESET (...) + * + * Verbose syntax + * ALTER COLUMN %{column}I RESET|SET (%{options:, }s) + */ +static void +deparse_ColumnSetOptions(JsonbInState *state, AlterTableCmd *subcmd) +{ + ListCell *cell; + bool is_reset = subcmd->subtype == AT_ResetOptions; + bool elem_found PG_USED_FOR_ASSERTS_ONLY = false; + + pushJsonbValue(state, WJB_BEGIN_OBJECT, NULL); + + new_jsonb_VA(state, 3, + "fmt", jbvString, "ALTER COLUMN %{column}I" + " %{option}s (%{options:, }s)", + "column", jbvString, subcmd->name, + "option", jbvString, is_reset ? "RESET" : "SET"); + + insert_jsonb_key(state, "options"); + pushJsonbValue(state, WJB_BEGIN_ARRAY, NULL); + + foreach(cell, (List *) subcmd->def) + { + DefElem *elem; + + elem = (DefElem *) lfirst(cell); + deparse_DefElem(state, elem, is_reset); + +#ifdef USE_ASSERT_CHECKING + elem_found = true; +#endif + } + + pushJsonbValue(state, WJB_END_ARRAY, NULL); + + Assert(elem_found); + + pushJsonbValue(state, WJB_END_OBJECT, NULL); +} + +/* + * Deparse SET/RESET as used by ALTER TABLE ... SET/RESET (...) + * + * Verbose syntax + * RESET|SET (%{options:, }s) + */ +static void +deparse_RelSetOptions(JsonbInState *state, AlterTableCmd *subcmd) +{ + ListCell *cell; + bool is_reset = subcmd->subtype == AT_ResetRelOptions; + bool elem_found PG_USED_FOR_ASSERTS_ONLY = false; + + pushJsonbValue(state, WJB_BEGIN_OBJECT, NULL); + + new_jsonb_VA(state, 2, + "fmt", jbvString, "%{set_reset}s (%{options:, }s)", + "set_reset", jbvString, is_reset ? "RESET" : "SET"); + + /* insert options array */ + insert_jsonb_key(state, "options"); + + pushJsonbValue(state, WJB_BEGIN_ARRAY, NULL); + + foreach(cell, (List *) subcmd->def) + { + DefElem *elem; + + elem = (DefElem *) lfirst(cell); + deparse_DefElem(state, elem, is_reset); + +#ifdef USE_ASSERT_CHECKING + elem_found = true; +#endif + } + + pushJsonbValue(state, WJB_END_ARRAY, NULL); + + Assert(elem_found); + + pushJsonbValue(state, WJB_END_OBJECT, NULL); +} + +/* + * Deparse WITH clause, as used by Create Table. + * + * Verbose syntax (formulated in helper function deparse_DefElem) + * %{label}s = %{value}L + */ +static void +deparse_withObj(JsonbInState *state, CreateStmt *node) +{ + ListCell *cell; + + /* WITH */ + insert_jsonb_key(state, "with"); + pushJsonbValue(state, WJB_BEGIN_ARRAY, NULL); + + /* add elements to array */ + foreach(cell, node->options) + { + DefElem *opt = (DefElem *) lfirst(cell); + + deparse_DefElem(state, opt, false); + } + + /* with's array end */ + pushJsonbValue(state, WJB_END_ARRAY, NULL); +} + +/* + * Deparse a CreateStmt (CREATE TABLE). + * + * Given a table OID and the parse tree that created it, return JsonbValue + * representing the creation command. + * + * Verbose syntax + * CREATE %{persistence}s TABLE %{if_not_exists}s %{identity}D [OF + * %{of_type}T | PARTITION OF %{parent_identity}D] %{table_elements}s + * %{inherits}s %{partition_bound}s %{partition_by}s %{access_method}s + * %{with_clause}s %{tablespace}s + */ +static Jsonb * +deparse_CreateStmt(Oid objectId, Node *parsetree) +{ + CreateStmt *node = (CreateStmt *) parsetree; + Relation relation = relation_open(objectId, AccessShareLock); + Oid nspid = relation->rd_rel->relnamespace; + char *relname = RelationGetRelationName(relation); + List *dpcontext; + char *perstr; + StringInfoData fmtStr; + JsonbInState state = {0}; + + initStringInfo(&fmtStr); + + /* mark the begin of ROOT object and start adding elements to it. */ + pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + + appendStringInfoString(&fmtStr, "CREATE"); + + /* PERSISTENCE */ + perstr = get_persistence_str(relation->rd_rel->relpersistence); + if (perstr) + { + appendStringInfoString(&fmtStr, " %{persistence}s"); + new_jsonb_VA(&state, 1, + "persistence", jbvString, perstr); + } + + appendStringInfoString(&fmtStr, " TABLE"); + + /* IF NOT EXISTS */ + if (node->if_not_exists) + { + appendStringInfoString(&fmtStr, " %{if_not_exists}s"); + new_jsonb_VA(&state, 1, + "if_not_exists", jbvString, "IF NOT EXISTS"); + } + + /* IDENTITY */ + appendStringInfoString(&fmtStr, " %{identity}D"); + insert_identity_object(&state, nspid, relname); + + dpcontext = deparse_context_for(RelationGetRelationName(relation), + objectId); + + /* + * TABLE-ELEMENTS array creation + */ + if (node->ofTypename || node->partbound) + { + /* Insert the "of type" or "partition of" clause whichever present */ + if (node->ofTypename) + { + appendStringInfoString(&fmtStr, " OF %{of_type}T"); + new_jsonb_for_type(&state, "of_type", + relation->rd_rel->reloftype, -1); + } + else + { + List *parents; + Oid objid; + + appendStringInfoString(&fmtStr, " PARTITION OF %{parent_identity}D"); + parents = relation_get_inh_parents(objectId); + objid = linitial_oid(parents); + Assert(list_length(parents) == 1); + new_jsonb_for_qualname_id(&state, RelationRelationId, + objid, "parent_identity", true); + } + + add_table_elems(&state, &fmtStr, relation, + node->tableElts, dpcontext, objectId, + false, /* not inherit */ + true, /* typed table */ + false); /* not composite */ + } + else + { + List *inhrelations; + + /* + * There is no need to process LIKE clauses separately; they have + * already been transformed into columns and constraints. + */ + + add_table_elems(&state, &fmtStr, relation, + node->tableElts, dpcontext, objectId, + true, /* inherit */ + false, /* not typed table */ + false); /* not composite */ + + /* + * 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. + */ + if (node->inhRelations != NIL) + { + appendStringInfoString(&fmtStr, " %{inherits}s"); + insert_jsonb_key(&state, "inherits"); + + pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + + new_jsonb_VA(&state, 1, "fmt", jbvString, "INHERITS (%{parents:, }D)"); + inhrelations = relation_get_inh_parents(objectId); + + new_jsonbArray_for_qualname_id(&state, "parents", inhrelations); + pushJsonbValue(&state, WJB_END_OBJECT, NULL); + } + } + + /* FOR VALUES clause */ + if (node->partbound) + { + appendStringInfoString(&fmtStr, " %{partition_bound}s"); + + /* + * Get pg_class.relpartbound. We cannot use partbound in the parsetree + * directly as it's the original partbound expression which haven't + * been transformed. + */ + new_jsonb_VA(&state, 1, + "partition_bound", jbvString, + relation_get_part_bound(objectId)); + } + + /* PARTITION BY clause */ + if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + { + appendStringInfoString(&fmtStr, " %{partition_by}s"); + insert_jsonb_key(&state, "partition_by"); + pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + + new_jsonb_VA(&state, 2, + "fmt", jbvString, "PARTITION BY %{definition}s", + "definition", jbvString, + pg_get_partkeydef_string(objectId)); + + pushJsonbValue(&state, WJB_END_OBJECT, NULL); + } + + /* USING clause */ + if (node->accessMethod) + { + appendStringInfoString(&fmtStr, " %{access_method}s"); + insert_jsonb_key(&state, "access_method"); + pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + + new_jsonb_VA(&state, 2, + "fmt", jbvString, "USING %{access_method}I", + "access_method", jbvString, node->accessMethod); + pushJsonbValue(&state, WJB_END_OBJECT, NULL); + } + + /* WITH clause */ + if (node->options) + { + appendStringInfoString(&fmtStr, " %{with_clause}s"); + insert_jsonb_key(&state, "with_clause"); + pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + new_jsonb_VA(&state, 1, + "fmt", jbvString, "WITH (%{with:, }s)"); + + deparse_withObj(&state, node); + + pushJsonbValue(&state, WJB_END_OBJECT, NULL); + + } + + /* TABLESPACE */ + if (node->tablespacename) + { + appendStringInfoString(&fmtStr, " %{tablespace}s"); + insert_jsonb_key(&state, "tablespace"); + pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + new_jsonb_VA(&state, 2, + "fmt", jbvString, "TABLESPACE %{tablespace}I", + "tablespace", jbvString, node->tablespacename); + pushJsonbValue(&state, WJB_END_OBJECT, NULL); + } + + relation_close(relation, AccessShareLock); + + /* We have full fmt by now, so add jsonb element for that */ + new_jsonb_VA(&state, 1, "fmt", jbvString, fmtStr.data); + + pfree(fmtStr.data); + + /* Mark the end of ROOT object */ + pushJsonbValue(&state, WJB_END_OBJECT, NULL); + + return JsonbValueToJsonb(state.result); +} + +/* + * Deparse a DropStmt (DROP TABLE). + * + * Given an object identity and the parse tree that created it, return + * jsonb string representing the drop command. + * + * Verbose syntax + * DROP TABLE %{concurrently}s %{if_exists}s %{objidentity}s %{cascade}s + */ +char * +deparse_drop_table(const char *objidentity, Node *parsetree) +{ + DropStmt *node = (DropStmt *) parsetree; + StringInfoData fmtStr; + Jsonb *jsonb; + StringInfoData str; + JsonbInState state = {0}; + + initStringInfo(&str); + initStringInfo(&fmtStr); + + /* mark the begin of ROOT object and start adding elements to it. */ + pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + + /* Start constructing fmt string */ + appendStringInfoString(&fmtStr, "DROP TABLE"); + + /* CONCURRENTLY */ + if (node->concurrent) + { + appendStringInfoString(&fmtStr, " %{concurrently}s"); + new_jsonb_VA(&state, 1, + "concurrently", jbvString, "CONCURRENTLY"); + } + + /* IF EXISTS */ + if (node->missing_ok) + { + appendStringInfoString(&fmtStr, " %{if_exists}s"); + new_jsonb_VA(&state, 1, "if_exists", jbvString, "IF EXISTS"); + } + + /* IDENTITY */ + appendStringInfoString(&fmtStr, " %{objidentity}s"); + new_jsonb_VA(&state, 1, "objidentity", jbvString, objidentity); + + /* CASCADE */ + if (node->behavior == DROP_CASCADE) + { + appendStringInfoString(&fmtStr, " %{cascade}s"); + new_jsonb_VA(&state, 1, "cascade", jbvString, "CASCADE"); + } + + /* We have full fmt by now, so add jsonb element for that */ + new_jsonb_VA(&state, 1, "fmt", jbvString, fmtStr.data); + pfree(fmtStr.data); + + pushJsonbValue(&state, WJB_END_OBJECT, NULL); + + jsonb = JsonbValueToJsonb(state.result); + return JsonbToCString(&str, &jsonb->root, JSONB_ESTIMATED_LEN); +} + +/* + * Deparse all the collected subcommands and return jsonb string representing + * the alter command. + * + * Verbose syntax + * ALTER TABLE %{only}s %{identity}D %{subcmds:, }s + */ +static Jsonb * +deparse_AlterTableStmt(CollectedCommand *cmd) +{ + List *dpcontext; + Relation rel; + ListCell *cell; + Oid relId = cmd->d.alterTable.objectId; + AlterTableStmt *stmt = NULL; + StringInfoData fmtStr; + JsonbInState state = {0}; + bool subCmdArray = false; + + Assert(cmd->type == SCT_AlterTable); + stmt = (AlterTableStmt *) cmd->parsetree; + + Assert(IsA(stmt, AlterTableStmt) || IsA(stmt, AlterTableMoveAllStmt)); + + initStringInfo(&fmtStr); + + /* + * ALTER TABLE subcommands generated for TableLikeClause is processed in + * the top level CREATE TABLE command; return empty here. + */ + if (IsA(stmt, AlterTableStmt) && stmt->table_like) + return NULL; + + rel = relation_open(relId, AccessShareLock); + + if (rel->rd_rel->relkind != RELKIND_RELATION && + rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE) + { + /* unsupported relkind */ + table_close(rel, AccessShareLock); + return NULL; + } + + dpcontext = deparse_context_for(RelationGetRelationName(rel), relId); + + pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + + /* Start constructing fmt string */ + appendStringInfoString(&fmtStr, "ALTER TABLE"); + + if (!stmt->relation->inh) + { + appendStringInfoString(&fmtStr, " %{only}s"); + new_jsonb_VA(&state, 1, "only", jbvString, "ONLY"); + } + + appendStringInfoString(&fmtStr, " %{identity}D"); + insert_identity_object(&state, rel->rd_rel->relnamespace, + RelationGetRelationName(rel)); + + foreach(cell, cmd->d.alterTable.subcmds) + { + CollectedATSubcmd *sub = (CollectedATSubcmd *) lfirst(cell); + AlterTableCmd *subcmd = (AlterTableCmd *) sub->parsetree; + + Assert(IsA(subcmd, AlterTableCmd)); + + /* + * Skip deparse of the subcommand if the objectId doesn't match the + * target relation ID. It can happen for inherited tables when + * subcommands for inherited tables and the parent table are both + * collected in the ALTER TABLE command for the parent table. + */ + if (subcmd->subtype != AT_AttachPartition && + sub->address.objectId != relId && + has_superclass(sub->address.objectId)) + continue; + + /* Mark the begin of subcmds array */ + if (!subCmdArray) + { + appendStringInfoString(&fmtStr, " %{subcmds:, }s"); + insert_jsonb_key(&state, "subcmds"); + pushJsonbValue(&state, WJB_BEGIN_ARRAY, NULL); + subCmdArray = true; + } + + switch (subcmd->subtype) + { + case AT_AddColumn: + { + StringInfoData fmtSub; + + initStringInfo(&fmtSub); + + /* XXX need to set the "recurse" bit somewhere? */ + Assert(IsA(subcmd->def, ColumnDef)); + + /* + * Syntax: ADD COLUMN %{if_not_exists}s %{definition}s" + * where definition: "%{name}I %{coltype}T STORAGE + * %{colstorage}s %{compression}s %{collation}s + * %{not_null}s %{default}s %{identity_column}s + * %{generated_column}s" + */ + pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + + appendStringInfoString(&fmtSub, "ADD COLUMN"); + new_jsonb_VA(&state, 1, "type", jbvString, "add column"); + + if (subcmd->missing_ok) + { + appendStringInfoString(&fmtSub, " %{if_not_exists}s"); + new_jsonb_VA(&state, 1, + "if_not_exists", jbvString, "IF NOT EXISTS"); + } + + /* Push definition key-value pair */ + appendStringInfoString(&fmtSub, " %{definition}s"); + insert_jsonb_key(&state, "definition"); + + deparse_ColumnDef(&state, rel, dpcontext, + false, (ColumnDef *) subcmd->def, + true); + + /* We have full fmt by now, so add jsonb element for that */ + new_jsonb_VA(&state, 1, "fmt", jbvString, fmtSub.data); + pfree(fmtSub.data); + + pushJsonbValue(&state, WJB_END_OBJECT, NULL); + break; + } + + case AT_AddIndexConstraint: + { + IndexStmt *istmt; + Relation idx; + Oid conOid = sub->address.objectId; + + Assert(IsA(subcmd->def, IndexStmt)); + istmt = (IndexStmt *) subcmd->def; + + Assert(istmt->isconstraint && istmt->unique); + + idx = relation_open(istmt->indexOid, AccessShareLock); + + /* + * Syntax: ADD CONSTRAINT %{name}I %{constraint_type}s + * USING INDEX %index_name}I %{deferrable}s + * %{init_deferred}s + */ + pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + + new_jsonb_VA(&state, 7, + "fmt", jbvString, + "ADD CONSTRAINT %{name}I %{constraint_type}s" + " USING INDEX %{index_name}I %{deferrable}s" + " %{init_deferred}s", + "type", jbvString, "add constraint using index", + "name", jbvString, get_constraint_name(conOid), + "constraint_type", jbvString, + istmt->primary ? "PRIMARY KEY" : "UNIQUE", + "index_name", jbvString, + RelationGetRelationName(idx), + "deferrable", jbvString, + istmt->deferrable ? "DEFERRABLE" : + "NOT DEFERRABLE", + "init_deferred", jbvString, + istmt->initdeferred ? "INITIALLY DEFERRED" : + "INITIALLY IMMEDIATE"); + + pushJsonbValue(&state, WJB_END_OBJECT, NULL); + relation_close(idx, AccessShareLock); + break; + } + case AT_ReAddIndex: + case AT_ReAddConstraint: + case AT_ReAddDomainConstraint: + case AT_ReAddComment: + case AT_ReplaceRelOptions: + case AT_ReAddStatistics: + /* Subtypes used for internal operations; nothing to do here */ + break; + + case AT_ColumnDefault: + if (subcmd->def == NULL) + { + /* + * Syntax: ALTER COLUMN %{column}I DROP DEFAULT + */ + pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + new_jsonb_VA(&state, 3, + "fmt", jbvString, + "ALTER COLUMN %{column}I DROP DEFAULT", + "type", jbvString, "drop default", + "column", jbvString, subcmd->name); + pushJsonbValue(&state, WJB_END_OBJECT, NULL); + } + else + { + List *dpcontext_rel; + HeapTuple attrtup; + AttrNumber attno; + + dpcontext_rel = deparse_context_for( + RelationGetRelationName(rel), + RelationGetRelid(rel)); + attrtup = SearchSysCacheAttName(RelationGetRelid(rel), + subcmd->name); + attno = ((Form_pg_attribute) GETSTRUCT(attrtup))->attnum; + + /* + * Syntax: ALTER COLUMN %{column}I SET DEFAULT + * %{definition}s + */ + pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + + new_jsonb_VA(&state, 4, + "fmt", jbvString, + "ALTER COLUMN %{column}I SET DEFAULT" + " %{definition}s", + "type", jbvString, "set default", + "column", jbvString, subcmd->name, + "definition", jbvString, + relation_get_column_default(rel, attno, + dpcontext_rel)); + + pushJsonbValue(&state, WJB_END_OBJECT, NULL); + ReleaseSysCache(attrtup); + } + + break; + + case AT_DropNotNull: + + /* + * Syntax: ALTER COLUMN %{column}I DROP NOT NULL + */ + pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + new_jsonb_VA(&state, 3, + "fmt", jbvString, + "ALTER COLUMN %{column}I DROP NOT NULL", + "type", jbvString, "drop not null", + "column", jbvString, subcmd->name); + pushJsonbValue(&state, WJB_END_OBJECT, NULL); + break; + + case AT_ForceRowSecurity: + + /* + * Syntax: FORCE ROW LEVEL SECURITY + */ + pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + new_jsonb_VA(&state, 1, "fmt", jbvString, + "FORCE ROW LEVEL SECURITY"); + pushJsonbValue(&state, WJB_END_OBJECT, NULL); + break; + + case AT_NoForceRowSecurity: + + /* + * Syntax: NO FORCE ROW LEVEL SECURITY + */ + pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + new_jsonb_VA(&state, 1, "fmt", jbvString, + "NO FORCE ROW LEVEL SECURITY"); + pushJsonbValue(&state, WJB_END_OBJECT, NULL); + break; + + case AT_SetNotNull: + + /* + * Syntax: ALTER COLUMN %{column}I SET NOT NULL + */ + pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + new_jsonb_VA(&state, 3, + "fmt", jbvString, + "ALTER COLUMN %{column}I SET NOT NULL", + "type", jbvString, "set not null", + "column", jbvString, subcmd->name); + pushJsonbValue(&state, WJB_END_OBJECT, NULL); + break; + + case AT_DropExpression: + { + StringInfoData fmtSub; + + initStringInfo(&fmtSub); + + /* + * Syntax: ALTER COLUMN %{column}I DROP EXPRESSION + * %{if_exists}s + */ + pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + appendStringInfoString(&fmtSub, "ALTER COLUMN" + " %{column}I DROP EXPRESSION"); + new_jsonb_VA(&state, 2, + "type", jbvString, "drop expression", + "column", jbvString, subcmd->name); + + if (subcmd->missing_ok) + { + appendStringInfoString(&fmtSub, " %{if_exists}s"); + new_jsonb_VA(&state, 1, + "if_exists", jbvString, "IF EXISTS"); + } + + /* We have full fmt by now, so add jsonb element for that */ + new_jsonb_VA(&state, 1, "fmt", jbvString, fmtSub.data); + pfree(fmtSub.data); + + pushJsonbValue(&state, WJB_END_OBJECT, NULL); + break; + } + + case AT_SetStatistics: + Assert(IsA(subcmd->def, Integer)); + + /* + * Syntax: ALTER COLUMN %{column}I SET STATISTICS + * %{statistics}n + */ + pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + new_jsonb_VA(&state, 4, + "fmt", jbvString, + "ALTER COLUMN %{column}I SET STATISTICS" + " %{statistics}n", + "type", jbvString, "set statistics", + "column", jbvString, subcmd->name, + "statistics", jbvNumeric, + intVal((Integer *) subcmd->def)); + pushJsonbValue(&state, WJB_END_OBJECT, NULL); + break; + + case AT_SetOptions: + case AT_ResetOptions: + + /* + * Syntax: ALTER COLUMN %{column}I RESET|SET (%{options:, }s) + */ + deparse_ColumnSetOptions(&state, subcmd); + break; + + case AT_SetStorage: + Assert(IsA(subcmd->def, String)); + + /* + * Syntax: ALTER COLUMN %{column}I SET STORAGE %{storage}s + */ + pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + new_jsonb_VA(&state, 4, + "fmt", jbvString, + "ALTER COLUMN %{column}I SET STORAGE" + " %{storage}s", + "type", jbvString, "set storage", + "column", jbvString, subcmd->name, + "storage", jbvString, + strVal((String *) subcmd->def)); + pushJsonbValue(&state, WJB_END_OBJECT, NULL); + break; + + case AT_SetCompression: + Assert(IsA(subcmd->def, String)); + + /* + * Syntax: ALTER COLUMN %{column}I SET COMPRESSION + * %{compression_method}s + */ + pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + new_jsonb_VA(&state, 4, + "fmt", jbvString, + "ALTER COLUMN %{column}I SET COMPRESSION" + " %{compression_method}s", + "type", jbvString, "set compression", + "column", jbvString, subcmd->name, + "compression_method", jbvString, + strVal((String *) subcmd->def)); + pushJsonbValue(&state, WJB_END_OBJECT, NULL); + break; + + case AT_DropColumn: + { + StringInfoData fmtSub; + + initStringInfo(&fmtSub); + + /* + * Syntax: DROP COLUMN %{if_exists}s %{column}I + * %{cascade}s + */ + pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + + appendStringInfoString(&fmtSub, "DROP COLUMN"); + new_jsonb_VA(&state, 1, "type", jbvString, "drop column"); + + if (subcmd->missing_ok) + { + appendStringInfoString(&fmtSub, " %{if_exists}s"); + new_jsonb_VA(&state, 1, + "if_exists", jbvString, "IF EXISTS"); + } + + appendStringInfoString(&fmtSub, " %{column}I"); + new_jsonb_VA(&state, 1, "column", jbvString, subcmd->name); + + if (subcmd->behavior == DROP_CASCADE) + { + appendStringInfoString(&fmtSub, " %{cascade}s"); + new_jsonb_VA(&state, 1, + "cascade", jbvString, "CASCADE"); + } + + /* We have full fmt by now, so add jsonb element for that */ + new_jsonb_VA(&state, 1, "fmt", jbvString, fmtSub.data); + pfree(fmtSub.data); + + pushJsonbValue(&state, WJB_END_OBJECT, NULL); + + break; + } + case AT_AddIndex: + { + Oid idxOid = sub->address.objectId; + IndexStmt *istmt PG_USED_FOR_ASSERTS_ONLY = + (IndexStmt *) subcmd->def; + Relation idx; + const char *idxname; + Oid constrOid; + + Assert(IsA(subcmd->def, IndexStmt)); + Assert(istmt->isconstraint); + + idx = relation_open(idxOid, AccessShareLock); + idxname = RelationGetRelationName(idx); + + constrOid = get_relation_constraint_oid( + cmd->d.alterTable.objectId, + idxname, false); + + /* + * Syntax: ADD CONSTRAINT %{name}I %{definition}s + */ + pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + + new_jsonb_VA(&state, 4, + "fmt", jbvString, + "ADD CONSTRAINT %{name}I %{definition}s", + "type", jbvString, "add constraint", + "name", jbvString, idxname, + "definition", jbvString, + pg_get_constraintdef_string(constrOid)); + + pushJsonbValue(&state, WJB_END_OBJECT, NULL); + + relation_close(idx, AccessShareLock); + } + break; + case AT_AddConstraint: + { + /* XXX need to set the "recurse" bit somewhere? */ + Oid constrOid = sub->address.objectId; + + /* Skip adding constraint for inherits table sub command */ + if (!OidIsValid(constrOid)) + continue; + + Assert(IsA(subcmd->def, Constraint)); + + /* + * Syntax: ADD CONSTRAINT %{name}I %{definition}s + */ + pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + new_jsonb_VA(&state, 4, + "fmt", jbvString, + "ADD CONSTRAINT %{name}I %{definition}s", + "type", jbvString, "add constraint", + "name", jbvString, get_constraint_name(constrOid), + "definition", jbvString, + pg_get_constraintdef_string(constrOid)); + pushJsonbValue(&state, WJB_END_OBJECT, NULL); + + break; + } + + case AT_AlterConstraint: + { + Oid conOid = sub->address.objectId; + Constraint *c = (Constraint *) subcmd->def; + + /* If no constraint was altered, silently skip it */ + if (!OidIsValid(conOid)) + break; + + Assert(IsA(c, Constraint)); + + /* + * Syntax: ALTER CONSTRAINT %{name}I %{deferrable}s + * %{init_deferred}s + */ + pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + new_jsonb_VA(&state, 5, + "fmt", jbvString, + "ALTER CONSTRAINT %{name}I %{deferrable}s" + " %{init_deferred}s", + "type", jbvString, "alter constraint", + "name", jbvString, get_constraint_name(conOid), + "deferrable", jbvString, + c->deferrable ? "DEFERRABLE" : "NOT DEFERRABLE", + "init_deferred", jbvString, + c->initdeferred ? "INITIALLY DEFERRED" : + "INITIALLY IMMEDIATE"); + pushJsonbValue(&state, WJB_END_OBJECT, NULL); + } + break; + + case AT_ValidateConstraint: + + /* + * Syntax: VALIDATE CONSTRAINT %{constraint}I + */ + pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + new_jsonb_VA(&state, 3, + "fmt", jbvString, + "VALIDATE CONSTRAINT %{constraint}I", + "type", jbvString, "validate constraint", + "constraint", jbvString, subcmd->name); + pushJsonbValue(&state, WJB_END_OBJECT, NULL); + break; + + case AT_DropConstraint: + { + StringInfoData fmtSub; + + initStringInfo(&fmtSub); + + /* + * Syntax: DROP CONSTRAINT %{if_exists}s %{constraint}I + * %{cascade}s + */ + pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + appendStringInfoString(&fmtSub, "DROP CONSTRAINT"); + new_jsonb_VA(&state, 1, "type", jbvString, "drop constraint"); + + if (subcmd->missing_ok) + { + appendStringInfoString(&fmtSub, " %{if_exists}s"); + new_jsonb_VA(&state, 1, + "if_exists", jbvString, "IF EXISTS"); + } + + appendStringInfoString(&fmtSub, " %{constraint}I"); + new_jsonb_VA(&state, 1, + "constraint", jbvString, subcmd->name); + + if (subcmd->behavior == DROP_CASCADE) + { + appendStringInfoString(&fmtSub, " %{cascade}s"); + new_jsonb_VA(&state, 1, "cascade", jbvString, "CASCADE"); + } + + /* We have full fmt by now, so add jsonb element for that */ + new_jsonb_VA(&state, 1, "fmt", jbvString, fmtSub.data); + pfree(fmtSub.data); + + pushJsonbValue(&state, WJB_END_OBJECT, NULL); + } + break; + + case AT_AlterColumnType: + { + TupleDesc tupdesc = RelationGetDescr(rel); + Form_pg_attribute att; + ColumnDef *def; + StringInfoData fmtSub; + + initStringInfo(&fmtSub); + + att = TupleDescAttr(tupdesc, sub->address.objectSubId - 1); + def = (ColumnDef *) subcmd->def; + Assert(IsA(def, ColumnDef)); + + /* + * Syntax: ALTER COLUMN %{column}I SET DATA TYPE + * %{datatype}T %{collation}s %{using}s where using: USING + * %{expression}s + */ + pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + appendStringInfoString(&fmtSub, "ALTER COLUMN %{column}I" + " SET DATA TYPE %{datatype}T"); + new_jsonb_VA(&state, 2, + "type", jbvString, "alter column type", + "column", jbvString, subcmd->name); + + new_jsonb_for_type(&state, "datatype", + att->atttypid, att->atttypmod); + + /* Add a COLLATE clause, if needed */ + if (OidIsValid(att->attcollation)) + { + appendStringInfoString(&fmtSub, " %{collation}s"); + insert_collate_object(&state, "collation", + "COLLATE %{name}D", + CollationRelationId, + att->attcollation, "name"); + } + + /* + * If there's a USING clause, transformAlterTableStmt ran + * it through transformExpr and stored the resulting node + * in cooked_default, which we can use here. + */ + if (def->raw_default) + { + Datum deparsed; + char *defexpr; + List *exprs = NIL; + + exprs = lappend(exprs, def->cooked_default); + defexpr = nodeToString(def->cooked_default); + deparsed = DirectFunctionCall2(pg_get_expr, + CStringGetTextDatum(defexpr), + RelationGetRelid(rel)); + appendStringInfoString(&fmtSub, " %{using}s"); + insert_jsonb_key(&state, "using"); + pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + new_jsonb_VA(&state, 2, + "fmt", jbvString, "USING %{expression}s", + "expression", jbvString, + TextDatumGetCString(deparsed)); + pushJsonbValue(&state, WJB_END_OBJECT, NULL); + } + + /* We have full fmt by now, so add jsonb element for that */ + new_jsonb_VA(&state, 1, "fmt", jbvString, fmtSub.data); + + pfree(fmtSub.data); + pushJsonbValue(&state, WJB_END_OBJECT, NULL); + } + break; + + case AT_ChangeOwner: + + /* + * Syntax: OWNER TO %{owner}I + */ + pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + new_jsonb_VA(&state, 3, + "fmt", jbvString, "OWNER TO %{owner}I", + "type", jbvString, "change owner", + "owner", jbvString, + get_rolespec_name(subcmd->newowner)); + pushJsonbValue(&state, WJB_END_OBJECT, NULL); + break; + + case AT_ClusterOn: + + /* + * Syntax: CLUSTER ON %{index}I + */ + pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + new_jsonb_VA(&state, 3, + "fmt", jbvString, "CLUSTER ON %{index}I", + "type", jbvString, "cluster on", + "index", jbvString, subcmd->name); + pushJsonbValue(&state, WJB_END_OBJECT, NULL); + break; + + + case AT_DropCluster: + + /* + * Syntax: SET WITHOUT CLUSTER + */ + pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + new_jsonb_VA(&state, 2, + "fmt", jbvString, "SET WITHOUT CLUSTER", + "type", jbvString, "set without cluster"); + pushJsonbValue(&state, WJB_END_OBJECT, NULL); + break; + + case AT_SetLogged: + + /* + * Syntax: SET LOGGED + */ + pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + new_jsonb_VA(&state, 2, + "fmt", jbvString, "SET LOGGED", + "type", jbvString, "set logged"); + pushJsonbValue(&state, WJB_END_OBJECT, NULL); + break; + + case AT_SetUnLogged: + + /* + * Syntax: SET UNLOGGED + */ + pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + new_jsonb_VA(&state, 2, + "fmt", jbvString, "SET UNLOGGED", + "type", jbvString, "set unlogged"); + pushJsonbValue(&state, WJB_END_OBJECT, NULL); + break; + + case AT_DropOids: + + /* + * Syntax: SET WITHOUT OIDS + */ + pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + new_jsonb_VA(&state, 2, + "fmt", jbvString, "SET WITHOUT OIDS", + "type", jbvString, "set without oids"); + pushJsonbValue(&state, WJB_END_OBJECT, NULL); + break; + + case AT_SetAccessMethod: + + /* + * Syntax: SET ACCESS METHOD %{access_method}I + */ + pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + new_jsonb_VA(&state, 3, + "fmt", jbvString, + "SET ACCESS METHOD %{access_method}I", + "type", jbvString, "set access method", + "access_method", jbvString, subcmd->name); + pushJsonbValue(&state, WJB_END_OBJECT, NULL); + break; + + case AT_SetTableSpace: + + /* + * Syntax: SET TABLESPACE %{tablespace}I + */ + pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + new_jsonb_VA(&state, 3, + "fmt", jbvString, "SET TABLESPACE %{tablespace}I", + "type", jbvString, "set tablespace", + "tablespace", jbvString, subcmd->name); + pushJsonbValue(&state, WJB_END_OBJECT, NULL); + break; + + case AT_SetRelOptions: + case AT_ResetRelOptions: + + /* + * Syntax: SET|RESET (%{options:, }s) + */ + deparse_RelSetOptions(&state, subcmd); + break; + + case AT_EnableTrig: + + /* + * Syntax: ENABLE TRIGGER %{trigger}I + */ + pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + new_jsonb_VA(&state, 3, + "fmt", jbvString, "ENABLE TRIGGER %{trigger}I", + "type", jbvString, "enable trigger", + "trigger", jbvString, subcmd->name); + pushJsonbValue(&state, WJB_END_OBJECT, NULL); + break; + + case AT_EnableAlwaysTrig: + + /* + * Syntax: ENABLE ALWAYS TRIGGER %{trigger}I + */ + pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + new_jsonb_VA(&state, 3, + "fmt", jbvString, + "ENABLE ALWAYS TRIGGER %{trigger}I", + "type", jbvString, "enable always trigger", + "trigger", jbvString, subcmd->name); + pushJsonbValue(&state, WJB_END_OBJECT, NULL); + break; + + case AT_EnableReplicaTrig: + + /* + * Syntax: ENABLE REPLICA TRIGGER %{trigger}I + */ + pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + new_jsonb_VA(&state, 3, + "fmt", jbvString, + "ENABLE REPLICA TRIGGER %{trigger}I", + "type", jbvString, "enable replica trigger", + "trigger", jbvString, subcmd->name); + pushJsonbValue(&state, WJB_END_OBJECT, NULL); + break; + + case AT_DisableTrig: + + /* + * Syntax: DISABLE TRIGGER %{trigger}I + */ + pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + new_jsonb_VA(&state, 3, + "fmt", jbvString, "DISABLE TRIGGER %{trigger}I", + "type", jbvString, "disable trigger", + "trigger", jbvString, subcmd->name); + pushJsonbValue(&state, WJB_END_OBJECT, NULL); + break; + + case AT_EnableTrigAll: + + /* + * Syntax: ENABLE TRIGGER ALL + */ + pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + new_jsonb_VA(&state, 2, + "fmt", jbvString, "ENABLE TRIGGER ALL", + "type", jbvString, "enable trigger all"); + pushJsonbValue(&state, WJB_END_OBJECT, NULL); + break; + + case AT_DisableTrigAll: + + /* + * Syntax: DISABLE TRIGGER ALL + */ + pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + new_jsonb_VA(&state, 2, + "fmt", jbvString, "DISABLE TRIGGER ALL", + "type", jbvString, "disable trigger all"); + pushJsonbValue(&state, WJB_END_OBJECT, NULL); + break; + + case AT_EnableTrigUser: + + /* + * Syntax: ENABLE TRIGGER USER + */ + pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + new_jsonb_VA(&state, 2, + "fmt", jbvString, "ENABLE TRIGGER USER", + "type", jbvString, "enable trigger user"); + pushJsonbValue(&state, WJB_END_OBJECT, NULL); + break; + + case AT_DisableTrigUser: + + /* + * Syntax: DISABLE TRIGGER USER + */ + pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + new_jsonb_VA(&state, 2, + "fmt", jbvString, "DISABLE TRIGGER USER", + "type", jbvString, "disable trigger user"); + pushJsonbValue(&state, WJB_END_OBJECT, NULL); + break; + + case AT_EnableRule: + + /* + * Syntax: ENABLE RULE %{rule}I + */ + pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + new_jsonb_VA(&state, 3, + "fmt", jbvString, "ENABLE RULE %{rule}I", + "type", jbvString, "enable rule", + "rule", jbvString, subcmd->name); + pushJsonbValue(&state, WJB_END_OBJECT, NULL); + break; + + case AT_EnableAlwaysRule: + + /* + * Syntax: ENABLE ALWAYS RULE %{rule}I + */ + pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + new_jsonb_VA(&state, 3, + "fmt", jbvString, "ENABLE ALWAYS RULE %{rule}I", + "type", jbvString, "enable always rule", + "rule", jbvString, subcmd->name); + pushJsonbValue(&state, WJB_END_OBJECT, NULL); + break; + + case AT_EnableReplicaRule: + + /* + * Syntax: ENABLE REPLICA RULE %{rule}I + */ + pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + new_jsonb_VA(&state, 3, + "fmt", jbvString, "ENABLE REPLICA RULE %{rule}I", + "type", jbvString, "enable replica rule", + "rule", jbvString, subcmd->name); + pushJsonbValue(&state, WJB_END_OBJECT, NULL); + break; + + case AT_DisableRule: + + /* + * Syntax: DISABLE RULE %{rule}I + */ + pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + new_jsonb_VA(&state, 3, + "fmt", jbvString, "DISABLE RULE %{rule}I", + "type", jbvString, "disable rule", + "rule", jbvString, subcmd->name); + pushJsonbValue(&state, WJB_END_OBJECT, NULL); + break; + + case AT_AddInherit: + + /* + * Syntax: INHERIT %{parent}D + */ + pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + new_jsonb_VA(&state, 2, + "fmt", jbvString, "INHERIT %{parent}D", + "type", jbvString, "inherit"); + new_jsonb_for_qualname_id(&state, RelationRelationId, + sub->address.objectId, "parent", true); + pushJsonbValue(&state, WJB_END_OBJECT, NULL); + break; + + case AT_DropInherit: + + /* + * Syntax: NO INHERIT %{parent}D + */ + pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + new_jsonb_VA(&state, 2, + "fmt", jbvString, "NO INHERIT %{parent}D", + "type", jbvString, "drop inherit"); + new_jsonb_for_qualname_id(&state, RelationRelationId, + sub->address.objectId, "parent", true); + pushJsonbValue(&state, WJB_END_OBJECT, NULL); + break; + + case AT_AddOf: + + /* + * Syntax: OF %{type_of}T + */ + pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + new_jsonb_VA(&state, 2, + "fmt", jbvString, "OF %{type_of}T", + "type", jbvString, "add of"); + new_jsonb_for_type(&state, "type_of", sub->address.objectId, -1); + pushJsonbValue(&state, WJB_END_OBJECT, NULL); + break; + + case AT_DropOf: + + /* + * Syntax: NOT OF + */ + pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + new_jsonb_VA(&state, 2, + "fmt", jbvString, "NOT OF", + "type", jbvString, "not of"); + pushJsonbValue(&state, WJB_END_OBJECT, NULL); + break; + + case AT_ReplicaIdentity: + + /* + * Syntax: REPLICA IDENTITY %{ident}s + */ + pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + new_jsonb_VA(&state, 2, + "fmt", jbvString, + "REPLICA IDENTITY %{ident}s", + "type", jbvString, "replica identity"); + switch (((ReplicaIdentityStmt *) subcmd->def)->identity_type) + { + case REPLICA_IDENTITY_DEFAULT: + new_jsonb_VA(&state, 1, "ident", jbvString, "DEFAULT"); + break; + case REPLICA_IDENTITY_FULL: + new_jsonb_VA(&state, 1, "ident", jbvString, "FULL"); + break; + case REPLICA_IDENTITY_NOTHING: + new_jsonb_VA(&state, 1, "ident", jbvString, "NOTHING"); + break; + case REPLICA_IDENTITY_INDEX: + insert_jsonb_key(&state, "ident"); + new_jsonb_VA(&state, 2, + "fmt", jbvString, "USING INDEX %{index}I", + "index", jbvString, + ((ReplicaIdentityStmt *) subcmd->def)->name); + break; + } + pushJsonbValue(&state, WJB_END_OBJECT, NULL); + break; + + case AT_EnableRowSecurity: + + /* + * Syntax: ENABLE ROW LEVEL SECURITY + */ + pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + new_jsonb_VA(&state, 2, + "fmt", jbvString, + "ENABLE ROW LEVEL SECURITY", + "type", jbvString, "enable row security"); + pushJsonbValue(&state, WJB_END_OBJECT, NULL); + break; + + case AT_DisableRowSecurity: + + /* + * Syntax: DISABLE ROW LEVEL SECURITY + */ + pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + new_jsonb_VA(&state, 2, + "fmt", jbvString, + "DISABLE ROW LEVEL SECURITY", + "type", jbvString, "disable row security"); + pushJsonbValue(&state, WJB_END_OBJECT, NULL); + break; + + case AT_AttachPartition: + { + StringInfoData fmtSub; + + initStringInfo(&fmtSub); + + /* + * Syntax: ATTACH PARTITION %{partition_identity}D + * %{partition_bound}s + */ + pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + appendStringInfoString(&fmtSub, "ATTACH PARTITION" + " %{partition_identity}D"); + + new_jsonb_VA(&state, 1, "type", jbvString, + "attach partition"); + new_jsonb_for_qualname_id(&state, RelationRelationId, + sub->address.objectId, + "partition_identity", true); + + if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + { + appendStringInfoString(&fmtSub, " %{partition_bound}s"); + new_jsonb_VA(&state, 1, + "partition_bound", jbvString, + relation_get_part_bound(sub->address.objectId)); + } + + /* We have full fmt by now, so add jsonb element for that */ + new_jsonb_VA(&state, 1, "fmt", jbvString, fmtSub.data); + pfree(fmtSub.data); + + pushJsonbValue(&state, WJB_END_OBJECT, NULL); + + break; + } + case AT_DetachPartition: + { + PartitionCmd *cmd; + StringInfoData fmtSub; + + initStringInfo(&fmtSub); + + Assert(IsA(subcmd->def, PartitionCmd)); + cmd = (PartitionCmd *) subcmd->def; + + /* + * Syntax: DETACH PARTITION %{partition_identity}D + * %{concurrent}s + */ + pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + appendStringInfoString(&fmtSub, "DETACH PARTITION" + " %{partition_identity}D"); + + new_jsonb_VA(&state, 1, "type", jbvString, "detach partition"); + new_jsonb_for_qualname_id(&state, RelationRelationId, + sub->address.objectId, + "partition_identity", true); + if (cmd->concurrent) + { + appendStringInfoString(&fmtSub, " %{concurrent}s"); + new_jsonb_VA(&state, 1, + "concurrent", jbvString, "CONCURRENTLY"); + } + + /* We have full fmt by now, so add jsonb element for that */ + new_jsonb_VA(&state, 1, "fmt", jbvString, fmtSub.data); + pfree(fmtSub.data); + + pushJsonbValue(&state, WJB_END_OBJECT, NULL); + break; + } + case AT_DetachPartitionFinalize: + + /* + * Syntax: DETACH PARTITION %{partition_identity}D FINALIZE + */ + pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + new_jsonb_VA(&state, 2, + "fmt", jbvString, "DETACH PARTITION" + " %{partition_identity}D FINALIZE", + "type", jbvString, "detach partition finalize"); + + new_jsonb_for_qualname_id(&state, RelationRelationId, + sub->address.objectId, + "partition_identity", true); + pushJsonbValue(&state, WJB_END_OBJECT, NULL); + break; + case AT_AddIdentity: + { + AttrNumber attnum; + Oid seq_relid; + ColumnDef *coldef = (ColumnDef *) subcmd->def; + StringInfoData fmtSub; + + initStringInfo(&fmtSub); + + /* + * Syntax: ALTER COLUMN %{column}I %{definition}s where + * definition : ADD %{identity_column}s where + * identity_column: %{identity_type}s ( %{seq_definition: + * }s ) + */ + pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + + appendStringInfoString(&fmtSub, "ALTER COLUMN %{column}I"); + new_jsonb_VA(&state, 2, + "type", jbvString, "add identity", + "column", jbvString, subcmd->name); + + attnum = get_attnum(RelationGetRelid(rel), subcmd->name); + seq_relid = getIdentitySequence(rel, attnum, true); + + if (OidIsValid(seq_relid)) + { + + appendStringInfoString(&fmtSub, " %{definition}s"); + insert_jsonb_key(&state, "definition"); + + /* insert definition's value now */ + pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + new_jsonb_VA(&state, 1, + "fmt", jbvString, "ADD %{identity_column}s"); + + /* insert identity_column */ + deparse_ColumnIdentity(&state, "identity_column", + seq_relid, + coldef->identity, false); + + /* mark definition's value end */ + pushJsonbValue(&state, WJB_END_OBJECT, NULL); + } + + /* We have full fmt by now, so add jsonb element for that */ + new_jsonb_VA(&state, 1, "fmt", jbvString, fmtSub.data); + + pfree(fmtSub.data); + + pushJsonbValue(&state, WJB_END_OBJECT, NULL); + } + break; + case AT_SetIdentity: + { + DefElem *defel; + char identity = 0; + AttrNumber attnum; + Oid seq_relid; + StringInfoData fmtSub; + + initStringInfo(&fmtSub); + + /* + * Syntax: ALTER COLUMN %{column}I %{definition}s where + * definition : %{identity_type}s ( %{seq_definition: }s ) + */ + pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + + appendStringInfoString(&fmtSub, "ALTER COLUMN %{column}I"); + new_jsonb_VA(&state, 2, + "type", jbvString, "set identity", + "column", jbvString, subcmd->name); + + if (subcmd->def) + { + List *def = (List *) subcmd->def; + + Assert(IsA(subcmd->def, List)); + + defel = linitial_node(DefElem, def); + identity = defGetInt32(defel); + } + + attnum = get_attnum(RelationGetRelid(rel), subcmd->name); + seq_relid = getIdentitySequence(rel, attnum, true); + + if (OidIsValid(seq_relid)) + { + appendStringInfoString(&fmtSub, " %{definition}s"); + deparse_ColumnIdentity(&state, "definition", + seq_relid, identity, + true); + } + + /* We have full fmt by now, so add jsonb element for that */ + new_jsonb_VA(&state, 1, "fmt", jbvString, fmtSub.data); + + pfree(fmtSub.data); + pushJsonbValue(&state, WJB_END_OBJECT, NULL); + + break; + } + case AT_DropIdentity: + { + StringInfoData fmtSub; + + initStringInfo(&fmtSub); + + /* + * Syntax: ALTER COLUMN %{column}I DROP IDENTITY + * %{if_exists}s + */ + pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + appendStringInfoString(&fmtSub, "ALTER COLUMN" + " %{column}I DROP IDENTITY"); + new_jsonb_VA(&state, 2, + "type", jbvString, "drop identity", + "column", jbvString, subcmd->name); + + if (subcmd->missing_ok) + { + appendStringInfoString(&fmtSub, " %{if_exists}s"); + new_jsonb_VA(&state, 1, + "if_exists", jbvString, "IF EXISTS"); + } + + /* We have full fmt by now, so add jsonb element for that */ + new_jsonb_VA(&state, 1, "fmt", jbvString, fmtSub.data); + pfree(fmtSub.data); + + pushJsonbValue(&state, WJB_END_OBJECT, NULL); + break; + } + default: + elog(WARNING, "unsupported alter table subtype %d", + subcmd->subtype); + break; + } + } + + table_close(rel, AccessShareLock); + + /* if subcmds array is not even created or has 0 elements, return NULL */ + if (!subCmdArray || + ((state.parseState->contVal.type == jbvArray) && + (state.parseState->contVal.val.array.nElems == 0))) + { + pfree(fmtStr.data); + return NULL; + } + + /* Mark the end of subcmds array */ + pushJsonbValue(&state, WJB_END_ARRAY, NULL); + + /* We have full fmt by now, so add jsonb element for that */ + new_jsonb_VA(&state, 1, "fmt", jbvString, fmtStr.data); + + pfree(fmtStr.data); + + /* Mark the end of ROOT object */ + pushJsonbValue(&state, WJB_END_OBJECT, NULL); + + return JsonbValueToJsonb(state.result); +} + +/* + * Deparse a CreateSeqStmt. + * + * Given a sequence OID and the parse tree that created it, return Jsonb + * representing the creation command. + * + * Note: We need to deparse the CREATE SEQUENCE command for the TABLE + * commands. For example, When creating a table, if we specify a column as a + * serial type, then we will create a sequence for that column and set that + * sequence OWNED BY the table. The serial column type information is not + * available during deparsing phase as that has already been converted to + * the column default value and sequences creation while parsing. + * + * Verbose syntax + * CREATE %{persistence}s SEQUENCE %{if_not_exists}s %{identity}D + * %{definition: }s + */ +static Jsonb * +deparse_CreateSeqStmt(Oid objectId, Node *parsetree) +{ + Relation relation; + Form_pg_sequence seqform; + Sequence_values *seqvalues; + CreateSeqStmt *createSeqStmt = (CreateSeqStmt *) parsetree; + JsonbInState state = {0}; + StringInfoData fmtStr; + char *perstr; + + /* + * Only support sequence for IDENTITY COLUMN output separately (via CREATE + * TABLE or ALTER TABLE). Otherwise, return empty here. + */ + if (createSeqStmt->for_identity) + return NULL; + + initStringInfo(&fmtStr); + relation = relation_open(objectId, AccessShareLock); + + /* mark the start of ROOT object */ + pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + appendStringInfoString(&fmtStr, "CREATE"); + + /* PERSISTENCE */ + perstr = get_persistence_str(relation->rd_rel->relpersistence); + if (perstr) + { + appendStringInfoString(&fmtStr, " %{persistence}s"); + new_jsonb_VA(&state, 1, + "persistence", jbvString, perstr); + } + + appendStringInfoString(&fmtStr, " SEQUENCE"); + + /* IF NOT EXISTS */ + if (createSeqStmt->if_not_exists) + { + appendStringInfoString(&fmtStr, " %{if_not_exists}s"); + new_jsonb_VA(&state, 1, + "if_not_exists", jbvString, "IF NOT EXISTS"); + } + + /* IDENTITY */ + appendStringInfoString(&fmtStr, " %{identity}D"); + insert_identity_object(&state, relation->rd_rel->relnamespace, + RelationGetRelationName(relation)); + + relation_close(relation, AccessShareLock); + + seqvalues = get_sequence_values(objectId); + seqform = seqvalues->seqform; + + /* sequence definition array object creation, push the key first */ + appendStringInfoString(&fmtStr, " %{definition: }s"); + insert_jsonb_key(&state, "definition"); + + pushJsonbValue(&state, WJB_BEGIN_ARRAY, NULL); + + /* Definition elements */ + deparse_Seq_Cache(&state, seqform, false); + deparse_Seq_Cycle(&state, seqform, false); + deparse_Seq_IncrementBy(&state, seqform, false); + deparse_Seq_Minvalue(&state, seqform, false); + deparse_Seq_Maxvalue(&state, seqform, false); + deparse_Seq_Startwith(&state, seqform, false); + deparse_Seq_Restart(&state, seqvalues->last_value); + deparse_Seq_As(&state, seqform); + + /* We purposefully do not emit OWNED BY here */ + + /* mark the end of sequence definition array */ + pushJsonbValue(&state, WJB_END_ARRAY, NULL); + + /* We have full fmt by now, so add jsonb element for that */ + new_jsonb_VA(&state, 1, "fmt", jbvString, fmtStr.data); + + pfree(fmtStr.data); + + /* mark the end of ROOT object */ + pushJsonbValue(&state, WJB_END_OBJECT, NULL); + + return JsonbValueToJsonb(state.result); +} + +/* + * Deparse an AlterSeqStmt. + * + * Given a sequence OID and a parse tree that modified it, return Jsonb + * representing the alter command. + * + * Note: We need to deparse the ALTER SEQUENCE command for the TABLE commands. + * For example, When creating a table, if we specify a column as a serial + * type, then we will create a sequence for that column and set that sequence + * OWNED BY the table. The serial column type information is not available + * during deparsing phase as that has already been converted to the column + * default value and sequences creation while parsing. + * + * Verbose syntax + * ALTER SEQUENCE %{identity}D %{definition: }s + */ +static Jsonb * +deparse_AlterSeqStmt(Oid objectId, Node *parsetree) +{ + Relation relation; + ListCell *cell; + Form_pg_sequence seqform; + Sequence_values *seqvalues; + AlterSeqStmt *alterSeqStmt = (AlterSeqStmt *) parsetree; + JsonbInState state = {0}; + + /* + * Sequence for IDENTITY COLUMN output separately (via CREATE TABLE or + * ALTER TABLE); return empty here. + */ + if (alterSeqStmt->for_identity) + return NULL; + + relation = relation_open(objectId, AccessShareLock); + + /* mark the start of ROOT object */ + pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + + new_jsonb_VA(&state, 1, + "fmt", jbvString, "ALTER SEQUENCE %{identity}D %{definition: }s"); + + insert_identity_object(&state, relation->rd_rel->relnamespace, + RelationGetRelationName(relation)); + relation_close(relation, AccessShareLock); + + seqvalues = get_sequence_values(objectId); + seqform = seqvalues->seqform; + + /* sequence definition array object creation, push the key first */ + insert_jsonb_key(&state, "definition"); + + /* mark the start of sequence definition array */ + pushJsonbValue(&state, WJB_BEGIN_ARRAY, NULL); + + foreach(cell, ((AlterSeqStmt *) parsetree)->options) + { + DefElem *elem = (DefElem *) lfirst(cell); + + if (strcmp(elem->defname, "cache") == 0) + deparse_Seq_Cache(&state, seqform, false); + else if (strcmp(elem->defname, "cycle") == 0) + deparse_Seq_Cycle(&state, seqform, false); + else if (strcmp(elem->defname, "increment") == 0) + deparse_Seq_IncrementBy(&state, seqform, false); + else if (strcmp(elem->defname, "minvalue") == 0) + deparse_Seq_Minvalue(&state, seqform, false); + else if (strcmp(elem->defname, "maxvalue") == 0) + deparse_Seq_Maxvalue(&state, seqform, false); + else if (strcmp(elem->defname, "start") == 0) + deparse_Seq_Startwith(&state, seqform, false); + else if (strcmp(elem->defname, "restart") == 0) + deparse_Seq_Restart(&state, seqvalues->last_value); + else if (strcmp(elem->defname, "owned_by") == 0) + deparse_Seq_OwnedBy(&state, objectId, false); + else if (strcmp(elem->defname, "as") == 0) + deparse_Seq_As(&state, seqform); + else + elog(ERROR, "invalid sequence option %s", elem->defname); + } + + /* mark the end of sequence definition array */ + pushJsonbValue(&state, WJB_END_ARRAY, NULL); + + /* mark the end of ROOT object */ + pushJsonbValue(&state, WJB_END_OBJECT, NULL); + + return JsonbValueToJsonb(state.result); +} + +/* + * Deparse a RenameStmt. + * + * Verbose syntax + * ALTER TABLE %{if_exists}s %{identity}D RENAME TO %{newname}I + * OR + * ALTER TABLE %{only}s %{identity}D RENAME CONSTRAINT %{oldname}I + * TO %{newname}I + * OR + * ALTER %{objtype}s %{if_exists}s %{only}s %{identity}D RENAME COLUMN + * %{colname}I TO %{newname}I %{cascade}s + */ + +static Jsonb * +deparse_RenameStmt(ObjectAddress address, Node *parsetree) +{ + RenameStmt *node = (RenameStmt *) parsetree; + Relation relation; + Oid schemaId; + JsonbInState state = {0}; + JsonbValue *value; + + /* + * In an ALTER .. RENAME command, we don't have the original name of the + * object in system catalogs: since we inspect them after the command has + * executed, the old name is already gone. Therefore, we extract it from + * the parse node. Note we still extract the schema name from the catalog + * (it might not be present in the parse node); it cannot possibly have + * changed anyway. + */ + switch (node->renameType) + { + case OBJECT_TABLE: + relation = relation_open(address.objectId, AccessShareLock); + schemaId = RelationGetNamespace(relation); + + pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + new_jsonb_VA(&state, 3, + "fmt", jbvString, + "ALTER TABLE %{if_exists}s %{identity}D" + " RENAME TO %{newname}I", + "if_exists", jbvString, + node->missing_ok ? "IF EXISTS" : "", + "newname", jbvString, node->newname); + + insert_identity_object(&state, schemaId, node->relation->relname); + pushJsonbValue(&state, WJB_END_OBJECT, NULL); + value = state.result; + + relation_close(relation, AccessShareLock); + break; + + case OBJECT_TABCONSTRAINT: + { + HeapTuple constrtup; + Form_pg_constraint constform; + + constrtup = SearchSysCache1(CONSTROID, + ObjectIdGetDatum(address.objectId)); + if (!HeapTupleIsValid(constrtup)) + elog(ERROR, "cache lookup failed for constraint with OID %u", + address.objectId); + constform = (Form_pg_constraint) GETSTRUCT(constrtup); + + pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + new_jsonb_VA(&state, 4, + "fmt", jbvString, + "ALTER TABLE %{only}s %{identity}D RENAME" + " CONSTRAINT %{oldname}I TO %{newname}I", + "only", jbvString, + node->relation->inh ? "" : "ONLY", + "oldname", jbvString, node->subname, + "newname", jbvString, node->newname); + + new_jsonb_for_qualname_id(&state, RelationRelationId, + constform->conrelid, "identity", true); + pushJsonbValue(&state, WJB_END_OBJECT, NULL); + value = state.result; + + ReleaseSysCache(constrtup); + } + break; + + case OBJECT_COLUMN: + { + StringInfoData fmtStr; + + initStringInfo(&fmtStr); + + relation = relation_open(address.objectId, AccessShareLock); + schemaId = RelationGetNamespace(relation); + + pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + + appendStringInfoString(&fmtStr, "ALTER %{objtype}s"); + + new_jsonb_VA(&state, 1, + "objtype", jbvString, + stringify_objtype(node->relationType)); + + /* Composite types do not support IF EXISTS */ + if (node->renameType == OBJECT_COLUMN) + { + appendStringInfoString(&fmtStr, " %{if_exists}s"); + new_jsonb_VA(&state, 1, + "if_exists", jbvString, + node->missing_ok ? "IF EXISTS" : ""); + } + + if (!node->relation->inh) + { + appendStringInfoString(&fmtStr, " %{only}s"); + new_jsonb_VA(&state, 1, "only", jbvString, "ONLY"); + } + + appendStringInfoString(&fmtStr, " %{identity}D RENAME COLUMN" + " %{colname}I TO %{newname}I"); + insert_identity_object(&state, schemaId, node->relation->relname); + new_jsonb_VA(&state, 2, + "colname", jbvString, node->subname, + "newname", jbvString, node->newname); + + if (node->renameType == OBJECT_ATTRIBUTE) + { + + if (node->behavior == DROP_CASCADE) + { + appendStringInfoString(&fmtStr, " %{cascade}s"); + new_jsonb_VA(&state, 1, "cascade", jbvString, "CASCADE"); + } + } + + /* We have full fmt by now, so add jsonb element for that */ + new_jsonb_VA(&state, 1, "fmt", jbvString, fmtStr.data); + + pfree(fmtStr.data); + + pushJsonbValue(&state, WJB_END_OBJECT, NULL); + value = state.result; + + relation_close(relation, AccessShareLock); + break; + } + + default: + elog(ERROR, "unsupported object type %d", node->renameType); + } + + return JsonbValueToJsonb(value); +} + +/* + * Deparse an AlterObjectSchemaStmt (ALTER TABLE... SET SCHEMA command) + * + * Given the object(table) address and the parse tree that created it, return + * Jsonb representing the alter command. + * + * Verbose syntax + * ALTER %{objtype}s %{identity}s SET SCHEMA %{newschema}I + */ +static Jsonb * +deparse_AlterObjectSchemaStmt(ObjectAddress address, Node *parsetree, + ObjectAddress old_schema) +{ + AlterObjectSchemaStmt *node = (AlterObjectSchemaStmt *) parsetree; + char *identity; + char *new_schema = node->newschema; + char *old_schname; + char *ident; + JsonbInState state = {0}; + + /* + * Since the command has already taken place from the point of view of + * catalogs, getObjectIdentity returns the object name with the already + * changed schema. The output of our deparsing must return the original + * schema name, however, so we chop the schema name off the identity + * string and then prepend the quoted schema name. + * + * XXX This is pretty clunky. Can we do better? + */ + identity = getObjectIdentity(&address, false); + old_schname = get_namespace_name(old_schema.objectId); + if (!old_schname) + elog(ERROR, "cache lookup failed for schema with OID %u", + old_schema.objectId); + + ident = psprintf("%s%s", quote_identifier(old_schname), + identity + strlen(quote_identifier(new_schema))); + + pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + new_jsonb_VA(&state, 4, + "fmt", jbvString, + "ALTER %{objtype}s %{identity}s SET SCHEMA" + " %{newschema}I", + "objtype", jbvString, + stringify_objtype(node->objectType), + "identity", jbvString, ident, + "newschema", jbvString, new_schema); + pushJsonbValue(&state, WJB_END_OBJECT, NULL); + + return JsonbValueToJsonb(state.result); +} + +/* + * Handle deparsing of simple commands. + * + * This function should cover all cases handled in ProcessUtilitySlow. + */ +static Jsonb * +deparse_simple_command(CollectedCommand *cmd) +{ + Oid objectId; + Node *parsetree; + + Assert(cmd->type == SCT_Simple); + + parsetree = cmd->parsetree; + objectId = cmd->d.simple.address.objectId; + + if (cmd->in_extension && (nodeTag(parsetree) != T_CreateExtensionStmt)) + return NULL; + + /* This switch needs to handle everything that ProcessUtilitySlow does */ + switch (nodeTag(parsetree)) + { + case T_AlterObjectSchemaStmt: + return deparse_AlterObjectSchemaStmt(cmd->d.simple.address, + parsetree, + cmd->d.simple.secondaryObject); + + case T_AlterSeqStmt: + return deparse_AlterSeqStmt(objectId, parsetree); + + case T_CreateSeqStmt: + return deparse_CreateSeqStmt(objectId, parsetree); + + case T_CreateStmt: + return deparse_CreateStmt(objectId, parsetree); + case T_RenameStmt: + return deparse_RenameStmt(cmd->d.simple.address, parsetree); + + default: + elog(LOG, "unrecognized node type in deparse command: %d", + (int) nodeTag(parsetree)); + } + + return NULL; +} + +/* + * Workhorse to deparse a CollectedCommand. + */ +char * +deparse_utility_command(CollectedCommand *cmd) +{ + MemoryContext oldcxt; + MemoryContext tmpcxt; + char *command = NULL; + StringInfoData str; + Jsonb *jsonb; + int save_nestlevel; + + /* + * Allocate everything done by the deparsing routines into a temp context, + * to avoid having to sprinkle them with memory handling code, but + * allocate the output StringInfo before switching. + */ + initStringInfo(&str); + tmpcxt = AllocSetContextCreate(CurrentMemoryContext, + "deparse ctx", + ALLOCSET_DEFAULT_MINSIZE, + ALLOCSET_DEFAULT_INITSIZE, + ALLOCSET_DEFAULT_MAXSIZE); + oldcxt = MemoryContextSwitchTo(tmpcxt); + + /* + * Many routines underlying this one will invoke ruleutils.c functionality + * to obtain deparsed versions of expressions. In such results, we want + * all object names to be qualified, so that results are "portable" to + * environments with different search_path settings. Rather than + * injecting what would be repetitive calls to override search path all + * over the place, we do it centrally here. + */ + save_nestlevel = NewGUCNestLevel(); + RestrictSearchPath(); + + switch (cmd->type) + { + case SCT_Simple: + jsonb = deparse_simple_command(cmd); + break; + case SCT_AlterTable: + jsonb = deparse_AlterTableStmt(cmd); + break; + default: + elog(ERROR, "unexpected deparse node type %d", cmd->type); + } + + AtEOXact_GUC(true, save_nestlevel); + + if (jsonb) + command = JsonbToCString(&str, &jsonb->root, JSONB_ESTIMATED_LEN); + + /* + * Clean up. Note that since we created the StringInfo in the caller's + * context, the output string is not deleted here. + */ + MemoryContextSwitchTo(oldcxt); + MemoryContextDelete(tmpcxt); + + return command; +} + +/* + * Given a CollectedCommand, return a JSON representation of it. + * + * The command is expanded fully so that there are no ambiguities even in the + * face of search_path changes. + */ +Datum +ddl_deparse_to_json(PG_FUNCTION_ARGS) +{ + CollectedCommand *cmd = (CollectedCommand *) PG_GETARG_POINTER(0); + char *command; + + command = deparse_utility_command(cmd); + + if (command) + PG_RETURN_TEXT_P(cstring_to_text(command)); + + PG_RETURN_NULL(); +} diff --git a/src/backend/commands/ddljson.c b/src/backend/commands/ddljson.c new file mode 100644 index 00000000000..d5c968b7c13 --- /dev/null +++ b/src/backend/commands/ddljson.c @@ -0,0 +1,759 @@ +/*------------------------------------------------------------------------- + * + * ddljson.c + * JSON code related to DDL command deparsing + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * NOTES + * + * Each JSONB object is supposed to have a "fmt" which will tell expansion + * routines how JSONB can be expanded to construct ddl command. One example + * snippet from JSONB object for 'ALTER TABLE sales ADD col1 int': + * + * { *1-level* + * "fmt": "ALTER %{objtype}s %{only}s %{identity}D %{subcmds:, }s", + * "only": "", + * "objtype": "TABLE", + * "identity": {"objname": "sales", "schemaname": "public"} + * "subcmds": [ + * { *2-level* + * "fmt": "ADD %{objtype}s %{if_not_exists}s %{definition}s", + * "type": "add column", + * "objtype": "COLUMN", + * "definition": {} + * ... + * } + * ... + * } + * + * From above, we can see different key-value pairs. + * level-1 represents ROOT object with 'fmt', 'only', 'objtype','identity', + * 'subcmds' as the keys with the values appended after ":" with each key. + * Value can be string, bool, numeric, array or any nested object. As an + * example, "objtype" has string value while "subcmds" has nested-object + * as its value which can further have multiple key-value pairs. + * + * The value of "fmt" tells us how the expansion will be carried on. The + * value of "fmt" may contain zero or more %-escapes, which consist of key + * name enclosed in { }, followed by a conversion specifier which tells us + * how the value for that particular key should be expanded. + * Possible conversion specifiers are: + * % expand to a literal % + * I expand as a single, non-qualified identifier + * D expand as a possibly-qualified identifier + * T expand as a type name + * L expand as a string literal (quote using single quotes) + * s expand as a simple string (no quoting) + * n expand as a simple number (no quoting) + * + * In order to build a DDL command, it will first extract "fmt" node in + * jsonb string and will read each key name enclosed in { } in fmt-string + * and will replace it with its value. For each name mentioned in { } in + * fmt string, there must be a key-value pair, in absence of which, the + * expansion will error out. While doing this expansion, it will consider + * the conversion-specifier maintained with each key in fmt string to figure + * out how value should actually be represented. This is how DDL command can + * be constructed back from the jsonb-string. + * + * IDENTIFICATION + * src/backend/commands/ddljson.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "tcop/ddldeparse.h" +#include "utils/builtins.h" +#include "utils/jsonb.h" + + +#define ADVANCE_PARSE_POINTER(ptr,end_ptr) \ + do { \ + if (++(ptr) >= (end_ptr)) \ + ereport(ERROR, \ + errcode(ERRCODE_INVALID_PARAMETER_VALUE), \ + errmsg("unterminated format specifier")); \ + } while (0) + +/* + * Conversion specifier which determines how to expand the JSON element + * into a string. + */ +typedef enum +{ + SpecDottedName, + SpecIdentifier, + SpecNumber, + SpecString, + SpecStringLiteral, + SpecTypeName +} convSpecifier; + +/* + * A ternary value that represents a boolean type JsonbValue. + */ +typedef enum +{ + tv_absent, + tv_true, + tv_false +} json_trivalue; + +static bool expand_one_jsonb_element(StringInfo buf, char *param, + JsonbValue *jsonval, convSpecifier specifier, + const char *fmt); +static void expand_jsonb_array(StringInfo buf, char *param, + JsonbValue *jsonarr, char *arraysep, + convSpecifier specifier, const char *fmt); +static void fmtstr_error_callback(void *arg); + +/* + * Given a JsonbContainer, find the JsonbValue with the given key name in it. + * If it's of a type other than jbvBool, an error is raised. If it doesn't + * exist, tv_absent is returned; otherwise return the actual json_trivalue. + */ +static json_trivalue +find_bool_in_jsonbcontainer(JsonbContainer *container, char *keyname) +{ + JsonbValue key; + JsonbValue *value; + json_trivalue result; + + key.type = jbvString; + key.val.string.val = keyname; + key.val.string.len = strlen(keyname); + value = findJsonbValueFromContainer(container, + JB_FOBJECT, &key); + if (value == NULL) + return tv_absent; + if (value->type != jbvBool) + ereport(ERROR, + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("element \"%s\" is not of type boolean", keyname)); + result = value->val.boolean ? tv_true : tv_false; + pfree(value); + + return result; +} + +/* + * Given a JsonbContainer, find the JsonbValue with the given key name in it. + * If it's of a type other than jbvString, an error is raised. If it doesn't + * exist, an error is raised unless missing_ok; otherwise return NULL. + * + * If it exists and is a string, a freshly palloc'ed copy is returned. + * + * If *length is not NULL, it is set to the length of the string. + */ +static char * +find_string_in_jsonbcontainer(JsonbContainer *container, char *keyname, + bool missing_ok, int *length) +{ + JsonbValue key; + JsonbValue *value; + char *str; + + /* XXX verify that this is an object, not an array */ + + key.type = jbvString; + key.val.string.val = keyname; + key.val.string.len = strlen(keyname); + value = findJsonbValueFromContainer(container, + JB_FOBJECT, &key); + if (value == NULL) + { + if (missing_ok) + return NULL; + ereport(ERROR, + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("missing element \"%s\" in JSON object", keyname)); + } + + if (value->type != jbvString) + ereport(ERROR, + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("element \"%s\" is not of type string", keyname)); + + str = pnstrdup(value->val.string.val, value->val.string.len); + if (length) + *length = value->val.string.len; + pfree(value); + return str; +} + +/* + * Recursive helper for deparse_ddl_json_to_string. + * + * Find the "fmt" element in the given container, and expand it into the + * provided StringInfo. + */ +static void +expand_fmt_recursive(StringInfo buf, JsonbContainer *container) +{ + JsonbValue key; + JsonbValue *value; + const char *cp; + const char *start_ptr; + const char *end_ptr; + int len; + + start_ptr = find_string_in_jsonbcontainer(container, "fmt", false, &len); + end_ptr = start_ptr + len; + + for (cp = start_ptr; cp < end_ptr; cp++) + { + convSpecifier specifier; + bool is_array = false; + char *param = NULL; + char *arraysep = NULL; + + if (*cp != '%') + { + appendStringInfoCharMacro(buf, *cp); + continue; + } + + ADVANCE_PARSE_POINTER(cp, end_ptr); + + /* Easy case: %% outputs a single % */ + if (*cp == '%') + { + appendStringInfoCharMacro(buf, *cp); + continue; + } + + /* + * Scan the mandatory element name. Allow for an array separator + * (which may be the empty string) to be specified after a colon. + */ + if (*cp == '{') + { + StringInfoData parbuf; + StringInfoData arraysepbuf; + StringInfo appendTo; + + initStringInfo(&parbuf); + appendTo = &parbuf; + + ADVANCE_PARSE_POINTER(cp, end_ptr); + while (cp < end_ptr) + { + if (*cp == ':') + { + /* + * Found array separator delimiter; element name is now + * complete, start filling the separator. + */ + initStringInfo(&arraysepbuf); + appendTo = &arraysepbuf; + is_array = true; + ADVANCE_PARSE_POINTER(cp, end_ptr); + continue; + } + + if (*cp == '}') + { + ADVANCE_PARSE_POINTER(cp, end_ptr); + break; + } + appendStringInfoCharMacro(appendTo, *cp); + ADVANCE_PARSE_POINTER(cp, end_ptr); + } + param = parbuf.data; + if (is_array) + arraysep = arraysepbuf.data; + } + if (param == NULL) + ereport(ERROR, + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("missing conversion name in conversion specifier")); + + switch (*cp) + { + case 'I': + specifier = SpecIdentifier; + break; + case 'D': + specifier = SpecDottedName; + break; + case 's': + specifier = SpecString; + break; + case 'L': + specifier = SpecStringLiteral; + break; + case 'T': + specifier = SpecTypeName; + break; + case 'n': + specifier = SpecNumber; + break; + default: + ereport(ERROR, + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid conversion specifier \"%c\"", *cp)); + } + + /* + * Obtain the element to be expanded. + */ + key.type = jbvString; + key.val.string.val = param; + key.val.string.len = strlen(param); + + value = findJsonbValueFromContainer(container, JB_FOBJECT, &key); + Assert(value != NULL); + + /* + * Expand the data (possibly an array) into the output StringInfo. + */ + if (is_array) + expand_jsonb_array(buf, param, value, arraysep, specifier, start_ptr); + else + expand_one_jsonb_element(buf, param, value, specifier, start_ptr); + + pfree(value); + } +} + +/* + * Expand a json value as a quoted identifier. The value must be of type string. + */ +static void +expand_jsonval_identifier(StringInfo buf, JsonbValue *jsonval) +{ + char *str; + + Assert(jsonval->type == jbvString); + + str = pnstrdup(jsonval->val.string.val, jsonval->val.string.len); + appendStringInfoString(buf, quote_identifier(str)); + pfree(str); +} + +/* + * Expand a json value as a dot-separated-name. The value must be of type + * binary and may contain elements "schemaname" (optional), "objname" + * (mandatory), "attrname" (optional). Double quotes are added to each element + * as necessary, and dot separators where needed. + * + * One day we might need a "catalog" element as well, but no current use case + * needs that. + */ +static void +expand_jsonval_dottedname(StringInfo buf, JsonbValue *jsonval) +{ + char *str; + JsonbContainer *data = jsonval->val.binary.data; + + Assert(jsonval->type == jbvBinary); + + str = find_string_in_jsonbcontainer(data, "schemaname", true, NULL); + if (str) + { + appendStringInfo(buf, "%s.", quote_identifier(str)); + pfree(str); + } + + str = find_string_in_jsonbcontainer(data, "objname", false, NULL); + appendStringInfo(buf, "%s", quote_identifier(str)); + pfree(str); + + str = find_string_in_jsonbcontainer(data, "attrname", true, NULL); + if (str) + { + appendStringInfo(buf, ".%s", quote_identifier(str)); + pfree(str); + } +} + +/* + * Expand a JSON value as a type name. + */ +static void +expand_jsonval_typename(StringInfo buf, JsonbValue *jsonval) +{ + char *schema = NULL; + char *typename = NULL; + char *typmodstr = NULL; + json_trivalue is_array; + char *array_decor; + JsonbContainer *data = jsonval->val.binary.data; + + /* + * We omit schema-qualifying the output name if the schema element is + * either the empty string or NULL; the difference between those two cases + * is that in the latter we quote the type name, in the former we don't. + * This allows for types with special typmod needs, such as interval and + * timestamp (see format_type_detailed), while at the same time allowing + * for the schema name to be omitted from type names that require quotes + * but are to be obtained from a user schema. + */ + + schema = find_string_in_jsonbcontainer(data, "schemaname", true, NULL); + typename = find_string_in_jsonbcontainer(data, "typename", false, NULL); + typmodstr = find_string_in_jsonbcontainer(data, "typmod", true, NULL); + is_array = find_bool_in_jsonbcontainer(data, "typarray"); + switch (is_array) + { + case tv_true: + array_decor = "[]"; + break; + + case tv_false: + array_decor = ""; + break; + + case tv_absent: + default: + ereport(ERROR, + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("missing typarray element")); + } + + if (schema == NULL) + appendStringInfo(buf, "%s", quote_identifier(typename)); + else if (schema[0] == '\0') + appendStringInfo(buf, "%s", typename); /* Special typmod needs */ + else + appendStringInfo(buf, "%s.%s", quote_identifier(schema), + quote_identifier(typename)); + + appendStringInfo(buf, "%s%s", typmodstr ? typmodstr : "", array_decor); + + if (schema) + pfree(schema); + if (typename) + pfree(typename); + if (typmodstr) + pfree(typmodstr); +} + +/* + * Expand a JSON value as a string. The value must be of type string or of + * type Binary. In the latter case, it must contain a "fmt" element which will + * be recursively expanded; also, if the object contains an element "present" + * and it is set to false, the expansion is the empty string. + * + * Returns false if no actual expansion was made due to the "present" flag + * being set to "false". + * + * The caller is responsible to check jsonval is of type jbvString or jbvBinary. + */ +static bool +expand_jsonval_string(StringInfo buf, JsonbValue *jsonval) +{ + bool expanded = false; + + Assert((jsonval->type == jbvString) || (jsonval->type == jbvBinary)); + + if (jsonval->type == jbvString) + { + appendBinaryStringInfo(buf, jsonval->val.string.val, + jsonval->val.string.len); + expanded = true; + } + else if (jsonval->type == jbvBinary) + { + json_trivalue present; + + present = find_bool_in_jsonbcontainer(jsonval->val.binary.data, + "present"); + + /* + * If "present" is set to false, this element expands to empty; + * otherwise (either true or absent), expand "fmt". + */ + if (present != tv_false) + { + expand_fmt_recursive(buf, jsonval->val.binary.data); + expanded = true; + } + } + + return expanded; +} + +/* + * Expand a JSON value as a string literal. + */ +static void +expand_jsonval_strlit(StringInfo buf, JsonbValue *jsonval) +{ + char *str; + StringInfoData dqdelim; + static const char dqsuffixes[] = "_XYZZYX_"; + int dqnextchar = 0; + + Assert(jsonval->type == jbvString); + + str = pnstrdup(jsonval->val.string.val, jsonval->val.string.len); + + /* Easy case: if there are no ' and no \, just use a single quote */ + if (strpbrk(str, "\'\\") == NULL) + { + appendStringInfo(buf, "'%s'", str); + pfree(str); + return; + } + + /* Otherwise need to find a useful dollar-quote delimiter */ + initStringInfo(&dqdelim); + appendStringInfoString(&dqdelim, "$"); + while (strstr(str, dqdelim.data) != NULL) + { + appendStringInfoChar(&dqdelim, dqsuffixes[dqnextchar++]); + dqnextchar = dqnextchar % (sizeof(dqsuffixes) - 1); + } + /* Add trailing $ */ + appendStringInfoChar(&dqdelim, '$'); + + /* And finally produce the quoted literal into the output StringInfo */ + appendStringInfo(buf, "%s%s%s", dqdelim.data, str, dqdelim.data); + pfree(dqdelim.data); + pfree(str); +} + +/* + * Expand a JSON value as an integer quantity. + */ +static void +expand_jsonval_number(StringInfo buf, JsonbValue *jsonval) +{ + char *strdatum; + + Assert(jsonval->type == jbvNumeric); + + strdatum = DatumGetCString(DirectFunctionCall1(numeric_out, + NumericGetDatum(jsonval->val.numeric))); + appendStringInfoString(buf, strdatum); + pfree(strdatum); +} + +/* + * Expand one JSON element into the output StringInfo according to the + * conversion specifier. The element type is validated, and an error is raised + * if it doesn't match what we expect for the conversion specifier. + * + * Returns true, except for the formatted string case if no actual expansion + * was made (due to the "present" flag being set to "false"). + */ +static bool +expand_one_jsonb_element(StringInfo buf, char *param, JsonbValue *jsonval, + convSpecifier specifier, const char *fmt) +{ + bool string_expanded = true; + ErrorContextCallback sqlerrcontext; + + /* If we were given a format string, setup an ereport() context callback */ + if (fmt) + { + sqlerrcontext.callback = fmtstr_error_callback; + sqlerrcontext.arg = (void *) fmt; + sqlerrcontext.previous = error_context_stack; + error_context_stack = &sqlerrcontext; + } + + if (!jsonval) + ereport(ERROR, + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("element \"%s\" not found", param)); + + switch (specifier) + { + case SpecIdentifier: + if (jsonval->type != jbvString) + ereport(ERROR, + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("expected JSON string for %%I element \"%s\", got %d", + param, jsonval->type)); + expand_jsonval_identifier(buf, jsonval); + break; + + case SpecDottedName: + if (jsonval->type != jbvBinary) + ereport(ERROR, + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("expected JSON struct for %%D element \"%s\", got %d", + param, jsonval->type)); + expand_jsonval_dottedname(buf, jsonval); + break; + + case SpecString: + if (jsonval->type != jbvString && + jsonval->type != jbvBinary) + ereport(ERROR, + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("expected JSON string or struct for %%s element \"%s\", got %d", + param, jsonval->type)); + string_expanded = expand_jsonval_string(buf, jsonval); + break; + + case SpecStringLiteral: + if (jsonval->type != jbvString) + ereport(ERROR, + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("expected JSON string for %%L element \"%s\", got %d", + param, jsonval->type)); + expand_jsonval_strlit(buf, jsonval); + break; + + case SpecTypeName: + if (jsonval->type != jbvBinary) + ereport(ERROR, + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("expected JSON struct for %%T element \"%s\", got %d", + param, jsonval->type)); + expand_jsonval_typename(buf, jsonval); + break; + + case SpecNumber: + if (jsonval->type != jbvNumeric) + ereport(ERROR, + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("expected JSON numeric for %%n element \"%s\", got %d", + param, jsonval->type)); + expand_jsonval_number(buf, jsonval); + break; + } + + if (fmt) + error_context_stack = sqlerrcontext.previous; + + return string_expanded; +} + +/* + * Iterate on the elements of a JSON array, expanding each one into the output + * StringInfo per the given conversion specifier, separated by the given + * separator. + */ +static void +expand_jsonb_array(StringInfo buf, char *param, + JsonbValue *jsonarr, char *arraysep, convSpecifier specifier, + const char *fmt) +{ + ErrorContextCallback sqlerrcontext; + JsonbContainer *container; + JsonbIterator *it; + JsonbValue v; + int type; + bool first = true; + StringInfoData arrayelem; + + /* If we were given a format string, setup an ereport() context callback */ + if (fmt) + { + sqlerrcontext.callback = fmtstr_error_callback; + sqlerrcontext.arg = (void *) fmt; + sqlerrcontext.previous = error_context_stack; + error_context_stack = &sqlerrcontext; + } + + if (!jsonarr) + ereport(ERROR, + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("element \"%s\" not found", param)); + + if (jsonarr->type != jbvBinary) + ereport(ERROR, + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("element \"%s\" is not a JSON array", param)); + + container = jsonarr->val.binary.data; + if (!JsonContainerIsArray(container)) + ereport(ERROR, + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("element \"%s\" is not a JSON array", param)); + + initStringInfo(&arrayelem); + + it = JsonbIteratorInit(container); + while ((type = JsonbIteratorNext(&it, &v, true)) != WJB_DONE) + { + if (type == WJB_ELEM) + { + resetStringInfo(&arrayelem); + + if (expand_one_jsonb_element(&arrayelem, param, &v, specifier, NULL)) + { + if (!first) + appendStringInfoString(buf, arraysep); + + appendBinaryStringInfo(buf, arrayelem.data, arrayelem.len); + first = false; + } + } + } + + if (fmt) + error_context_stack = sqlerrcontext.previous; +} + +/* + * Workhorse for ddl_deparse_expand_command. + */ +char * +deparse_ddl_json_to_string(char *json_str) +{ + Datum d; + Jsonb *jsonb; + StringInfo buf = (StringInfo) palloc0(sizeof(StringInfoData)); + + initStringInfo(buf); + + d = DirectFunctionCall1(jsonb_in, PointerGetDatum(json_str)); + jsonb = (Jsonb *) DatumGetPointer(d); + + expand_fmt_recursive(buf, &jsonb->root); + + return buf->data; +} + +/*------ + * Returns a formatted string from a JSON object. + * + * The starting point is the element named "fmt" (which must be a string). + * This format string may contain zero or more %-escapes, which consist of an + * element name enclosed in { }, possibly followed by a conversion modifier, + * followed by a conversion specifier. Possible conversion specifiers are: + * + * % expand to a literal % + * I expand as a single, non-qualified identifier + * D expand as a possibly-qualified identifier + * T expand as a type name + * L expand as a string literal (quote using single quotes) + * s expand as a simple string (no quoting) + * n expand as a simple number (no quoting) + * + * The element name may have an optional separator specification preceded + * by a colon. Its presence indicates that the element is expected to be + * an array; the specified separator is used to join the array elements. + * + * The actual conversion of single JSON element into string according to + * above conversion specifiers takes place in expand_one_jsonb_element() + *------ + */ +Datum +ddl_deparse_expand_command(PG_FUNCTION_ARGS) +{ + text *json = PG_GETARG_TEXT_P(0); + char *json_str; + + json_str = text_to_cstring(json); + + PG_RETURN_TEXT_P(cstring_to_text(deparse_ddl_json_to_string(json_str))); +} + +/* + * Error context callback for JSON format string expansion. + * + * XXX: indicate which element we're expanding, if applicable. + */ +static void +fmtstr_error_callback(void *arg) +{ + errcontext("while expanding format string \"%s\"", (char *) arg); +} diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c index adc6eabc0f4..842be373a0d 100644 --- a/src/backend/commands/event_trigger.c +++ b/src/backend/commands/event_trigger.c @@ -46,6 +46,7 @@ #include "parser/parse_func.h" #include "pgstat.h" #include "storage/lmgr.h" +#include "tcop/ddldeparse.h" #include "tcop/deparse_utility.h" #include "tcop/utility.h" #include "utils/acl.h" diff --git a/src/backend/commands/meson.build b/src/backend/commands/meson.build index 9f258d566eb..aeeba4bce9f 100644 --- a/src/backend/commands/meson.build +++ b/src/backend/commands/meson.build @@ -16,6 +16,8 @@ backend_sources += files( 'copyto.c', 'createas.c', 'dbcommands.c', + 'ddldeparse.c', + 'ddljson.c', 'define.c', 'discard.c', 'dropcmds.c', diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c index 551667650ba..23ee6cbac3a 100644 --- a/src/backend/commands/sequence.c +++ b/src/backend/commands/sequence.c @@ -1704,6 +1704,49 @@ process_owned_by(Relation seqrel, List *owned_by, bool for_identity) relation_close(tablerel, NoLock); } +/* + * Return sequence parameters, detailed + */ +Sequence_values * +get_sequence_values(Oid sequenceId) +{ + Buffer buf; + SeqTable elm; + Relation seqrel; + HeapTuple seqtuple; + HeapTupleData seqtupledata; + Form_pg_sequence seqform; + Form_pg_sequence_data seq; + Sequence_values *seqvalues; + + seqtuple = SearchSysCache1(SEQRELID, sequenceId); + if (!HeapTupleIsValid(seqtuple)) + elog(ERROR, "cache lookup failed for sequence %u", sequenceId); + seqform = (Form_pg_sequence) GETSTRUCT(seqtuple); + + ReleaseSysCache(seqtuple); + + /* Open and lock sequence */ + init_sequence(sequenceId, &elm, &seqrel); + + if (pg_class_aclcheck(sequenceId, GetUserId(), + ACL_SELECT | ACL_USAGE) != ACLCHECK_OK) + ereport(ERROR, + errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("permission denied for sequence %s", + RelationGetRelationName(seqrel))); + + seq = read_seq_tuple(seqrel, &buf, &seqtupledata); + + seqvalues = (Sequence_values *) palloc(sizeof(Sequence_values)); + seqvalues->last_value = seq->last_value; + seqvalues->seqform = seqform; + + UnlockReleaseBuffer(buf); + relation_close(seqrel, NoLock); + + return seqvalues; +} /* * Return sequence parameters in a list of the form created by the parser. diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 265dcfe7fda..71db35b5b68 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -739,7 +739,6 @@ static ObjectAddress ATExecSetCompression(Relation rel, const char *column, Node *newValue, LOCKMODE lockmode); static void index_copy_data(Relation rel, RelFileLocator newrlocator); -static const char *storage_name(char c); static void RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid, void *arg); @@ -2515,7 +2514,7 @@ truncate_check_activity(Relation rel) * storage_name * returns the name corresponding to a typstorage/attstorage enum value */ -static const char * +char * storage_name(char c) { switch (c) diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index a049cc67ed6..40e2464c932 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -1535,6 +1535,7 @@ expandTableLikeClause(RangeVar *heapRel, TableLikeClause *table_like_clause) atcmd->cmds = atsubcmds; atcmd->objtype = OBJECT_TABLE; atcmd->missing_ok = false; + atcmd->table_like = true; result = lcons(atcmd, result); } diff --git a/src/backend/partitioning/partbounds.c b/src/backend/partitioning/partbounds.c index 6fb150a8763..ca3b95e36fd 100644 --- a/src/backend/partitioning/partbounds.c +++ b/src/backend/partitioning/partbounds.c @@ -6023,3 +6023,31 @@ check_partitions_for_split(Relation parent, pfree(new_parts); } + +/* + * Obtain the deparsed partition bound expression for the given table. + */ +char * +relation_get_part_bound(Oid relid) +{ + Datum deparsed; + Datum bound; + bool isnull; + HeapTuple tuple; + + tuple = SearchSysCache1(RELOID, relid); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for relation with OID %u", relid); + + bound = SysCacheGetAttr(RELOID, tuple, + Anum_pg_class_relpartbound, + &isnull); + + deparsed = DirectFunctionCall2(pg_get_expr, + CStringGetTextDatum(TextDatumGetCString(bound)), + relid); + + ReleaseSysCache(tuple); + + return TextDatumGetCString(deparsed); +} diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 73a56f1df1d..6d29d914cfa 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -2225,6 +2225,23 @@ UtilityContainsQuery(Node *parsetree) } } +/* + * stringify_objtype + * Return the given object type as a string. + */ +const char * +stringify_objtype(ObjectType objtype) +{ + switch (objtype) + { + case OBJECT_TABLE: + return "TABLE"; + default: + elog(ERROR, "unsupported object type %d", objtype); + } + + return "???"; /* keep compiler quiet */ +} /* * AlterObjectTypeCommandTag diff --git a/src/backend/utils/adt/format_type.c b/src/backend/utils/adt/format_type.c index 7a5695c6245..c4dd3c1664f 100644 --- a/src/backend/utils/adt/format_type.c +++ b/src/backend/utils/adt/format_type.c @@ -27,8 +27,6 @@ #include "utils/numeric.h" #include "utils/syscache.h" -static char *printTypmod(const char *typname, int32 typmod, Oid typmodout); - /* * SQL function: format_type(type_oid, typemod) @@ -333,6 +331,110 @@ format_type_extended(Oid type_oid, int32 typemod, uint16 flags) return buf; } +/* + * Similar to format_type_extended, except we return each bit of information + * separately: + * + * - nspid is the schema OID. For certain SQL-standard types which have weird + * typmod rules, we return InvalidOid; the caller is expected to not schema- + * qualify the name nor add quotes to the type name in this case. + * + * - typname is set to the type name, without quotes + * + * - typemodstr is set to the typemod, if any, as a string with parentheses + * + * - typarray indicates whether []s must be added + * + * We don't try to decode type names to their standard-mandated names, except + * in the cases of types with unusual typmod rules. + */ +void +format_type_detailed(Oid type_oid, int32 typemod, + Oid *nspid, char **typname, char **typemodstr, + bool *typearray) +{ + HeapTuple tuple; + Form_pg_type typeform; + Oid array_base_type; + + tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(type_oid)); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for type with OID %u", type_oid); + + typeform = (Form_pg_type) GETSTRUCT(tuple); + + /* + * We switch our attention to the array element type for certain cases. + * Check if it's a "true" array type. Pseudo-array types such as "name" + * shouldn't get deconstructed. Also check the toast property, and don't + * deconstruct "plain storage" array types --- this is because we don't + * want to show oidvector as oid[]. + */ + array_base_type = typeform->typelem; + + *typearray = (IsTrueArrayType(typeform) && typeform->typstorage != TYPSTORAGE_PLAIN); + + if (*typearray) + { + ReleaseSysCache(tuple); + tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(array_base_type)); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for type with OID %u", type_oid); + + typeform = (Form_pg_type) GETSTRUCT(tuple); + type_oid = array_base_type; + } + + /* + * Special-case crock for types with strange typmod rules where we put + * typemod at the middle of name (e.g. TIME(6) with time zone). We cannot + * schema-qualify nor add quotes to the type name in these cases. + */ + *nspid = InvalidOid; + + switch (type_oid) + { + case TIMEOID: + *typname = pstrdup("TIME"); + break; + case TIMESTAMPOID: + *typname = pstrdup("TIMESTAMP"); + break; + case TIMESTAMPTZOID: + if (typemod < 0) + *typname = pstrdup("TIMESTAMP WITH TIME ZONE"); + else + /* otherwise, WITH TZ is added by typmod. */ + *typname = pstrdup("TIMESTAMP"); + break; + case INTERVALOID: + *typname = pstrdup("INTERVAL"); + break; + case TIMETZOID: + if (typemod < 0) + *typname = pstrdup("TIME WITH TIME ZONE"); + else + /* otherwise, WITH TZ is added by typmod. */ + *typname = pstrdup("TIME"); + break; + default: + + /* + * No additional processing is required for other types, so get + * the type name and schema directly from the catalog. + */ + *nspid = typeform->typnamespace; + *typname = pstrdup(NameStr(typeform->typname)); + } + + if (typemod >= 0) + *typemodstr = printTypmod("", typemod, typeform->typmodout); + else + *typemodstr = pstrdup(""); + + ReleaseSysCache(tuple); +} + /* * This version is for use within the backend in error messages, etc. * One difference is that it will fail for an invalid type. @@ -367,7 +469,7 @@ format_type_with_typemod(Oid type_oid, int32 typemod) /* * Add typmod decoration to the basic type name */ -static char * +char * printTypmod(const char *typname, int32 typmod, Oid typmodout) { char *res; diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 88de5c0481c..f670188f64c 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -531,23 +531,15 @@ static void get_from_clause_coldeflist(RangeTblFunction *rtfunc, deparse_context *context); static void get_tablesample_def(TableSampleClause *tablesample, deparse_context *context); -static void get_opclass_name(Oid opclass, Oid actual_datatype, - StringInfo buf); static Node *processIndirection(Node *node, deparse_context *context); static void printSubscripts(SubscriptingRef *sbsref, deparse_context *context); static char *get_relation_name(Oid relid); static char *generate_relation_name(Oid relid, List *namespaces); static char *generate_qualified_relation_name(Oid relid); -static char *generate_function_name(Oid funcid, int nargs, - List *argnames, Oid *argtypes, - bool has_variadic, bool *use_variadic_p, - bool inGroupBy); static char *generate_operator_name(Oid operid, Oid arg1, Oid arg2); static void add_cast_to(StringInfo buf, Oid typid); static char *generate_qualified_type_name(Oid typid); static text *string_to_text(char *str); -static char *flatten_reloptions(Oid relid); -void get_reloptions(StringInfo buf, Datum reloptions); static void get_json_path_spec(Node *path_spec, deparse_context *context, bool showimplicit); static void get_json_table_columns(TableFunc *tf, JsonTablePathScan *scan, @@ -2288,6 +2280,14 @@ pg_get_partkeydef_columns(Oid relid, bool pretty) return pg_get_partkeydef_worker(relid, prettyFlags, true, false); } +/* Internal version that reports the full partition key definition */ +char * +pg_get_partkeydef_string(Oid relid) +{ + return pg_get_partkeydef_worker(relid, GET_PRETTY_FLAGS(false), false, + false); +} + /* * Internal workhorse to decompile a partition key definition. */ @@ -2535,6 +2535,16 @@ pg_get_constraintdef_ext(PG_FUNCTION_ARGS) PG_RETURN_TEXT_P(string_to_text(res)); } +/* + * Internal version that returns the definition of a CONSTRAINT command + */ +char * +pg_get_constraintdef_string(Oid constraintId) +{ + return pg_get_constraintdef_worker(constraintId, false, + GET_PRETTY_FLAGS(false), false); +} + /* * Internal version that returns a full ALTER TABLE ... ADD CONSTRAINT command */ @@ -13519,7 +13529,7 @@ get_tablesample_def(TableSampleClause *tablesample, deparse_context *context) * actual_datatype. (If you don't want this behavior, just pass * InvalidOid for actual_datatype.) */ -static void +void get_opclass_name(Oid opclass, Oid actual_datatype, StringInfo buf) { @@ -13907,7 +13917,7 @@ generate_qualified_relation_name(Oid relid) * * The result includes all necessary quoting and schema-prefixing. */ -static char * +char * generate_function_name(Oid funcid, int nargs, List *argnames, Oid *argtypes, bool has_variadic, bool *use_variadic_p, bool inGroupBy) @@ -14295,7 +14305,7 @@ get_reloptions(StringInfo buf, Datum reloptions) /* * Generate a C string representing a relation's reloptions, or NULL if none. */ -static char * +char * flatten_reloptions(Oid relid) { char *result = NULL; @@ -14364,3 +14374,23 @@ get_range_partbound_string(List *bound_datums) return buf.data; } + +/* + * Obtain the deparsed default value for the given column of the given table. + * + * Caller must have set a correct deparse context and must ensure that the + * passed attribute has a default value. + */ +char * +relation_get_column_default(Relation rel, AttrNumber attno, List *dpcontext) +{ + Node *defval; + char *defstr; + + defval = build_column_default(rel, attno); + Assert(defval != NULL); + + defstr = deparse_expression(defval, dpcontext, false, false); + + return defstr; +} diff --git a/src/include/catalog/pg_inherits.h b/src/include/catalog/pg_inherits.h index cc874abaabb..4a0770475b2 100644 --- a/src/include/catalog/pg_inherits.h +++ b/src/include/catalog/pg_inherits.h @@ -68,4 +68,6 @@ extern bool DeleteInheritsTuple(Oid inhrelid, Oid inhparent, const char *childname); extern bool PartitionHasPendingDetach(Oid partoid); +extern List *relation_get_inh_parents(Oid objectId); + #endif /* PG_INHERITS_H */ diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index be157a5fbe9..7322198f59a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -12693,4 +12693,11 @@ proname => 'hashoid8extended', prorettype => 'int8', proargtypes => 'oid8 int8', prosrc => 'hashoid8extended' }, +{ oid => '4642', descr => 'deparse the DDL command into a JSON format string', + proname => 'ddl_deparse_to_json', prorettype => 'text', + proargtypes => 'pg_ddl_command', prosrc => 'ddl_deparse_to_json' }, +{ oid => '4643', descr => 'expand JSON format DDL to a plain text DDL command', + proname => 'ddl_deparse_expand_command', prorettype => 'text', + proargtypes => 'text', prosrc => 'ddl_deparse_expand_command' }, + ] diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h index 2c3c4a3f074..563a3d4efd8 100644 --- a/src/include/commands/sequence.h +++ b/src/include/commands/sequence.h @@ -14,6 +14,7 @@ #define SEQUENCE_H #include "catalog/objectaddress.h" +#include "catalog/pg_sequence.h" #include "fmgr.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" @@ -38,9 +39,17 @@ typedef FormData_pg_sequence_data *Form_pg_sequence_data; #define SEQ_COL_FIRSTCOL SEQ_COL_LASTVAL #define SEQ_COL_LASTCOL SEQ_COL_CALLED +/* Information needed to define a sequence. */ +typedef struct Sequence_values +{ + Form_pg_sequence seqform; + int64 last_value; +} Sequence_values; + extern int64 nextval_internal(Oid relid, bool check_permissions); extern Datum nextval(PG_FUNCTION_ARGS); extern List *sequence_options(Oid relid); +extern Sequence_values *get_sequence_values(Oid sequenceId); extern ObjectAddress DefineSequence(ParseState *pstate, CreateSeqStmt *seq); extern ObjectAddress AlterSequence(ParseState *pstate, AlterSeqStmt *stmt); diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h index c3d8518cb62..d69f0bd3ca6 100644 --- a/src/include/commands/tablecmds.h +++ b/src/include/commands/tablecmds.h @@ -108,4 +108,6 @@ extern void RangeVarCallbackOwnsRelation(const RangeVar *relation, extern bool PartConstraintImpliedByRelConstraint(Relation scanrel, List *partConstraint); +extern char *storage_name(char c); + #endif /* TABLECMDS_H */ diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 4133c404a6b..65d17f07853 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -2514,6 +2514,7 @@ typedef struct AlterTableStmt List *cmds; /* list of subcommands */ ObjectType objtype; /* type of object */ bool missing_ok; /* skip error if table missing */ + bool table_like; /* internally generated for TableLikeClause */ } AlterTableStmt; typedef enum AlterTableType diff --git a/src/include/partitioning/partbounds.h b/src/include/partitioning/partbounds.h index 25c926f4c8b..109879b8750 100644 --- a/src/include/partitioning/partbounds.h +++ b/src/include/partitioning/partbounds.h @@ -152,5 +152,6 @@ extern void calculate_partition_bound_for_merge(Relation parent, List *partOids, PartitionBoundSpec *spec, ParseState *pstate); +extern char *relation_get_part_bound(Oid relid); #endif /* PARTBOUNDS_H */ diff --git a/src/include/tcop/ddldeparse.h b/src/include/tcop/ddldeparse.h new file mode 100644 index 00000000000..0ccb4d1ede1 --- /dev/null +++ b/src/include/tcop/ddldeparse.h @@ -0,0 +1,21 @@ +/*------------------------------------------------------------------------- + * + * ddldeparse.h + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/tcop/ddldeparse.h + * + *------------------------------------------------------------------------- + */ +#ifndef DDL_DEPARSE_H +#define DDL_DEPARSE_H + +#include "tcop/deparse_utility.h" + +extern char *deparse_utility_command(CollectedCommand *cmd); +extern char *deparse_ddl_json_to_string(char *jsonb); +extern char *deparse_drop_table(const char *objidentity, Node *parsetree); + +#endif /* DDL_DEPARSE_H */ diff --git a/src/include/tcop/utility.h b/src/include/tcop/utility.h index abbdb401bf6..604bfd87f2b 100644 --- a/src/include/tcop/utility.h +++ b/src/include/tcop/utility.h @@ -99,6 +99,8 @@ extern Query *UtilityContainsQuery(Node *parsetree); extern CommandTag CreateCommandTag(Node *parsetree); +extern const char *stringify_objtype(ObjectType objtype); + static inline const char * CreateCommandName(Node *parsetree) { diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h index b6a11bfa288..edd0f6ea8e9 100644 --- a/src/include/utils/builtins.h +++ b/src/include/utils/builtins.h @@ -128,9 +128,14 @@ extern Datum numeric_float8_no_overflow(PG_FUNCTION_ARGS); #define FORMAT_TYPE_INVALID_AS_NULL 0x08 /* NULL if undefined */ extern char *format_type_extended(Oid type_oid, int32 typemod, uint16 flags); +extern void format_type_detailed(Oid type_oid, int32 typemod, + Oid *nspid, char **typname, + char **typemodstr, bool *typearray); + extern char *format_type_be(Oid type_oid); extern char *format_type_be_qualified(Oid type_oid); extern char *format_type_with_typemod(Oid type_oid, int32 typemod); +extern char *printTypmod(const char *typname, int32 typmod, Oid typmodout); extern int32 type_maximum_size(Oid type_oid, int32 typemod); diff --git a/src/include/utils/ruleutils.h b/src/include/utils/ruleutils.h index 25c05e2f649..c67774211ac 100644 --- a/src/include/utils/ruleutils.h +++ b/src/include/utils/ruleutils.h @@ -16,6 +16,7 @@ #include "nodes/nodes.h" #include "nodes/parsenodes.h" #include "nodes/pg_list.h" +#include "parser/parse_node.h" typedef struct Plan Plan; /* avoid including plannodes.h here */ typedef struct PlannedStmt PlannedStmt; @@ -31,9 +32,11 @@ extern char *pg_get_indexdef_columns_extended(Oid indexrelid, extern char *pg_get_querydef(Query *query, bool pretty); extern char *pg_get_partkeydef_columns(Oid relid, bool pretty); +extern char *pg_get_partkeydef_string(Oid relid); extern char *pg_get_partconstrdef_string(Oid partitionId, char *aliasname); extern char *pg_get_constraintdef_command(Oid constraintId); +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); @@ -50,9 +53,20 @@ extern char *get_window_frame_options_for_explain(int frameOptions, bool forceprefix); extern char *generate_collation_name(Oid collid); extern char *generate_opclass_name(Oid opclass); +extern char *generate_function_name(Oid funcid, int nargs, + List *argnames, Oid *argtypes, + bool has_variadic, bool *use_variadic_p, + bool inGroupBy); extern char *get_range_partbound_string(List *bound_datums); extern void get_reloptions(StringInfo buf, Datum reloptions); +extern void get_opclass_name(Oid opclass, Oid actual_datatype, + StringInfo buf); +extern char *flatten_reloptions(Oid relid); +extern void get_reloptions(StringInfo buf, Datum reloptions); extern char *pg_get_statisticsobjdef_string(Oid statextid); +extern char *relation_get_column_default(Relation rel, AttrNumber attno, + List *dpcontext); + #endif /* RULEUTILS_H */ diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 1969d467c1d..2ccd10baabf 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3693,6 +3693,7 @@ config_var_value contain_aggs_of_level_context contain_placeholder_references_context convert_testexpr_context +convSpecifier copy_data_dest_cb copy_data_source_cb core_YYSTYPE @@ -3922,6 +3923,8 @@ json_scalar_action json_struct_action keepwal_entry keepwal_hash +json_trivalue +keyEntryData key_t lclContext lclTocEntry