From a3bbf2c35d16885d3266e01310d504132eadee2f Mon Sep 17 00:00:00 2001 From: Shveta Malik Date: Mon, 8 May 2023 15:37:43 +0530 Subject: [PATCH 8/8] create table object tree removal --- src/backend/commands/ddldeparse.c | 1412 ++++++++++++++++++++++++++++- 1 file changed, 1404 insertions(+), 8 deletions(-) diff --git a/src/backend/commands/ddldeparse.c b/src/backend/commands/ddldeparse.c index 5878d7e465..7920f1d0a6 100644 --- a/src/backend/commands/ddldeparse.c +++ b/src/backend/commands/ddldeparse.c @@ -160,6 +160,36 @@ static List *deparse_TableElements(Relation relation, List *tableElements, List bool typed, bool composite); static void mark_function_volatile(ddl_deparse_context *context, Node *expr); +static void deparse_ColumnDef_toJsonb(JsonbParseState **state, + Relation relation, List *dpcontext, + bool composite, ColumnDef *coldef, + bool is_alter, Node **expr, + bool *belemsAdded); +static void deparse_TableElementsToJsonb(JsonbParseState **state, + Relation relation, List *tableElements, + List *dpcontext, bool typed, + bool composite, bool *belemsAdded); + +static void insert_jsonb_key(JsonbParseState *state, char *name); + +static void insert_jsonb_str_element(JsonbParseState *state, char *name, char *value); + +static void insert_jsonb_bool_element(JsonbParseState *state, char *name, bool value); + +static void insert_str_object(JsonbParseState *state, char *fmt, char *key, char *val); + +static void insert_not_present_obj(JsonbParseState *state, char *fmt); + +static void fmt_to_jsonb_element(JsonbParseState *state, char *fmtStr); + +static void new_jsonb_for_type(JsonbParseState *state, Oid typeId, int32 typmod); + +static void new_jsonb_for_qualname(JsonbParseState *state, Oid nspid, char *objName, char* keyName); + +static void new_jsonb_for_qualname_id(JsonbParseState *state, Oid classId, Oid objectId, char* keyName); + +static JsonbValue *parsetree_to_jsonb_create_table(Oid objectId, Node *parsetree, char *owner); + /* * Mark the func_volatile flag for an expression in the command. */ @@ -3154,7 +3184,7 @@ deparse_RenameStmt(ObjectAddress address, Node *parsetree) * This function should cover all cases handled in ProcessUtilitySlow. */ static ObjTree * -deparse_simple_command(CollectedCommand *cmd, bool *include_owner) +deparse_simple_command(CollectedCommand *cmd, ddl_deparse_context *context) { Oid objectId; Node *parsetree; @@ -3171,13 +3201,13 @@ deparse_simple_command(CollectedCommand *cmd, bool *include_owner) switch (nodeTag(parsetree)) { case T_AlterObjectSchemaStmt: - *include_owner = false; + context->include_owner = false; return deparse_AlterObjectSchemaStmt(cmd->d.simple.address, parsetree, cmd->d.simple.secondaryObject); case T_AlterOwnerStmt: - *include_owner = false; + context->include_owner = false; return deparse_AlterOwnerStmt(cmd->d.simple.address, parsetree); case T_AlterSeqStmt: @@ -3187,10 +3217,21 @@ deparse_simple_command(CollectedCommand *cmd, bool *include_owner) return deparse_CreateSeqStmt(objectId, parsetree); case T_CreateStmt: - return deparse_CreateStmt(objectId, parsetree); + JsonbValue *value; + + /* get direct jsonbValue object from parsetree */ + value = context->include_owner ? + parsetree_to_jsonb_create_table(objectId, parsetree, cmd->role) : + parsetree_to_jsonb_create_table(objectId, parsetree, NULL); + + /* + * we are returning jsonb*, but typecast to ObjTree* for the + * time being to satisfy the prototype + */ + return (ObjTree *)JsonbValueToJsonb(value); case T_RenameStmt: - *include_owner = false; + context->include_owner = false; return deparse_RenameStmt(cmd->d.simple.address, parsetree); default: @@ -3246,7 +3287,7 @@ deparse_utility_command(CollectedCommand *cmd, ddl_deparse_context *context) switch (cmd->type) { case SCT_Simple: - tree = deparse_simple_command(cmd, &context->include_owner); + tree = deparse_simple_command(cmd, context); break; case SCT_AlterTable: tree = deparse_AlterRelation(cmd, context); @@ -3265,9 +3306,18 @@ deparse_utility_command(CollectedCommand *cmd, ddl_deparse_context *context) { Jsonb *jsonb; - jsonb = context->include_owner ? objtree_to_jsonb(tree, cmd->role) : + if (nodeTag(cmd->parsetree) == T_CreateStmt) + { + /* no object tree as intermediate stage for Create-Table */ + jsonb = (Jsonb *)tree; + command = JsonbToCString(&str, &jsonb->root, JSONB_ESTIMATED_LEN); + } + else + { + jsonb = context->include_owner ? objtree_to_jsonb(tree, cmd->role) : objtree_to_jsonb(tree, NULL); - command = JsonbToCString(&str, &jsonb->root, JSONB_ESTIMATED_LEN); + command = JsonbToCString(&str, &jsonb->root, JSONB_ESTIMATED_LEN); + } } /* @@ -3304,3 +3354,1349 @@ ddl_deparse_to_json(PG_FUNCTION_ARGS) PG_RETURN_NULL(); } + +/* + * Insert JsonbValue key to JsonbParseState + */ +static void +insert_jsonb_key(JsonbParseState *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); +} + +/* + * Insert JsonbValue key:value pair to JsonbParseState, the + * value here is of type jbvString. + */ +static void +insert_jsonb_str_element(JsonbParseState *state, char *name, char *value) +{ + JsonbValue val; + + /* Push the key first */ + insert_jsonb_key(state, name); + + /* Push the value now */ + val.type = jbvString; + val.val.string.len = strlen(value); + val.val.string.val = pstrdup(value); + pushJsonbValue(&state, WJB_VALUE, &val); +} + +/* + * Insert JsonbValue key:value pair to JsonbParseState, the + * value here is of type jbvBool. + */ +static void +insert_jsonb_bool_element(JsonbParseState *state, char *name, bool value) +{ + JsonbValue val; + + /* Push the key first */ + insert_jsonb_key(state, name); + + /* Push the value now */ + val.type = jbvBool; + val.val.boolean = value; + pushJsonbValue(&state, WJB_VALUE, &val); +} + +/* + * Insert JsonbValue key and the jsonb array to JsonbParseState + */ +static void +insert_jsonb_array_oid(JsonbParseState *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); + + pushJsonbValue(&state, WJB_END_ARRAY, NULL); +} + +/* + * Insert new Object with one key:value pair to JsonbParseState, the + * value here is of jbvString. + */ +static void +insert_str_object(JsonbParseState *state, char *fmt, char *keynm, char *keyval) +{ + + pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + + fmt_to_jsonb_element(state, fmt); + + /* push key-value pair */ + insert_jsonb_str_element(state, keynm, keyval); + + pushJsonbValue(&state, WJB_END_OBJECT, NULL); +} + + +/* + * Insert new object with present:false as key-value pair + * to JsonbParseState + */ +static void +insert_not_present_obj(JsonbParseState *state, char *fmt) +{ + + pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + + fmt_to_jsonb_element(state, fmt); + + /* push key-value pair */ + insert_jsonb_bool_element(state, "present", false); + + pushJsonbValue(&state, WJB_END_OBJECT, NULL); +} + +/* + * Insert the format string into the output parse state. + */ +static void +fmt_to_jsonb_element(JsonbParseState *state, char *fmtStr) +{ + Assert(fmtStr); + insert_jsonb_str_element(state, "fmt", fmtStr); +} + +/* + * A helper routine to insert jsonb for coltyp to JsonbParseState + */ +static void +new_jsonb_for_type(JsonbParseState *state, Oid typeId, int32 typmod) +{ + Oid typnspid; + char *type_nsp; + char *type_name = NULL; + char *typmodstr; + bool type_array; + + format_type_detailed(typeId, typmod, + &typnspid, &type_name, &typmodstr, &type_array); + + if (OidIsValid(typnspid)) + type_nsp = get_namespace_name_or_temp(typnspid); + else + type_nsp = pstrdup(""); + + /* create object now for its value */ + pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + + /* coltype object will have 4 elements */ + insert_jsonb_str_element(state, "schemaname", type_nsp); + insert_jsonb_str_element(state, "typename", type_name); + insert_jsonb_str_element(state, "typmod", typmodstr); + insert_jsonb_bool_element(state, "typarray", type_array); + + /* mark end of object */ + 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(JsonbParseState *state, Oid nspid, char *objName, char *keyName) +{ + 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); + + /* create object now for its value */ + pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + + /* Add schemaname and objname now */ + insert_jsonb_str_element(state, "schemaname", namespace); + insert_jsonb_str_element(state, "objname", objName); + + /* mark end of object */ + 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(JsonbParseState *state, Oid classId, Oid objectId, char *keyName) +{ + 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); + table_close(catalog, AccessShareLock); +} + +/* + * A helper function to insert collate object for column definition + */ +static void +insert_collate_object(JsonbParseState *state, char *fmt, Oid classId, Oid objectId) +{ + + pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + + fmt_to_jsonb_element(state, fmt); + + /* push object now */ + new_jsonb_for_qualname_id(state, classId, objectId, "collation_name"); + + pushJsonbValue(&state, WJB_END_OBJECT, NULL); +} + +/* + * A helper function to insert identity object for the table definition + */ +static void +insert_identity_object(JsonbParseState *state, Oid nspid, char *relname) +{ + char *namespace; + + if (isAnyTempNamespace(nspid)) + namespace = pstrdup("pg_temp"); + else + namespace = get_namespace_name(nspid); + + /* Push the key first */ + insert_jsonb_key(state, "identity"); + + /* create object now for its value */ + pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + + /* identity object will have 2 elements */ + insert_jsonb_str_element(state, "schemaname", namespace); + insert_jsonb_str_element(state, "objname", relname); + + /* mark end of object */ + pushJsonbValue(&state, WJB_END_OBJECT, NULL); +} + +/* + * Deparse the sequence CACHE option to Jsonb + * + * Verbose syntax + * SET CACHE %{value}s + * OR + * CACHE %{value} + */ +static inline void +deparse_Seq_Cache_toJsonb(JsonbParseState *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); + fmt_to_jsonb_element(state, fmt); + + insert_jsonb_str_element(state, "clause", "cache"); + insert_jsonb_str_element(state, "value", 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_toJsonb(JsonbParseState *state, Form_pg_sequence seqdata, bool alter_table) +{ + char *fmt; + + fmt = alter_table ? "SET %{no}s CYCLE" : "%{no}s CYCLE"; + + pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + fmt_to_jsonb_element(state, fmt); + + insert_jsonb_str_element(state, "clause", "cycle"); + insert_jsonb_str_element(state, "no", seqdata->seqcycle ? "" : "NO"); + + 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_toJsonb(JsonbParseState *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); + fmt_to_jsonb_element(state, fmt); + + insert_jsonb_str_element(state, "clause", "seqincrement"); + insert_jsonb_str_element(state, "value", 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_toJsonb(JsonbParseState *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); + fmt_to_jsonb_element(state, fmt); + + insert_jsonb_str_element(state, "clause", "maxvalue"); + insert_jsonb_str_element(state, "value", 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_toJsonb(JsonbParseState *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); + fmt_to_jsonb_element(state, fmt); + + insert_jsonb_str_element(state, "clause", "minvalue"); + insert_jsonb_str_element(state, "value", psprintf(INT64_FORMAT, seqdata->seqmin)); + + pushJsonbValue(&state, WJB_END_OBJECT, NULL); +} + +/* + * 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_toJsonb(JsonbParseState *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); + fmt_to_jsonb_element(state, fmt); + + insert_jsonb_str_element(state, "clause", "start"); + insert_jsonb_str_element(state, "value", 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_toJsonb(JsonbParseState *state, int64 last_value) +{ + pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + fmt_to_jsonb_element(state, "RESTART %{value}s"); + + insert_jsonb_str_element(state, "clause", "restart"); + insert_jsonb_str_element(state, "value", psprintf(INT64_FORMAT, last_value)); + + pushJsonbValue(&state, WJB_END_OBJECT, NULL); +} + + +/* + * Deparse the definition of column identity to Jsonb. + * + * Verbose syntax + * SET GENERATED %{option}s %{identity_type}s %{seq_definition: }s + * OR + * GENERATED %{option}s AS IDENTITY %{identity_type}s ( %{seq_definition: }s ) + */ +static void +deparse_ColumnIdentity_toJsonb(JsonbParseState *state, Oid seqrelid, char identity, bool alter_table) +{ + Form_pg_sequence seqform; + Sequence_values *seqvalues; + char *fmt; + + if (alter_table) + fmt = "%{identity_type}s %{seq_definition: }s"; + else + fmt = "%{identity_type}s ( %{seq_definition: }s )"; + + /* create object now for value of identity_column */ + pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + fmt_to_jsonb_element(state, fmt); + + /* identity_type object creation */ + { + char *identfmt; + + /* Push the key first */ + insert_jsonb_key(state, "identity_type"); + + if (alter_table) + identfmt = "SET GENERATED %{option}s"; + else + identfmt = "GENERATED %{option}s AS IDENTITY"; + + if (identity == ATTRIBUTE_IDENTITY_ALWAYS) + insert_str_object(state, identfmt, "option", "ALWAYS"); + else if (identity == ATTRIBUTE_IDENTITY_BY_DEFAULT) + insert_str_object(state, identfmt, "option", "BY DEFAULT"); + else + insert_not_present_obj(state, verbose ? identfmt : (alter_table ? "SET GENERATED " : "GENERATED ")); + } + + /* seq_definition array object creation */ + { + + /* Push the key first */ + insert_jsonb_key(state, "seq_definition"); + + pushJsonbValue(&state, WJB_BEGIN_ARRAY, NULL); + + seqvalues = get_sequence_values(seqrelid); + seqform = seqvalues->seqform; + + /* Definition elements */ + deparse_Seq_Cache_toJsonb(state, seqform, alter_table); + deparse_Seq_Cycle_toJsonb(state, seqform, alter_table); + deparse_Seq_IncrementBy_toJsonb(state, seqform, alter_table); + deparse_Seq_Minvalue_toJsonb(state, seqform, alter_table); + deparse_Seq_Maxvalue_toJsonb(state, seqform, alter_table); + deparse_Seq_Startwith_toJsonb(state, seqform, alter_table); + deparse_Seq_Restart_toJsonb(state, seqvalues->last_value); + /* We purposefully do not emit OWNED BY here */ + + pushJsonbValue(&state, WJB_END_ARRAY, NULL); + } + + /* 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_toJsonb(JsonbParseState **state, Relation relation, + List *dpcontext, bool composite, ColumnDef *coldef, + bool is_alter, Node **expr, bool *belemsAdded) +{ + Oid relid = RelationGetRelid(relation); + HeapTuple attrTup; + Form_pg_attribute attrForm; + Oid typid; + int32 typmod; + Oid typcollation; + char *fmtStr = "%{name}I %{coltype}T STORAGE %{colstorage}s %{compression}s %{collation}s " + "%{not_null}s %{default}s %{identity_column}s %{generated_column}s"; + bool saw_notnull; + ListCell *cell; + + /* + * Inherited columns without local definitions must not be emitted. + * + * XXX maybe it is useful to have them with "present = false" or some + * such? + */ + if (!coldef->is_local) + return; + + 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); + + if (!*belemsAdded) + { + *belemsAdded = true; + insert_jsonb_key(*state, "table_elements"); + pushJsonbValue(state, WJB_BEGIN_OBJECT, NULL); + + fmt_to_jsonb_element(*state, "(%{elements:, }s)"); + + insert_jsonb_key(*state, "elements"); + pushJsonbValue(state, WJB_BEGIN_ARRAY, NULL); + } + + /* start making column object */ + pushJsonbValue(state, WJB_BEGIN_OBJECT, NULL); + fmt_to_jsonb_element(*state, fmtStr); + + /* creat name and type elements for column */ + insert_jsonb_str_element(*state, "name", coldef->colname); + insert_jsonb_str_element(*state, "type", "column"); + + /* + * create coltype object having 4 elements: schemaname, typename, typemod, + * typearray + */ + { + /* Push the key first */ + insert_jsonb_key(*state, "coltype"); + + /* Push the value */ + new_jsonb_for_type(*state, typid, typmod); + } + + /* STORAGE clause */ + if (!composite) + insert_jsonb_str_element(*state, "colstorage", storage_name(attrForm->attstorage)); + + /* COMPRESSION clause */ + { + /* Push the key first */ + insert_jsonb_key(*state, "compression"); + + /* Push the value now: object in this case */ + if (coldef->compression) + insert_str_object(*state, "COMPRESSION %{compression_method}I", + "compression_method", coldef->compression); + else + insert_not_present_obj(*state, verbose ? "COMPRESSION %{compression_method}I" : "COMPRESSION"); + } + + /* COLLATE clause */ + { + /* Push the key first */ + insert_jsonb_key(*state, "collation"); + + if (OidIsValid(typcollation)) + insert_collate_object(*state, "COLLATE %{collation_name}D", CollationRelationId, typcollation); + else + insert_not_present_obj(*state, verbose ? "COLLATE %{collation_name}D" : "COLLATE"); + } + + 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 */ + insert_jsonb_str_element(*state, "not_null", saw_notnull ? "NOT NULL" : ""); + + /* DEFAULT: Push the key first */ + insert_jsonb_key(*state, "default"); + + /* DEFAULT: Push the value now */ + if (attrForm->atthasdef && + coldef->generated != ATTRIBUTE_GENERATED_STORED) + { + char *defstr; + + defstr = RelationGetColumnDefault(relation, attrForm->attnum, + dpcontext, expr); + + insert_str_object(*state, "DEFAULT %{default}s", "default", defstr); + } + else + insert_not_present_obj(*state, verbose ? "DEFAULT %{default}s" : "DEFAULT"); + + + /* IDENTITY COLUMN */ + if (coldef->identity) + { + /* + * For identity column, find the sequence owned by column in order + * to deparse the column definition. + */ + seqrelid = getIdentitySequence(relid, attrForm->attnum, true); + if (OidIsValid(seqrelid) && coldef->identitySequence) + seqrelid = RangeVarGetRelid(coldef->identitySequence, NoLock, false); + } + + /* IDENTITY COLUMN: Push the key first */ + insert_jsonb_key(*state, "identity_column"); + + /* IDENTITY COLUMN: Push the value now */ + if (OidIsValid(seqrelid)) + { + + deparse_ColumnIdentity_toJsonb(*state, seqrelid, coldef->identity, is_alter); + } + else + insert_not_present_obj(*state, verbose ? "%{identity_column}s" : ""); + + /* GENERATED COLUMN EXPRESSION: Push the key first */ + insert_jsonb_key(*state, "generated_column"); + + /* GENERATED COLUMN EXPRESSION: Push the value now */ + if (coldef->generated == ATTRIBUTE_GENERATED_STORED) + { + char *defstr; + + defstr = RelationGetColumnDefault(relation, attrForm->attnum, + dpcontext, expr); + insert_str_object(*state, "GENERATED ALWAYS AS (%{generation_expr}s) STORED", "generation_expr", defstr); + } + else + insert_not_present_obj(*state, verbose ? "GENERATED ALWAYS AS (%{generation_expr}s) STORED" : "GENERATED ALWAYS AS"); + } + ReleaseSysCache(attrTup); + + /* mark the end of one column object */ + pushJsonbValue(state, WJB_END_OBJECT, NULL); +} + +/* + * 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_toJsonb(JsonbParseState **state, Relation relation, + List *dpcontext, ColumnDef *coldef, + bool *belemsAdded) +{ + Oid relid = RelationGetRelid(relation); + HeapTuple attrTup; + Form_pg_attribute attrForm; + Oid typid; + int32 typmod; + Oid typcollation; + bool saw_notnull; + ListCell *cell; + char *fmtStr = "%{name}I WITH OPTIONS %{not_null}s %{default}s"; + + attrTup = SearchSysCacheAttName(relid, coldef->colname); + if (!HeapTupleIsValid(attrTup)) + elog(ERROR, "could not find cache entry for column \"%s\" of relation %u", + coldef->colname, relid); + attrForm = (Form_pg_attribute) GETSTRUCT(attrTup); + + get_atttypetypmodcoll(relid, attrForm->attnum, + &typid, &typmod, &typcollation); + + /* + * Search for a NOT NULL declaration. As in deparse_ColumnDef, we rely on + * finding a constraint on the column rather than coldef->is_not_null. + * (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 (!saw_notnull && !attrForm->atthasdef) + { + ReleaseSysCache(attrTup); + return; + } + + if (!*belemsAdded) + { + *belemsAdded = true; + insert_jsonb_key(*state, "table_elements"); + pushJsonbValue(state, WJB_BEGIN_OBJECT, NULL); + + fmt_to_jsonb_element(*state, "(%{elements:, }s)"); + + insert_jsonb_key(*state, "elements"); + pushJsonbValue(state, WJB_BEGIN_ARRAY, NULL); + } + + /* start making column object */ + pushJsonbValue(state, WJB_BEGIN_OBJECT, NULL); + fmt_to_jsonb_element(*state, fmtStr); + + /* Insert TYPE, NAME and NOT_NULL elements */ + insert_jsonb_str_element(*state, "type", "column"); + insert_jsonb_str_element(*state, "name", coldef->colname); + insert_jsonb_str_element(*state, "not_null", saw_notnull ? "NOT NULL" : ""); + + /* DEFAULT: Push the key first */ + insert_jsonb_key(*state, "default"); + + /* DEFAULT: Push the value now */ + if (attrForm->atthasdef) + { + char *defstr; + + defstr = RelationGetColumnDefault(relation, attrForm->attnum, + dpcontext, NULL); + insert_str_object(*state, "DEFAULT %{default}s", "default", defstr); + } + else + insert_not_present_obj(*state, verbose ? "DEFAULT %{default}s" : "DEFAULT"); + + /* mark the end of column object */ + pushJsonbValue(state, WJB_END_OBJECT, NULL); + + /* Generated columns are not supported on typed tables, so we are done */ + + ReleaseSysCache(attrTup); +} + + +/* + * 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_TableElementsToJsonb(JsonbParseState **state, Relation relation, + List *tableElements, List *dpcontext, + bool typed, bool composite, bool *belemsAdded) +{ + ListCell *lc; + + foreach(lc, tableElements) + { + Node *elt = (Node *) lfirst(lc); + + switch (nodeTag(elt)) + { + case T_ColumnDef: + { + if (typed) + deparse_ColumnDef_typed_toJsonb(state, relation, dpcontext, + (ColumnDef *) elt, belemsAdded); + else + deparse_ColumnDef_toJsonb(state, relation, dpcontext, + composite, (ColumnDef *) elt, + false, NULL, belemsAdded); + } + break; + case T_Constraint: + break; + default: + elog(ERROR, "invalid node type %d", nodeTag(elt)); + } + } + +} + +/* + * Subroutine for CREATE TABLE deparsing. + * + * Deparse the INHERITS relations. + * + * Given a table OID, return a schema-qualified table list representing + * the parent tables. + */ +static List * +deparse_InhRelationsToJsonb(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; +} + +/* + * Subroutine for CREATE TABLE deparsing. + * + * Given a table OID, obtain its constraints and append them to the given + * JsonbParseState. + * + * This works for typed tables, regular tables. + * + * Note that CONSTRAINT_FOREIGN constraints are always ignored. + */ +static void +obtainConstraintsInJsonb(JsonbParseState **state, Oid relationId, + bool *belemsAdded) +{ + 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; + char *fmtStr = "CONSTRAINT %{name}I %{definition}s"; + + constrForm = (Form_pg_constraint) GETSTRUCT(tuple); + + switch (constrForm->contype) + { + case CONSTRAINT_CHECK: + contype = "check"; + break; + case CONSTRAINT_FOREIGN: + continue; /* not here */ + case CONSTRAINT_PRIMARY: + contype = "primary key"; + break; + case CONSTRAINT_UNIQUE: + contype = "unique"; + break; + case CONSTRAINT_EXCLUSION: + contype = "exclusion"; + break; + default: + elog(ERROR, "unrecognized constraint type"); + } + + if (!*belemsAdded) + { + *belemsAdded = true; + insert_jsonb_key(*state, "table_elements"); + pushJsonbValue(state, WJB_BEGIN_OBJECT, NULL); + + fmt_to_jsonb_element(*state, "(%{elements:, }s)"); + + insert_jsonb_key(*state, "elements"); + pushJsonbValue(state, WJB_BEGIN_ARRAY, NULL); + } + + /* + * "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. + */ + + pushJsonbValue(state, WJB_BEGIN_OBJECT, NULL); + fmt_to_jsonb_element(*state, fmtStr); + + /* create name and type elements for constraint */ + insert_jsonb_str_element(*state, "type", "constraint"); + insert_jsonb_str_element(*state, "contype", contype); + insert_jsonb_str_element(*state, "name", NameStr(constrForm->conname)); + insert_jsonb_str_element(*state, "definition", + 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); + + fmt_to_jsonb_element(*state, "USING INDEX TABLESPACE %{tblspc}s"); + insert_jsonb_str_element(*state, "tblspc", tblspcname); + } + } + + pushJsonbValue(state, WJB_END_OBJECT, NULL); + } + + systable_endscan(scan); + table_close(conRel, AccessShareLock); +} + +/* + * Deparse DefElems, as used by Create Table + * + * Verbose syntax + * %{label}s = %{value}L + */ +static void +deparse_DefElem_ToJsonb(JsonbParseState *state, DefElem *elem, bool is_reset) +{ + StringInfoData fmtStr; + initStringInfo(&fmtStr); + + appendStringInfoString(&fmtStr, "%{label}s"); + pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + + /* LABEL */ + { + StringInfoData labelfmt; + initStringInfo(&labelfmt); + + /* insert LABEL as key */ + insert_jsonb_key(state, "label"); + + /* LABEL's value is an object */ + pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + + if (elem->defnamespace != NULL) + { + appendStringInfoString(&labelfmt, "%{schema}I."); + insert_jsonb_str_element(state, "schema", elem->defnamespace); + } + + appendStringInfoString(&labelfmt, "%{label}I"); + insert_jsonb_str_element(state, "label", elem->defname); + + fmt_to_jsonb_element(state, labelfmt.data); + pfree(labelfmt.data); + + pushJsonbValue(&state, WJB_END_OBJECT, NULL); + } + + /* VALUE */ + if (!is_reset) + { + appendStringInfoString(&fmtStr, "= %{value}L"); + insert_jsonb_str_element(state, "value", + elem->arg ? defGetString(elem) : + defGetBoolean(elem) ? "TRUE" : "FALSE"); + } + + fmt_to_jsonb_element(state, fmtStr.data); + pfree(fmtStr.data); + + pushJsonbValue(&state, WJB_END_OBJECT, NULL); +} + +/* + * Subroutine for CREATE TABLE deparsing. + * + * Insert with-clause object for table definition + */ +static void +deparse_with_object(JsonbParseState *state, char *fmt, CreateStmt *node) +{ + ListCell *cell; + + /* with_clause's value is an object */ + pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + fmt_to_jsonb_element(state, fmt); + + /* WITH */ + { + /* insert with as key */ + insert_jsonb_key(state, "with"); + + /* with's value is an array */ + pushJsonbValue(&state, WJB_BEGIN_ARRAY, NULL); + + /* add elements to array */ + foreach(cell, node->options) + { + DefElem *opt = (DefElem *) lfirst(cell); + deparse_DefElem_ToJsonb(state, opt, false); + } + + /* with's array end */ + pushJsonbValue(&state, WJB_END_ARRAY, NULL); + } + + /* with_clause's object end */ + pushJsonbValue(&state, WJB_END_OBJECT, 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_by}s %{access_method}s %{with_clause}s + * %{on_commit}s %{tablespace}s + */ +static JsonbValue * +parsetree_to_jsonb_create_table(Oid objectId, Node *parsetree, char *owner) +{ + CreateStmt *node = (CreateStmt *) parsetree; + Relation relation = relation_open(objectId, AccessShareLock); + Oid nspid = relation->rd_rel->relnamespace; + char *relname = RelationGetRelationName(relation); + List *dpcontext; + bool belemsAdded = false; + StringInfoData fmtStr; + JsonbParseState *state = NULL; + + initStringInfo(&fmtStr); + + dpcontext = deparse_context_for(RelationGetRelationName(relation), + objectId); + + appendStringInfoString(&fmtStr, "CREATE %{persistence}s TABLE %{if_not_exists}s %{identity}D"); + + /* mark the start of ROOT object */ + pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + + /* create owner jsonb element */ + role_to_jsonb_element(state, owner); + + /* PERSISTENCE clause creation */ + insert_jsonb_str_element(state, "persistence", get_persistence_str(relation->rd_rel->relpersistence)); + + /* IF NOT EXISTS clause creation */ + insert_jsonb_str_element(state, "if_not_exists", node->if_not_exists ? "IF NOT EXISTS" : ""); + + + /* IDENTITY creation */ + insert_identity_object(state, nspid, relname); + + /* + * TABLE-ELEMENTS array creation + * + * Typed tables and partitions use a slightly different format string: we + * must not put table_elements with parents directly in the fmt string, + * because if there are no options the parentheses must not be emitted; + * and also, typed tables do not allow for inheritance. + */ + if (node->ofTypename || node->partbound) + { + /* + * We can't put table elements directly in the fmt string as an array + * surrounded by parentheses here, because an empty clause would cause + * a syntax error. Therefore, we use an indirection element and set + * present=false when there are no elements. + */ + if (node->ofTypename) + { + appendStringInfoString(&fmtStr, " OF %{of_type}T"); + + /* Push the key first */ + insert_jsonb_key(state, "of_type"); + + /* Push the value */ + new_jsonb_for_type(state, relation->rd_rel->reloftype, -1); + } + else + { + List *parents; + Oid objid; + + appendStringInfoString(&fmtStr, " PARTITION OF %{parent_identity}D"); + + parents = deparse_InhRelationsToJsonb(objectId); + objid = linitial_oid(parents); + + Assert(list_length(parents) == 1); + + new_jsonb_for_qualname_id(state, RelationRelationId, + objid, "parent_identity"); + } + + deparse_TableElementsToJsonb(&state, relation, node->tableElts, dpcontext, + true, /* typed table */ + false, /* not composite */ + &belemsAdded); + + obtainConstraintsInJsonb(&state, objectId, &belemsAdded); + + appendStringInfoString(&fmtStr, " %{table_elements}s"); + + if (belemsAdded) + { + pushJsonbValue(&state, WJB_END_ARRAY, NULL); + pushJsonbValue(&state, WJB_END_OBJECT, NULL); + } + else + { + insert_jsonb_key(state, "table_elements"); + insert_not_present_obj(state, "(%{elements:, }s)"); + } + } + else + { + List *inhrelations; + belemsAdded = true; + + /* + * There is no need to process LIKE clauses separately; they have + * already been transformed into columns and constraints. + */ + + /* + * 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. + */ + appendStringInfoString(&fmtStr, " (%{table_elements:, }s)"); + + insert_jsonb_key(state, "table_elements"); + + /* + * It will be of array type for multi-columns table, so lets begin an + * arrayobject. deparse_TableElementsToJsonb() will add elements to + * it. + */ + pushJsonbValue(&state, WJB_BEGIN_ARRAY, NULL); + + deparse_TableElementsToJsonb(&state, relation, node->tableElts, dpcontext, + false, /* not typed table */ + false, /* not composite */ + &belemsAdded); + obtainConstraintsInJsonb(&state, objectId, &belemsAdded); + + pushJsonbValue(&state, WJB_END_ARRAY, NULL); + + appendStringInfoString(&fmtStr, " %{inherits}s"); + insert_jsonb_key(state, "inherits"); + + /* + * 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) + { + pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + + fmt_to_jsonb_element(state, "INHERITS (%{parents:, }D)"); + inhrelations = deparse_InhRelationsToJsonb(objectId); + + insert_jsonb_array_oid(state, "parents", inhrelations); + pushJsonbValue(&state, WJB_END_OBJECT, NULL); + } + else + insert_not_present_obj(state, verbose ? "INHERITS (%{parents:, }D)" : "INHERITS"); + } + + + /* FOR VALUES clause */ + if (node->partbound) + { + appendStringInfoString(&fmtStr, " %{partition_bound}s"); + insert_jsonb_key(state, "partition_bound"); + + /* + * Get pg_class.relpartbound. We cannot use partbound in the parsetree + * directly as it's the original partbound expression which haven't + * been transformed. + */ + insert_jsonb_str_element(state, "partition_bound", + RelationGetPartitionBound(objectId)); + } + + /* PARTITION BY clause */ + appendStringInfoString(&fmtStr, " %{partition_by}s"); + insert_jsonb_key(state, "partition_by"); + + if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + insert_str_object(state, "PARTITION BY %{definition}s", "definition", + pg_get_partkeydef_string(objectId)); + else + insert_not_present_obj(state, verbose ? "PARTITION BY %{definition}s" : "PARTITION BY"); + + + /* USING clause */ + appendStringInfoString(&fmtStr, " %{access_method}s"); + insert_jsonb_key(state, "access_method"); + + if (node->accessMethod) + insert_str_object(state, "USING %{access_method}I", "access_method", + node->accessMethod); + else + insert_not_present_obj(state, verbose ? "USING %{access_method}I" : "USING"); + + /* WITH clause */ + appendStringInfoString(&fmtStr, " %{with_clause}s"); + insert_jsonb_key(state, "with_clause"); + + if (node->options) + deparse_with_object(state, "WITH (%{with:, }s)", node); + else + insert_not_present_obj(state, verbose ? "WITH (%{with:, }s)" : "WITH"); + + /* TABLESPACE */ + appendStringInfoString(&fmtStr, " %{tablespace}s"); + insert_jsonb_key(state, "tablespace"); + + /* Push the value now: object in this case */ + if (node->tablespacename) + insert_str_object(state, "TABLESPACE %{tablespace}I", "tablespace", node->tablespacename); + else + insert_not_present_obj(state, verbose ? "TABLESPACE %{tablespace}I" : "TABLESPACE"); + + + relation_close(relation, AccessShareLock); + + /* We have full fmt by now, so add jsonb element for that */ + fmt_to_jsonb_element(state, fmtStr.data); + + pfree(fmtStr.data); + + /* Mark the end of ROOT object */ + return pushJsonbValue(&state, WJB_END_OBJECT, NULL); +} -- 2.34.1