/*------------------------------------------------------------------------- * * deparse_function_stmts.c * * All routines to deparse function and procedure statements. * This file contains all entry points specific for function and procedure statement * deparsing * * Functions that could move later are AppendDefElem, AppendDefElemStrict, etc. These * should be reused across multiple statements and should live in their own deparse * file. * * Copyright (c), Citus Data, Inc. * *------------------------------------------------------------------------- */ #include "postgres.h" #include "access/htup_details.h" #include "catalog/namespace.h" #include "catalog/pg_proc.h" #include "catalog/pg_type.h" #include "commands/defrem.h" #include "lib/stringinfo.h" #include "nodes/makefuncs.h" #include "nodes/nodes.h" #include "nodes/value.h" #include "parser/parse_func.h" #include "parser/parse_type.h" #include "utils/builtins.h" #include "utils/fmgroids.h" #include "utils/fmgrprotos.h" #include "utils/guc.h" #include "utils/lsyscache.h" #include "utils/memutils.h" #include "utils/regproc.h" #include "utils/syscache.h" #include "distributed/citus_ruleutils.h" #include "distributed/commands.h" #include "distributed/deparser.h" #include "distributed/version_compat.h" /* forward declaration for deparse functions */ static char * ObjectTypeToKeyword(ObjectType objtype); static void AppendAlterFunctionStmt(StringInfo buf, AlterFunctionStmt *stmt); static void AppendDropFunctionStmt(StringInfo buf, DropStmt *stmt); static void AppendFunctionName(StringInfo buf, ObjectWithArgs *func, ObjectType objtype); static void AppendFunctionNameList(StringInfo buf, List *objects, ObjectType objtype); static void AppendDefElem(StringInfo buf, DefElem *def); static void AppendDefElemStrict(StringInfo buf, DefElem *def); static void AppendDefElemVolatility(StringInfo buf, DefElem *def); static void AppendDefElemLeakproof(StringInfo buf, DefElem *def); static void AppendDefElemSecurity(StringInfo buf, DefElem *def); static void AppendDefElemParallel(StringInfo buf, DefElem *def); static void AppendDefElemCost(StringInfo buf, DefElem *def); static void AppendDefElemRows(StringInfo buf, DefElem *def); static void AppendDefElemSet(StringInfo buf, DefElem *def); static void AppendDefElemSupport(StringInfo buf, DefElem *def); static void AppendRenameFunctionStmt(StringInfo buf, RenameStmt *stmt); static void AppendAlterFunctionSchemaStmt(StringInfo buf, AlterObjectSchemaStmt *stmt); static void AppendAlterFunctionOwnerStmt(StringInfo buf, AlterOwnerStmt *stmt); static void AppendAlterFunctionDependsStmt(StringInfo buf, AlterObjectDependsStmt *stmt); static void AppendGrantOnFunctionStmt(StringInfo buf, GrantStmt *stmt); static void AppendGrantOnFunctionFunctions(StringInfo buf, GrantStmt *stmt); static char * CopyAndConvertToUpperCase(const char *str); /* * DeparseAlterFunctionStmt builds and returns a string representing the AlterFunctionStmt */ char * DeparseAlterFunctionStmt(Node *node) { AlterFunctionStmt *stmt = castNode(AlterFunctionStmt, node); StringInfoData str = { 0 }; initStringInfo(&str); AppendAlterFunctionStmt(&str, stmt); return str.data; } /* * ObjectTypeToKeyword returns an appropriate string for the given ObjectType * Where the string will be one of "FUNCTION", "PROCEDURE", or "AGGREGATE" */ static char * ObjectTypeToKeyword(ObjectType objtype) { switch (objtype) { case OBJECT_FUNCTION: { return "FUNCTION"; } case OBJECT_PROCEDURE: { return "PROCEDURE"; } case OBJECT_AGGREGATE: { return "AGGREGATE"; } case OBJECT_ROUTINE: { return "ROUTINE"; } default: elog(ERROR, "Unknown object type: %d", objtype); return NULL; } } /* * AppendAlterFunctionStmt appends a string representing the AlterFunctionStmt to a buffer */ static void AppendAlterFunctionStmt(StringInfo buf, AlterFunctionStmt *stmt) { ListCell *actionCell = NULL; appendStringInfo(buf, "ALTER %s ", ObjectTypeToKeyword(stmt->objtype)); AppendFunctionName(buf, stmt->func, stmt->objtype); foreach(actionCell, stmt->actions) { DefElem *def = castNode(DefElem, lfirst(actionCell)); AppendDefElem(buf, def); } appendStringInfoString(buf, ";"); } /* * AppendDefElem appends a string representing the DefElem to a buffer */ static void AppendDefElem(StringInfo buf, DefElem *def) { if (strcmp(def->defname, "strict") == 0) { AppendDefElemStrict(buf, def); } else if (strcmp(def->defname, "volatility") == 0) { AppendDefElemVolatility(buf, def); } else if (strcmp(def->defname, "leakproof") == 0) { AppendDefElemLeakproof(buf, def); } else if (strcmp(def->defname, "security") == 0) { AppendDefElemSecurity(buf, def); } else if (strcmp(def->defname, "parallel") == 0) { AppendDefElemParallel(buf, def); } else if (strcmp(def->defname, "cost") == 0) { AppendDefElemCost(buf, def); } else if (strcmp(def->defname, "rows") == 0) { AppendDefElemRows(buf, def); } else if (strcmp(def->defname, "set") == 0) { AppendDefElemSet(buf, def); } else if (strcmp(def->defname, "support") == 0) { AppendDefElemSupport(buf, def); } } /* * AppendDefElemStrict appends a string representing the DefElem to a buffer */ static void AppendDefElemStrict(StringInfo buf, DefElem *def) { if (boolVal(def->arg)) { appendStringInfo(buf, " STRICT"); } else { appendStringInfo(buf, " CALLED ON NULL INPUT"); } } /* * AppendDefElemVolatility appends a string representing the DefElem to a buffer */ static void AppendDefElemVolatility(StringInfo buf, DefElem *def) { appendStringInfo(buf, " %s", CopyAndConvertToUpperCase(strVal(def->arg))); } /* * AppendDefElemLeakproof appends a string representing the DefElem to a buffer */ static void AppendDefElemLeakproof(StringInfo buf, DefElem *def) { if (!boolVal(def->arg)) { appendStringInfo(buf, " NOT"); } appendStringInfo(buf, " LEAKPROOF"); } /* * AppendDefElemSecurity appends a string representing the DefElem to a buffer */ static void AppendDefElemSecurity(StringInfo buf, DefElem *def) { if (!boolVal(def->arg)) { appendStringInfo(buf, " SECURITY INVOKER"); } else { appendStringInfo(buf, " SECURITY DEFINER"); } } /* * AppendDefElemParallel appends a string representing the DefElem to a buffer */ static void AppendDefElemParallel(StringInfo buf, DefElem *def) { appendStringInfo(buf, " PARALLEL %s", CopyAndConvertToUpperCase(strVal(def->arg))); } /* * AppendDefElemCost appends a string representing the DefElem to a buffer */ static void AppendDefElemCost(StringInfo buf, DefElem *def) { appendStringInfo(buf, " COST %lf", defGetNumeric(def)); } /* * AppendDefElemRows appends a string representing the DefElem to a buffer */ static void AppendDefElemRows(StringInfo buf, DefElem *def) { appendStringInfo(buf, " ROWS %lf", defGetNumeric(def)); } /* * AppendDefElemSet appends a string representing the DefElem to a buffer */ static void AppendDefElemSet(StringInfo buf, DefElem *def) { VariableSetStmt *setStmt = castNode(VariableSetStmt, def->arg); AppendVariableSet(buf, setStmt); } /* * AppendDefElemSupport appends a string representing the DefElem to a buffer */ static void AppendDefElemSupport(StringInfo buf, DefElem *def) { appendStringInfo(buf, " SUPPORT %s", defGetString(def)); } /* * DeparseRenameFunctionStmt builds and returns a string representing the RenameStmt */ char * DeparseRenameFunctionStmt(Node *node) { RenameStmt *stmt = castNode(RenameStmt, node); StringInfoData str = { 0 }; initStringInfo(&str); AssertObjectTypeIsFunctional(stmt->renameType); AppendRenameFunctionStmt(&str, stmt); return str.data; } /* * AppendRenameFunctionStmt appends a string representing the RenameStmt to a buffer */ static void AppendRenameFunctionStmt(StringInfo buf, RenameStmt *stmt) { ObjectWithArgs *func = castNode(ObjectWithArgs, stmt->object); appendStringInfo(buf, "ALTER %s ", ObjectTypeToKeyword(stmt->renameType)); AppendFunctionName(buf, func, stmt->renameType); appendStringInfo(buf, " RENAME TO %s;", quote_identifier(stmt->newname)); } /* * DeparseAlterFunctionSchemaStmt builds and returns a string representing the AlterObjectSchemaStmt */ char * DeparseAlterFunctionSchemaStmt(Node *node) { AlterObjectSchemaStmt *stmt = castNode(AlterObjectSchemaStmt, node); StringInfoData str = { 0 }; initStringInfo(&str); AssertObjectTypeIsFunctional(stmt->objectType); AppendAlterFunctionSchemaStmt(&str, stmt); return str.data; } /* * AppendAlterFunctionSchemaStmt appends a string representing the AlterObjectSchemaStmt to a buffer */ static void AppendAlterFunctionSchemaStmt(StringInfo buf, AlterObjectSchemaStmt *stmt) { ObjectWithArgs *func = castNode(ObjectWithArgs, stmt->object); appendStringInfo(buf, "ALTER %s ", ObjectTypeToKeyword(stmt->objectType)); AppendFunctionName(buf, func, stmt->objectType); appendStringInfo(buf, " SET SCHEMA %s;", quote_identifier(stmt->newschema)); } /* * DeparseAlterFunctionOwnerStmt builds and returns a string representing the AlterOwnerStmt */ char * DeparseAlterFunctionOwnerStmt(Node *node) { AlterOwnerStmt *stmt = castNode(AlterOwnerStmt, node); StringInfoData str = { 0 }; initStringInfo(&str); AssertObjectTypeIsFunctional(stmt->objectType); AppendAlterFunctionOwnerStmt(&str, stmt); return str.data; } /* * AppendAlterFunctionOwnerStmt appends a string representing the AlterOwnerStmt to a buffer */ static void AppendAlterFunctionOwnerStmt(StringInfo buf, AlterOwnerStmt *stmt) { ObjectWithArgs *func = castNode(ObjectWithArgs, stmt->object); appendStringInfo(buf, "ALTER %s ", ObjectTypeToKeyword(stmt->objectType)); AppendFunctionName(buf, func, stmt->objectType); appendStringInfo(buf, " OWNER TO %s;", RoleSpecString(stmt->newowner, true)); } /* * DeparseAlterFunctionDependsStmt builds and returns a string representing the AlterObjectDependsStmt */ char * DeparseAlterFunctionDependsStmt(Node *node) { AlterObjectDependsStmt *stmt = castNode(AlterObjectDependsStmt, node); StringInfoData str = { 0 }; initStringInfo(&str); AssertObjectTypeIsFunctional(stmt->objectType); AppendAlterFunctionDependsStmt(&str, stmt); return str.data; } /* * AppendAlterFunctionDependsStmt appends a string representing the AlterObjectDependsStmt to a buffer */ static void AppendAlterFunctionDependsStmt(StringInfo buf, AlterObjectDependsStmt *stmt) { ObjectWithArgs *func = castNode(ObjectWithArgs, stmt->object); appendStringInfo(buf, "ALTER %s ", ObjectTypeToKeyword(stmt->objectType)); AppendFunctionName(buf, func, stmt->objectType); appendStringInfo(buf, " DEPENDS ON EXTENSION %s;", strVal(stmt->extname)); } /* * DeparseDropFunctionStmt builds and returns a string representing the DropStmt */ char * DeparseDropFunctionStmt(Node *node) { DropStmt *stmt = castNode(DropStmt, node); StringInfoData str = { 0 }; initStringInfo(&str); AssertObjectTypeIsFunctional(stmt->removeType); AppendDropFunctionStmt(&str, stmt); return str.data; } /* * AppendDropFunctionStmt appends a string representing the DropStmt to a buffer */ static void AppendDropFunctionStmt(StringInfo buf, DropStmt *stmt) { appendStringInfo(buf, "DROP %s ", ObjectTypeToKeyword(stmt->removeType)); if (stmt->missing_ok) { appendStringInfoString(buf, "IF EXISTS "); } AppendFunctionNameList(buf, stmt->objects, stmt->removeType); if (stmt->behavior == DROP_CASCADE) { appendStringInfoString(buf, " CASCADE"); } appendStringInfoString(buf, ";"); } /* * AppendFunctionNameList appends a string representing the list of function names to a buffer */ static void AppendFunctionNameList(StringInfo buf, List *objects, ObjectType objtype) { ListCell *objectCell = NULL; foreach(objectCell, objects) { Node *object = lfirst(objectCell); if (objectCell != list_head(objects)) { appendStringInfo(buf, ", "); } ObjectWithArgs *func = castNode(ObjectWithArgs, object); AppendFunctionName(buf, func, objtype); } } /* * AppendFunctionName appends a string representing a single function name to a buffer */ static void AppendFunctionName(StringInfo buf, ObjectWithArgs *func, ObjectType objtype) { Oid funcid = LookupFuncWithArgs(objtype, func, true); if (funcid == InvalidOid) { /* * DROP FUNCTION IF EXISTS absent_function arrives here * * There is no namespace associated with the nonexistent function, * thus we return the function name as it is provided */ char *functionName = NULL; char *schemaName = NULL; DeconstructQualifiedName(func->objname, &schemaName, &functionName); char *qualifiedFunctionName = quote_qualified_identifier(schemaName, functionName); appendStringInfoString(buf, qualifiedFunctionName); if (!func->args_unspecified) { /* * The function is not found, but there is an argument list specified, this has * some known issues with the "any" type. However this is mostly a bug in * postgres' TypeNameListToString. For now the best we can do until we understand * the underlying cause better. */ const char *args = TypeNameListToString(func->objargs); appendStringInfo(buf, "(%s)", args); } } else { char *functionSignature = format_procedure_qualified(funcid); appendStringInfoString(buf, functionSignature); } } /* * CopyAndConvertToUpperCase copies a string and converts all characters to uppercase */ static char * CopyAndConvertToUpperCase(const char *str) { char *result, *p; result = pstrdup(str); for (p = result; *p; p++) { *p = pg_toupper((unsigned char) *p); } return result; } /* * DeparseGrantOnFunctionStmt builds and returns a string representing the GrantOnFunctionStmt */ char * DeparseGrantOnFunctionStmt(Node *node) { GrantStmt *stmt = castNode(GrantStmt, node); Assert(isFunction(stmt->objtype)); StringInfoData str = { 0 }; initStringInfo(&str); AppendGrantOnFunctionStmt(&str, stmt); return str.data; } /* * AppendGrantOnFunctionStmt builds and returns an SQL command representing a * GRANT .. ON FUNCTION command from given GrantStmt object. */ static void AppendGrantOnFunctionStmt(StringInfo buf, GrantStmt *stmt) { Assert(isFunction(stmt->objtype)); if (stmt->targtype == ACL_TARGET_ALL_IN_SCHEMA) { elog(ERROR, "GRANT .. ALL FUNCTIONS/PROCEDURES IN SCHEMA is not supported for formatting."); } AppendGrantSharedPrefix(buf, stmt); AppendGrantOnFunctionFunctions(buf, stmt); AppendGrantSharedSuffix(buf, stmt); } /* * AppendGrantOnFunctionFunctions appends the function names along with their arguments * to the given StringInfo from the given GrantStmt */ static void AppendGrantOnFunctionFunctions(StringInfo buf, GrantStmt *stmt) { ListCell *cell = NULL; /* * The FUNCTION syntax works for plain functions, aggregate functions, and window * functions, but not for procedures; use PROCEDURE for those. Alternatively, use * ROUTINE to refer to a function, aggregate function, window function, or procedure * regardless of its precise type. * https://www.postgresql.org/docs/current/sql-grant.html */ appendStringInfo(buf, " ON ROUTINE "); foreach(cell, stmt->objects) { /* * GrantOnFunction statement keeps its objects (functions) as * a list of ObjectWithArgs */ ObjectWithArgs *function = (ObjectWithArgs *) lfirst(cell); appendStringInfoString(buf, NameListToString(function->objname)); if (!function->args_unspecified) { /* if args are specified, we should append "(arg1, arg2, ...)" to the function name */ const char *args = TypeNameListToString(function->objargs); appendStringInfo(buf, "(%s)", args); } if (cell != list_tail(stmt->objects)) { appendStringInfoString(buf, ", "); } } } /* * isFunction returns true if the given ObjectType is a function, a procedure, a routine * or an aggregate otherwise returns false */ bool isFunction(ObjectType objectType) { return (objectType == OBJECT_FUNCTION || objectType == OBJECT_PROCEDURE || objectType == OBJECT_ROUTINE || objectType == OBJECT_AGGREGATE); }