From 66b9f2e88792d54f4e27142df4b9cffa9ab062bb Mon Sep 17 00:00:00 2001 From: Hanefi Onaldi Date: Fri, 27 Sep 2019 20:02:52 +0300 Subject: [PATCH] Deparsing and qualifiying for FUNCTION/PROCEDURE statements (#3014) This PR aims to add all the necessary logic to qualify and deparse all possible `{ALTER|DROP} .. {FUNCTION|PROCEDURE}` queries. As Procedures are introduced in PG11, the code contains many PG version checks. I tried my best to make it easy to clean up once we drop PG10 support. Here are some caveats: - I assumed that the parse tree is a valid one. There are some queries that are not allowed, but still are parsed successfully by postgres planner. Such queries will result in errors in execution time. (e.g. `ALTER PROCEDURE p STRICT` -> `STRICT` action is valid for functions but not procedures. Postgres decides to parse them nevertheless.) --- src/backend/distributed/deparser/deparse.c | 120 ++- .../deparser/deparse_function_stmts.c | 674 +++++++++++++++++ src/backend/distributed/deparser/qualify.c | 67 +- .../deparser/qualify_function_stmt.c | 181 +++++ .../distributed/test/deparse_function_query.c | 45 ++ src/include/distributed/deparser.h | 15 + src/include/distributed/version_compat.h | 26 + .../expected/multi_deparse_function.out | 707 ++++++++++++++++++ .../expected/multi_deparse_procedure.out | 379 ++++++++++ .../expected/multi_deparse_procedure_0.out | 448 +++++++++++ src/test/regress/multi_schedule | 5 + .../regress/sql/multi_deparse_function.sql | 366 +++++++++ .../regress/sql/multi_deparse_procedure.sql | 212 ++++++ 13 files changed, 3243 insertions(+), 2 deletions(-) create mode 100644 src/backend/distributed/deparser/deparse_function_stmts.c create mode 100644 src/backend/distributed/deparser/qualify_function_stmt.c create mode 100644 src/backend/distributed/test/deparse_function_query.c create mode 100644 src/test/regress/expected/multi_deparse_function.out create mode 100644 src/test/regress/expected/multi_deparse_procedure.out create mode 100644 src/test/regress/expected/multi_deparse_procedure_0.out create mode 100644 src/test/regress/sql/multi_deparse_function.sql create mode 100644 src/test/regress/sql/multi_deparse_procedure.sql diff --git a/src/backend/distributed/deparser/deparse.c b/src/backend/distributed/deparser/deparse.c index 79e85dc22..c57b94629 100644 --- a/src/backend/distributed/deparser/deparse.c +++ b/src/backend/distributed/deparser/deparse.c @@ -23,7 +23,7 @@ static const char * DeparseRenameStmt(RenameStmt *stmt); static const char * DeparseRenameAttributeStmt(RenameStmt *stmt); static const char * DeparseAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt); static const char * DeparseAlterOwnerStmt(AlterOwnerStmt *stmt); - +static const char * DeparseAlterObjectDependsStmt(AlterObjectDependsStmt *stmt); /* * DeparseTreeNode aims to be the inverse of postgres' ParseTreeNode. Currently with @@ -34,6 +34,12 @@ static const char * DeparseAlterOwnerStmt(AlterOwnerStmt *stmt); * - CREATE TYPE * - ALTER TYPE * - DROP TYPE + * + * - ALTER FUNCTION + * - DROP FUNCTION + * + * - ALTER PROCEDURE + * - DROP PROCEDURE */ const char * DeparseTreeNode(Node *stmt) @@ -65,6 +71,11 @@ DeparseTreeNode(Node *stmt) return DeparseAlterEnumStmt(castNode(AlterEnumStmt, stmt)); } + case T_AlterFunctionStmt: + { + return DeparseAlterFunctionStmt(castNode(AlterFunctionStmt, stmt)); + } + case T_RenameStmt: { return DeparseRenameStmt(castNode(RenameStmt, stmt)); @@ -80,6 +91,11 @@ DeparseTreeNode(Node *stmt) return DeparseAlterOwnerStmt(castNode(AlterOwnerStmt, stmt)); } + case T_AlterObjectDependsStmt: + { + return DeparseAlterObjectDependsStmt(castNode(AlterObjectDependsStmt, stmt)); + } + default: { ereport(ERROR, (errmsg("unsupported statement for deparsing"))); @@ -88,6 +104,12 @@ DeparseTreeNode(Node *stmt) } +/* + * DeparseDropStmt aims to deparse DROP statements. + * + * Currently with limited support. Check support before using, and add support for new + * statements as required. + */ static const char * DeparseDropStmt(DropStmt *stmt) { @@ -98,6 +120,14 @@ DeparseDropStmt(DropStmt *stmt) return DeparseDropTypeStmt(stmt); } +#if PG_VERSION_NUM >= 110000 + case OBJECT_PROCEDURE: +#endif + case OBJECT_FUNCTION: + { + return DeparseDropFunctionStmt(stmt); + } + default: { ereport(ERROR, (errmsg("unsupported drop statement for deparsing"))); @@ -106,6 +136,15 @@ DeparseDropStmt(DropStmt *stmt) } +/* + * DeparseAlterTableStmt deparses an AlterTableStmt to its SQL command. + * Contrary to its name these statements are covering not only ALTER TABLE ... + * statements but are used for almost all relation-esque objects in postgres, + * including but not limited to, Tables, Types, ... + * + * Currently with limited support. Check support before using, and add support for new + * statements as required. + */ static const char * DeparseAlterTableStmt(AlterTableStmt *stmt) { @@ -124,6 +163,16 @@ DeparseAlterTableStmt(AlterTableStmt *stmt) } +/* + * DeparseRenameStmt deparses an RenameStmt to its SQL command. + * Contrary to its name these statements are not covering all ALTER .. RENAME + * statements. + * + * e.g. ALTER TYPE name RENAME VALUE old TO new is an AlterEnumStmt + * + * Currently with limited support. Check support before using, and add support for new + * statements as required. + */ static const char * DeparseRenameStmt(RenameStmt *stmt) { @@ -139,6 +188,14 @@ DeparseRenameStmt(RenameStmt *stmt) return DeparseRenameAttributeStmt(stmt); } +#if PG_VERSION_NUM >= 110000 + case OBJECT_PROCEDURE: +#endif + case OBJECT_FUNCTION: + { + return DeparseRenameFunctionStmt(stmt); + } + default: { ereport(ERROR, (errmsg("unsupported rename statement for deparsing"))); @@ -168,6 +225,14 @@ DeparseRenameAttributeStmt(RenameStmt *stmt) } +/* + * DeparseAlterObjectSchemaStmt aims to deparse + * ALTER .. SET SCHEMA .. + * statements. + * + * Currently with limited support. Check support before using, and add support for new + * statements as required. + */ static const char * DeparseAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt) { @@ -178,6 +243,14 @@ DeparseAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt) return DeparseAlterTypeSchemaStmt(stmt); } +#if PG_VERSION_NUM >= 110000 + case OBJECT_PROCEDURE: +#endif + case OBJECT_FUNCTION: + { + return DeparseAlterFunctionSchemaStmt(stmt); + } + default: { ereport(ERROR, (errmsg("unsupported rename statement for deparsing"))); @@ -186,6 +259,14 @@ DeparseAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt) } +/* + * DeparseAlterOwnerStmt aims to deparse + * ALTER .. OWNER TO .. + * statements. + * + * Currently with limited support. Check support before using, and add support for new + * statements as required. + */ static const char * DeparseAlterOwnerStmt(AlterOwnerStmt *stmt) { @@ -196,9 +277,46 @@ DeparseAlterOwnerStmt(AlterOwnerStmt *stmt) return DeparseAlterTypeOwnerStmt(stmt); } +#if PG_VERSION_NUM >= 110000 + case OBJECT_PROCEDURE: +#endif + case OBJECT_FUNCTION: + { + return DeparseAlterFunctionOwnerStmt(stmt); + } + default: { ereport(ERROR, (errmsg("unsupported alter owner statement for deparsing"))); } } } + + +/* + * DeparseAlterObjectDependsStmt aims to deparse + * ALTER .. DEPENDS ON EXTENSION .. + * statements. + * + * Currently with limited support. Check support before using, and add support for new + * statements as required. + */ +static const char * +DeparseAlterObjectDependsStmt(AlterObjectDependsStmt *stmt) +{ + switch (stmt->objectType) + { +#if PG_VERSION_NUM >= 110000 + case OBJECT_PROCEDURE: +#endif + case OBJECT_FUNCTION: + { + return DeparseAlterFunctionDependsStmt(stmt); + } + + default: + { + ereport(ERROR, (errmsg("unsupported alter depends statement for deparsing"))); + } + } +} diff --git a/src/backend/distributed/deparser/deparse_function_stmts.c b/src/backend/distributed/deparser/deparse_function_stmts.c new file mode 100644 index 000000000..a11632a91 --- /dev/null +++ b/src/backend/distributed/deparser/deparse_function_stmts.c @@ -0,0 +1,674 @@ +/*------------------------------------------------------------------------- + * + * 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 "commands/defrem.h" +#include "distributed/citus_ruleutils.h" +#include "distributed/commands.h" +#include "distributed/deparser.h" +#include "distributed/version_compat.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/syscache.h" + + +/* forward declaration for deparse functions */ +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 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 char * CopyAndConvertToUpperCase(const char *str); + +/* + * DeparseAlterFunctionStmt builds and returns a string representing the AlterFunctionStmt + */ +const char * +DeparseAlterFunctionStmt(AlterFunctionStmt *stmt) +{ + StringInfoData str = { 0 }; + initStringInfo(&str); + + AppendAlterFunctionStmt(&str, stmt); + + return str.data; +} + + +/* + * AppendAlterFunctionStmt appends a string representing the AlterFunctionStmt to a buffer + */ +static void +AppendAlterFunctionStmt(StringInfo buf, AlterFunctionStmt *stmt) +{ + ListCell *actionCell = NULL; + +#if (PG_VERSION_NUM < 110000) + appendStringInfo(buf, "ALTER FUNCTION "); + + AppendFunctionName(buf, stmt->func, OBJECT_FUNCTION); +#else + if (stmt->objtype == OBJECT_FUNCTION) + { + appendStringInfo(buf, "ALTER FUNCTION "); + } + else + { + appendStringInfo(buf, "ALTER PROCEDURE "); + } + + AppendFunctionName(buf, stmt->func, stmt->objtype); +#endif + + + 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); + } +} + + +/* + * AppendDefElemStrict appends a string representing the DefElem to a buffer + */ +static void +AppendDefElemStrict(StringInfo buf, DefElem *def) +{ + if (intVal(def->arg) == 1) + { + 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 (intVal(def->arg) == 0) + { + appendStringInfo(buf, " NOT"); + } + appendStringInfo(buf, " LEAKPROOF"); +} + + +/* + * AppendDefElemSecurity appends a string representing the DefElem to a buffer + */ +static void +AppendDefElemSecurity(StringInfo buf, DefElem *def) +{ + if (intVal(def->arg) == 0) + { + 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); + char *setVariableArgs = ExtractSetVariableArgs(setStmt); + + switch (setStmt->kind) + { + case VAR_SET_VALUE: + { + appendStringInfo(buf, " SET %s = %s", setStmt->name, setVariableArgs); + break; + } + + case VAR_SET_CURRENT: + { + appendStringInfo(buf, " SET %s FROM CURRENT", setStmt->name); + break; + } + + case VAR_SET_DEFAULT: + { + appendStringInfo(buf, " SET %s TO DEFAULT", setStmt->name); + break; + } + + case VAR_RESET: + { + appendStringInfo(buf, " RESET %s", setStmt->name); + break; + } + + case VAR_RESET_ALL: + { + appendStringInfoString(buf, " RESET ALL"); + break; + } + + /* VAR_SET_MULTI is a special case for SET TRANSACTION that should not occur here */ + case VAR_SET_MULTI: + default: + { + ereport(ERROR, (errmsg("Unable to deparse SET statement"))); + break; + } + } +} + + +/* + * DeparseRenameFunctionStmt builds and returns a string representing the RenameStmt + */ +const char * +DeparseRenameFunctionStmt(RenameStmt *stmt) +{ + StringInfoData str = { 0 }; + initStringInfo(&str); + +#if (PG_VERSION_NUM < 110000) + Assert(stmt->renameType == OBJECT_FUNCTION); +#else + Assert(stmt->renameType == OBJECT_FUNCTION || stmt->renameType == OBJECT_PROCEDURE); +#endif + + 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); + +#if (PG_VERSION_NUM < 110000) + appendStringInfo(buf, "ALTER FUNCTION "); +#else + if (stmt->renameType == OBJECT_FUNCTION) + { + appendStringInfoString(buf, "ALTER FUNCTION "); + } + else + { + appendStringInfoString(buf, "ALTER PROCEDURE "); + } +#endif + + AppendFunctionName(buf, func, stmt->renameType); + + appendStringInfo(buf, " RENAME TO %s;", quote_identifier(stmt->newname)); +} + + +/* + * DeparseAlterFunctionSchemaStmt builds and returns a string representing the AlterObjectSchemaStmt + */ +const char * +DeparseAlterFunctionSchemaStmt(AlterObjectSchemaStmt *stmt) +{ + StringInfoData str = { 0 }; + initStringInfo(&str); + +#if (PG_VERSION_NUM < 110000) + Assert(stmt->objectType == OBJECT_FUNCTION); +#else + Assert(stmt->objectType == OBJECT_FUNCTION || stmt->objectType == OBJECT_PROCEDURE); +#endif + + 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); + +#if (PG_VERSION_NUM < 110000) + appendStringInfo(buf, "ALTER FUNCTION "); +#else + if (stmt->objectType == OBJECT_FUNCTION) + { + appendStringInfoString(buf, "ALTER FUNCTION "); + } + else + { + appendStringInfoString(buf, "ALTER PROCEDURE "); + } +#endif + + AppendFunctionName(buf, func, stmt->objectType); + appendStringInfo(buf, " SET SCHEMA %s;", quote_identifier(stmt->newschema)); +} + + +/* + * DeparseAlterFunctionOwnerStmt builds and returns a string representing the AlterOwnerStmt + */ +const char * +DeparseAlterFunctionOwnerStmt(AlterOwnerStmt *stmt) +{ + StringInfoData str = { 0 }; + initStringInfo(&str); + +#if (PG_VERSION_NUM < 110000) + Assert(stmt->objectType == OBJECT_FUNCTION); +#else + Assert(stmt->objectType == OBJECT_FUNCTION || stmt->objectType == OBJECT_PROCEDURE); +#endif + + 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); + +#if (PG_VERSION_NUM < 110000) + appendStringInfo(buf, "ALTER FUNCTION "); +#else + if (stmt->objectType == OBJECT_FUNCTION) + { + appendStringInfoString(buf, "ALTER FUNCTION "); + } + else + { + appendStringInfoString(buf, "ALTER PROCEDURE "); + } +#endif + + AppendFunctionName(buf, func, stmt->objectType); + appendStringInfo(buf, " OWNER TO %s;", RoleSpecString(stmt->newowner)); +} + + +/* + * DeparseAlterFunctionDependsStmt builds and returns a string representing the AlterObjectDependsStmt + */ +const char * +DeparseAlterFunctionDependsStmt(AlterObjectDependsStmt *stmt) +{ + StringInfoData str = { 0 }; + initStringInfo(&str); + +#if (PG_VERSION_NUM < 110000) + Assert(stmt->objectType == OBJECT_FUNCTION); +#else + Assert(stmt->objectType == OBJECT_FUNCTION || stmt->objectType == OBJECT_PROCEDURE); +#endif + + 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); + +#if (PG_VERSION_NUM < 110000) + appendStringInfo(buf, "ALTER FUNCTION "); +#else + if (stmt->objectType == OBJECT_FUNCTION) + { + appendStringInfoString(buf, "ALTER FUNCTION "); + } + else + { + appendStringInfoString(buf, "ALTER PROCEDURE "); + } +#endif + + AppendFunctionName(buf, func, stmt->objectType); + appendStringInfo(buf, " DEPENDS ON EXTENSION %s;", strVal(stmt->extname)); +} + + +/* + * DeparseDropFunctionStmt builds and returns a string representing the DropStmt + */ +const char * +DeparseDropFunctionStmt(DropStmt *stmt) +{ + StringInfoData str = { 0 }; + initStringInfo(&str); + +#if (PG_VERSION_NUM < 110000) + Assert(stmt->removeType == OBJECT_FUNCTION); +#else + Assert(stmt->removeType == OBJECT_FUNCTION || stmt->removeType == OBJECT_PROCEDURE); +#endif + + AppendDropFunctionStmt(&str, stmt); + + return str.data; +} + + +/* + * AppendDropFunctionStmt appends a string representing the DropStmt to a buffer + */ +static void +AppendDropFunctionStmt(StringInfo buf, DropStmt *stmt) +{ +#if (PG_VERSION_NUM < 110000) + appendStringInfo(buf, "DROP FUNCTION "); +#else + if (stmt->removeType == OBJECT_FUNCTION) + { + appendStringInfoString(buf, "DROP FUNCTION "); + } + else + { + appendStringInfoString(buf, "DROP PROCEDURE "); + } +#endif + + 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); + ObjectWithArgs *func = NULL; + + if (objectCell != list_head(objects)) + { + appendStringInfo(buf, ", "); + } + + 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 = InvalidOid; + HeapTuple proctup; + char *functionName = NULL; + char *schemaName = NULL; + char *qualifiedFunctionName; + + funcid = LookupFuncWithArgsCompat(objtype, func, true); + proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid)); + + if (!HeapTupleIsValid(proctup)) + { + /* + * 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 + */ + DeconstructQualifiedName(func->objname, &schemaName, &functionName); + } + else + { + Form_pg_proc procform; + + procform = (Form_pg_proc) GETSTRUCT(proctup); + functionName = NameStr(procform->proname); + functionName = pstrdup(functionName); /* we release the tuple before used */ + schemaName = get_namespace_name(procform->pronamespace); + + ReleaseSysCache(proctup); + } + + qualifiedFunctionName = quote_qualified_identifier(schemaName, functionName); + appendStringInfoString(buf, qualifiedFunctionName); + + if (OidIsValid(funcid)) + { + /* + * If the function exists we want to use pg_get_function_identity_arguments to + * serialize its canonical arguments + */ + OverrideSearchPath *overridePath = NULL; + Datum sqlTextDatum = 0; + const char *args = NULL; + + /* + * Set search_path to NIL so that all objects outside of pg_catalog will be + * schema-prefixed. pg_catalog will be added automatically when we call + * PushOverrideSearchPath(), since we set addCatalog to true; + */ + overridePath = GetOverrideSearchPath(CurrentMemoryContext); + overridePath->schemas = NIL; + overridePath->addCatalog = true; + + PushOverrideSearchPath(overridePath); + + sqlTextDatum = DirectFunctionCall1(pg_get_function_identity_arguments, + ObjectIdGetDatum(funcid)); + + /* revert back to original search_path */ + PopOverrideSearchPath(); + + args = TextDatumGetCString(sqlTextDatum); + appendStringInfo(buf, "(%s)", args); + } + else 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 = NULL; + + args = TypeNameListToString(func->objargs); + appendStringInfo(buf, "(%s)", args); + } + + /* + * If the type is not found, and no argument list given we don't append anything here. + * This will cause mostly the same sql as the original statement. + */ +} + + +/* + * 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; +} diff --git a/src/backend/distributed/deparser/qualify.c b/src/backend/distributed/deparser/qualify.c index e8ea1333a..6e5ab94ef 100644 --- a/src/backend/distributed/deparser/qualify.c +++ b/src/backend/distributed/deparser/qualify.c @@ -29,7 +29,7 @@ static void QualifyRenameAttributeStmt(RenameStmt *stmt); static void QualifyAlterTableStmt(AlterTableStmt *stmt); static void QualifyAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt); static void QualifyAlterOwnerStmt(AlterOwnerStmt *stmt); - +static void QualifyAlterObjectDependsStmt(AlterObjectDependsStmt *stmt); /* * QualifyTreeNode transforms the statement in place and makes all (supported) statements @@ -83,6 +83,18 @@ QualifyTreeNode(Node *stmt) return; } + case T_AlterFunctionStmt: + { + QualifyAlterFunctionStmt(castNode(AlterFunctionStmt, stmt)); + return; + } + + case T_AlterObjectDependsStmt: + { + QualifyAlterObjectDependsStmt(castNode(AlterObjectDependsStmt, stmt)); + return; + } + default: { /* skip unsupported statements */ @@ -92,6 +104,10 @@ QualifyTreeNode(Node *stmt) } +/* + * QualifyRenameStmt transforms a RENAME statement in place and makes all (supported) + * statements fully qualified. + */ static void QualifyRenameStmt(RenameStmt *stmt) { @@ -109,6 +125,14 @@ QualifyRenameStmt(RenameStmt *stmt) return; } + case OBJECT_FUNCTION: +#if PG_VERSION_NUM >= 110000 + case OBJECT_PROCEDURE: +#endif + { + QualifyRenameFunctionStmt(stmt); + } + default: { /* skip unsupported statements */ @@ -118,6 +142,10 @@ QualifyRenameStmt(RenameStmt *stmt) } +/* + * QualifyRenameAttributeStmt transforms a RENAME ATTRIBUTE statement in place and makes all (supported) + * statements fully qualified. + */ static void QualifyRenameAttributeStmt(RenameStmt *stmt) { @@ -170,6 +198,14 @@ QualifyAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt) return; } + case OBJECT_FUNCTION: +#if PG_VERSION_NUM >= 110000 + case OBJECT_PROCEDURE: +#endif + { + QualifyAlterFunctionSchemaStmt(stmt); + } + default: { /* skip unsupported statements */ @@ -190,6 +226,35 @@ QualifyAlterOwnerStmt(AlterOwnerStmt *stmt) return; } + case OBJECT_FUNCTION: +#if PG_VERSION_NUM >= 110000 + case OBJECT_PROCEDURE: +#endif + { + QualifyAlterFunctionOwnerStmt(stmt); + } + + default: + { + return; + } + } +} + + +static void +QualifyAlterObjectDependsStmt(AlterObjectDependsStmt *stmt) +{ + switch (stmt->objectType) + { + case OBJECT_FUNCTION: +#if PG_VERSION_NUM >= 110000 + case OBJECT_PROCEDURE: +#endif + { + QualifyAlterFunctionDependsStmt(stmt); + } + default: { return; diff --git a/src/backend/distributed/deparser/qualify_function_stmt.c b/src/backend/distributed/deparser/qualify_function_stmt.c new file mode 100644 index 000000000..d9e635329 --- /dev/null +++ b/src/backend/distributed/deparser/qualify_function_stmt.c @@ -0,0 +1,181 @@ +/*------------------------------------------------------------------------- + * + * qualify_function_stmt.c + * Functions specialized in fully qualifying all function statements. These + * functions are dispatched from qualify.c + * + * Fully qualifying function statements consists of adding the schema name + * to the subject of the function and types as well as any other branch of + * the parsetree. + * + * Goal would be that the deparser functions for these statements can + * serialize the statement without any external lookups. + * + * Copyright (c), Citus Data, Inc. + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "access/htup_details.h" +#include "catalog/namespace.h" +#include "catalog/pg_proc.h" +#include "distributed/deparser.h" +#include "distributed/version_compat.h" +#include "parser/parse_func.h" +#include "utils/lsyscache.h" +#include "utils/syscache.h" + +/* forward declaration for qualify functions */ +void QualifyFunction(ObjectWithArgs *func, ObjectType type); +void QualifyFunctionSchemaName(ObjectWithArgs *func, ObjectType type); + + +/* + * QualifyAlterFunctionStmt transforms a + * ALTER {FUNCTION|PROCEDURE} .. + * statement in place and makes all (supported) statements fully qualified. + * + * Note that not all queries of this form are valid AlterFunctionStmt + * (e.g. ALTER FUNCTION .. RENAME .. queries are RenameStmt ) + */ +void +QualifyAlterFunctionStmt(AlterFunctionStmt *stmt) +{ + ObjectType objtype = OBJECT_FUNCTION; + +#if (PG_VERSION_NUM >= 110000) + objtype = stmt->objtype; +#endif + + QualifyFunction(stmt->func, objtype); +} + + +/* + * QualifyRenameFunctionStmt transforms a + * ALTER {FUNCTION|PROCEDURE} .. RENAME TO .. + * statement in place and makes the function name fully qualified. + */ +void +QualifyRenameFunctionStmt(RenameStmt *stmt) +{ +#if (PG_VERSION_NUM < 110000) + Assert(stmt->renameType == OBJECT_FUNCTION); +#else + Assert(stmt->renameType == OBJECT_FUNCTION || stmt->renameType == OBJECT_PROCEDURE); +#endif + + QualifyFunction(castNode(ObjectWithArgs, stmt->object), stmt->renameType); +} + + +/* + * QualifyAlterFunctionSchemaStmt transforms a + * ALTER {FUNCTION|PROCEDURE} .. SET SCHEMA .. + * statement in place and makes the function name fully qualified. + */ +void +QualifyAlterFunctionSchemaStmt(AlterObjectSchemaStmt *stmt) +{ +#if (PG_VERSION_NUM < 110000) + Assert(stmt->objectType == OBJECT_FUNCTION); +#else + Assert(stmt->objectType == OBJECT_FUNCTION || stmt->objectType == OBJECT_PROCEDURE); +#endif + + QualifyFunction(castNode(ObjectWithArgs, stmt->object), stmt->objectType); +} + + +/* + * QualifyAlterFunctionOwnerStmt transforms a + * ALTER {FUNCTION|PROCEDURE} .. OWNER TO .. + * statement in place and makes the function name fully qualified. + */ +void +QualifyAlterFunctionOwnerStmt(AlterOwnerStmt *stmt) +{ +#if (PG_VERSION_NUM < 110000) + Assert(stmt->objectType == OBJECT_FUNCTION); +#else + Assert(stmt->objectType == OBJECT_FUNCTION || stmt->objectType == OBJECT_PROCEDURE); +#endif + + QualifyFunction(castNode(ObjectWithArgs, stmt->object), stmt->objectType); +} + + +/* + * QualifyAlterFunctionDependsStmt transforms a + * ALTER {FUNCTION|PROCEDURE} .. DEPENDS ON EXTENSIOIN .. + * statement in place and makes the function name fully qualified. + */ +void +QualifyAlterFunctionDependsStmt(AlterObjectDependsStmt *stmt) +{ +#if (PG_VERSION_NUM < 110000) + Assert(stmt->objectType == OBJECT_FUNCTION); +#else + Assert(stmt->objectType == OBJECT_FUNCTION || stmt->objectType == OBJECT_PROCEDURE); +#endif + + QualifyFunction(castNode(ObjectWithArgs, stmt->object), stmt->objectType); +} + + +/* + * QualifyFunction transforms a function in place and makes it's name fully qualified. + */ +void +QualifyFunction(ObjectWithArgs *func, ObjectType type) +{ + char *functionName = NULL; + char *schemaName = NULL; + + /* check if the function name is already qualified */ + DeconstructQualifiedName(func->objname, &schemaName, &functionName); + + /* do a lookup for the schema name if the statement does not include one */ + if (schemaName == NULL) + { + QualifyFunctionSchemaName(func, type); + } +} + + +/* + * QualifyFunction transforms a function in place using a catalog lookup for its schema name to make it fully qualified. + */ +void +QualifyFunctionSchemaName(ObjectWithArgs *func, ObjectType type) +{ + char *schemaName = NULL; + char *functionName = NULL; + Oid funcid = InvalidOid; + HeapTuple proctup; + + funcid = LookupFuncWithArgsCompat(type, func, true); + proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid)); + + /* + * We can not qualify the function if the catalogs do not have any records. + * + * e.g. DROP FUNC IF EXISTS non_existent_func() do not result in a valid heap tuple + */ + if (HeapTupleIsValid(proctup)) + { + Form_pg_proc procform; + + procform = (Form_pg_proc) GETSTRUCT(proctup); + schemaName = get_namespace_name(procform->pronamespace); + functionName = NameStr(procform->proname); + functionName = pstrdup(functionName); /* we release the tuple before used */ + + ReleaseSysCache(proctup); + + /* update the function using the schema name */ + func->objname = list_make2(makeString(schemaName), makeString(functionName)); + } +} diff --git a/src/backend/distributed/test/deparse_function_query.c b/src/backend/distributed/test/deparse_function_query.c new file mode 100644 index 000000000..f990f7edb --- /dev/null +++ b/src/backend/distributed/test/deparse_function_query.c @@ -0,0 +1,45 @@ +/*------------------------------------------------------------------------- + * + * test/src/deparse_function_query.c + * + * This file contains functions to exercise deparsing of + * CREATE|ALTER|DROP [...] {FUNCTION|PROCEDURE} ... + * queries + * + * Copyright (c) Citus Data, Inc. + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "distributed/deparser.h" +#include "distributed/multi_executor.h" +#include "utils/builtins.h" + +/* declarations for dynamic loading */ +PG_FUNCTION_INFO_V1(deparse_test); + + +/* + * deparse_test UDF is a UDF to test deparsing in Citus. + * + * This function accepts a query string; parses, qualifies and then deparses it to create + * a qualified query string. + */ +Datum +deparse_test(PG_FUNCTION_ARGS) +{ + text *queryStringText = PG_GETARG_TEXT_P(0); + char *queryStringChar = NULL; + Query *query = NULL; + const char *deparsedQuery = NULL; + + queryStringChar = text_to_cstring(queryStringText); + query = ParseQueryString(queryStringChar, NULL, 0); + + QualifyTreeNode(query->utilityStmt); + deparsedQuery = DeparseTreeNode(query->utilityStmt); + + PG_RETURN_TEXT_P(cstring_to_text(deparsedQuery)); +} diff --git a/src/include/distributed/deparser.h b/src/include/distributed/deparser.h index e6417e5ee..e28a63d4c 100644 --- a/src/include/distributed/deparser.h +++ b/src/include/distributed/deparser.h @@ -52,4 +52,19 @@ extern void QualifyAlterTypeOwnerStmt(AlterOwnerStmt *stmt); extern const ObjectAddress * GetObjectAddressFromParseTree(Node *parseTree, bool missing_ok); +/* forward declarations for deparse_function_stmts.c */ +extern const char * DeparseDropFunctionStmt(DropStmt *stmt); +extern const char * DeparseAlterFunctionStmt(AlterFunctionStmt *stmt); + +extern const char * DeparseRenameFunctionStmt(RenameStmt *stmt); +extern const char * DeparseAlterFunctionSchemaStmt(AlterObjectSchemaStmt *stmt); +extern const char * DeparseAlterFunctionOwnerStmt(AlterOwnerStmt *stmt); +extern const char * DeparseAlterFunctionDependsStmt(AlterObjectDependsStmt *stmt); + +extern void QualifyAlterFunctionStmt(AlterFunctionStmt *stmt); +extern void QualifyRenameFunctionStmt(RenameStmt *stmt); +extern void QualifyAlterFunctionSchemaStmt(AlterObjectSchemaStmt *stmt); +extern void QualifyAlterFunctionOwnerStmt(AlterOwnerStmt *stmt); +extern void QualifyAlterFunctionDependsStmt(AlterObjectDependsStmt *stmt); + #endif /* CITUS_DEPARSER_H */ diff --git a/src/include/distributed/version_compat.h b/src/include/distributed/version_compat.h index d4c6f7854..df592213f 100644 --- a/src/include/distributed/version_compat.h +++ b/src/include/distributed/version_compat.h @@ -15,6 +15,7 @@ #include "commands/explain.h" #include "catalog/namespace.h" #include "nodes/parsenodes.h" +#include "parser/parse_func.h" #if (PG_VERSION_NUM >= 120000) #include "optimizer/optimizer.h" @@ -166,6 +167,23 @@ get_expr_result_tupdesc(Node *expr, bool noError) } +/* following compat function and macro should be removed when we drop support for PG10 */ +static inline Oid +LookupFuncWithArgsCompat(ObjectType objtype, ObjectWithArgs *func, bool noError) +{ + if (objtype == OBJECT_FUNCTION) + { + return LookupFuncWithArgs(func, noError); + } + else if (objtype == OBJECT_AGGREGATE) + { + return LookupAggWithArgs(func, noError); + } + + return InvalidOid; +} + + #endif #if (PG_VERSION_NUM >= 110000) @@ -242,6 +260,14 @@ RangeVarGetRelidInternal(const RangeVar *relation, LOCKMODE lockmode, uint32 fla } +/* following compat function and macro should be removed when we drop support for PG10 */ +static inline Oid +LookupFuncWithArgsCompat(ObjectType objtype, ObjectWithArgs *func, bool noError) +{ + return LookupFuncWithArgs(objtype, func, noError); +} + + #endif #if PG_VERSION_NUM >= 120000 diff --git a/src/test/regress/expected/multi_deparse_function.out b/src/test/regress/expected/multi_deparse_function.out new file mode 100644 index 000000000..6a2d10728 --- /dev/null +++ b/src/test/regress/expected/multi_deparse_function.out @@ -0,0 +1,707 @@ +-- +-- Regression tests for deparsing ALTER/DROP FUNCTION Queries +-- +-- This test implements all the possible queries as of Postgres 11 +-- in the order they are listed in the docs +-- +-- ALTER FUNCTION name [ ( [ [ argmode ] [ argname ] argtype [, ...] ] ) ] +-- action [ ... ] [ RESTRICT ] +-- ALTER FUNCTION name [ ( [ [ argmode ] [ argname ] argtype [, ...] ] ) ] +-- RENAME TO new_name +-- ALTER FUNCTION name [ ( [ [ argmode ] [ argname ] argtype [, ...] ] ) ] +-- OWNER TO { new_owner | CURRENT_USER | SESSION_USER } +-- ALTER FUNCTION name [ ( [ [ argmode ] [ argname ] argtype [, ...] ] ) ] +-- SET SCHEMA new_schema +-- ALTER FUNCTION name [ ( [ [ argmode ] [ argname ] argtype [, ...] ] ) ] +-- DEPENDS ON EXTENSION extension_name +-- +-- where action is one of: +-- +-- CALLED ON NULL INPUT | RETURNS NULL ON NULL INPUT | STRICT +-- IMMUTABLE | STABLE | VOLATILE | [ NOT ] LEAKPROOF +-- [ EXTERNAL ] SECURITY INVOKER | [ EXTERNAL ] SECURITY DEFINER +-- PARALLEL { UNSAFE | RESTRICTED | SAFE } +-- COST execution_cost +-- ROWS result_rows +-- SET configuration_parameter { TO | = } { value | DEFAULT } +-- SET configuration_parameter FROM CURRENT +-- RESET configuration_parameter +-- RESET ALL +-- +-- DROP FUNCTION [ IF EXISTS ] name [ ( [ [ argmode ] [ argname ] argtype [, ...] ] ) ] [, ...] +-- [ CASCADE | RESTRICT ] +SET citus.next_shard_id TO 20020000; +CREATE SCHEMA function_tests; +SET search_path TO function_tests; +SET citus.shard_count TO 4; +SET client_min_messages TO INFO; +CREATE FUNCTION deparse_test(text) + RETURNS text + AS 'citus' + LANGUAGE C STRICT; +CREATE OR REPLACE FUNCTION deparse_and_run_on_workers(IN query text, + OUT nodename text, + OUT nodeport int, + OUT success bool, + OUT result text) + RETURNS SETOF record + LANGUAGE PLPGSQL AS $fnc$ + DECLARE + deparsed_query character varying(255); + BEGIN + deparsed_query := ( SELECT deparse_test($1) ); + RAISE INFO 'Propagating deparsed query: %', deparsed_query; + RETURN QUERY SELECT * FROM run_command_on_workers(deparsed_query); + END; + $fnc$; +-- Create a simple function and distribute it +CREATE FUNCTION add(integer, integer) RETURNS integer + AS 'select $1 + $2;' + LANGUAGE SQL + IMMUTABLE + RETURNS NULL ON NULL INPUT; +SELECT create_distributed_function('add(int,int)'); + create_distributed_function +----------------------------- + +(1 row) + +SELECT deparse_and_run_on_workers($cmd$ +ALTER FUNCTION add CALLED ON NULL INPUT +$cmd$); +INFO: Propagating deparsed query: ALTER FUNCTION function_tests.add(integer, integer) CALLED ON NULL INPUT; +CONTEXT: PL/pgSQL function deparse_and_run_on_workers(text) line 6 at RAISE + deparse_and_run_on_workers +-------------------------------------- + (localhost,57637,t,"ALTER FUNCTION") + (localhost,57638,t,"ALTER FUNCTION") +(2 rows) + +-- RETURNS NULL ON NULL INPUT and STRICT are synonyms and can be used interchangeably +-- RETURNS NULL ON NULL INPUT is actually stored as STRICT in the query parse tree +SELECT deparse_and_run_on_workers($cmd$ +ALTER FUNCTION add RETURNS NULL ON NULL INPUT +$cmd$); +INFO: Propagating deparsed query: ALTER FUNCTION function_tests.add(integer, integer) STRICT; +CONTEXT: PL/pgSQL function deparse_and_run_on_workers(text) line 6 at RAISE + deparse_and_run_on_workers +-------------------------------------- + (localhost,57637,t,"ALTER FUNCTION") + (localhost,57638,t,"ALTER FUNCTION") +(2 rows) + +SELECT deparse_and_run_on_workers($cmd$ +ALTER FUNCTION add STRICT +$cmd$); +INFO: Propagating deparsed query: ALTER FUNCTION function_tests.add(integer, integer) STRICT; +CONTEXT: PL/pgSQL function deparse_and_run_on_workers(text) line 6 at RAISE + deparse_and_run_on_workers +-------------------------------------- + (localhost,57637,t,"ALTER FUNCTION") + (localhost,57638,t,"ALTER FUNCTION") +(2 rows) + +SELECT deparse_and_run_on_workers($cmd$ +ALTER FUNCTION add IMMUTABLE +$cmd$); +INFO: Propagating deparsed query: ALTER FUNCTION function_tests.add(integer, integer) IMMUTABLE; +CONTEXT: PL/pgSQL function deparse_and_run_on_workers(text) line 6 at RAISE + deparse_and_run_on_workers +-------------------------------------- + (localhost,57637,t,"ALTER FUNCTION") + (localhost,57638,t,"ALTER FUNCTION") +(2 rows) + +SELECT deparse_and_run_on_workers($cmd$ +ALTER FUNCTION add STABLE +$cmd$); +INFO: Propagating deparsed query: ALTER FUNCTION function_tests.add(integer, integer) STABLE; +CONTEXT: PL/pgSQL function deparse_and_run_on_workers(text) line 6 at RAISE + deparse_and_run_on_workers +-------------------------------------- + (localhost,57637,t,"ALTER FUNCTION") + (localhost,57638,t,"ALTER FUNCTION") +(2 rows) + +SELECT deparse_and_run_on_workers($cmd$ +ALTER FUNCTION add VOLATILE +$cmd$); +INFO: Propagating deparsed query: ALTER FUNCTION function_tests.add(integer, integer) VOLATILE; +CONTEXT: PL/pgSQL function deparse_and_run_on_workers(text) line 6 at RAISE + deparse_and_run_on_workers +-------------------------------------- + (localhost,57637,t,"ALTER FUNCTION") + (localhost,57638,t,"ALTER FUNCTION") +(2 rows) + +SELECT deparse_and_run_on_workers($cmd$ +ALTER FUNCTION add LEAKPROOF +$cmd$); +INFO: Propagating deparsed query: ALTER FUNCTION function_tests.add(integer, integer) LEAKPROOF; +CONTEXT: PL/pgSQL function deparse_and_run_on_workers(text) line 6 at RAISE + deparse_and_run_on_workers +-------------------------------------- + (localhost,57637,t,"ALTER FUNCTION") + (localhost,57638,t,"ALTER FUNCTION") +(2 rows) + +SELECT deparse_and_run_on_workers($cmd$ +ALTER FUNCTION add NOT LEAKPROOF +$cmd$); +INFO: Propagating deparsed query: ALTER FUNCTION function_tests.add(integer, integer) NOT LEAKPROOF; +CONTEXT: PL/pgSQL function deparse_and_run_on_workers(text) line 6 at RAISE + deparse_and_run_on_workers +-------------------------------------- + (localhost,57637,t,"ALTER FUNCTION") + (localhost,57638,t,"ALTER FUNCTION") +(2 rows) + +-- EXTERNAL keyword is ignored by Postgres Parser. It is allowed only for SQL conformance +-- The following queries will not have the EXTERNAL keyword after deparsing +SELECT deparse_and_run_on_workers($cmd$ +ALTER FUNCTION add EXTERNAL SECURITY INVOKER +$cmd$); +INFO: Propagating deparsed query: ALTER FUNCTION function_tests.add(integer, integer) SECURITY INVOKER; +CONTEXT: PL/pgSQL function deparse_and_run_on_workers(text) line 6 at RAISE + deparse_and_run_on_workers +-------------------------------------- + (localhost,57637,t,"ALTER FUNCTION") + (localhost,57638,t,"ALTER FUNCTION") +(2 rows) + +SELECT deparse_and_run_on_workers($cmd$ +ALTER FUNCTION add SECURITY INVOKER +$cmd$); +INFO: Propagating deparsed query: ALTER FUNCTION function_tests.add(integer, integer) SECURITY INVOKER; +CONTEXT: PL/pgSQL function deparse_and_run_on_workers(text) line 6 at RAISE + deparse_and_run_on_workers +-------------------------------------- + (localhost,57637,t,"ALTER FUNCTION") + (localhost,57638,t,"ALTER FUNCTION") +(2 rows) + +SELECT deparse_and_run_on_workers($cmd$ +ALTER FUNCTION add EXTERNAL SECURITY DEFINER +$cmd$); +INFO: Propagating deparsed query: ALTER FUNCTION function_tests.add(integer, integer) SECURITY DEFINER; +CONTEXT: PL/pgSQL function deparse_and_run_on_workers(text) line 6 at RAISE + deparse_and_run_on_workers +-------------------------------------- + (localhost,57637,t,"ALTER FUNCTION") + (localhost,57638,t,"ALTER FUNCTION") +(2 rows) + +SELECT deparse_and_run_on_workers($cmd$ +ALTER FUNCTION add SECURITY DEFINER +$cmd$); +INFO: Propagating deparsed query: ALTER FUNCTION function_tests.add(integer, integer) SECURITY DEFINER; +CONTEXT: PL/pgSQL function deparse_and_run_on_workers(text) line 6 at RAISE + deparse_and_run_on_workers +-------------------------------------- + (localhost,57637,t,"ALTER FUNCTION") + (localhost,57638,t,"ALTER FUNCTION") +(2 rows) + +SELECT deparse_and_run_on_workers($cmd$ +ALTER FUNCTION add PARALLEL UNSAFE +$cmd$); +INFO: Propagating deparsed query: ALTER FUNCTION function_tests.add(integer, integer) PARALLEL UNSAFE; +CONTEXT: PL/pgSQL function deparse_and_run_on_workers(text) line 6 at RAISE + deparse_and_run_on_workers +-------------------------------------- + (localhost,57637,t,"ALTER FUNCTION") + (localhost,57638,t,"ALTER FUNCTION") +(2 rows) + +SELECT deparse_and_run_on_workers($cmd$ +ALTER FUNCTION add PARALLEL RESTRICTED +$cmd$); +INFO: Propagating deparsed query: ALTER FUNCTION function_tests.add(integer, integer) PARALLEL RESTRICTED; +CONTEXT: PL/pgSQL function deparse_and_run_on_workers(text) line 6 at RAISE + deparse_and_run_on_workers +-------------------------------------- + (localhost,57637,t,"ALTER FUNCTION") + (localhost,57638,t,"ALTER FUNCTION") +(2 rows) + +SELECT deparse_and_run_on_workers($cmd$ +ALTER FUNCTION add PARALLEL SAFE +$cmd$); +INFO: Propagating deparsed query: ALTER FUNCTION function_tests.add(integer, integer) PARALLEL SAFE; +CONTEXT: PL/pgSQL function deparse_and_run_on_workers(text) line 6 at RAISE + deparse_and_run_on_workers +-------------------------------------- + (localhost,57637,t,"ALTER FUNCTION") + (localhost,57638,t,"ALTER FUNCTION") +(2 rows) + +-- The COST arguments should always be numeric +SELECT deparse_and_run_on_workers($cmd$ +ALTER FUNCTION add COST 1234 +$cmd$); +INFO: Propagating deparsed query: ALTER FUNCTION function_tests.add(integer, integer) COST 1234.000000; +CONTEXT: PL/pgSQL function deparse_and_run_on_workers(text) line 6 at RAISE + deparse_and_run_on_workers +-------------------------------------- + (localhost,57637,t,"ALTER FUNCTION") + (localhost,57638,t,"ALTER FUNCTION") +(2 rows) + +SELECT deparse_and_run_on_workers($cmd$ +ALTER FUNCTION add COST 1234.5 +$cmd$); +INFO: Propagating deparsed query: ALTER FUNCTION function_tests.add(integer, integer) COST 1234.500000; +CONTEXT: PL/pgSQL function deparse_and_run_on_workers(text) line 6 at RAISE + deparse_and_run_on_workers +-------------------------------------- + (localhost,57637,t,"ALTER FUNCTION") + (localhost,57638,t,"ALTER FUNCTION") +(2 rows) + +SELECT deparse_and_run_on_workers($cmd$ +ALTER FUNCTION add SET log_min_messages = ERROR +$cmd$); +INFO: Propagating deparsed query: ALTER FUNCTION function_tests.add(integer, integer) SET log_min_messages = error; +CONTEXT: PL/pgSQL function deparse_and_run_on_workers(text) line 6 at RAISE + deparse_and_run_on_workers +-------------------------------------- + (localhost,57637,t,"ALTER FUNCTION") + (localhost,57638,t,"ALTER FUNCTION") +(2 rows) + +SELECT deparse_and_run_on_workers($cmd$ +ALTER FUNCTION add SET log_min_messages TO DEFAULT +$cmd$); +INFO: Propagating deparsed query: ALTER FUNCTION function_tests.add(integer, integer) SET log_min_messages TO DEFAULT; +CONTEXT: PL/pgSQL function deparse_and_run_on_workers(text) line 6 at RAISE + deparse_and_run_on_workers +-------------------------------------- + (localhost,57637,t,"ALTER FUNCTION") + (localhost,57638,t,"ALTER FUNCTION") +(2 rows) + +SELECT deparse_and_run_on_workers($cmd$ +ALTER FUNCTION add SET log_min_messages FROM CURRENT +$cmd$); +INFO: Propagating deparsed query: ALTER FUNCTION function_tests.add(integer, integer) SET log_min_messages FROM CURRENT; +CONTEXT: PL/pgSQL function deparse_and_run_on_workers(text) line 6 at RAISE + deparse_and_run_on_workers +-------------------------------------- + (localhost,57637,t,"ALTER FUNCTION") + (localhost,57638,t,"ALTER FUNCTION") +(2 rows) + +SELECT deparse_and_run_on_workers($cmd$ +ALTER FUNCTION add RESET log_min_messages +$cmd$); +INFO: Propagating deparsed query: ALTER FUNCTION function_tests.add(integer, integer) RESET log_min_messages; +CONTEXT: PL/pgSQL function deparse_and_run_on_workers(text) line 6 at RAISE + deparse_and_run_on_workers +-------------------------------------- + (localhost,57637,t,"ALTER FUNCTION") + (localhost,57638,t,"ALTER FUNCTION") +(2 rows) + +SELECT deparse_and_run_on_workers($cmd$ +ALTER FUNCTION add RESET ALL +$cmd$); +INFO: Propagating deparsed query: ALTER FUNCTION function_tests.add(integer, integer) RESET ALL; +CONTEXT: PL/pgSQL function deparse_and_run_on_workers(text) line 6 at RAISE + deparse_and_run_on_workers +-------------------------------------- + (localhost,57637,t,"ALTER FUNCTION") + (localhost,57638,t,"ALTER FUNCTION") +(2 rows) + +-- Rename the function in the workers +SELECT deparse_and_run_on_workers($cmd$ +ALTER FUNCTION add RENAME TO summation +$cmd$); +INFO: Propagating deparsed query: ALTER FUNCTION function_tests.add(integer, integer) RENAME TO summation; +CONTEXT: PL/pgSQL function deparse_and_run_on_workers(text) line 6 at RAISE + deparse_and_run_on_workers +-------------------------------------- + (localhost,57637,t,"ALTER FUNCTION") + (localhost,57638,t,"ALTER FUNCTION") +(2 rows) + +-- Rename the function inb the coordinator as well. +-- This is needed so the next query is parsed on the coordinator +ALTER FUNCTION add RENAME TO summation; +-- Rename it back to the original so that the next tests can pass +SELECT deparse_and_run_on_workers($cmd$ +ALTER FUNCTION summation RENAME TO add +$cmd$); +INFO: Propagating deparsed query: ALTER FUNCTION function_tests.summation(integer, integer) RENAME TO add; +CONTEXT: PL/pgSQL function deparse_and_run_on_workers(text) line 6 at RAISE + deparse_and_run_on_workers +-------------------------------------- + (localhost,57637,t,"ALTER FUNCTION") + (localhost,57638,t,"ALTER FUNCTION") +(2 rows) + +-- Rename the function back to the original name in the coordinator +ALTER FUNCTION summation RENAME TO add; +CREATE ROLE function_role; +NOTICE: not propagating CREATE ROLE/USER commands to worker nodes +HINT: Connect to worker nodes directly to manually create all necessary users and roles. +SELECT run_command_on_workers('CREATE ROLE function_role'); + run_command_on_workers +----------------------------------- + (localhost,57637,t,"CREATE ROLE") + (localhost,57638,t,"CREATE ROLE") +(2 rows) + +SELECT deparse_and_run_on_workers($cmd$ +ALTER FUNCTION add OWNER TO function_role +$cmd$); +INFO: Propagating deparsed query: ALTER FUNCTION function_tests.add(integer, integer) OWNER TO function_role; +CONTEXT: PL/pgSQL function deparse_and_run_on_workers(text) line 6 at RAISE + deparse_and_run_on_workers +-------------------------------------- + (localhost,57637,t,"ALTER FUNCTION") + (localhost,57638,t,"ALTER FUNCTION") +(2 rows) + +SELECT deparse_and_run_on_workers($cmd$ +ALTER FUNCTION add OWNER TO missing_role +$cmd$); +INFO: Propagating deparsed query: ALTER FUNCTION function_tests.add(integer, integer) OWNER TO missing_role; +CONTEXT: PL/pgSQL function deparse_and_run_on_workers(text) line 6 at RAISE + deparse_and_run_on_workers +-------------------------------------------------------------------- + (localhost,57637,f,"ERROR: role ""missing_role"" does not exist") + (localhost,57638,f,"ERROR: role ""missing_role"" does not exist") +(2 rows) + +-- SET the schema in workers as well as the coordinator so that it remains in the same schema +SELECT deparse_and_run_on_workers($cmd$ +ALTER FUNCTION add SET SCHEMA public +$cmd$); +INFO: Propagating deparsed query: ALTER FUNCTION function_tests.add(integer, integer) SET SCHEMA public; +CONTEXT: PL/pgSQL function deparse_and_run_on_workers(text) line 6 at RAISE + deparse_and_run_on_workers +-------------------------------------- + (localhost,57637,t,"ALTER FUNCTION") + (localhost,57638,t,"ALTER FUNCTION") +(2 rows) + +ALTER FUNCTION add SET SCHEMA public; +-- Revert the schema back +SELECT deparse_and_run_on_workers($cmd$ +ALTER FUNCTION public.add SET SCHEMA function_tests +$cmd$); +INFO: Propagating deparsed query: ALTER FUNCTION public.add(integer, integer) SET SCHEMA function_tests; +CONTEXT: PL/pgSQL function deparse_and_run_on_workers(text) line 6 at RAISE + deparse_and_run_on_workers +-------------------------------------- + (localhost,57637,t,"ALTER FUNCTION") + (localhost,57638,t,"ALTER FUNCTION") +(2 rows) + +ALTER FUNCTION public.add SET SCHEMA function_tests; +SELECT deparse_and_run_on_workers($cmd$ +ALTER FUNCTION add DEPENDS ON EXTENSION citus +$cmd$); +INFO: Propagating deparsed query: ALTER FUNCTION function_tests.add(integer, integer) DEPENDS ON EXTENSION citus; +CONTEXT: PL/pgSQL function deparse_and_run_on_workers(text) line 6 at RAISE + deparse_and_run_on_workers +-------------------------------------- + (localhost,57637,t,"ALTER FUNCTION") + (localhost,57638,t,"ALTER FUNCTION") +(2 rows) + +-- make sure "any" type is correctly deparsed +SELECT deparse_and_run_on_workers($cmd$ +ALTER FUNCTION pg_catalog.get_shard_id_for_distribution_column(table_name regclass, distribution_value "any") PARALLEL SAFE; +$cmd$); +INFO: Propagating deparsed query: ALTER FUNCTION pg_catalog.get_shard_id_for_distribution_column(table_name regclass, distribution_value "any") PARALLEL SAFE; +CONTEXT: PL/pgSQL function deparse_and_run_on_workers(text) line 6 at RAISE + deparse_and_run_on_workers +-------------------------------------- + (localhost,57637,t,"ALTER FUNCTION") + (localhost,57638,t,"ALTER FUNCTION") +(2 rows) + +-- Do not run valid drop queries in the workers +SELECT deparse_test($cmd$ +DROP FUNCTION add(int,int); +$cmd$); + deparse_test +----------------------------------------------------- + DROP FUNCTION function_tests.add(integer, integer); +(1 row) + +-- have multiple actions in a single query +SELECT deparse_and_run_on_workers($cmd$ +ALTER FUNCTION add volatile leakproof SECURITY DEFINER PARALLEL unsafe; +$cmd$); +INFO: Propagating deparsed query: ALTER FUNCTION function_tests.add(integer, integer) VOLATILE LEAKPROOF SECURITY DEFINER PARALLEL UNSAFE; +CONTEXT: PL/pgSQL function deparse_and_run_on_workers(text) line 6 at RAISE + deparse_and_run_on_workers +-------------------------------------- + (localhost,57637,t,"ALTER FUNCTION") + (localhost,57638,t,"ALTER FUNCTION") +(2 rows) + +-- Check that an invalid function name is still parsed correctly +-- Test that it fails when run without IF EXISTS clause +SELECT deparse_and_run_on_workers($cmd$ +DROP FUNCTION missing_function(int, text); +$cmd$); +INFO: Propagating deparsed query: DROP FUNCTION missing_function(pg_catalog.int4,text); +CONTEXT: PL/pgSQL function deparse_and_run_on_workers(text) line 6 at RAISE + deparse_and_run_on_workers +--------------------------------------------------------------------------------------- + (localhost,57637,f,"ERROR: function missing_function(integer, text) does not exist") + (localhost,57638,f,"ERROR: function missing_function(integer, text) does not exist") +(2 rows) + +-- Check that an invalid function name is still parsed correctly +-- Test that it is successful when run with IF EXISTS clause +SELECT deparse_and_run_on_workers($cmd$ +DROP FUNCTION IF EXISTS missing_function(int, text); +$cmd$); +INFO: Propagating deparsed query: DROP FUNCTION IF EXISTS missing_function(pg_catalog.int4,text); +CONTEXT: PL/pgSQL function deparse_and_run_on_workers(text) line 6 at RAISE + deparse_and_run_on_workers +------------------------------------- + (localhost,57637,t,"DROP FUNCTION") + (localhost,57638,t,"DROP FUNCTION") +(2 rows) + +SELECT deparse_and_run_on_workers($cmd$ +DROP FUNCTION IF EXISTS missing_schema.missing_function(int,float); +$cmd$); +INFO: Propagating deparsed query: DROP FUNCTION IF EXISTS missing_schema.missing_function(pg_catalog.int4,pg_catalog.float8); +CONTEXT: PL/pgSQL function deparse_and_run_on_workers(text) line 6 at RAISE + deparse_and_run_on_workers +------------------------------------- + (localhost,57637,t,"DROP FUNCTION") + (localhost,57638,t,"DROP FUNCTION") +(2 rows) + +SELECT deparse_and_run_on_workers($cmd$ +DROP FUNCTION IF EXISTS missing_func_without_args; +$cmd$); +INFO: Propagating deparsed query: DROP FUNCTION IF EXISTS missing_func_without_args; +CONTEXT: PL/pgSQL function deparse_and_run_on_workers(text) line 6 at RAISE + deparse_and_run_on_workers +------------------------------------- + (localhost,57637,t,"DROP FUNCTION") + (localhost,57638,t,"DROP FUNCTION") +(2 rows) + +-- create schema with weird names +CREATE SCHEMA "CiTuS.TeeN"; +CREATE SCHEMA "CiTUS.TEEN2"; +SELECT run_command_on_workers($$ + CREATE SCHEMA IF NOT EXISTS "CiTuS.TeeN"; + CREATE SCHEMA IF NOT EXISTS "CiTUS.TEEN2"; +$$); + run_command_on_workers +------------------------------------- + (localhost,57637,t,"CREATE SCHEMA") + (localhost,57638,t,"CREATE SCHEMA") +(2 rows) + +-- create table with weird names +CREATE FUNCTION "CiTuS.TeeN"."TeeNFunCT10N.1!?!"() RETURNS TEXT + AS $$ SELECT 'test function without params' $$ + LANGUAGE SQL; +CREATE FUNCTION "CiTuS.TeeN"."TeeNFunCT10N.1!?!"(text) RETURNS TEXT + AS $$ SELECT 'Overloaded function called with param: ' || $1 $$ + LANGUAGE SQL; +SELECT create_distributed_function('"CiTuS.TeeN"."TeeNFunCT10N.1!?!"()'); + create_distributed_function +----------------------------- + +(1 row) + +SELECT create_distributed_function('"CiTuS.TeeN"."TeeNFunCT10N.1!?!"(text)'); + create_distributed_function +----------------------------- + +(1 row) + +SELECT deparse_and_run_on_workers($cmd$ +ALTER FUNCTION "CiTuS.TeeN"."TeeNFunCT10N.1!?!"() SET SCHEMA "CiTUS.TEEN2" +$cmd$); +INFO: Propagating deparsed query: ALTER FUNCTION "CiTuS.TeeN"."TeeNFunCT10N.1!?!"() SET SCHEMA "CiTUS.TEEN2"; +CONTEXT: PL/pgSQL function deparse_and_run_on_workers(text) line 6 at RAISE + deparse_and_run_on_workers +-------------------------------------- + (localhost,57637,t,"ALTER FUNCTION") + (localhost,57638,t,"ALTER FUNCTION") +(2 rows) + +-- drop 2 functions at the same time +SELECT deparse_and_run_on_workers($cmd$ +DROP FUNCTION "CiTUS.TEEN2"."TeeNFunCT10N.1!?!"(),"CiTuS.TeeN"."TeeNFunCT10N.1!?!"(text); +$cmd$); +INFO: Propagating deparsed query: DROP FUNCTION "CiTUS.TEEN2"."TeeNFunCT10N.1!?!"(), "CiTuS.TeeN"."TeeNFunCT10N.1!?!"(text); +CONTEXT: PL/pgSQL function deparse_and_run_on_workers(text) line 6 at RAISE + deparse_and_run_on_workers +------------------------------------- + (localhost,57637,t,"DROP FUNCTION") + (localhost,57638,t,"DROP FUNCTION") +(2 rows) + +-- a function with a default parameter +CREATE FUNCTION func_default_param(param INT DEFAULT 0) RETURNS TEXT + AS $$ SELECT 'supplied param is : ' || param; $$ + LANGUAGE SQL; +SELECT create_distributed_function('func_default_param(INT)'); + create_distributed_function +----------------------------- + +(1 row) + +SELECT deparse_and_run_on_workers($cmd$ +ALTER FUNCTION func_default_param RENAME TO func_with_default_param; +$cmd$); +INFO: Propagating deparsed query: ALTER FUNCTION function_tests.func_default_param(param integer) RENAME TO func_with_default_param; +CONTEXT: PL/pgSQL function deparse_and_run_on_workers(text) line 6 at RAISE + deparse_and_run_on_workers +-------------------------------------- + (localhost,57637,t,"ALTER FUNCTION") + (localhost,57638,t,"ALTER FUNCTION") +(2 rows) + +-- a function with IN and OUT parameters +CREATE FUNCTION func_out_param(IN param INT, OUT result TEXT) + AS $$ SELECT 'supplied param is : ' || param; $$ + LANGUAGE SQL; +SELECT create_distributed_function('func_out_param(INT)'); + create_distributed_function +----------------------------- + +(1 row) + +SELECT deparse_and_run_on_workers($cmd$ +ALTER FUNCTION func_out_param RENAME TO func_in_and_out_param; +$cmd$); +INFO: Propagating deparsed query: ALTER FUNCTION function_tests.func_out_param(param integer, OUT result text) RENAME TO func_in_and_out_param; +CONTEXT: PL/pgSQL function deparse_and_run_on_workers(text) line 6 at RAISE + deparse_and_run_on_workers +-------------------------------------- + (localhost,57637,t,"ALTER FUNCTION") + (localhost,57638,t,"ALTER FUNCTION") +(2 rows) + +-- a function with INOUT parameter +CREATE FUNCTION square(INOUT a NUMERIC) +AS $$ +BEGIN + a := a * a; +END; $$ +LANGUAGE plpgsql; +SELECT create_distributed_function('square(NUMERIC)'); + create_distributed_function +----------------------------- + +(1 row) + +SELECT deparse_and_run_on_workers($cmd$ +ALTER FUNCTION square SET search_path TO DEFAULT; +$cmd$); +INFO: Propagating deparsed query: ALTER FUNCTION function_tests.square(INOUT a numeric) SET search_path TO DEFAULT; +CONTEXT: PL/pgSQL function deparse_and_run_on_workers(text) line 6 at RAISE + deparse_and_run_on_workers +-------------------------------------- + (localhost,57637,t,"ALTER FUNCTION") + (localhost,57638,t,"ALTER FUNCTION") +(2 rows) + +-- a function with variadic input. +CREATE FUNCTION sum_avg( + VARIADIC list NUMERIC[], + OUT total NUMERIC, + OUT average NUMERIC) +AS $$ +BEGIN + SELECT INTO total SUM(list[i]) + FROM generate_subscripts(list, 1) g(i); + + SELECT INTO average AVG(list[i]) + FROM generate_subscripts(list, 1) g(i); +END; $$ +LANGUAGE plpgsql; +SELECT create_distributed_function('sum_avg(NUMERIC[])'); + create_distributed_function +----------------------------- + +(1 row) + +SELECT deparse_and_run_on_workers($cmd$ +ALTER FUNCTION sum_avg COST 10000; +$cmd$); +INFO: Propagating deparsed query: ALTER FUNCTION function_tests.sum_avg(VARIADIC list numeric[], OUT total numeric, OUT average numeric) COST 10000.000000; +CONTEXT: PL/pgSQL function deparse_and_run_on_workers(text) line 6 at RAISE + deparse_and_run_on_workers +-------------------------------------- + (localhost,57637,t,"ALTER FUNCTION") + (localhost,57638,t,"ALTER FUNCTION") +(2 rows) + +-- a function with a custom type IN parameter +CREATE TYPE intpair AS (x int, y int); +CREATE FUNCTION func_custom_param(IN param intpair, OUT total INT) + AS $$ SELECT param.x + param.y $$ + LANGUAGE SQL; +SELECT create_distributed_function('func_custom_param(intpair)'); + create_distributed_function +----------------------------- + +(1 row) + +SELECT deparse_and_run_on_workers($cmd$ +ALTER FUNCTION func_custom_param RENAME TO func_with_custom_param; +$cmd$); +INFO: Propagating deparsed query: ALTER FUNCTION function_tests.func_custom_param(param function_tests.intpair, OUT total integer) RENAME TO func_with_custom_param; +CONTEXT: PL/pgSQL function deparse_and_run_on_workers(text) line 6 at RAISE + deparse_and_run_on_workers +-------------------------------------- + (localhost,57637,t,"ALTER FUNCTION") + (localhost,57638,t,"ALTER FUNCTION") +(2 rows) + +-- a function that returns TABLE +CREATE FUNCTION func_returns_table(IN count INT) + RETURNS TABLE (x INT, y INT) + AS $$ SELECT i,i FROM generate_series(1,count) i $$ + LANGUAGE SQL; +SELECT create_distributed_function('func_returns_table(INT)'); + create_distributed_function +----------------------------- + +(1 row) + +SELECT deparse_and_run_on_workers($cmd$ +ALTER FUNCTION func_returns_table ROWS 100; +$cmd$); +INFO: Propagating deparsed query: ALTER FUNCTION function_tests.func_returns_table(count integer) ROWS 100.000000; +CONTEXT: PL/pgSQL function deparse_and_run_on_workers(text) line 6 at RAISE + deparse_and_run_on_workers +-------------------------------------- + (localhost,57637,t,"ALTER FUNCTION") + (localhost,57638,t,"ALTER FUNCTION") +(2 rows) + +-- clear objects +SET client_min_messages TO WARNING; -- suppress cascading objects dropping +DROP SCHEMA "CiTuS.TeeN" CASCADE; +DROP SCHEMA "CiTUS.TEEN2" CASCADE; +DROP SCHEMA function_tests CASCADE; +SELECT run_command_on_workers($$ + DROP SCHEMA "CiTuS.TeeN" CASCADE; + DROP SCHEMA "CiTUS.TEEN2" CASCADE; + DROP SCHEMA function_tests CASCADE; +$$); + run_command_on_workers +----------------------------------- + (localhost,57637,t,"DROP SCHEMA") + (localhost,57638,t,"DROP SCHEMA") +(2 rows) + +DROP ROLE function_role; diff --git a/src/test/regress/expected/multi_deparse_procedure.out b/src/test/regress/expected/multi_deparse_procedure.out new file mode 100644 index 000000000..355eac644 --- /dev/null +++ b/src/test/regress/expected/multi_deparse_procedure.out @@ -0,0 +1,379 @@ +-- +-- Regression tests for deparsing ALTER/DROP PROCEDURE Queries +-- +-- ALTER PROCEDURE name [ ( [ [ argmode ] [ argname ] argtype [, ...] ] ) ] +-- action [ ... ] [ RESTRICT ] +-- ALTER PROCEDURE name [ ( [ [ argmode ] [ argname ] argtype [, ...] ] ) ] +-- RENAME TO new_name +-- ALTER PROCEDURE name [ ( [ [ argmode ] [ argname ] argtype [, ...] ] ) ] +-- OWNER TO { new_owner | CURRENT_USER | SESSION_USER } +-- ALTER PROCEDURE name [ ( [ [ argmode ] [ argname ] argtype [, ...] ] ) ] +-- SET SCHEMA new_schema +-- ALTER PROCEDURE name [ ( [ [ argmode ] [ argname ] argtype [, ...] ] ) ] +-- DEPENDS ON EXTENSION extension_name +-- where action is one of: +-- [ EXTERNAL ] SECURITY INVOKER | [ EXTERNAL ] SECURITY DEFINER +-- SET configuration_parameter { TO | = } { value | DEFAULT } +-- SET configuration_parameter FROM CURRENT +-- RESET configuration_parameter +-- RESET ALL +-- +-- DROP PROCEDURE [ IF EXISTS ] name [ ( [ [ argmode ] [ argname ] argtype [, ...] ] ) ] [, ...] +-- [ CASCADE | RESTRICT ] +-- +-- Please note that current deparser does not return errors on some invalid queries. +-- +-- For example CALLED ON NULL INPUT action is valid only for FUNCTIONS, but we still +-- allow deparsing them here. +SET citus.next_shard_id TO 20030000; +CREATE SCHEMA procedure_tests; +SET search_path TO procedure_tests; +SET citus.shard_count TO 4; +SET client_min_messages TO INFO; +-- print whether we're using version > 10 to make version-specific tests clear +SHOW server_version \gset +SELECT substring(:'server_version', '\d+')::int > 10 AS version_above_ten; + version_above_ten +------------------- + t +(1 row) + +CREATE FUNCTION deparse_test(text) + RETURNS text + AS 'citus' + LANGUAGE C STRICT; +CREATE FUNCTION deparse_and_run_on_workers(text) + RETURNS SETOF record + AS $fnc$ + WITH deparsed_query AS ( SELECT deparse_test($1) qualified_query ) + SELECT run_command_on_workers(qualified_query) FROM deparsed_query d + $fnc$ + LANGUAGE SQL; +-- Create a simple PROCEDURE and distribute it +CREATE OR REPLACE PROCEDURE raise_info(text) +LANGUAGE PLPGSQL AS $proc$ +BEGIN + RAISE INFO 'information message %', $1; +END; +$proc$; +SELECT create_distributed_function('raise_info(text)'); + create_distributed_function +----------------------------- + +(1 row) + +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info CALLED ON NULL INPUT +$cmd$); + deparse_and_run_on_workers +------------------------------------------------------------------------- + (localhost,57637,f,"ERROR: invalid attribute in procedure definition") + (localhost,57638,f,"ERROR: invalid attribute in procedure definition") +(2 rows) + +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info RETURNS NULL ON NULL INPUT +$cmd$); + deparse_and_run_on_workers +------------------------------------------------------------------------- + (localhost,57637,f,"ERROR: invalid attribute in procedure definition") + (localhost,57638,f,"ERROR: invalid attribute in procedure definition") +(2 rows) + +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info STRICT +$cmd$); + deparse_and_run_on_workers +------------------------------------------------------------------------- + (localhost,57637,f,"ERROR: invalid attribute in procedure definition") + (localhost,57638,f,"ERROR: invalid attribute in procedure definition") +(2 rows) + +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info IMMUTABLE +$cmd$); + deparse_and_run_on_workers +------------------------------------------------------------------------- + (localhost,57637,f,"ERROR: invalid attribute in procedure definition") + (localhost,57638,f,"ERROR: invalid attribute in procedure definition") +(2 rows) + +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info STABLE +$cmd$); + deparse_and_run_on_workers +------------------------------------------------------------------------- + (localhost,57637,f,"ERROR: invalid attribute in procedure definition") + (localhost,57638,f,"ERROR: invalid attribute in procedure definition") +(2 rows) + +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info VOLATILE +$cmd$); + deparse_and_run_on_workers +------------------------------------------------------------------------- + (localhost,57637,f,"ERROR: invalid attribute in procedure definition") + (localhost,57638,f,"ERROR: invalid attribute in procedure definition") +(2 rows) + +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info LEAKPROOF +$cmd$); + deparse_and_run_on_workers +------------------------------------------------------------------------- + (localhost,57637,f,"ERROR: invalid attribute in procedure definition") + (localhost,57638,f,"ERROR: invalid attribute in procedure definition") +(2 rows) + +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info NOT LEAKPROOF +$cmd$); + deparse_and_run_on_workers +------------------------------------------------------------------------- + (localhost,57637,f,"ERROR: invalid attribute in procedure definition") + (localhost,57638,f,"ERROR: invalid attribute in procedure definition") +(2 rows) + +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info EXTERNAL SECURITY INVOKER +$cmd$); + deparse_and_run_on_workers +--------------------------------------- + (localhost,57637,t,"ALTER PROCEDURE") + (localhost,57638,t,"ALTER PROCEDURE") +(2 rows) + +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info SECURITY INVOKER +$cmd$); + deparse_and_run_on_workers +--------------------------------------- + (localhost,57637,t,"ALTER PROCEDURE") + (localhost,57638,t,"ALTER PROCEDURE") +(2 rows) + +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info EXTERNAL SECURITY DEFINER +$cmd$); + deparse_and_run_on_workers +--------------------------------------- + (localhost,57637,t,"ALTER PROCEDURE") + (localhost,57638,t,"ALTER PROCEDURE") +(2 rows) + +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info SECURITY DEFINER +$cmd$); + deparse_and_run_on_workers +--------------------------------------- + (localhost,57637,t,"ALTER PROCEDURE") + (localhost,57638,t,"ALTER PROCEDURE") +(2 rows) + +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info PARALLEL UNSAFE +$cmd$); + deparse_and_run_on_workers +------------------------------------------------------------------------- + (localhost,57637,f,"ERROR: invalid attribute in procedure definition") + (localhost,57638,f,"ERROR: invalid attribute in procedure definition") +(2 rows) + +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info PARALLEL RESTRICTED +$cmd$); + deparse_and_run_on_workers +------------------------------------------------------------------------- + (localhost,57637,f,"ERROR: invalid attribute in procedure definition") + (localhost,57638,f,"ERROR: invalid attribute in procedure definition") +(2 rows) + +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info PARALLEL SAFE +$cmd$); + deparse_and_run_on_workers +------------------------------------------------------------------------- + (localhost,57637,f,"ERROR: invalid attribute in procedure definition") + (localhost,57638,f,"ERROR: invalid attribute in procedure definition") +(2 rows) + +-- The COST/ROWS arguments should always be numeric +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info COST 1234 +$cmd$); + deparse_and_run_on_workers +------------------------------------------------------------------------- + (localhost,57637,f,"ERROR: invalid attribute in procedure definition") + (localhost,57638,f,"ERROR: invalid attribute in procedure definition") +(2 rows) + +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info COST 1234.5 +$cmd$); + deparse_and_run_on_workers +------------------------------------------------------------------------- + (localhost,57637,f,"ERROR: invalid attribute in procedure definition") + (localhost,57638,f,"ERROR: invalid attribute in procedure definition") +(2 rows) + +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info ROWS 10 +$cmd$); + deparse_and_run_on_workers +------------------------------------------------------------------------- + (localhost,57637,f,"ERROR: invalid attribute in procedure definition") + (localhost,57638,f,"ERROR: invalid attribute in procedure definition") +(2 rows) + +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info ROWS 10.8 +$cmd$); + deparse_and_run_on_workers +------------------------------------------------------------------------- + (localhost,57637,f,"ERROR: invalid attribute in procedure definition") + (localhost,57638,f,"ERROR: invalid attribute in procedure definition") +(2 rows) + +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info SECURITY INVOKER SET client_min_messages TO warning; +$cmd$); + deparse_and_run_on_workers +--------------------------------------- + (localhost,57637,t,"ALTER PROCEDURE") + (localhost,57638,t,"ALTER PROCEDURE") +(2 rows) + +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info SET log_min_messages = ERROR +$cmd$); + deparse_and_run_on_workers +--------------------------------------- + (localhost,57637,t,"ALTER PROCEDURE") + (localhost,57638,t,"ALTER PROCEDURE") +(2 rows) + +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info SET log_min_messages TO DEFAULT +$cmd$); + deparse_and_run_on_workers +--------------------------------------- + (localhost,57637,t,"ALTER PROCEDURE") + (localhost,57638,t,"ALTER PROCEDURE") +(2 rows) + +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info SET log_min_messages FROM CURRENT +$cmd$); + deparse_and_run_on_workers +--------------------------------------- + (localhost,57637,t,"ALTER PROCEDURE") + (localhost,57638,t,"ALTER PROCEDURE") +(2 rows) + +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info RESET log_min_messages +$cmd$); + deparse_and_run_on_workers +--------------------------------------- + (localhost,57637,t,"ALTER PROCEDURE") + (localhost,57638,t,"ALTER PROCEDURE") +(2 rows) + +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info RESET ALL +$cmd$); + deparse_and_run_on_workers +--------------------------------------- + (localhost,57637,t,"ALTER PROCEDURE") + (localhost,57638,t,"ALTER PROCEDURE") +(2 rows) + +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info RENAME TO summation +$cmd$); + deparse_and_run_on_workers +--------------------------------------- + (localhost,57637,t,"ALTER PROCEDURE") + (localhost,57638,t,"ALTER PROCEDURE") +(2 rows) + +CREATE ROLE PROCEDURE_role; +NOTICE: not propagating CREATE ROLE/USER commands to worker nodes +HINT: Connect to worker nodes directly to manually create all necessary users and roles. +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info OWNER TO PROCEDURE_role +$cmd$); + deparse_and_run_on_workers +---------------------------------------------------------------------- + (localhost,57637,f,"ERROR: role ""procedure_role"" does not exist") + (localhost,57638,f,"ERROR: role ""procedure_role"" does not exist") +(2 rows) + +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info OWNER TO missing_role +$cmd$); + deparse_and_run_on_workers +-------------------------------------------------------------------- + (localhost,57637,f,"ERROR: role ""missing_role"" does not exist") + (localhost,57638,f,"ERROR: role ""missing_role"" does not exist") +(2 rows) + +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info SET SCHEMA public +$cmd$); + deparse_and_run_on_workers +----------------------------------------------------------------------------------------- + (localhost,57637,f,"ERROR: procedure procedure_tests.raise_info(text) does not exist") + (localhost,57638,f,"ERROR: procedure procedure_tests.raise_info(text) does not exist") +(2 rows) + +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info DEPENDS ON EXTENSION citus +$cmd$); + deparse_and_run_on_workers +----------------------------------------------------------------------------------------- + (localhost,57637,f,"ERROR: procedure procedure_tests.raise_info(text) does not exist") + (localhost,57638,f,"ERROR: procedure procedure_tests.raise_info(text) does not exist") +(2 rows) + +SELECT deparse_and_run_on_workers($cmd$ +DROP PROCEDURE IF EXISTS raise_info(int,int); +$cmd$); + deparse_and_run_on_workers +-------------------------------------- + (localhost,57637,t,"DROP PROCEDURE") + (localhost,57638,t,"DROP PROCEDURE") +(2 rows) + +-- Check that an invalid PROCEDURE name is still parsed correctly +SELECT deparse_and_run_on_workers($cmd$ +DROP PROCEDURE IF EXISTS missing_PROCEDURE(int, text); +$cmd$); + deparse_and_run_on_workers +-------------------------------------- + (localhost,57637,t,"DROP PROCEDURE") + (localhost,57638,t,"DROP PROCEDURE") +(2 rows) + +SELECT deparse_and_run_on_workers($cmd$ +DROP PROCEDURE IF EXISTS missing_schema.missing_PROCEDURE(int,float); +$cmd$); + deparse_and_run_on_workers +-------------------------------------- + (localhost,57637,t,"DROP PROCEDURE") + (localhost,57638,t,"DROP PROCEDURE") +(2 rows) + +SELECT deparse_and_run_on_workers($cmd$ +DROP PROCEDURE IF EXISTS missing_schema.missing_PROCEDURE(int,float) CASCADE; +$cmd$); + deparse_and_run_on_workers +-------------------------------------- + (localhost,57637,t,"DROP PROCEDURE") + (localhost,57638,t,"DROP PROCEDURE") +(2 rows) + +-- clear objects +SET client_min_messages TO WARNING; -- suppress cascading objects dropping +DROP SCHEMA procedure_tests CASCADE; +DROP ROLE PROCEDURE_role; diff --git a/src/test/regress/expected/multi_deparse_procedure_0.out b/src/test/regress/expected/multi_deparse_procedure_0.out new file mode 100644 index 000000000..7ce060636 --- /dev/null +++ b/src/test/regress/expected/multi_deparse_procedure_0.out @@ -0,0 +1,448 @@ +-- +-- Regression tests for deparsing ALTER/DROP PROCEDURE Queries +-- +-- ALTER PROCEDURE name [ ( [ [ argmode ] [ argname ] argtype [, ...] ] ) ] +-- action [ ... ] [ RESTRICT ] +-- ALTER PROCEDURE name [ ( [ [ argmode ] [ argname ] argtype [, ...] ] ) ] +-- RENAME TO new_name +-- ALTER PROCEDURE name [ ( [ [ argmode ] [ argname ] argtype [, ...] ] ) ] +-- OWNER TO { new_owner | CURRENT_USER | SESSION_USER } +-- ALTER PROCEDURE name [ ( [ [ argmode ] [ argname ] argtype [, ...] ] ) ] +-- SET SCHEMA new_schema +-- ALTER PROCEDURE name [ ( [ [ argmode ] [ argname ] argtype [, ...] ] ) ] +-- DEPENDS ON EXTENSION extension_name +-- where action is one of: +-- [ EXTERNAL ] SECURITY INVOKER | [ EXTERNAL ] SECURITY DEFINER +-- SET configuration_parameter { TO | = } { value | DEFAULT } +-- SET configuration_parameter FROM CURRENT +-- RESET configuration_parameter +-- RESET ALL +-- +-- DROP PROCEDURE [ IF EXISTS ] name [ ( [ [ argmode ] [ argname ] argtype [, ...] ] ) ] [, ...] +-- [ CASCADE | RESTRICT ] +-- +-- Please note that current deparser does not return errors on some invalid queries. +-- +-- For example CALLED ON NULL INPUT action is valid only for FUNCTIONS, but we still +-- allow deparsing them here. +SET citus.next_shard_id TO 20030000; +CREATE SCHEMA procedure_tests; +SET search_path TO procedure_tests; +SET citus.shard_count TO 4; +SET client_min_messages TO INFO; +-- print whether we're using version > 10 to make version-specific tests clear +SHOW server_version \gset +SELECT substring(:'server_version', '\d+')::int > 10 AS version_above_ten; + version_above_ten +------------------- + f +(1 row) + +CREATE FUNCTION deparse_test(text) + RETURNS text + AS 'citus' + LANGUAGE C STRICT; +CREATE FUNCTION deparse_and_run_on_workers(text) + RETURNS SETOF record + AS $fnc$ + WITH deparsed_query AS ( SELECT deparse_test($1) qualified_query ) + SELECT run_command_on_workers(qualified_query) FROM deparsed_query d + $fnc$ + LANGUAGE SQL; +-- Create a simple PROCEDURE and distribute it +CREATE OR REPLACE PROCEDURE raise_info(text) +LANGUAGE PLPGSQL AS $proc$ +BEGIN + RAISE INFO 'information message %', $1; +END; +$proc$; +ERROR: syntax error at or near "PROCEDURE" +LINE 1: CREATE OR REPLACE PROCEDURE raise_info(text) + ^ +SELECT create_distributed_function('raise_info(text)'); +ERROR: function "raise_info(text)" does not exist +LINE 1: SELECT create_distributed_function('raise_info(text)'); + ^ +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info CALLED ON NULL INPUT +$cmd$); +ERROR: syntax error at or near "PROCEDURE" +LINE 2: WITH deparsed_query AS ( SELECT deparse_test($1) qualifi... + ^ +QUERY: + WITH deparsed_query AS ( SELECT deparse_test($1) qualified_query ) + SELECT run_command_on_workers(qualified_query) FROM deparsed_query d + +CONTEXT: SQL function "deparse_and_run_on_workers" statement 1 +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info RETURNS NULL ON NULL INPUT +$cmd$); +ERROR: syntax error at or near "PROCEDURE" +LINE 2: WITH deparsed_query AS ( SELECT deparse_test($1) qualifi... + ^ +QUERY: + WITH deparsed_query AS ( SELECT deparse_test($1) qualified_query ) + SELECT run_command_on_workers(qualified_query) FROM deparsed_query d + +CONTEXT: SQL function "deparse_and_run_on_workers" statement 1 +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info STRICT +$cmd$); +ERROR: syntax error at or near "PROCEDURE" +LINE 2: WITH deparsed_query AS ( SELECT deparse_test($1) qualifi... + ^ +QUERY: + WITH deparsed_query AS ( SELECT deparse_test($1) qualified_query ) + SELECT run_command_on_workers(qualified_query) FROM deparsed_query d + +CONTEXT: SQL function "deparse_and_run_on_workers" statement 1 +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info IMMUTABLE +$cmd$); +ERROR: syntax error at or near "PROCEDURE" +LINE 2: WITH deparsed_query AS ( SELECT deparse_test($1) qualifi... + ^ +QUERY: + WITH deparsed_query AS ( SELECT deparse_test($1) qualified_query ) + SELECT run_command_on_workers(qualified_query) FROM deparsed_query d + +CONTEXT: SQL function "deparse_and_run_on_workers" statement 1 +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info STABLE +$cmd$); +ERROR: syntax error at or near "PROCEDURE" +LINE 2: WITH deparsed_query AS ( SELECT deparse_test($1) qualifi... + ^ +QUERY: + WITH deparsed_query AS ( SELECT deparse_test($1) qualified_query ) + SELECT run_command_on_workers(qualified_query) FROM deparsed_query d + +CONTEXT: SQL function "deparse_and_run_on_workers" statement 1 +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info VOLATILE +$cmd$); +ERROR: syntax error at or near "PROCEDURE" +LINE 2: WITH deparsed_query AS ( SELECT deparse_test($1) qualifi... + ^ +QUERY: + WITH deparsed_query AS ( SELECT deparse_test($1) qualified_query ) + SELECT run_command_on_workers(qualified_query) FROM deparsed_query d + +CONTEXT: SQL function "deparse_and_run_on_workers" statement 1 +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info LEAKPROOF +$cmd$); +ERROR: syntax error at or near "PROCEDURE" +LINE 2: WITH deparsed_query AS ( SELECT deparse_test($1) qualifi... + ^ +QUERY: + WITH deparsed_query AS ( SELECT deparse_test($1) qualified_query ) + SELECT run_command_on_workers(qualified_query) FROM deparsed_query d + +CONTEXT: SQL function "deparse_and_run_on_workers" statement 1 +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info NOT LEAKPROOF +$cmd$); +ERROR: syntax error at or near "PROCEDURE" +LINE 2: WITH deparsed_query AS ( SELECT deparse_test($1) qualifi... + ^ +QUERY: + WITH deparsed_query AS ( SELECT deparse_test($1) qualified_query ) + SELECT run_command_on_workers(qualified_query) FROM deparsed_query d + +CONTEXT: SQL function "deparse_and_run_on_workers" statement 1 +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info EXTERNAL SECURITY INVOKER +$cmd$); +ERROR: syntax error at or near "PROCEDURE" +LINE 2: WITH deparsed_query AS ( SELECT deparse_test($1) qualifi... + ^ +QUERY: + WITH deparsed_query AS ( SELECT deparse_test($1) qualified_query ) + SELECT run_command_on_workers(qualified_query) FROM deparsed_query d + +CONTEXT: SQL function "deparse_and_run_on_workers" statement 1 +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info SECURITY INVOKER +$cmd$); +ERROR: syntax error at or near "PROCEDURE" +LINE 2: WITH deparsed_query AS ( SELECT deparse_test($1) qualifi... + ^ +QUERY: + WITH deparsed_query AS ( SELECT deparse_test($1) qualified_query ) + SELECT run_command_on_workers(qualified_query) FROM deparsed_query d + +CONTEXT: SQL function "deparse_and_run_on_workers" statement 1 +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info EXTERNAL SECURITY DEFINER +$cmd$); +ERROR: syntax error at or near "PROCEDURE" +LINE 2: WITH deparsed_query AS ( SELECT deparse_test($1) qualifi... + ^ +QUERY: + WITH deparsed_query AS ( SELECT deparse_test($1) qualified_query ) + SELECT run_command_on_workers(qualified_query) FROM deparsed_query d + +CONTEXT: SQL function "deparse_and_run_on_workers" statement 1 +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info SECURITY DEFINER +$cmd$); +ERROR: syntax error at or near "PROCEDURE" +LINE 2: WITH deparsed_query AS ( SELECT deparse_test($1) qualifi... + ^ +QUERY: + WITH deparsed_query AS ( SELECT deparse_test($1) qualified_query ) + SELECT run_command_on_workers(qualified_query) FROM deparsed_query d + +CONTEXT: SQL function "deparse_and_run_on_workers" statement 1 +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info PARALLEL UNSAFE +$cmd$); +ERROR: syntax error at or near "PROCEDURE" +LINE 2: WITH deparsed_query AS ( SELECT deparse_test($1) qualifi... + ^ +QUERY: + WITH deparsed_query AS ( SELECT deparse_test($1) qualified_query ) + SELECT run_command_on_workers(qualified_query) FROM deparsed_query d + +CONTEXT: SQL function "deparse_and_run_on_workers" statement 1 +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info PARALLEL RESTRICTED +$cmd$); +ERROR: syntax error at or near "PROCEDURE" +LINE 2: WITH deparsed_query AS ( SELECT deparse_test($1) qualifi... + ^ +QUERY: + WITH deparsed_query AS ( SELECT deparse_test($1) qualified_query ) + SELECT run_command_on_workers(qualified_query) FROM deparsed_query d + +CONTEXT: SQL function "deparse_and_run_on_workers" statement 1 +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info PARALLEL SAFE +$cmd$); +ERROR: syntax error at or near "PROCEDURE" +LINE 2: WITH deparsed_query AS ( SELECT deparse_test($1) qualifi... + ^ +QUERY: + WITH deparsed_query AS ( SELECT deparse_test($1) qualified_query ) + SELECT run_command_on_workers(qualified_query) FROM deparsed_query d + +CONTEXT: SQL function "deparse_and_run_on_workers" statement 1 +-- The COST/ROWS arguments should always be numeric +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info COST 1234 +$cmd$); +ERROR: syntax error at or near "PROCEDURE" +LINE 2: WITH deparsed_query AS ( SELECT deparse_test($1) qualifi... + ^ +QUERY: + WITH deparsed_query AS ( SELECT deparse_test($1) qualified_query ) + SELECT run_command_on_workers(qualified_query) FROM deparsed_query d + +CONTEXT: SQL function "deparse_and_run_on_workers" statement 1 +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info COST 1234.5 +$cmd$); +ERROR: syntax error at or near "PROCEDURE" +LINE 2: WITH deparsed_query AS ( SELECT deparse_test($1) qualifi... + ^ +QUERY: + WITH deparsed_query AS ( SELECT deparse_test($1) qualified_query ) + SELECT run_command_on_workers(qualified_query) FROM deparsed_query d + +CONTEXT: SQL function "deparse_and_run_on_workers" statement 1 +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info ROWS 10 +$cmd$); +ERROR: syntax error at or near "PROCEDURE" +LINE 2: WITH deparsed_query AS ( SELECT deparse_test($1) qualifi... + ^ +QUERY: + WITH deparsed_query AS ( SELECT deparse_test($1) qualified_query ) + SELECT run_command_on_workers(qualified_query) FROM deparsed_query d + +CONTEXT: SQL function "deparse_and_run_on_workers" statement 1 +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info ROWS 10.8 +$cmd$); +ERROR: syntax error at or near "PROCEDURE" +LINE 2: WITH deparsed_query AS ( SELECT deparse_test($1) qualifi... + ^ +QUERY: + WITH deparsed_query AS ( SELECT deparse_test($1) qualified_query ) + SELECT run_command_on_workers(qualified_query) FROM deparsed_query d + +CONTEXT: SQL function "deparse_and_run_on_workers" statement 1 +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info SECURITY INVOKER SET client_min_messages TO warning; +$cmd$); +ERROR: syntax error at or near "PROCEDURE" +LINE 2: WITH deparsed_query AS ( SELECT deparse_test($1) qualifi... + ^ +QUERY: + WITH deparsed_query AS ( SELECT deparse_test($1) qualified_query ) + SELECT run_command_on_workers(qualified_query) FROM deparsed_query d + +CONTEXT: SQL function "deparse_and_run_on_workers" statement 1 +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info SET log_min_messages = ERROR +$cmd$); +ERROR: syntax error at or near "PROCEDURE" +LINE 2: WITH deparsed_query AS ( SELECT deparse_test($1) qualifi... + ^ +QUERY: + WITH deparsed_query AS ( SELECT deparse_test($1) qualified_query ) + SELECT run_command_on_workers(qualified_query) FROM deparsed_query d + +CONTEXT: SQL function "deparse_and_run_on_workers" statement 1 +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info SET log_min_messages TO DEFAULT +$cmd$); +ERROR: syntax error at or near "PROCEDURE" +LINE 2: WITH deparsed_query AS ( SELECT deparse_test($1) qualifi... + ^ +QUERY: + WITH deparsed_query AS ( SELECT deparse_test($1) qualified_query ) + SELECT run_command_on_workers(qualified_query) FROM deparsed_query d + +CONTEXT: SQL function "deparse_and_run_on_workers" statement 1 +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info SET log_min_messages FROM CURRENT +$cmd$); +ERROR: syntax error at or near "PROCEDURE" +LINE 2: WITH deparsed_query AS ( SELECT deparse_test($1) qualifi... + ^ +QUERY: + WITH deparsed_query AS ( SELECT deparse_test($1) qualified_query ) + SELECT run_command_on_workers(qualified_query) FROM deparsed_query d + +CONTEXT: SQL function "deparse_and_run_on_workers" statement 1 +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info RESET log_min_messages +$cmd$); +ERROR: syntax error at or near "PROCEDURE" +LINE 2: WITH deparsed_query AS ( SELECT deparse_test($1) qualifi... + ^ +QUERY: + WITH deparsed_query AS ( SELECT deparse_test($1) qualified_query ) + SELECT run_command_on_workers(qualified_query) FROM deparsed_query d + +CONTEXT: SQL function "deparse_and_run_on_workers" statement 1 +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info RESET ALL +$cmd$); +ERROR: syntax error at or near "PROCEDURE" +LINE 2: WITH deparsed_query AS ( SELECT deparse_test($1) qualifi... + ^ +QUERY: + WITH deparsed_query AS ( SELECT deparse_test($1) qualified_query ) + SELECT run_command_on_workers(qualified_query) FROM deparsed_query d + +CONTEXT: SQL function "deparse_and_run_on_workers" statement 1 +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info RENAME TO summation +$cmd$); +ERROR: syntax error at or near "PROCEDURE" +LINE 2: WITH deparsed_query AS ( SELECT deparse_test($1) qualifi... + ^ +QUERY: + WITH deparsed_query AS ( SELECT deparse_test($1) qualified_query ) + SELECT run_command_on_workers(qualified_query) FROM deparsed_query d + +CONTEXT: SQL function "deparse_and_run_on_workers" statement 1 +CREATE ROLE PROCEDURE_role; +NOTICE: not propagating CREATE ROLE/USER commands to worker nodes +HINT: Connect to worker nodes directly to manually create all necessary users and roles. +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info OWNER TO PROCEDURE_role +$cmd$); +ERROR: syntax error at or near "PROCEDURE" +LINE 2: WITH deparsed_query AS ( SELECT deparse_test($1) qualifi... + ^ +QUERY: + WITH deparsed_query AS ( SELECT deparse_test($1) qualified_query ) + SELECT run_command_on_workers(qualified_query) FROM deparsed_query d + +CONTEXT: SQL function "deparse_and_run_on_workers" statement 1 +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info OWNER TO missing_role +$cmd$); +ERROR: syntax error at or near "PROCEDURE" +LINE 2: WITH deparsed_query AS ( SELECT deparse_test($1) qualifi... + ^ +QUERY: + WITH deparsed_query AS ( SELECT deparse_test($1) qualified_query ) + SELECT run_command_on_workers(qualified_query) FROM deparsed_query d + +CONTEXT: SQL function "deparse_and_run_on_workers" statement 1 +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info SET SCHEMA public +$cmd$); +ERROR: syntax error at or near "PROCEDURE" +LINE 2: WITH deparsed_query AS ( SELECT deparse_test($1) qualifi... + ^ +QUERY: + WITH deparsed_query AS ( SELECT deparse_test($1) qualified_query ) + SELECT run_command_on_workers(qualified_query) FROM deparsed_query d + +CONTEXT: SQL function "deparse_and_run_on_workers" statement 1 +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info DEPENDS ON EXTENSION citus +$cmd$); +ERROR: syntax error at or near "PROCEDURE" +LINE 2: WITH deparsed_query AS ( SELECT deparse_test($1) qualifi... + ^ +QUERY: + WITH deparsed_query AS ( SELECT deparse_test($1) qualified_query ) + SELECT run_command_on_workers(qualified_query) FROM deparsed_query d + +CONTEXT: SQL function "deparse_and_run_on_workers" statement 1 +SELECT deparse_and_run_on_workers($cmd$ +DROP PROCEDURE IF EXISTS raise_info(int,int); +$cmd$); +ERROR: syntax error at or near "PROCEDURE" +LINE 2: WITH deparsed_query AS ( SELECT deparse_test($1) qualifi... + ^ +QUERY: + WITH deparsed_query AS ( SELECT deparse_test($1) qualified_query ) + SELECT run_command_on_workers(qualified_query) FROM deparsed_query d + +CONTEXT: SQL function "deparse_and_run_on_workers" statement 1 +-- Check that an invalid PROCEDURE name is still parsed correctly +SELECT deparse_and_run_on_workers($cmd$ +DROP PROCEDURE IF EXISTS missing_PROCEDURE(int, text); +$cmd$); +ERROR: syntax error at or near "PROCEDURE" +LINE 2: WITH deparsed_query AS ( SELECT deparse_test($1) qualifi... + ^ +QUERY: + WITH deparsed_query AS ( SELECT deparse_test($1) qualified_query ) + SELECT run_command_on_workers(qualified_query) FROM deparsed_query d + +CONTEXT: SQL function "deparse_and_run_on_workers" statement 1 +SELECT deparse_and_run_on_workers($cmd$ +DROP PROCEDURE IF EXISTS missing_schema.missing_PROCEDURE(int,float); +$cmd$); +ERROR: syntax error at or near "PROCEDURE" +LINE 2: WITH deparsed_query AS ( SELECT deparse_test($1) qualifi... + ^ +QUERY: + WITH deparsed_query AS ( SELECT deparse_test($1) qualified_query ) + SELECT run_command_on_workers(qualified_query) FROM deparsed_query d + +CONTEXT: SQL function "deparse_and_run_on_workers" statement 1 +SELECT deparse_and_run_on_workers($cmd$ +DROP PROCEDURE IF EXISTS missing_schema.missing_PROCEDURE(int,float) CASCADE; +$cmd$); +ERROR: syntax error at or near "PROCEDURE" +LINE 2: WITH deparsed_query AS ( SELECT deparse_test($1) qualifi... + ^ +QUERY: + WITH deparsed_query AS ( SELECT deparse_test($1) qualified_query ) + SELECT run_command_on_workers(qualified_query) FROM deparsed_query d + +CONTEXT: SQL function "deparse_and_run_on_workers" statement 1 +-- clear objects +SET client_min_messages TO WARNING; -- suppress cascading objects dropping +DROP SCHEMA procedure_tests CASCADE; +DROP ROLE PROCEDURE_role; diff --git a/src/test/regress/multi_schedule b/src/test/regress/multi_schedule index 49ef8fdbb..2c1084cd0 100644 --- a/src/test/regress/multi_schedule +++ b/src/test/regress/multi_schedule @@ -282,3 +282,8 @@ test: ssl_by_default # --------- test: distributed_types distributed_types_conflict disable_object_propagation test: distributed_functions + +# --------- +# deparsing logic tests +# --------- +test: multi_deparse_function multi_deparse_procedure diff --git a/src/test/regress/sql/multi_deparse_function.sql b/src/test/regress/sql/multi_deparse_function.sql new file mode 100644 index 000000000..a673e3170 --- /dev/null +++ b/src/test/regress/sql/multi_deparse_function.sql @@ -0,0 +1,366 @@ +-- +-- Regression tests for deparsing ALTER/DROP FUNCTION Queries +-- +-- This test implements all the possible queries as of Postgres 11 +-- in the order they are listed in the docs +-- +-- ALTER FUNCTION name [ ( [ [ argmode ] [ argname ] argtype [, ...] ] ) ] +-- action [ ... ] [ RESTRICT ] +-- ALTER FUNCTION name [ ( [ [ argmode ] [ argname ] argtype [, ...] ] ) ] +-- RENAME TO new_name +-- ALTER FUNCTION name [ ( [ [ argmode ] [ argname ] argtype [, ...] ] ) ] +-- OWNER TO { new_owner | CURRENT_USER | SESSION_USER } +-- ALTER FUNCTION name [ ( [ [ argmode ] [ argname ] argtype [, ...] ] ) ] +-- SET SCHEMA new_schema +-- ALTER FUNCTION name [ ( [ [ argmode ] [ argname ] argtype [, ...] ] ) ] +-- DEPENDS ON EXTENSION extension_name +-- +-- where action is one of: +-- +-- CALLED ON NULL INPUT | RETURNS NULL ON NULL INPUT | STRICT +-- IMMUTABLE | STABLE | VOLATILE | [ NOT ] LEAKPROOF +-- [ EXTERNAL ] SECURITY INVOKER | [ EXTERNAL ] SECURITY DEFINER +-- PARALLEL { UNSAFE | RESTRICTED | SAFE } +-- COST execution_cost +-- ROWS result_rows +-- SET configuration_parameter { TO | = } { value | DEFAULT } +-- SET configuration_parameter FROM CURRENT +-- RESET configuration_parameter +-- RESET ALL +-- +-- DROP FUNCTION [ IF EXISTS ] name [ ( [ [ argmode ] [ argname ] argtype [, ...] ] ) ] [, ...] +-- [ CASCADE | RESTRICT ] + +SET citus.next_shard_id TO 20020000; + +CREATE SCHEMA function_tests; +SET search_path TO function_tests; +SET citus.shard_count TO 4; +SET client_min_messages TO INFO; + +CREATE FUNCTION deparse_test(text) + RETURNS text + AS 'citus' + LANGUAGE C STRICT; + +CREATE OR REPLACE FUNCTION deparse_and_run_on_workers(IN query text, + OUT nodename text, + OUT nodeport int, + OUT success bool, + OUT result text) + RETURNS SETOF record + LANGUAGE PLPGSQL AS $fnc$ + DECLARE + deparsed_query character varying(255); + BEGIN + deparsed_query := ( SELECT deparse_test($1) ); + RAISE INFO 'Propagating deparsed query: %', deparsed_query; + RETURN QUERY SELECT * FROM run_command_on_workers(deparsed_query); + END; + $fnc$; + + +-- Create a simple function and distribute it +CREATE FUNCTION add(integer, integer) RETURNS integer + AS 'select $1 + $2;' + LANGUAGE SQL + IMMUTABLE + RETURNS NULL ON NULL INPUT; +SELECT create_distributed_function('add(int,int)'); + +SELECT deparse_and_run_on_workers($cmd$ +ALTER FUNCTION add CALLED ON NULL INPUT +$cmd$); + +-- RETURNS NULL ON NULL INPUT and STRICT are synonyms and can be used interchangeably +-- RETURNS NULL ON NULL INPUT is actually stored as STRICT in the query parse tree +SELECT deparse_and_run_on_workers($cmd$ +ALTER FUNCTION add RETURNS NULL ON NULL INPUT +$cmd$); + +SELECT deparse_and_run_on_workers($cmd$ +ALTER FUNCTION add STRICT +$cmd$); + + +SELECT deparse_and_run_on_workers($cmd$ +ALTER FUNCTION add IMMUTABLE +$cmd$); + +SELECT deparse_and_run_on_workers($cmd$ +ALTER FUNCTION add STABLE +$cmd$); + +SELECT deparse_and_run_on_workers($cmd$ +ALTER FUNCTION add VOLATILE +$cmd$); + +SELECT deparse_and_run_on_workers($cmd$ +ALTER FUNCTION add LEAKPROOF +$cmd$); + +SELECT deparse_and_run_on_workers($cmd$ +ALTER FUNCTION add NOT LEAKPROOF +$cmd$); + + +-- EXTERNAL keyword is ignored by Postgres Parser. It is allowed only for SQL conformance +-- The following queries will not have the EXTERNAL keyword after deparsing +SELECT deparse_and_run_on_workers($cmd$ +ALTER FUNCTION add EXTERNAL SECURITY INVOKER +$cmd$); + +SELECT deparse_and_run_on_workers($cmd$ +ALTER FUNCTION add SECURITY INVOKER +$cmd$); + +SELECT deparse_and_run_on_workers($cmd$ +ALTER FUNCTION add EXTERNAL SECURITY DEFINER +$cmd$); + +SELECT deparse_and_run_on_workers($cmd$ +ALTER FUNCTION add SECURITY DEFINER +$cmd$); + + +SELECT deparse_and_run_on_workers($cmd$ +ALTER FUNCTION add PARALLEL UNSAFE +$cmd$); + +SELECT deparse_and_run_on_workers($cmd$ +ALTER FUNCTION add PARALLEL RESTRICTED +$cmd$); + +SELECT deparse_and_run_on_workers($cmd$ +ALTER FUNCTION add PARALLEL SAFE +$cmd$); + + +-- The COST arguments should always be numeric +SELECT deparse_and_run_on_workers($cmd$ +ALTER FUNCTION add COST 1234 +$cmd$); + +SELECT deparse_and_run_on_workers($cmd$ +ALTER FUNCTION add COST 1234.5 +$cmd$); + +SELECT deparse_and_run_on_workers($cmd$ +ALTER FUNCTION add SET log_min_messages = ERROR +$cmd$); + +SELECT deparse_and_run_on_workers($cmd$ +ALTER FUNCTION add SET log_min_messages TO DEFAULT +$cmd$); + +SELECT deparse_and_run_on_workers($cmd$ +ALTER FUNCTION add SET log_min_messages FROM CURRENT +$cmd$); + +SELECT deparse_and_run_on_workers($cmd$ +ALTER FUNCTION add RESET log_min_messages +$cmd$); + +SELECT deparse_and_run_on_workers($cmd$ +ALTER FUNCTION add RESET ALL +$cmd$); + +-- Rename the function in the workers +SELECT deparse_and_run_on_workers($cmd$ +ALTER FUNCTION add RENAME TO summation +$cmd$); + +-- Rename the function inb the coordinator as well. +-- This is needed so the next query is parsed on the coordinator +ALTER FUNCTION add RENAME TO summation; + +-- Rename it back to the original so that the next tests can pass +SELECT deparse_and_run_on_workers($cmd$ +ALTER FUNCTION summation RENAME TO add +$cmd$); + +-- Rename the function back to the original name in the coordinator +ALTER FUNCTION summation RENAME TO add; + +CREATE ROLE function_role; +SELECT run_command_on_workers('CREATE ROLE function_role'); + +SELECT deparse_and_run_on_workers($cmd$ +ALTER FUNCTION add OWNER TO function_role +$cmd$); + +SELECT deparse_and_run_on_workers($cmd$ +ALTER FUNCTION add OWNER TO missing_role +$cmd$); + +-- SET the schema in workers as well as the coordinator so that it remains in the same schema +SELECT deparse_and_run_on_workers($cmd$ +ALTER FUNCTION add SET SCHEMA public +$cmd$); +ALTER FUNCTION add SET SCHEMA public; + +-- Revert the schema back +SELECT deparse_and_run_on_workers($cmd$ +ALTER FUNCTION public.add SET SCHEMA function_tests +$cmd$); +ALTER FUNCTION public.add SET SCHEMA function_tests; + +SELECT deparse_and_run_on_workers($cmd$ +ALTER FUNCTION add DEPENDS ON EXTENSION citus +$cmd$); + +-- make sure "any" type is correctly deparsed +SELECT deparse_and_run_on_workers($cmd$ +ALTER FUNCTION pg_catalog.get_shard_id_for_distribution_column(table_name regclass, distribution_value "any") PARALLEL SAFE; +$cmd$); + +-- Do not run valid drop queries in the workers +SELECT deparse_test($cmd$ +DROP FUNCTION add(int,int); +$cmd$); + +-- have multiple actions in a single query +SELECT deparse_and_run_on_workers($cmd$ +ALTER FUNCTION add volatile leakproof SECURITY DEFINER PARALLEL unsafe; +$cmd$); + +-- Check that an invalid function name is still parsed correctly +-- Test that it fails when run without IF EXISTS clause +SELECT deparse_and_run_on_workers($cmd$ +DROP FUNCTION missing_function(int, text); +$cmd$); + +-- Check that an invalid function name is still parsed correctly +-- Test that it is successful when run with IF EXISTS clause +SELECT deparse_and_run_on_workers($cmd$ +DROP FUNCTION IF EXISTS missing_function(int, text); +$cmd$); + +SELECT deparse_and_run_on_workers($cmd$ +DROP FUNCTION IF EXISTS missing_schema.missing_function(int,float); +$cmd$); + +SELECT deparse_and_run_on_workers($cmd$ +DROP FUNCTION IF EXISTS missing_func_without_args; +$cmd$); + +-- create schema with weird names +CREATE SCHEMA "CiTuS.TeeN"; +CREATE SCHEMA "CiTUS.TEEN2"; + +SELECT run_command_on_workers($$ + CREATE SCHEMA IF NOT EXISTS "CiTuS.TeeN"; + CREATE SCHEMA IF NOT EXISTS "CiTUS.TEEN2"; +$$); + +-- create table with weird names +CREATE FUNCTION "CiTuS.TeeN"."TeeNFunCT10N.1!?!"() RETURNS TEXT + AS $$ SELECT 'test function without params' $$ + LANGUAGE SQL; + +CREATE FUNCTION "CiTuS.TeeN"."TeeNFunCT10N.1!?!"(text) RETURNS TEXT + AS $$ SELECT 'Overloaded function called with param: ' || $1 $$ + LANGUAGE SQL; + +SELECT create_distributed_function('"CiTuS.TeeN"."TeeNFunCT10N.1!?!"()'); +SELECT create_distributed_function('"CiTuS.TeeN"."TeeNFunCT10N.1!?!"(text)'); + +SELECT deparse_and_run_on_workers($cmd$ +ALTER FUNCTION "CiTuS.TeeN"."TeeNFunCT10N.1!?!"() SET SCHEMA "CiTUS.TEEN2" +$cmd$); + +-- drop 2 functions at the same time +SELECT deparse_and_run_on_workers($cmd$ +DROP FUNCTION "CiTUS.TEEN2"."TeeNFunCT10N.1!?!"(),"CiTuS.TeeN"."TeeNFunCT10N.1!?!"(text); +$cmd$); + +-- a function with a default parameter +CREATE FUNCTION func_default_param(param INT DEFAULT 0) RETURNS TEXT + AS $$ SELECT 'supplied param is : ' || param; $$ + LANGUAGE SQL; +SELECT create_distributed_function('func_default_param(INT)'); + +SELECT deparse_and_run_on_workers($cmd$ +ALTER FUNCTION func_default_param RENAME TO func_with_default_param; +$cmd$); + +-- a function with IN and OUT parameters +CREATE FUNCTION func_out_param(IN param INT, OUT result TEXT) + AS $$ SELECT 'supplied param is : ' || param; $$ + LANGUAGE SQL; +SELECT create_distributed_function('func_out_param(INT)'); + +SELECT deparse_and_run_on_workers($cmd$ +ALTER FUNCTION func_out_param RENAME TO func_in_and_out_param; +$cmd$); + +-- a function with INOUT parameter +CREATE FUNCTION square(INOUT a NUMERIC) +AS $$ +BEGIN + a := a * a; +END; $$ +LANGUAGE plpgsql; +SELECT create_distributed_function('square(NUMERIC)'); + +SELECT deparse_and_run_on_workers($cmd$ +ALTER FUNCTION square SET search_path TO DEFAULT; +$cmd$); + +-- a function with variadic input. +CREATE FUNCTION sum_avg( + VARIADIC list NUMERIC[], + OUT total NUMERIC, + OUT average NUMERIC) +AS $$ +BEGIN + SELECT INTO total SUM(list[i]) + FROM generate_subscripts(list, 1) g(i); + + SELECT INTO average AVG(list[i]) + FROM generate_subscripts(list, 1) g(i); +END; $$ +LANGUAGE plpgsql; +SELECT create_distributed_function('sum_avg(NUMERIC[])'); + +SELECT deparse_and_run_on_workers($cmd$ +ALTER FUNCTION sum_avg COST 10000; +$cmd$); + +-- a function with a custom type IN parameter +CREATE TYPE intpair AS (x int, y int); +CREATE FUNCTION func_custom_param(IN param intpair, OUT total INT) + AS $$ SELECT param.x + param.y $$ + LANGUAGE SQL; +SELECT create_distributed_function('func_custom_param(intpair)'); + +SELECT deparse_and_run_on_workers($cmd$ +ALTER FUNCTION func_custom_param RENAME TO func_with_custom_param; +$cmd$); + + +-- a function that returns TABLE +CREATE FUNCTION func_returns_table(IN count INT) + RETURNS TABLE (x INT, y INT) + AS $$ SELECT i,i FROM generate_series(1,count) i $$ + LANGUAGE SQL; +SELECT create_distributed_function('func_returns_table(INT)'); + +SELECT deparse_and_run_on_workers($cmd$ +ALTER FUNCTION func_returns_table ROWS 100; +$cmd$); + +-- clear objects +SET client_min_messages TO WARNING; -- suppress cascading objects dropping + +DROP SCHEMA "CiTuS.TeeN" CASCADE; +DROP SCHEMA "CiTUS.TEEN2" CASCADE; +DROP SCHEMA function_tests CASCADE; + +SELECT run_command_on_workers($$ + DROP SCHEMA "CiTuS.TeeN" CASCADE; + DROP SCHEMA "CiTUS.TEEN2" CASCADE; + DROP SCHEMA function_tests CASCADE; +$$); + +DROP ROLE function_role; diff --git a/src/test/regress/sql/multi_deparse_procedure.sql b/src/test/regress/sql/multi_deparse_procedure.sql new file mode 100644 index 000000000..0261b3c9e --- /dev/null +++ b/src/test/regress/sql/multi_deparse_procedure.sql @@ -0,0 +1,212 @@ +-- +-- Regression tests for deparsing ALTER/DROP PROCEDURE Queries +-- +-- ALTER PROCEDURE name [ ( [ [ argmode ] [ argname ] argtype [, ...] ] ) ] +-- action [ ... ] [ RESTRICT ] +-- ALTER PROCEDURE name [ ( [ [ argmode ] [ argname ] argtype [, ...] ] ) ] +-- RENAME TO new_name +-- ALTER PROCEDURE name [ ( [ [ argmode ] [ argname ] argtype [, ...] ] ) ] +-- OWNER TO { new_owner | CURRENT_USER | SESSION_USER } +-- ALTER PROCEDURE name [ ( [ [ argmode ] [ argname ] argtype [, ...] ] ) ] +-- SET SCHEMA new_schema +-- ALTER PROCEDURE name [ ( [ [ argmode ] [ argname ] argtype [, ...] ] ) ] +-- DEPENDS ON EXTENSION extension_name + +-- where action is one of: + +-- [ EXTERNAL ] SECURITY INVOKER | [ EXTERNAL ] SECURITY DEFINER +-- SET configuration_parameter { TO | = } { value | DEFAULT } +-- SET configuration_parameter FROM CURRENT +-- RESET configuration_parameter +-- RESET ALL +-- +-- DROP PROCEDURE [ IF EXISTS ] name [ ( [ [ argmode ] [ argname ] argtype [, ...] ] ) ] [, ...] +-- [ CASCADE | RESTRICT ] +-- +-- Please note that current deparser does not return errors on some invalid queries. +-- +-- For example CALLED ON NULL INPUT action is valid only for FUNCTIONS, but we still +-- allow deparsing them here. + +SET citus.next_shard_id TO 20030000; + +CREATE SCHEMA procedure_tests; +SET search_path TO procedure_tests; +SET citus.shard_count TO 4; +SET client_min_messages TO INFO; + +-- print whether we're using version > 10 to make version-specific tests clear +SHOW server_version \gset +SELECT substring(:'server_version', '\d+')::int > 10 AS version_above_ten; + +CREATE FUNCTION deparse_test(text) + RETURNS text + AS 'citus' + LANGUAGE C STRICT; + +CREATE FUNCTION deparse_and_run_on_workers(text) + RETURNS SETOF record + AS $fnc$ + WITH deparsed_query AS ( SELECT deparse_test($1) qualified_query ) + SELECT run_command_on_workers(qualified_query) FROM deparsed_query d + $fnc$ + LANGUAGE SQL; + +-- Create a simple PROCEDURE and distribute it +CREATE OR REPLACE PROCEDURE raise_info(text) +LANGUAGE PLPGSQL AS $proc$ +BEGIN + RAISE INFO 'information message %', $1; +END; +$proc$; +SELECT create_distributed_function('raise_info(text)'); + +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info CALLED ON NULL INPUT +$cmd$); + +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info RETURNS NULL ON NULL INPUT +$cmd$); + +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info STRICT +$cmd$); + + +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info IMMUTABLE +$cmd$); + +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info STABLE +$cmd$); + +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info VOLATILE +$cmd$); + +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info LEAKPROOF +$cmd$); + +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info NOT LEAKPROOF +$cmd$); + + +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info EXTERNAL SECURITY INVOKER +$cmd$); + +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info SECURITY INVOKER +$cmd$); + +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info EXTERNAL SECURITY DEFINER +$cmd$); + +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info SECURITY DEFINER +$cmd$); + + +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info PARALLEL UNSAFE +$cmd$); + +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info PARALLEL RESTRICTED +$cmd$); + +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info PARALLEL SAFE +$cmd$); + + +-- The COST/ROWS arguments should always be numeric +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info COST 1234 +$cmd$); + +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info COST 1234.5 +$cmd$); + +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info ROWS 10 +$cmd$); + +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info ROWS 10.8 +$cmd$); + +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info SECURITY INVOKER SET client_min_messages TO warning; +$cmd$); + + +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info SET log_min_messages = ERROR +$cmd$); + +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info SET log_min_messages TO DEFAULT +$cmd$); + +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info SET log_min_messages FROM CURRENT +$cmd$); + +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info RESET log_min_messages +$cmd$); + +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info RESET ALL +$cmd$); + +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info RENAME TO summation +$cmd$); + +CREATE ROLE PROCEDURE_role; + +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info OWNER TO PROCEDURE_role +$cmd$); + +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info OWNER TO missing_role +$cmd$); + +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info SET SCHEMA public +$cmd$); + +SELECT deparse_and_run_on_workers($cmd$ +ALTER PROCEDURE raise_info DEPENDS ON EXTENSION citus +$cmd$); + +SELECT deparse_and_run_on_workers($cmd$ +DROP PROCEDURE IF EXISTS raise_info(int,int); +$cmd$); + +-- Check that an invalid PROCEDURE name is still parsed correctly +SELECT deparse_and_run_on_workers($cmd$ +DROP PROCEDURE IF EXISTS missing_PROCEDURE(int, text); +$cmd$); + +SELECT deparse_and_run_on_workers($cmd$ +DROP PROCEDURE IF EXISTS missing_schema.missing_PROCEDURE(int,float); +$cmd$); + +SELECT deparse_and_run_on_workers($cmd$ +DROP PROCEDURE IF EXISTS missing_schema.missing_PROCEDURE(int,float) CASCADE; +$cmd$); + +-- clear objects +SET client_min_messages TO WARNING; -- suppress cascading objects dropping +DROP SCHEMA procedure_tests CASCADE; +DROP ROLE PROCEDURE_role;