/*------------------------------------------------------------------------- * * deparse_extension_stmts.c * All routines to deparse extension statements. * This file contains deparse functions for extension statement deparsing * as well as related helper functions. * * Copyright (c), Citus Data, Inc. * *------------------------------------------------------------------------- */ #include "postgres.h" #include "catalog/namespace.h" #include "commands/defrem.h" #include "lib/stringinfo.h" #include "nodes/parsenodes.h" #include "nodes/pg_list.h" #include "utils/builtins.h" #include "distributed/deparser.h" #include "distributed/listutils.h" /* Local functions forward declarations for helper functions */ static void AppendCreateExtensionStmt(StringInfo buf, CreateExtensionStmt *stmt); static void AppendCreateExtensionStmtOptions(StringInfo buf, List *options); static void AppendDropExtensionStmt(StringInfo buf, DropStmt *stmt); static void AppendExtensionNameList(StringInfo buf, List *objects); static void AppendAlterExtensionSchemaStmt(StringInfo buf, AlterObjectSchemaStmt *alterExtensionSchemaStmt); static void AppendAlterExtensionStmt(StringInfo buf, AlterExtensionStmt *alterExtensionStmt); /* * GetExtensionOption returns DefElem * node with "defname" from "options" list */ DefElem * GetExtensionOption(List *extensionOptions, const char *defname) { DefElem *defElement = NULL; foreach_declared_ptr(defElement, extensionOptions) { if (IsA(defElement, DefElem) && strncmp(defElement->defname, defname, NAMEDATALEN) == 0) { return defElement; } } return NULL; } /* * DeparseCreateExtensionStmt builds and returns a string representing the * CreateExtensionStmt to be sent to worker nodes. */ char * DeparseCreateExtensionStmt(Node *node) { CreateExtensionStmt *stmt = castNode(CreateExtensionStmt, node); StringInfoData sql = { 0 }; initStringInfo(&sql); AppendCreateExtensionStmt(&sql, stmt); return sql.data; } /* * AppendCreateExtensionStmt appends a string representing the CreateExtensionStmt to a buffer */ static void AppendCreateExtensionStmt(StringInfo buf, CreateExtensionStmt *createExtensionStmt) { appendStringInfoString(buf, "CREATE EXTENSION "); if (createExtensionStmt->if_not_exists) { appendStringInfoString(buf, "IF NOT EXISTS "); } /* * Up until here we have been ending the statement in a space, which makes it possible * to just append the quoted extname. From here onwards we will not have the string * ending in a space so appends should begin with a whitespace. */ appendStringInfoString(buf, quote_identifier(createExtensionStmt->extname)); AppendCreateExtensionStmtOptions(buf, createExtensionStmt->options); appendStringInfoString(buf, ";"); } /* * AppendCreateExtensionStmtOptions takes the option list of a CreateExtensionStmt and * loops over the options to add them to the statement we are building. * * An error will be thrown if we run into an unsupported option, comparable to how * postgres gives an error when parsing this list. */ static void AppendCreateExtensionStmtOptions(StringInfo buf, List *options) { if (list_length(options) > 0) { /* only append WITH if we actual will add options to the statement */ appendStringInfoString(buf, " WITH"); } /* Add the options to the statement */ DefElem *defElem = NULL; foreach_declared_ptr(defElem, options) { if (strcmp(defElem->defname, "schema") == 0) { const char *schemaName = defGetString(defElem); appendStringInfo(buf, " SCHEMA %s", quote_identifier(schemaName)); } else if (strcmp(defElem->defname, "new_version") == 0) { const char *newVersion = defGetString(defElem); appendStringInfo(buf, " VERSION %s", quote_identifier(newVersion)); } else if (strcmp(defElem->defname, "old_version") == 0) { const char *oldVersion = defGetString(defElem); appendStringInfo(buf, " FROM %s", quote_identifier(oldVersion)); } else if (strcmp(defElem->defname, "cascade") == 0) { bool cascade = defGetBoolean(defElem); if (cascade) { appendStringInfoString(buf, " CASCADE"); } } else { elog(ERROR, "unrecognized option: %s", defElem->defname); } } } /* * DeparseAlterExtensionStmt builds and returns a string representing the * AlterExtensionStmt to be sent to worker nodes. */ char * DeparseAlterExtensionStmt(Node *node) { AlterExtensionStmt *stmt = castNode(AlterExtensionStmt, node); StringInfoData sql = { 0 }; initStringInfo(&sql); AppendAlterExtensionStmt(&sql, stmt); return sql.data; } /* * AppendAlterExtensionStmt appends a string representing the AlterExtensionStmt to a buffer */ static void AppendAlterExtensionStmt(StringInfo buf, AlterExtensionStmt *alterExtensionStmt) { List *optionsList = alterExtensionStmt->options; const char *extensionName = alterExtensionStmt->extname; extensionName = quote_identifier(extensionName); appendStringInfo(buf, "ALTER EXTENSION %s UPDATE", extensionName); /* * Append the options for ALTER EXTENSION ... UPDATE * Currently there is only 1 option, but this structure follows how postgres parses * the options. */ DefElem *option = NULL; foreach_declared_ptr(option, optionsList) { if (strcmp(option->defname, "new_version") == 0) { const char *newVersion = defGetString(option); appendStringInfo(buf, " TO %s", quote_identifier(newVersion)); } else { elog(ERROR, "unrecognized option: %s", option->defname); } } appendStringInfoString(buf, ";"); } /* * DeparseDropExtensionStmt builds and returns a string representing the DropStmt */ char * DeparseDropExtensionStmt(Node *node) { DropStmt *stmt = castNode(DropStmt, node); StringInfoData str = { 0 }; initStringInfo(&str); AppendDropExtensionStmt(&str, stmt); return str.data; } /* * AppendDropExtensionStmt appends a string representing the DropStmt for * an extension to a buffer. */ static void AppendDropExtensionStmt(StringInfo str, DropStmt *dropStmt) { /* we append "IF NOT EXISTS" clause regardless of the content of the statement. */ appendStringInfoString(str, "DROP EXTENSION IF EXISTS "); /* * Pick the distributed ones from the "objects" list that is storing * the object names to be deleted. */ AppendExtensionNameList(str, dropStmt->objects); /* depending on behaviour field of DropStmt, we should append CASCADE or RESTRICT */ if (dropStmt->behavior == DROP_CASCADE) { appendStringInfoString(str, " CASCADE;"); } else { appendStringInfoString(str, " RESTRICT;"); } } /* * AppendExtensionNameList appends a string representing the list of * extension names to a buffer. */ static void AppendExtensionNameList(StringInfo str, List *objects) { ListCell *objectCell = NULL; foreach(objectCell, objects) { const char *extensionName = strVal(lfirst(objectCell)); extensionName = quote_identifier(extensionName); if (objectCell != list_head(objects)) { appendStringInfo(str, ", "); } appendStringInfoString(str, extensionName); } } /* * DeparseAlterExtensionSchemaStmt builds and returns a string representing the * AlterObjectSchemaStmt (ALTER EXTENSION SET SCHEMA). */ char * DeparseAlterExtensionSchemaStmt(Node *node) { AlterObjectSchemaStmt *stmt = castNode(AlterObjectSchemaStmt, node); StringInfoData str = { 0 }; initStringInfo(&str); Assert(stmt->objectType == OBJECT_EXTENSION); AppendAlterExtensionSchemaStmt(&str, stmt); return str.data; } /* * AppendAlterExtensionSchemaStmt appends a string representing the AlterObjectSchemaStmt * for an extension to a buffer. */ static void AppendAlterExtensionSchemaStmt(StringInfo buf, AlterObjectSchemaStmt *alterExtensionSchemaStmt) { Assert(alterExtensionSchemaStmt->objectType == OBJECT_EXTENSION); const char *extensionName = strVal(alterExtensionSchemaStmt->object); const char *newSchemaName = alterExtensionSchemaStmt->newschema; extensionName = quote_identifier(extensionName); newSchemaName = quote_identifier(newSchemaName); appendStringInfo(buf, "ALTER EXTENSION %s SET SCHEMA %s;", extensionName, newSchemaName); }