v1-0001-Add-initial-code-to-generate-table-definition-SQL.patch
application/x-patch
Filename: v1-0001-Add-initial-code-to-generate-table-definition-SQL.patch
Type: application/x-patch
Part: 0
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: format-patch
Series: patch v1-0001
Subject: Add initial code to generate table definition SQL
| File | + | − |
|---|---|---|
| src/backend/utils/adt/acl.c | 1 | 1 |
| src/backend/utils/adt/ruleutils.c | 1185 | 2 |
| src/include/utils/acl.h | 1 | 0 |
| src/include/utils/ruleutils.h | 51 | 0 |
| src/tools/pgindent/typedefs.list | 2 | 0 |
From ddb375afc74339bd0eaf0c272d06805637fd85cc Mon Sep 17 00:00:00 2001
From: Jelte Fennema <jelte.fennema@microsoft.com>
Date: Mon, 5 Jun 2023 12:32:12 +0200
Subject: [PATCH v1] Add initial code to generate table definition SQL
This patch adds various functions which generate table DDL statements
based on a table oid. These functions originated in Citus source code,
but are now contributed to upstream Postgres by means of this patch.
It currently contains no wrappers around these C functions that can be
called from SQL.
---
src/backend/utils/adt/acl.c | 2 +-
src/backend/utils/adt/ruleutils.c | 1187 ++++++++++++++++++++++++++++-
src/include/utils/acl.h | 1 +
src/include/utils/ruleutils.h | 51 ++
src/tools/pgindent/typedefs.list | 2 +
5 files changed, 1240 insertions(+), 3 deletions(-)
diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c
index c660fd3e701..abe329d4c83 100644
--- a/src/backend/utils/adt/acl.c
+++ b/src/backend/utils/adt/acl.c
@@ -1702,7 +1702,7 @@ convert_any_priv_string(text *priv_type_text,
}
-static const char *
+const char *
convert_aclright_to_string(int aclright)
{
switch (aclright)
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index d3a973d86b7..30bb4e7ffd9 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -24,12 +24,16 @@
#include "access/relation.h"
#include "access/sysattr.h"
#include "access/table.h"
+#include "access/toast_compression.h"
#include "catalog/pg_aggregate.h"
#include "catalog/pg_am.h"
+#include "catalog/pg_attrdef.h"
#include "catalog/pg_authid.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_constraint.h"
#include "catalog/pg_depend.h"
+#include "catalog/pg_extension.h"
+#include "catalog/pg_foreign_data_wrapper.h"
#include "catalog/pg_language.h"
#include "catalog/pg_opclass.h"
#include "catalog/pg_operator.h"
@@ -39,10 +43,13 @@
#include "catalog/pg_trigger.h"
#include "catalog/pg_type.h"
#include "commands/defrem.h"
+#include "commands/extension.h"
+#include "commands/sequence.h"
#include "commands/tablespace.h"
#include "common/keywords.h"
#include "executor/spi.h"
#include "funcapi.h"
+#include "foreign/foreign.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
@@ -59,6 +66,7 @@
#include "rewrite/rewriteHandler.h"
#include "rewrite/rewriteManip.h"
#include "rewrite/rewriteSupport.h"
+#include "utils/acl.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/fmgroids.h"
@@ -507,7 +515,6 @@ 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,
@@ -521,6 +528,10 @@ static void get_reloptions(StringInfo buf, Datum reloptions);
#define only_marker(rte) ((rte)->inh ? "" : "ONLY ")
+#define CREATE_SEQUENCE_COMMAND \
+ "CREATE %sSEQUENCE IF NOT EXISTS %s AS %s INCREMENT BY " INT64_FORMAT \
+ " MINVALUE " INT64_FORMAT " MAXVALUE " INT64_FORMAT \
+ " START WITH " INT64_FORMAT " CACHE " INT64_FORMAT " %sCYCLE"
/* ----------
* pg_get_ruledef - Do it all and return a text
@@ -12110,7 +12121,7 @@ generate_relation_name(Oid relid, List *namespaces)
*
* As above, but unconditionally schema-qualify the name.
*/
-static char *
+char *
generate_qualified_relation_name(Oid relid)
{
HeapTuple tp;
@@ -12606,3 +12617,1175 @@ get_range_partbound_string(List *bound_datums)
return buf->data;
}
+
+/*
+ * pg_get_extensiondef_string finds the foreign data wrapper that corresponds to
+ * the given foreign tableId, and checks if an extension owns this foreign data
+ * wrapper. If it does, the function returns the extension's definition. If not,
+ * the function returns null.
+ */
+char *
+pg_get_extensiondef_string(Oid tableRelationId)
+{
+ ForeignTable *foreignTable = GetForeignTable(tableRelationId);
+ ForeignServer *server = GetForeignServer(foreignTable->serverid);
+ ForeignDataWrapper *foreignDataWrapper = GetForeignDataWrapper(server->fdwid);
+ StringInfoData buffer = {NULL, 0, 0, 0};
+
+ Oid classId = ForeignDataWrapperRelationId;
+ Oid objectId = server->fdwid;
+
+ Oid extensionId = getExtensionOfObject(classId, objectId);
+
+ if (OidIsValid(extensionId))
+ {
+ char *extensionName = get_extension_name(extensionId);
+ Oid extensionSchemaId = get_extension_schema(extensionId);
+ char *extensionSchema = get_namespace_name(extensionSchemaId);
+
+ initStringInfo(&buffer);
+ appendStringInfo(&buffer, "CREATE EXTENSION IF NOT EXISTS %s WITH SCHEMA %s",
+ quote_identifier(extensionName),
+ quote_identifier(extensionSchema));
+ }
+ else
+ {
+ ereport(NOTICE, (errmsg("foreign-data wrapper \"%s\" does not have an "
+ "extension defined", foreignDataWrapper->fdwname)));
+ }
+
+ return (buffer.data);
+}
+
+/*
+ * get_extension_version - given an extension OID, fetch its extversion
+ * or NULL if not found.
+ */
+char *
+get_extension_version(Oid extensionId)
+{
+ char *versionName = NULL;
+
+ Relation relation = table_open(ExtensionRelationId, AccessShareLock);
+ SysScanDesc scanDesc;
+ HeapTuple tuple;
+
+ ScanKeyData entry[1];
+
+ ScanKeyInit(&entry[0],
+ Anum_pg_extension_oid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(extensionId));
+
+ scanDesc = systable_beginscan(relation, ExtensionOidIndexId, true,
+ NULL, 1, entry);
+
+ tuple = systable_getnext(scanDesc);
+
+ /* We assume that there can be at most one matching tuple */
+ if (HeapTupleIsValid(tuple))
+ {
+ bool isNull = false;
+ Datum versionDatum = heap_getattr(tuple, Anum_pg_extension_extversion,
+ RelationGetDescr(relation), &isNull);
+
+ if (!isNull)
+ {
+ versionName = text_to_cstring(DatumGetTextPP(versionDatum));
+ }
+ }
+
+ systable_endscan(scanDesc);
+
+ table_close(relation, AccessShareLock);
+
+ return versionName;
+}
+
+/*
+ * pg_get_serverdef_string finds the foreign server that corresponds to the
+ * given foreign tableId, and returns this server's definition.
+ */
+char *
+pg_get_serverdef_string(Oid tableRelationId)
+{
+ ForeignTable *foreignTable = GetForeignTable(tableRelationId);
+ ForeignServer *server = GetForeignServer(foreignTable->serverid);
+ ForeignDataWrapper *foreignDataWrapper = GetForeignDataWrapper(server->fdwid);
+
+ StringInfoData buffer = {NULL, 0, 0, 0};
+
+ initStringInfo(&buffer);
+
+ appendStringInfo(&buffer, "CREATE SERVER IF NOT EXISTS %s",
+ quote_identifier(server->servername));
+ if (server->servertype != NULL)
+ {
+ appendStringInfo(&buffer, " TYPE %s",
+ quote_literal_cstr(server->servertype));
+ }
+ if (server->serverversion != NULL)
+ {
+ appendStringInfo(&buffer, " VERSION %s",
+ quote_literal_cstr(server->serverversion));
+ }
+
+ appendStringInfo(&buffer, " FOREIGN DATA WRAPPER %s",
+ quote_identifier(foreignDataWrapper->fdwname));
+
+ /* append server options, if any */
+ AppendOptionListToString(&buffer, server->options);
+
+ return (buffer.data);
+}
+
+/*
+ * pg_get_sequencedef_string returns the definition of a given sequence. This
+ * definition includes explicit values for all CREATE SEQUENCE options.
+ */
+char *
+pg_get_sequencedef_string(Oid sequenceRelationId)
+{
+ Form_pg_sequence pgSequenceForm = pg_get_sequencedef(sequenceRelationId);
+
+ /* build our DDL command */
+ char *qualifiedSequenceName = generate_qualified_relation_name(sequenceRelationId);
+ char *typeName = format_type_be(pgSequenceForm->seqtypid);
+
+ char *sequenceDef = psprintf(CREATE_SEQUENCE_COMMAND,
+ get_rel_persistence(sequenceRelationId) ==
+ RELPERSISTENCE_UNLOGGED ? "UNLOGGED " : "",
+ qualifiedSequenceName,
+ typeName,
+ pgSequenceForm->seqincrement, pgSequenceForm->seqmin,
+ pgSequenceForm->seqmax, pgSequenceForm->seqstart,
+ pgSequenceForm->seqcache,
+ pgSequenceForm->seqcycle ? "" : "NO ");
+
+ return sequenceDef;
+}
+
+/*
+ * pg_get_sequencedef returns the Form_pg_sequence data about the sequence with the given
+ * object id.
+ */
+Form_pg_sequence
+pg_get_sequencedef(Oid sequenceRelationId)
+{
+ Form_pg_sequence pgSequenceForm;
+ HeapTuple heapTuple = SearchSysCache1(SEQRELID, sequenceRelationId);
+
+ if (!HeapTupleIsValid(heapTuple))
+ {
+ elog(ERROR, "cache lookup failed for sequence %u", sequenceRelationId);
+ }
+
+ pgSequenceForm = (Form_pg_sequence) GETSTRUCT(heapTuple);
+
+ ReleaseSysCache(heapTuple);
+
+ return pgSequenceForm;
+}
+
+/*
+ * RegularTable function returns true if given table's relation kind is RELKIND_RELATION
+ * or RELKIND_PARTITIONED_TABLE otherwise it returns false.
+ */
+bool
+RegularTable(Oid relationId)
+{
+ char relationKind = get_rel_relkind(relationId);
+
+ if (relationKind == RELKIND_RELATION || relationKind == RELKIND_PARTITIONED_TABLE)
+ {
+ return true;
+ }
+
+ return false;
+}
+
+/*
+ * pg_get_tableschemadef_string returns the definition of a given table. This
+ * definition includes table's schema, default column values, not null and check
+ * constraints. The definition does not include constraints that trigger index
+ * creations; specifically, unique and primary key constraints are excluded.
+ * When includeSequenceDefaults is INCLUDE_SEQUENCE_DEFAULTS, the function also creates
+ * DEFAULT clauses for columns getting their default values from a sequence.
+ * When IncludeIdentities is NO_IDENTITY, the function does not include identity column
+ * specifications. When it's INCLUDE_IDENTITY it creates GENERATED .. AS IDENTIY clauses.
+ */
+char *
+pg_get_tableschemadef_string(Oid tableRelationId,
+ IncludeSequenceDefaults includeSequenceDefaults,
+ IncludeIdentities includeIdentityDefaults,
+ char *accessMethod)
+{
+ bool firstAttributePrinted = false;
+ AttrNumber defaultValueIndex = 0;
+ AttrNumber constraintIndex = 0;
+ AttrNumber constraintCount = 0;
+ StringInfoData buffer = {NULL, 0, 0, 0};
+
+ /*
+ * Instead of retrieving values from system catalogs as other functions in
+ * ruleutils.c do, we follow an unusual approach here: we open the
+ * relation, and fetch the relation's tuple descriptor. We do this because
+ * the tuple descriptor already contains information harnessed from
+ * pg_attrdef, pg_attribute, pg_constraint, and pg_class; and therefore
+ * using the descriptor saves us from a lot of additional work.
+ */
+ Relation relation = relation_open(tableRelationId, AccessShareLock);
+ char *relationName = generate_relation_name(tableRelationId, NIL);
+ char relationKind;
+ TupleDesc tupleDescriptor;
+ TupleConstr *tupleConstraints;
+
+ /* TODO: Actually implement functionality */
+ /* EnsureRelationKindSupported(tableRelationId); */
+
+ initStringInfo(&buffer);
+
+ if (RegularTable(tableRelationId))
+ {
+ appendStringInfoString(&buffer, "CREATE ");
+
+ if (relation->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED)
+ {
+ appendStringInfoString(&buffer, "UNLOGGED ");
+ }
+
+ appendStringInfo(&buffer, "TABLE %s (", relationName);
+ }
+ else
+ {
+ appendStringInfo(&buffer, "CREATE FOREIGN TABLE %s (", relationName);
+ }
+
+ /*
+ * Iterate over the table's columns. If a particular column is not dropped
+ * and is not inherited from another table, print the column's name and
+ * its formatted type.
+ */
+ tupleDescriptor = RelationGetDescr(relation);
+ tupleConstraints = tupleDescriptor->constr;
+
+ for (int attributeIndex = 0; attributeIndex < tupleDescriptor->natts;
+ attributeIndex++)
+ {
+ Form_pg_attribute attributeForm = TupleDescAttr(tupleDescriptor, attributeIndex);
+
+ /*
+ * We disregard the inherited attributes (i.e., attinhcount > 0) here.
+ * The reasoning behind this is that Citus implements declarative
+ * partitioning by creating the partitions first and then sending
+ * "ALTER TABLE parent_table ATTACH PARTITION .." command. This may
+ * not play well with regular inherited tables, which isn't a big
+ * concern from Citus' perspective.
+ */
+ if (!attributeForm->attisdropped)
+ {
+ const char *attributeName = NameStr(attributeForm->attname);
+
+ const char *attributeTypeName = format_type_with_typemod(
+ attributeForm->atttypid,
+ attributeForm->
+ atttypmod);
+
+ if (firstAttributePrinted)
+ {
+ appendStringInfoString(&buffer, ", ");
+ }
+ firstAttributePrinted = true;
+
+ appendStringInfo(&buffer, "%s ", quote_identifier(attributeName));
+ appendStringInfoString(&buffer, attributeTypeName);
+
+ if (CompressionMethodIsValid(attributeForm->attcompression))
+ {
+ appendStringInfo(&buffer, " COMPRESSION %s",
+ GetCompressionMethodName(attributeForm->attcompression));
+ }
+
+ if (attributeForm->attidentity && includeIdentityDefaults)
+ {
+ bool missing_ok = false;
+ Oid seqOid = getIdentitySequence(RelationGetRelid(relation),
+ attributeForm->attnum, missing_ok);
+
+ if (includeIdentityDefaults == INCLUDE_IDENTITY)
+ {
+ Form_pg_sequence pgSequenceForm = pg_get_sequencedef(seqOid);
+ char *sequenceDef = psprintf(
+ " GENERATED %s AS IDENTITY (INCREMENT BY " INT64_FORMAT \
+ " MINVALUE " INT64_FORMAT " MAXVALUE "
+ INT64_FORMAT \
+ " START WITH " INT64_FORMAT " CACHE "
+ INT64_FORMAT " %sCYCLE)",
+ attributeForm->attidentity == ATTRIBUTE_IDENTITY_ALWAYS ?
+ "ALWAYS" : "BY DEFAULT",
+ pgSequenceForm->seqincrement,
+ pgSequenceForm->seqmin,
+ pgSequenceForm->seqmax,
+ pgSequenceForm->seqstart,
+ pgSequenceForm->seqcache,
+ pgSequenceForm->seqcycle ? "" : "NO ");
+
+ appendStringInfo(&buffer, "%s", sequenceDef);
+ }
+ }
+
+ /* if this column has a default value, append the default value */
+ if (attributeForm->atthasdef)
+ {
+ List *defaultContext = NULL;
+ char *defaultString = NULL;
+ AttrDefault *defaultValueList;
+ AttrDefault *defaultValue;
+ Node *defaultNode;
+
+ Assert(tupleConstraints != NULL);
+
+ defaultValueList = tupleConstraints->defval;
+ Assert(defaultValueList != NULL);
+
+ defaultValue = &(defaultValueList[defaultValueIndex]);
+ defaultValueIndex++;
+
+ Assert(defaultValue->adnum == (attributeIndex + 1));
+ Assert(defaultValueIndex <= tupleConstraints->num_defval);
+
+ /*
+ * convert expression to node tree, and prepare deparse
+ * context
+ */
+ defaultNode = (Node *) stringToNode(defaultValue->adbin);
+
+ /*
+ * if column default value is explicitly requested, or it is
+ * not set from a sequence then we include DEFAULT clause for
+ * this column.
+ */
+ if (includeSequenceDefaults == INCLUDE_SEQUENCE_DEFAULTS ||
+ !contain_nextval_expression_walker(defaultNode, NULL))
+ {
+ defaultContext = deparse_context_for(relationName, tableRelationId);
+
+ /* deparse default value string */
+ defaultString = deparse_expression(defaultNode, defaultContext,
+ false, false);
+
+ if (attributeForm->attgenerated == ATTRIBUTE_GENERATED_STORED)
+ {
+ appendStringInfo(&buffer, " GENERATED ALWAYS AS (%s) STORED",
+ defaultString);
+ }
+ else
+ {
+ appendStringInfo(&buffer, " DEFAULT %s", defaultString);
+ }
+ }
+ }
+
+ /* if this column has a not null constraint, append the constraint */
+ if (attributeForm->attnotnull)
+ {
+ appendStringInfoString(&buffer, " NOT NULL");
+ }
+
+ if (attributeForm->attcollation != InvalidOid &&
+ attributeForm->attcollation != DEFAULT_COLLATION_OID)
+ {
+ appendStringInfo(&buffer, " COLLATE %s", generate_collation_name(
+ attributeForm->attcollation));
+ }
+ }
+ }
+
+ /*
+ * Now check if the table has any constraints. If it does, set the number
+ * of check constraints here. Then iterate over all check constraints and
+ * print them.
+ */
+ if (tupleConstraints != NULL)
+ {
+ constraintCount = tupleConstraints->num_check;
+ }
+
+ for (constraintIndex = 0; constraintIndex < constraintCount; constraintIndex++)
+ {
+ ConstrCheck *checkConstraintList = tupleConstraints->check;
+ ConstrCheck *checkConstraint = &(checkConstraintList[constraintIndex]);
+
+ /* convert expression to node tree, and prepare deparse context */
+ Node *checkNode = (Node *) stringToNode(checkConstraint->ccbin);
+ List *checkContext = deparse_context_for(relationName, tableRelationId);
+
+ /* deparse check constraint string */
+ char *checkString = deparse_expression(checkNode, checkContext, false, false);
+
+ /* if an attribute or constraint has been printed, format properly */
+ if (firstAttributePrinted || constraintIndex > 0)
+ {
+ appendStringInfoString(&buffer, ", ");
+ }
+
+ appendStringInfo(&buffer, "CONSTRAINT %s CHECK ",
+ quote_identifier(checkConstraint->ccname));
+
+ appendStringInfoString(&buffer, "(");
+ appendStringInfoString(&buffer, checkString);
+ appendStringInfoString(&buffer, ")");
+ }
+
+ /* close create table's outer parentheses */
+ appendStringInfoString(&buffer, ")");
+
+ /*
+ * If the relation is a foreign table, append the server name and options
+ * to the create table statement.
+ */
+ relationKind = relation->rd_rel->relkind;
+ if (relationKind == RELKIND_FOREIGN_TABLE)
+ {
+ ForeignTable *foreignTable = GetForeignTable(tableRelationId);
+ ForeignServer *foreignServer = GetForeignServer(foreignTable->serverid);
+
+ char *serverName = foreignServer->servername;
+
+ appendStringInfo(&buffer, " SERVER %s", quote_identifier(serverName));
+ AppendOptionListToString(&buffer, foreignTable->options);
+ }
+ else if (relationKind == RELKIND_PARTITIONED_TABLE)
+ {
+ char *partitioningInformation = GeneratePartitioningInformation(tableRelationId);
+
+ appendStringInfo(&buffer, " PARTITION BY %s ", partitioningInformation);
+ }
+
+ /*
+ * Add table access methods when the table is configured with an access
+ * method
+ */
+ if (accessMethod)
+ {
+ appendStringInfo(&buffer, " USING %s", quote_identifier(accessMethod));
+ }
+ else if (OidIsValid(relation->rd_rel->relam))
+ {
+ Form_pg_am amForm;
+ HeapTuple amTup = SearchSysCache1(AMOID, ObjectIdGetDatum(
+ relation->rd_rel->relam));
+
+ if (!HeapTupleIsValid(amTup))
+ {
+ elog(ERROR, "cache lookup failed for access method %u",
+ relation->rd_rel->relam);
+ }
+ amForm = (Form_pg_am) GETSTRUCT(amTup);
+ appendStringInfo(&buffer, " USING %s", quote_identifier(NameStr(amForm->amname)));
+ ReleaseSysCache(amTup);
+ }
+
+ /*
+ * Add any reloptions (storage parameters) defined on the table in a WITH
+ * clause.
+ */
+ {
+ char *reloptions = flatten_reloptions(tableRelationId);
+
+ if (reloptions)
+ {
+ appendStringInfo(&buffer, " WITH (%s)", reloptions);
+ pfree(reloptions);
+ }
+ }
+
+ relation_close(relation, AccessShareLock);
+
+ return (buffer.data);
+}
+
+/*
+ * EnsureRelationKindSupported errors out if the given relation is not supported
+ * as a distributed relation.
+ */
+/*
+ * void
+ * EnsureRelationKindSupported(Oid relationId)
+ * {
+ * char relationKind = get_rel_relkind(relationId);
+ * if (!relationKind)
+ * {
+ * ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ * errmsg("relation with OID %d does not exist",
+ * relationId)));
+ * }
+ *
+ * bool supportedRelationKind = RegularTable(relationId) ||
+ * relationKind == RELKIND_FOREIGN_TABLE;
+ *
+ * /\*
+ * * Citus doesn't support bare inherited tables (i.e., not a partition or
+ * * partitioned table)
+ * *\/
+ * supportedRelationKind = supportedRelationKind && !(IsChildTable(relationId) ||
+ * IsParentTable(relationId));
+ *
+ * if (!supportedRelationKind)
+ * {
+ * char *relationName = get_rel_name(relationId);
+ *
+ * ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ * errmsg("%s is not a regular, foreign or partitioned table",
+ * relationName)));
+ * }
+ * }
+ */
+
+/*
+ * pg_get_tablecolumnoptionsdef_string returns column storage type and column
+ * statistics definitions for given table, _if_ these definitions differ from
+ * their default values. The function returns null if all columns use default
+ * values for their storage types and statistics.
+ */
+char *
+pg_get_tablecolumnoptionsdef_string(Oid tableRelationId)
+{
+ List *columnOptionList = NIL;
+ ListCell *columnOptionCell = NULL;
+ bool firstOptionPrinted = false;
+ StringInfoData buffer = {NULL, 0, 0, 0};
+
+ /*
+ * Instead of retrieving values from system catalogs, we open the
+ * relation, and use the relation's tuple descriptor to access attribute
+ * information. This is primarily to maintain symmetry with
+ * pg_get_tableschemadef.
+ */
+ Relation relation = relation_open(tableRelationId, AccessShareLock);
+
+ /* TODO: Actually implement functionality */
+ /* EnsureRelationKindSupported(tableRelationId); */
+
+ /*
+ * Iterate over the table's columns. If a particular column is not dropped
+ * and is not inherited from another table, check if column storage or
+ * statistics statements need to be printed.
+ */
+ TupleDesc tupleDescriptor = RelationGetDescr(relation);
+ AttrNumber natts;
+
+ if (tupleDescriptor->natts > MaxAttrNumber)
+ {
+ ereport(ERROR, (errmsg("bad number of tuple descriptor attributes")));
+ }
+
+ natts = tupleDescriptor->natts;
+ for (AttrNumber attributeIndex = 0;
+ attributeIndex < natts;
+ attributeIndex++)
+ {
+ Form_pg_attribute attributeForm = TupleDescAttr(tupleDescriptor, attributeIndex);
+ char *attributeName = NameStr(attributeForm->attname);
+ char defaultStorageType = get_typstorage(attributeForm->atttypid);
+
+ if (!attributeForm->attisdropped && attributeForm->attinhcount == 0)
+ {
+ /*
+ * If the user changed the column's default storage type, create
+ * alter statement and add statement to a list for later
+ * processing.
+ */
+ if (attributeForm->attstorage != defaultStorageType)
+ {
+ char *storageName = 0;
+ StringInfoData statement = {NULL, 0, 0, 0};
+
+ initStringInfo(&statement);
+
+ switch (attributeForm->attstorage)
+ {
+ case 'p':
+ {
+ storageName = "PLAIN";
+ break;
+ }
+
+ case 'e':
+ {
+ storageName = "EXTERNAL";
+ break;
+ }
+
+ case 'm':
+ {
+ storageName = "MAIN";
+ break;
+ }
+
+ case 'x':
+ {
+ storageName = "EXTENDED";
+ break;
+ }
+
+ default:
+ {
+ ereport(ERROR, (errmsg("unrecognized storage type: %c",
+ attributeForm->attstorage)));
+ break;
+ }
+ }
+
+ appendStringInfo(&statement, "ALTER COLUMN %s ",
+ quote_identifier(attributeName));
+ appendStringInfo(&statement, "SET STORAGE %s", storageName);
+
+ columnOptionList = lappend(columnOptionList, statement.data);
+ }
+
+ /*
+ * If the user changed the column's statistics target, create
+ * alter statement and add statement to a list for later
+ * processing.
+ */
+ if (attributeForm->attstattarget >= 0)
+ {
+ StringInfoData statement = {NULL, 0, 0, 0};
+
+ initStringInfo(&statement);
+
+ appendStringInfo(&statement, "ALTER COLUMN %s ",
+ quote_identifier(attributeName));
+ appendStringInfo(&statement, "SET STATISTICS %d",
+ attributeForm->attstattarget);
+
+ columnOptionList = lappend(columnOptionList, statement.data);
+ }
+ }
+ }
+
+ /*
+ * Iterate over column storage and statistics statements that we created,
+ * and append them to a single alter table statement.
+ */
+ foreach(columnOptionCell, columnOptionList)
+ {
+ char *columnOptionStatement = (char *) lfirst(columnOptionCell);
+
+ if (!firstOptionPrinted)
+ {
+ initStringInfo(&buffer);
+ appendStringInfo(&buffer, "ALTER TABLE ONLY %s ",
+ generate_relation_name(tableRelationId, NIL));
+ }
+ else
+ {
+ appendStringInfoString(&buffer, ", ");
+ }
+ firstOptionPrinted = true;
+ appendStringInfoString(&buffer, columnOptionStatement);
+
+ pfree(columnOptionStatement);
+ }
+
+ list_free(columnOptionList);
+ relation_close(relation, AccessShareLock);
+
+ return (buffer.data);
+}
+
+/*
+ * pg_get_indexclusterdef_string returns the definition of a cluster statement
+ * for given index. The function returns null if the table is not clustered on
+ * given index.
+ */
+char *
+pg_get_indexclusterdef_string(Oid indexRelationId)
+{
+ StringInfoData buffer = {NULL, 0, 0, 0};
+ Form_pg_index indexForm;
+ Oid tableRelationId;
+
+ HeapTuple indexTuple = SearchSysCache(INDEXRELID, ObjectIdGetDatum(indexRelationId),
+ 0, 0, 0);
+
+ if (!HeapTupleIsValid(indexTuple))
+ {
+ ereport(ERROR, (errmsg("cache lookup failed for index %u", indexRelationId)));
+ }
+
+ indexForm = (Form_pg_index) GETSTRUCT(indexTuple);
+ tableRelationId = indexForm->indrelid;
+
+ /* check if the table is clustered on this index */
+ if (indexForm->indisclustered)
+ {
+ char *qualifiedRelationName =
+ generate_qualified_relation_name(tableRelationId);
+ char *indexName = get_rel_name(indexRelationId); /* needs to be quoted */
+
+ initStringInfo(&buffer);
+ appendStringInfo(&buffer, "ALTER TABLE %s CLUSTER ON %s",
+ qualifiedRelationName, quote_identifier(indexName));
+ }
+
+ ReleaseSysCache(indexTuple);
+
+ return (buffer.data);
+}
+
+/*
+ * pg_get_table_grants returns a list of sql statements which recreate the
+ * permissions for a specific table.
+ *
+ * This function is modeled after aclexplode(), don't change too heavily.
+ */
+List *
+pg_get_table_grants(Oid relationId)
+{
+ StringInfoData buffer;
+ List *defs = NIL;
+ bool isNull = false;
+
+ Relation relation = relation_open(relationId, AccessShareLock);
+ char *relationName = generate_relation_name(relationId, NIL);
+ HeapTuple classTuple;
+ Datum aclDatum;
+
+ initStringInfo(&buffer);
+
+ /* lookup all table level grants */
+ classTuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relationId));
+ if (!HeapTupleIsValid(classTuple))
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation with OID %u does not exist",
+ relationId)));
+ }
+
+ aclDatum = SysCacheGetAttr(RELOID, classTuple, Anum_pg_class_relacl,
+ &isNull);
+
+ ReleaseSysCache(classTuple);
+
+ if (!isNull)
+ {
+ Acl *acl = DatumGetAclP(aclDatum);
+ AclItem *aidat = ACL_DAT(acl);
+
+ int offtype = -1;
+ int i = 0;
+
+ /*
+ * First revoke all default permissions, so we can start adding the
+ * exact permissions from the master. Note that we only do so if there
+ * are any actual grants; an empty grant set signals default
+ * permissions.
+ *
+ * Note: This doesn't work correctly if default permissions have been
+ * changed with ALTER DEFAULT PRIVILEGES - but that's hard to fix
+ * properly currently.
+ */
+ appendStringInfo(&buffer, "REVOKE ALL ON %s FROM PUBLIC",
+ relationName);
+ defs = lappend(defs, pstrdup(buffer.data));
+ resetStringInfo(&buffer);
+
+ /* iterate through the acl datastructure, emit GRANTs */
+ while (i < ACL_NUM(acl))
+ {
+ AclItem *aidata = NULL;
+ AclMode priv_bit = 0;
+
+ offtype++;
+
+ if (offtype == N_ACL_RIGHTS)
+ {
+ offtype = 0;
+ i++;
+ if (i >= ACL_NUM(acl)) /* done */
+ {
+ break;
+ }
+ }
+
+ aidata = &aidat[i];
+ priv_bit = 1 << offtype;
+
+ if (ACLITEM_GET_PRIVS(*aidata) & priv_bit)
+ {
+ const char *roleName = NULL;
+ const char *withGrant = "";
+
+ if (aidata->ai_grantee != 0)
+ {
+
+ HeapTuple htup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(aidata->ai_grantee));
+
+ if (HeapTupleIsValid(htup))
+ {
+ Form_pg_authid authForm = ((Form_pg_authid) GETSTRUCT(htup));
+
+ roleName = quote_identifier(NameStr(authForm->rolname));
+
+ ReleaseSysCache(htup);
+ }
+ else
+ {
+ elog(ERROR, "cache lookup failed for role %u", aidata->ai_grantee);
+ }
+ }
+ else
+ {
+ roleName = "PUBLIC";
+ }
+
+ if ((ACLITEM_GET_GOPTIONS(*aidata) & priv_bit) != 0)
+ {
+ withGrant = " WITH GRANT OPTION";
+ }
+
+ appendStringInfo(&buffer, "GRANT %s ON %s TO %s%s",
+ convert_aclright_to_string(priv_bit),
+ relationName,
+ roleName,
+ withGrant);
+
+ defs = lappend(defs, pstrdup(buffer.data));
+
+ resetStringInfo(&buffer);
+ }
+ }
+ }
+
+ resetStringInfo(&buffer);
+
+ relation_close(relation, NoLock);
+ return defs;
+}
+
+/*
+ * pg_get_replica_identity_command function returns the required ALTER .. TABLE
+ * command to define the replica identity.
+ */
+char *
+pg_get_replica_identity_command(Oid tableRelationId)
+{
+ StringInfo buf = makeStringInfo();
+
+ Relation relation = table_open(tableRelationId, AccessShareLock);
+
+ char replicaIdentity = relation->rd_rel->relreplident;
+
+ char *relationName = generate_qualified_relation_name(tableRelationId);
+
+ if (replicaIdentity == REPLICA_IDENTITY_INDEX)
+ {
+ Oid indexId = RelationGetReplicaIndex(relation);
+
+ if (OidIsValid(indexId))
+ {
+ appendStringInfo(buf, "ALTER TABLE %s REPLICA IDENTITY USING INDEX %s ",
+ relationName,
+ quote_identifier(get_rel_name(indexId)));
+ }
+ }
+ else if (replicaIdentity == REPLICA_IDENTITY_NOTHING)
+ {
+ appendStringInfo(buf, "ALTER TABLE %s REPLICA IDENTITY NOTHING",
+ relationName);
+ }
+ else if (replicaIdentity == REPLICA_IDENTITY_FULL)
+ {
+ appendStringInfo(buf, "ALTER TABLE %s REPLICA IDENTITY FULL",
+ relationName);
+ }
+
+ table_close(relation, AccessShareLock);
+
+ return (buf->len > 0) ? buf->data : NULL;
+}
+
+/*
+ * pg_get_row_level_security_commands function returns the required ALTER .. TABLE
+ * commands to define the row level security settings for a relation.
+ */
+List *
+pg_get_row_level_security_commands(Oid relationId)
+{
+ StringInfoData buffer;
+ List *commands = NIL;
+ Relation relation = table_open(relationId, AccessShareLock);
+
+ initStringInfo(&buffer);
+
+ if (relation->rd_rel->relrowsecurity)
+ {
+ char *relationName = generate_qualified_relation_name(relationId);
+
+ appendStringInfo(&buffer, "ALTER TABLE %s ENABLE ROW LEVEL SECURITY",
+ relationName);
+ commands = lappend(commands, pstrdup(buffer.data));
+ resetStringInfo(&buffer);
+ }
+
+ if (relation->rd_rel->relforcerowsecurity)
+ {
+ char *relationName = generate_qualified_relation_name(relationId);
+
+ appendStringInfo(&buffer, "ALTER TABLE %s FORCE ROW LEVEL SECURITY",
+ relationName);
+ commands = lappend(commands, pstrdup(buffer.data));
+ resetStringInfo(&buffer);
+ }
+
+ table_close(relation, AccessShareLock);
+
+ return commands;
+}
+
+/*
+ * contain_nextval_expression_walker walks over expression tree and returns
+ * true if it contains call to 'nextval' function or it has an identity column.
+ */
+bool
+contain_nextval_expression_walker(Node *node, void *context)
+{
+ if (node == NULL)
+ {
+ return false;
+ }
+
+ /* check if the node contains an identity column */
+ if (IsA(node, NextValueExpr))
+ {
+ return true;
+ }
+
+ /* check if the node contains call to 'nextval' */
+ if (IsA(node, FuncExpr))
+ {
+ FuncExpr *funcExpr = (FuncExpr *) node;
+
+ if (funcExpr->funcid == F_NEXTVAL)
+ {
+ return true;
+ }
+ }
+ return expression_tree_walker(node, contain_nextval_expression_walker, context);
+}
+
+
+/*
+ * AppendOptionListToString converts the option list to its textual format, and
+ * appends this text to the given string buffer.
+ */
+void
+AppendOptionListToString(StringInfo stringBuffer, List *optionList)
+{
+ if (optionList != NIL)
+ {
+ ListCell *optionCell = NULL;
+ bool firstOptionPrinted = false;
+
+ appendStringInfo(stringBuffer, " OPTIONS (");
+
+ foreach(optionCell, optionList)
+ {
+ DefElem *option = (DefElem *) lfirst(optionCell);
+ char *optionName = option->defname;
+ char *optionValue = defGetString(option);
+
+ if (firstOptionPrinted)
+ {
+ appendStringInfo(stringBuffer, ", ");
+ }
+ firstOptionPrinted = true;
+
+ appendStringInfo(stringBuffer, "%s ", quote_identifier(optionName));
+ appendStringInfo(stringBuffer, "%s", quote_literal_cstr(optionValue));
+ }
+
+ appendStringInfo(stringBuffer, ")");
+ }
+}
+
+/*
+ * AppendStorageParametersToString converts the storage parameter list to its
+ * textual format, and appends this text to the given string buffer.
+ */
+void
+AppendStorageParametersToString(StringInfo stringBuffer, List *optionList)
+{
+ ListCell *optionCell = NULL;
+ bool firstOptionPrinted = false;
+
+ foreach(optionCell, optionList)
+ {
+ DefElem *option = (DefElem *) lfirst(optionCell);
+ char *optionName = option->defname;
+ char *optionValue = defGetString(option);
+
+ if (firstOptionPrinted)
+ {
+ appendStringInfo(stringBuffer, ", ");
+ }
+ firstOptionPrinted = true;
+
+ appendStringInfo(stringBuffer, "%s = %s ",
+ quote_identifier(optionName),
+ quote_literal_cstr(optionValue));
+ }
+}
+
+/*
+ * GenereatePartitioningInformation returns the partitioning type and partition column
+ * for the given parent table in the form of "PARTITION TYPE (partitioning column(s)/expression(s))".
+ */
+char *
+GeneratePartitioningInformation(Oid parentTableId)
+{
+ char *partitionBoundCString = "";
+ Datum partitionBoundDatum;
+
+ if (!PartitionedTable(parentTableId))
+ {
+ char *relationName = get_rel_name(parentTableId);
+
+ ereport(ERROR, (errmsg("\"%s\" is not a parent table", relationName)));
+ }
+
+ partitionBoundDatum = DirectFunctionCall1(pg_get_partkeydef,
+ ObjectIdGetDatum(parentTableId));
+
+ partitionBoundCString = TextDatumGetCString(partitionBoundDatum);
+
+ return partitionBoundCString;
+}
+
+/*
+ * Returns true if the given relation is a partitioned table.
+ */
+bool
+PartitionedTable(Oid relationId)
+{
+ Relation rel = try_relation_open(relationId, AccessShareLock);
+ bool partitionedTable = false;
+
+ /* don't error out for tables that are dropped */
+ if (rel == NULL)
+ {
+ return false;
+ }
+
+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ {
+ partitionedTable = true;
+ }
+
+ /* keep the lock */
+ table_close(rel, NoLock);
+
+ return partitionedTable;
+}
+
+/*
+ * RoleSpecString resolves the role specification to its string form that is suitable for transport to a worker node.
+ * This function resolves the following identifiers from the current context so they are safe to transfer.
+ *
+ * CURRENT_USER - resolved to the user name of the current role being used
+ * SESSION_USER - resolved to the user name of the user that opened the session
+ * CURRENT_ROLE - same as CURRENT_USER, resolved to the user name of the current role being used
+ * Postgres treats CURRENT_ROLE is equivalent to CURRENT_USER, and we follow the same approach.
+ *
+ * withQuoteIdentifier is used, because if the results will be used in a query the quotes are needed but if not there
+ * should not be extra quotes.
+ */
+const char *
+RoleSpecString(RoleSpec *spec, bool withQuoteIdentifier)
+{
+ switch (spec->roletype)
+ {
+ case ROLESPEC_CSTRING:
+ {
+ return withQuoteIdentifier ?
+ quote_identifier(spec->rolename) :
+ spec->rolename;
+ }
+
+#if PG_VERSION_NUM >= PG_VERSION_14
+ case ROLESPEC_CURRENT_ROLE:
+#endif
+ case ROLESPEC_CURRENT_USER:
+ {
+ return withQuoteIdentifier ?
+ quote_identifier(GetUserNameFromId(GetUserId(), false)) :
+ GetUserNameFromId(GetUserId(), false);
+ }
+
+ case ROLESPEC_SESSION_USER:
+ {
+ return withQuoteIdentifier ?
+ quote_identifier(GetUserNameFromId(GetSessionUserId(), false)) :
+ GetUserNameFromId(GetSessionUserId(), false);
+ }
+
+ case ROLESPEC_PUBLIC:
+ {
+ return "PUBLIC";
+ }
+
+ default:
+ {
+ elog(ERROR, "unexpected role type %d", spec->roletype);
+ }
+ }
+}
+
+/*
+ * TableOwnerResetCommand generates a commands that can be executed
+ * to reset the table owner.
+ */
+char *
+TableOwnerResetCommand(Oid relationId)
+{
+ StringInfo ownerResetCommand = makeStringInfo();
+ char *qualifiedRelationName = generate_qualified_relation_name(relationId);
+ char *tableOwnerName = TableOwner(relationId);
+
+ appendStringInfo(ownerResetCommand,
+ "ALTER TABLE %s OWNER TO %s",
+ qualifiedRelationName,
+ quote_identifier(tableOwnerName));
+
+ return ownerResetCommand->data;
+}
+
+Oid
+TableOwnerOid(Oid relationId)
+{
+ Oid userId;
+ HeapTuple tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relationId));
+
+ if (!HeapTupleIsValid(tuple))
+ {
+ ereport(ERROR, (errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation with OID %u does not exist", relationId)));
+ }
+
+ userId = ((Form_pg_class) GETSTRUCT(tuple))->relowner;
+
+ ReleaseSysCache(tuple);
+ return userId;
+}
+
+/*
+ * Return a table's owner as a string.
+ */
+char *
+TableOwner(Oid relationId)
+{
+ return GetUserNameFromId(TableOwnerOid(relationId), false);
+}
diff --git a/src/include/utils/acl.h b/src/include/utils/acl.h
index f8e1238fa2c..c384a455950 100644
--- a/src/include/utils/acl.h
+++ b/src/include/utils/acl.h
@@ -275,5 +275,6 @@ extern void removeExtObjInitPriv(Oid objoid, Oid classoid);
extern bool object_ownercheck(Oid classid, Oid objectid, Oid roleid);
extern bool has_createrole_privilege(Oid roleid);
extern bool has_bypassrls_privilege(Oid roleid);
+extern const char *convert_aclright_to_string(int aclright);
#endif /* ACL_H */
diff --git a/src/include/utils/ruleutils.h b/src/include/utils/ruleutils.h
index b006d9d475e..ea81a68545d 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 "catalog/pg_sequence.h"
struct Plan; /* avoid including plannodes.h here */
struct PlannedStmt;
@@ -24,6 +25,27 @@ struct PlannedStmt;
#define RULE_INDEXDEF_PRETTY 0x01
#define RULE_INDEXDEF_KEYS_ONLY 0x02 /* ignore included attributes */
+/*
+ * IncludeSequenceDefaults decides on inclusion of DEFAULT clauses for columns
+ * getting their default values from a sequence when creating the definition
+ * of a table.
+ */
+typedef enum IncludeSequenceDefaults
+{
+ NO_SEQUENCE_DEFAULTS = 0, /* don't include sequence defaults */
+ INCLUDE_SEQUENCE_DEFAULTS = 1, /* include sequence defaults */
+} IncludeSequenceDefaults;
+
+/*
+ * IncludeIdentities decides on how we include identity information
+ * when creating the definition of a table.
+ */
+typedef enum IncludeIdentities
+{
+ NO_IDENTITY = 0, /* don't include identities */
+ INCLUDE_IDENTITY = 1 /* include identities as-is */
+} IncludeIdentities;
+
extern char *pg_get_indexdef_string(Oid indexrelid);
extern char *pg_get_indexdef_columns(Oid indexrelid, bool pretty);
extern char *pg_get_indexdef_columns_extended(Oid indexrelid,
@@ -43,10 +65,39 @@ extern List *set_deparse_context_plan(List *dpcontext,
struct Plan *plan, List *ancestors);
extern List *select_rtable_names_for_explain(List *rtable,
Bitmapset *rels_used);
+extern char *generate_qualified_relation_name(Oid collid);
extern char *generate_collation_name(Oid collid);
extern char *generate_opclass_name(Oid opclass);
extern char *get_range_partbound_string(List *bound_datums);
extern char *pg_get_statisticsobjdef_string(Oid statextid);
+/* Function declarations for version independent Citus ruleutils wrapper functions */
+extern char *pg_get_extensiondef_string(Oid tableRelationId);
+extern char *get_extension_version(Oid extensionId);
+extern char *pg_get_serverdef_string(Oid tableRelationId);
+extern char *pg_get_sequencedef_string(Oid sequenceRelid);
+extern Form_pg_sequence pg_get_sequencedef(Oid sequenceRelationId);
+extern char *pg_get_tableschemadef_string(Oid tableRelationId,
+ IncludeSequenceDefaults includeSequenceDefaults,
+ IncludeIdentities includeIdentityDefaults,
+ char *accessMethod);
+extern char *pg_get_tablecolumnoptionsdef_string(Oid tableRelationId);
+extern char *pg_get_indexclusterdef_string(Oid indexRelationId);
+extern List *pg_get_table_grants(Oid relationId);
+extern char *pg_get_replica_identity_command(Oid tableRelationId);
+extern List *pg_get_row_level_security_commands(Oid relationId);
+extern bool contain_nextval_expression_walker(Node *node, void *context);
+extern void AppendOptionListToString(StringInfo stringData, List *options);
+extern void AppendStorageParametersToString(StringInfo stringBuffer,
+ List *optionList);
+
+extern bool RegularTable(Oid relationId);
+extern char *GeneratePartitioningInformation(Oid parentTableId);
+extern bool PartitionedTable(Oid relationId);
+extern const char *RoleSpecString(RoleSpec *spec, bool withQuoteIdentifier);
+extern char *TableOwnerResetCommand(Oid relationId);
+extern Oid TableOwnerOid(Oid relationId);
+extern char *TableOwner(Oid relationId);
+
#endif /* RULEUTILS_H */
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 260854747b4..241594e6333 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1141,6 +1141,8 @@ ImportForeignSchema_function
ImportQual
InProgressEnt
IncludeWal
+IncludeSequenceDefaults
+IncludeIdentities
InclusionOpaque
IncrementVarSublevelsUp_context
IncrementalSort
base-commit: 8cddea9a539cbbdd1b316255c9f404323243fd37
--
2.34.1