| /*------------------------------------------------------------------------- |
| * |
| * functioncmds.c |
| * |
| * Routines for CREATE and DROP FUNCTION commands and CREATE and DROP |
| * CAST commands. |
| * |
| * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group |
| * Portions Copyright (c) 1994, Regents of the University of California |
| * |
| * |
| * IDENTIFICATION |
| * src/backend/commands/functioncmds.c |
| * |
| * DESCRIPTION |
| * These routines take the parse tree and pick out the |
| * appropriate arguments/flags, and pass the results to the |
| * corresponding "FooDefine" routines (in src/catalog) that do |
| * the actual catalog-munging. These routines also verify permission |
| * of the user to execute the command. |
| * |
| * NOTES |
| * These things must be defined and committed in the following order: |
| * "create function": |
| * input/output, recv/send procedures |
| * "create type": |
| * type |
| * "create operator": |
| * operators |
| * |
| *------------------------------------------------------------------------- |
| */ |
| #include "postgres.h" |
| |
| #include "access/genam.h" |
| #include "access/htup_details.h" |
| #include "access/sysattr.h" |
| #include "access/table.h" |
| #include "catalog/catalog.h" |
| #include "catalog/dependency.h" |
| #include "catalog/indexing.h" |
| #include "catalog/objectaccess.h" |
| #include "catalog/oid_dispatch.h" |
| #include "catalog/pg_aggregate.h" |
| #include "catalog/pg_cast.h" |
| #include "catalog/pg_language.h" |
| #include "catalog/pg_namespace.h" |
| #include "catalog/pg_proc.h" |
| #include "catalog/pg_proc_callback.h" |
| #include "catalog/pg_transform.h" |
| #include "catalog/pg_type.h" |
| #include "commands/alter.h" |
| #include "commands/defrem.h" |
| #include "commands/extension.h" |
| #include "commands/proclang.h" |
| #include "executor/execdesc.h" |
| #include "executor/executor.h" |
| #include "executor/functions.h" |
| #include "funcapi.h" |
| #include "miscadmin.h" |
| #include "optimizer/optimizer.h" |
| #include "parser/analyze.h" |
| #include "parser/parse_coerce.h" |
| #include "parser/parse_collate.h" |
| #include "parser/parse_expr.h" |
| #include "parser/parse_func.h" |
| #include "parser/parse_type.h" |
| #include "pgstat.h" |
| #include "tcop/pquery.h" |
| #include "tcop/utility.h" |
| #include "utils/acl.h" |
| #include "utils/builtins.h" |
| #include "utils/faultinjector.h" |
| #include "utils/fmgroids.h" |
| #include "utils/guc.h" |
| #include "utils/lsyscache.h" |
| #include "utils/memutils.h" |
| #include "utils/rel.h" |
| #include "utils/snapmgr.h" |
| #include "utils/syscache.h" |
| #include "utils/typcache.h" |
| |
| #include "catalog/heap.h" |
| #include "catalog/oid_dispatch.h" |
| #include "cdb/cdbdisp_query.h" |
| #include "cdb/cdbvars.h" |
| |
| |
| static void CheckForModifySystemFunc(Oid funcOid, List *funcName); |
| |
| |
| /* |
| * Examine the RETURNS clause of the CREATE FUNCTION statement |
| * and return information about it as *prorettype_p and *returnsSet. |
| * |
| * This is more complex than the average typename lookup because we want to |
| * allow a shell type to be used, or even created if the specified return type |
| * doesn't exist yet. (Without this, there's no way to define the I/O procs |
| * for a new type.) But SQL function creation won't cope, so error out if |
| * the target language is SQL. (We do this here, not in the SQL-function |
| * validator, so as not to produce a NOTICE and then an ERROR for the same |
| * condition.) |
| */ |
| static void |
| compute_return_type(TypeName *returnType, Oid languageOid, |
| Oid *prorettype_p, bool *returnsSet_p) |
| { |
| Oid rettype; |
| Type typtup; |
| AclResult aclresult; |
| |
| typtup = LookupTypeName(NULL, returnType, NULL, false); |
| |
| if (typtup) |
| { |
| if (!((Form_pg_type) GETSTRUCT(typtup))->typisdefined) |
| { |
| if (languageOid == SQLlanguageId) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), |
| errmsg("SQL function cannot return shell type %s", |
| TypeNameToString(returnType)))); |
| else if (Gp_role != GP_ROLE_EXECUTE) |
| ereport(NOTICE, |
| (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
| errmsg("return type %s is only a shell", |
| TypeNameToString(returnType)))); |
| } |
| rettype = typeTypeId(typtup); |
| ReleaseSysCache(typtup); |
| } |
| else |
| { |
| char *typnam = TypeNameToString(returnType); |
| Oid namespaceId; |
| AclResult aclresult; |
| char *typname; |
| ObjectAddress address; |
| |
| /* |
| * Only C-coded functions can be I/O functions. We enforce this |
| * restriction here mainly to prevent littering the catalogs with |
| * shell types due to simple typos in user-defined function |
| * definitions. |
| */ |
| if (languageOid != INTERNALlanguageId && |
| languageOid != ClanguageId) |
| ereport(ERROR, |
| (errcode(ERRCODE_UNDEFINED_OBJECT), |
| errmsg("type \"%s\" does not exist", typnam))); |
| |
| /* Reject if there's typmod decoration, too */ |
| if (returnType->typmods != NIL) |
| ereport(ERROR, |
| (errcode(ERRCODE_SYNTAX_ERROR), |
| errmsg("type modifier cannot be specified for shell type \"%s\"", |
| typnam))); |
| |
| /* Otherwise, go ahead and make a shell type */ |
| if (Gp_role == GP_ROLE_EXECUTE) |
| { |
| ereport(DEBUG1, |
| (errcode(ERRCODE_UNDEFINED_OBJECT), |
| errmsg("type \"%s\" is not yet defined", typnam), |
| errdetail("Creating a shell type definition."))); |
| } |
| else |
| { |
| ereport(NOTICE, |
| (errcode(ERRCODE_UNDEFINED_OBJECT), |
| errmsg("type \"%s\" is not yet defined", typnam), |
| errdetail("Creating a shell type definition."))); |
| } |
| namespaceId = QualifiedNameGetCreationNamespace(returnType->names, |
| &typname); |
| aclresult = pg_namespace_aclcheck(namespaceId, GetUserId(), |
| ACL_CREATE); |
| if (aclresult != ACLCHECK_OK) |
| aclcheck_error(aclresult, OBJECT_SCHEMA, |
| get_namespace_name(namespaceId)); |
| address = TypeShellMake(typname, namespaceId, GetUserId()); |
| rettype = address.objectId; |
| Assert(OidIsValid(rettype)); |
| } |
| |
| aclresult = pg_type_aclcheck(rettype, GetUserId(), ACL_USAGE); |
| if (aclresult != ACLCHECK_OK) |
| aclcheck_error_type(aclresult, rettype); |
| |
| *prorettype_p = rettype; |
| *returnsSet_p = returnType->setof; |
| } |
| |
| /* |
| * Interpret the function parameter list of a CREATE FUNCTION, |
| * CREATE PROCEDURE, or CREATE AGGREGATE statement. |
| * |
| * Input parameters: |
| * parameters: list of FunctionParameter structs |
| * languageOid: OID of function language (InvalidOid if it's CREATE AGGREGATE) |
| * objtype: identifies type of object being created |
| * |
| * Results are stored into output parameters. parameterTypes must always |
| * be created, but the other arrays/lists can be NULL pointers if not needed. |
| * variadicArgType is set to the variadic array type if there's a VARIADIC |
| * parameter (there can be only one); or to InvalidOid if not. |
| * requiredResultType is set to InvalidOid if there are no OUT parameters, |
| * else it is set to the OID of the implied result type. |
| */ |
| void |
| interpret_function_parameter_list(ParseState *pstate, |
| List *parameters, |
| Oid languageOid, |
| ObjectType objtype, |
| oidvector **parameterTypes, |
| List **parameterTypes_list, |
| ArrayType **allParameterTypes, |
| ArrayType **parameterModes, |
| ArrayType **parameterNames, |
| List **inParameterNames_list, |
| List **parameterDefaults, |
| Oid *variadicArgType, |
| Oid *requiredResultType) |
| { |
| int parameterCount = list_length(parameters); |
| Oid *inTypes; |
| int inCount = 0; |
| Datum *allTypes; |
| Datum *paramModes; |
| Datum *paramNames; |
| int outCount = 0; |
| int varCount = 0; |
| int multisetCount = 0; |
| bool have_names = false; |
| bool have_defaults = false; |
| ListCell *x; |
| int i; |
| |
| *variadicArgType = InvalidOid; /* default result */ |
| *requiredResultType = InvalidOid; /* default result */ |
| |
| inTypes = (Oid *) palloc(parameterCount * sizeof(Oid)); |
| allTypes = (Datum *) palloc(parameterCount * sizeof(Datum)); |
| paramModes = (Datum *) palloc(parameterCount * sizeof(Datum)); |
| paramNames = (Datum *) palloc0(parameterCount * sizeof(Datum)); |
| *parameterDefaults = NIL; |
| |
| /* Scan the list and extract data into work arrays */ |
| i = 0; |
| foreach(x, parameters) |
| { |
| FunctionParameter *fp = (FunctionParameter *) lfirst(x); |
| TypeName *t = fp->argType; |
| FunctionParameterMode fpmode = fp->mode; |
| bool isinput = false; |
| Oid toid; |
| Type typtup; |
| AclResult aclresult; |
| |
| /* For our purposes here, a defaulted mode spec is identical to IN */ |
| if (fpmode == FUNC_PARAM_DEFAULT) |
| fpmode = FUNC_PARAM_IN; |
| |
| typtup = LookupTypeName(NULL, t, NULL, false); |
| if (typtup) |
| { |
| if (!((Form_pg_type) GETSTRUCT(typtup))->typisdefined) |
| { |
| /* As above, hard error if language is SQL */ |
| if (languageOid == SQLlanguageId) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), |
| errmsg("SQL function cannot accept shell type %s", |
| TypeNameToString(t)))); |
| /* We don't allow creating aggregates on shell types either */ |
| else if (objtype == OBJECT_AGGREGATE) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), |
| errmsg("aggregate cannot accept shell type %s", |
| TypeNameToString(t)))); |
| else if (Gp_role != GP_ROLE_EXECUTE) |
| ereport(NOTICE, |
| (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
| errmsg("argument type %s is only a shell", |
| TypeNameToString(t)))); |
| } |
| toid = typeTypeId(typtup); |
| ReleaseSysCache(typtup); |
| } |
| else |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_UNDEFINED_OBJECT), |
| errmsg("type %s does not exist", |
| TypeNameToString(t)))); |
| toid = InvalidOid; /* keep compiler quiet */ |
| } |
| |
| aclresult = pg_type_aclcheck(toid, GetUserId(), ACL_USAGE); |
| if (aclresult != ACLCHECK_OK) |
| aclcheck_error_type(aclresult, toid); |
| |
| if (t->setof) |
| { |
| if (objtype == OBJECT_AGGREGATE) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), |
| errmsg("aggregates cannot accept set arguments"))); |
| else if (objtype == OBJECT_PROCEDURE) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), |
| errmsg("procedures cannot accept set arguments"))); |
| else |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), |
| errmsg("functions cannot accept set arguments"))); |
| } |
| |
| /* handle input parameters */ |
| if (fpmode != FUNC_PARAM_OUT && fpmode != FUNC_PARAM_TABLE) |
| { |
| /* other input parameters can't follow a VARIADIC parameter */ |
| if (varCount > 0) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), |
| errmsg("VARIADIC parameter must be the last input parameter"))); |
| inTypes[inCount++] = toid; |
| isinput = true; |
| |
| /* Keep track of the number of anytable arguments */ |
| if (toid == ANYTABLEOID) |
| multisetCount++; |
| |
| if (parameterTypes_list) |
| *parameterTypes_list = lappend_oid(*parameterTypes_list, toid); |
| } |
| |
| /* handle output parameters */ |
| if (fpmode != FUNC_PARAM_IN && fpmode != FUNC_PARAM_VARIADIC) |
| { |
| if (toid == ANYTABLEOID) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), |
| errmsg("functions cannot return \"anytable\" arguments"))); |
| |
| if (objtype == OBJECT_PROCEDURE) |
| { |
| /* |
| * We disallow OUT-after-VARIADIC only for procedures. While |
| * such a case causes no confusion in ordinary function calls, |
| * it would cause confusion in a CALL statement. |
| */ |
| if (varCount > 0) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), |
| errmsg("VARIADIC parameter must be the last parameter"))); |
| /* Procedures with output parameters always return RECORD */ |
| *requiredResultType = RECORDOID; |
| } |
| else if (outCount == 0) /* save first output param's type */ |
| *requiredResultType = toid; |
| outCount++; |
| } |
| |
| if (fpmode == FUNC_PARAM_VARIADIC) |
| { |
| *variadicArgType = toid; |
| varCount++; |
| /* validate variadic parameter type */ |
| switch (toid) |
| { |
| case ANYARRAYOID: |
| case ANYCOMPATIBLEARRAYOID: |
| case ANYOID: |
| /* okay */ |
| break; |
| default: |
| if (!OidIsValid(get_element_type(toid))) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), |
| errmsg("VARIADIC parameter must be an array"))); |
| break; |
| } |
| |
| isinput = true; |
| } |
| |
| allTypes[i] = ObjectIdGetDatum(toid); |
| |
| paramModes[i] = CharGetDatum(fpmode); |
| |
| if (fp->name && fp->name[0]) |
| { |
| ListCell *px; |
| |
| /* |
| * As of Postgres 9.0 we disallow using the same name for two |
| * input or two output function parameters. Depending on the |
| * function's language, conflicting input and output names might |
| * be bad too, but we leave it to the PL to complain if so. |
| */ |
| foreach(px, parameters) |
| { |
| FunctionParameter *prevfp = (FunctionParameter *) lfirst(px); |
| FunctionParameterMode prevfpmode; |
| |
| if (prevfp == fp) |
| break; |
| /* as above, default mode is IN */ |
| prevfpmode = prevfp->mode; |
| if (prevfpmode == FUNC_PARAM_DEFAULT) |
| prevfpmode = FUNC_PARAM_IN; |
| /* pure in doesn't conflict with pure out */ |
| if ((fpmode == FUNC_PARAM_IN || |
| fpmode == FUNC_PARAM_VARIADIC) && |
| (prevfpmode == FUNC_PARAM_OUT || |
| prevfpmode == FUNC_PARAM_TABLE)) |
| continue; |
| if ((prevfpmode == FUNC_PARAM_IN || |
| prevfpmode == FUNC_PARAM_VARIADIC) && |
| (fpmode == FUNC_PARAM_OUT || |
| fpmode == FUNC_PARAM_TABLE)) |
| continue; |
| if (prevfp->name && prevfp->name[0] && |
| strcmp(prevfp->name, fp->name) == 0) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), |
| errmsg("parameter name \"%s\" used more than once", |
| fp->name))); |
| } |
| |
| paramNames[i] = CStringGetTextDatum(fp->name); |
| have_names = true; |
| } |
| |
| if (inParameterNames_list) |
| *inParameterNames_list = lappend(*inParameterNames_list, makeString(fp->name ? fp->name : pstrdup(""))); |
| |
| if (fp->defexpr) |
| { |
| Node *def; |
| |
| if (!isinput) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), |
| errmsg("only input parameters can have default values"))); |
| |
| if (toid == ANYTABLEOID) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), |
| errmsg("anytable parameter cannot have default value"))); |
| |
| def = transformExpr(pstate, fp->defexpr, |
| EXPR_KIND_FUNCTION_DEFAULT); |
| def = coerce_to_specific_type(pstate, def, toid, "DEFAULT"); |
| assign_expr_collations(pstate, def); |
| |
| /* |
| * Make sure no variables are referred to (this is probably dead |
| * code now that add_missing_from is history). |
| */ |
| if (list_length(pstate->p_rtable) != 0 || |
| contain_var_clause(def)) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_COLUMN_REFERENCE), |
| errmsg("cannot use table references in parameter default value"))); |
| |
| /* |
| * transformExpr() should have already rejected subqueries, |
| * aggregates, and window functions, based on the EXPR_KIND_ for a |
| * default expression. |
| * |
| * It can't return a set either --- but coerce_to_specific_type |
| * already checked that for us. |
| * |
| * Note: the point of these restrictions is to ensure that an |
| * expression that, on its face, hasn't got subplans, aggregates, |
| * etc cannot suddenly have them after function default arguments |
| * are inserted. |
| */ |
| |
| *parameterDefaults = lappend(*parameterDefaults, def); |
| have_defaults = true; |
| } |
| else |
| { |
| if (isinput && have_defaults) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), |
| errmsg("input parameters after one with a default value must also have defaults"))); |
| |
| /* |
| * For procedures, we also can't allow OUT parameters after one |
| * with a default, because the same sort of confusion arises in a |
| * CALL statement. |
| */ |
| if (objtype == OBJECT_PROCEDURE && have_defaults) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), |
| errmsg("procedure OUT parameters cannot appear after one with a default value"))); |
| } |
| |
| i++; |
| } |
| |
| /* Currently only support single multiset input parameters */ |
| if (multisetCount > 1) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| errmsg("functions cannot have multiple \"anytable\" arguments"))); |
| } |
| |
| /* Now construct the proper outputs as needed */ |
| *parameterTypes = buildoidvector(inTypes, inCount); |
| |
| if (outCount > 0 || varCount > 0) |
| { |
| *allParameterTypes = construct_array(allTypes, parameterCount, OIDOID, |
| sizeof(Oid), true, TYPALIGN_INT); |
| *parameterModes = construct_array(paramModes, parameterCount, CHAROID, |
| 1, true, TYPALIGN_CHAR); |
| if (outCount > 1) |
| *requiredResultType = RECORDOID; |
| /* otherwise we set requiredResultType correctly above */ |
| } |
| else |
| { |
| *allParameterTypes = NULL; |
| *parameterModes = NULL; |
| } |
| |
| if (have_names) |
| { |
| for (i = 0; i < parameterCount; i++) |
| { |
| if (paramNames[i] == PointerGetDatum(NULL)) |
| paramNames[i] = CStringGetTextDatum(""); |
| } |
| *parameterNames = construct_array(paramNames, parameterCount, TEXTOID, |
| -1, false, TYPALIGN_INT); |
| } |
| else |
| *parameterNames = NULL; |
| } |
| |
| |
| /* |
| * Recognize one of the options that can be passed to both CREATE |
| * FUNCTION and ALTER FUNCTION and return it via one of the out |
| * parameters. Returns true if the passed option was recognized. If |
| * the out parameter we were going to assign to points to non-NULL, |
| * raise a duplicate-clause error. (We don't try to detect duplicate |
| * SET parameters though --- if you're redundant, the last one wins.) |
| */ |
| static bool |
| compute_common_attribute(ParseState *pstate, |
| bool is_procedure, |
| DefElem *defel, |
| DefElem **volatility_item, |
| DefElem **strict_item, |
| DefElem **security_item, |
| DefElem **leakproof_item, |
| List **set_items, |
| DefElem **cost_item, |
| DefElem **rows_item, |
| DefElem **support_item, |
| DefElem **parallel_item, |
| DefElem **describe_item, |
| DefElem **data_access_item, |
| DefElem **exec_location_item) |
| { |
| if (strcmp(defel->defname, "volatility") == 0) |
| { |
| if (is_procedure) |
| goto procedure_error; |
| if (*volatility_item) |
| goto duplicate_error; |
| |
| *volatility_item = defel; |
| } |
| else if (strcmp(defel->defname, "strict") == 0) |
| { |
| if (is_procedure) |
| goto procedure_error; |
| if (*strict_item) |
| goto duplicate_error; |
| |
| *strict_item = defel; |
| } |
| else if (strcmp(defel->defname, "security") == 0) |
| { |
| if (*security_item) |
| goto duplicate_error; |
| |
| *security_item = defel; |
| } |
| else if (strcmp(defel->defname, "leakproof") == 0) |
| { |
| if (is_procedure) |
| goto procedure_error; |
| if (*leakproof_item) |
| goto duplicate_error; |
| |
| *leakproof_item = defel; |
| } |
| else if (strcmp(defel->defname, "set") == 0) |
| { |
| *set_items = lappend(*set_items, defel->arg); |
| } |
| else if (strcmp(defel->defname, "cost") == 0) |
| { |
| if (is_procedure) |
| goto procedure_error; |
| if (*cost_item) |
| goto duplicate_error; |
| |
| *cost_item = defel; |
| } |
| else if (strcmp(defel->defname, "rows") == 0) |
| { |
| if (is_procedure) |
| goto procedure_error; |
| if (*rows_item) |
| goto duplicate_error; |
| |
| *rows_item = defel; |
| } |
| else if (strcmp(defel->defname, "support") == 0) |
| { |
| if (is_procedure) |
| goto procedure_error; |
| if (*support_item) |
| goto duplicate_error; |
| |
| *support_item = defel; |
| } |
| else if (strcmp(defel->defname, "parallel") == 0) |
| { |
| if (is_procedure) |
| goto procedure_error; |
| if (*parallel_item) |
| goto duplicate_error; |
| |
| *parallel_item = defel; |
| } |
| else if (strcmp(defel->defname, "describe") == 0) |
| { |
| if (*describe_item) |
| goto duplicate_error; |
| |
| *describe_item = defel; |
| } |
| else if (strcmp(defel->defname, "data_access") == 0) |
| { |
| if (*data_access_item) |
| goto duplicate_error; |
| |
| *data_access_item = defel; |
| } |
| else if (strcmp(defel->defname, "exec_location") == 0) |
| { |
| if (*exec_location_item) |
| goto duplicate_error; |
| |
| *exec_location_item = defel; |
| } |
| else |
| return false; |
| |
| /* Recognized an option */ |
| return true; |
| |
| duplicate_error: |
| ereport(ERROR, |
| (errcode(ERRCODE_SYNTAX_ERROR), |
| errmsg("conflicting or redundant options"), |
| parser_errposition(pstate, defel->location))); |
| return false; /* keep compiler quiet */ |
| |
| procedure_error: |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), |
| errmsg("invalid attribute in procedure definition"), |
| parser_errposition(pstate, defel->location))); |
| return false; |
| } |
| |
| static char |
| interpret_func_volatility(DefElem *defel) |
| { |
| char *str = strVal(defel->arg); |
| |
| if (strcmp(str, "immutable") == 0) |
| return PROVOLATILE_IMMUTABLE; |
| else if (strcmp(str, "stable") == 0) |
| return PROVOLATILE_STABLE; |
| else if (strcmp(str, "volatile") == 0) |
| return PROVOLATILE_VOLATILE; |
| else |
| { |
| elog(ERROR, "invalid volatility \"%s\"", str); |
| return 0; /* keep compiler quiet */ |
| } |
| } |
| |
| static char |
| interpret_data_access(DefElem *defel) |
| { |
| char *str = strVal(defel->arg); |
| char proDataAccess = PRODATAACCESS_NONE; |
| |
| if (strcmp(str, "none") == 0) |
| proDataAccess = PRODATAACCESS_NONE; |
| else if (strcmp(str, "contains") == 0) |
| proDataAccess = PRODATAACCESS_CONTAINS; |
| else if (strcmp(str, "reads") == 0) |
| proDataAccess = PRODATAACCESS_READS; |
| else if (strcmp(str, "modifies") == 0) |
| proDataAccess = PRODATAACCESS_MODIFIES; |
| else |
| elog(ERROR, "invalid data access \"%s\"", str); |
| |
| return proDataAccess; |
| } |
| |
| static char |
| getDefaultDataAccess(Oid languageOid) |
| { |
| char proDataAccess = PRODATAACCESS_NONE; |
| if (languageOid == SQLlanguageId) |
| proDataAccess = PRODATAACCESS_CONTAINS; |
| |
| return proDataAccess; |
| } |
| |
| static void |
| validate_sql_data_access(char data_access, char volatility, Oid languageOid) |
| { |
| /* IMMUTABLE is not compatible with READS SQL DATA or MODIFIES SQL DATA */ |
| if (volatility == PROVOLATILE_IMMUTABLE && |
| data_access == PRODATAACCESS_READS) |
| ereport(ERROR, |
| (errcode(ERRCODE_SYNTAX_ERROR), |
| errmsg("conflicting options"), |
| errhint("IMMUTABLE conflicts with READS SQL DATA."))); |
| |
| if (volatility == PROVOLATILE_IMMUTABLE && |
| data_access == PRODATAACCESS_MODIFIES) |
| ereport(ERROR, |
| (errcode(ERRCODE_SYNTAX_ERROR), |
| errmsg("conflicting options"), |
| errhint("IMMUTABLE conflicts with MODIFIES SQL DATA."))); |
| |
| /* SQL language function cannot specify NO SQL */ |
| if (languageOid == SQLlanguageId && data_access == PRODATAACCESS_NONE) |
| ereport(ERROR, |
| (errcode(ERRCODE_SYNTAX_ERROR), |
| errmsg("conflicting options"), |
| errhint("A SQL function cannot specify NO SQL."))); |
| } |
| |
| static char |
| interpret_exec_location(DefElem *defel) |
| { |
| char *str = strVal(defel->arg); |
| char exec_location; |
| |
| if (strcmp(str, "any") == 0) |
| exec_location = PROEXECLOCATION_ANY; |
| else if (strcmp(str, "coordinator") == 0) |
| exec_location = PROEXECLOCATION_COORDINATOR; |
| else if (strcmp(str, "initplan") == 0) |
| exec_location = PROEXECLOCATION_INITPLAN; |
| else if (strcmp(str, "all_segments") == 0) |
| exec_location = PROEXECLOCATION_ALL_SEGMENTS; |
| else |
| elog(ERROR, "invalid data access \"%s\"", str); |
| |
| return exec_location; |
| } |
| |
| |
| static void |
| validate_sql_exec_location(char exec_location, bool proretset) |
| { |
| /* |
| * ON COORDINATOR and ON ALL SEGMENTS are only supported for set-returning |
| * functions. |
| */ |
| switch (exec_location) |
| { |
| case PROEXECLOCATION_ANY: |
| /* ok */ |
| break; |
| |
| case PROEXECLOCATION_COORDINATOR: |
| if (!proretset) |
| ereport(ERROR, |
| (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| errmsg("EXECUTE ON COORDINATOR is only supported for set-returning functions"))); |
| break; |
| |
| case PROEXECLOCATION_INITPLAN: |
| if (!proretset) |
| ereport(ERROR, |
| (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| errmsg("EXECUTE ON INITPLAN is only supported for set-returning functions"))); |
| break; |
| |
| case PROEXECLOCATION_ALL_SEGMENTS: |
| if (!proretset) |
| ereport(ERROR, |
| (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| errmsg("EXECUTE ON ALL SEGMENTS is only supported for set-returning functions"))); |
| break; |
| |
| default: |
| elog(ERROR, "unrecognized EXECUTE ON type '%c'", exec_location); |
| } |
| } |
| |
| static char |
| interpret_func_parallel(DefElem *defel) |
| { |
| char *str = strVal(defel->arg); |
| |
| if (strcmp(str, "safe") == 0) |
| return PROPARALLEL_SAFE; |
| else if (strcmp(str, "unsafe") == 0) |
| return PROPARALLEL_UNSAFE; |
| else if (strcmp(str, "restricted") == 0) |
| return PROPARALLEL_RESTRICTED; |
| else |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_SYNTAX_ERROR), |
| errmsg("parameter \"parallel\" must be SAFE, RESTRICTED, or UNSAFE"))); |
| return PROPARALLEL_UNSAFE; /* keep compiler quiet */ |
| } |
| } |
| |
| /* |
| * Update a proconfig value according to a list of VariableSetStmt items. |
| * |
| * The input and result may be NULL to signify a null entry. |
| */ |
| static ArrayType * |
| update_proconfig_value(ArrayType *a, List *set_items) |
| { |
| ListCell *l; |
| |
| foreach(l, set_items) |
| { |
| VariableSetStmt *sstmt = lfirst_node(VariableSetStmt, l); |
| |
| if (sstmt->kind == VAR_RESET_ALL) |
| a = NULL; |
| else |
| { |
| char *valuestr = ExtractSetVariableArgs(sstmt); |
| |
| if (valuestr) |
| a = GUCArrayAdd(a, sstmt->name, valuestr); |
| else /* RESET */ |
| a = GUCArrayDelete(a, sstmt->name); |
| } |
| } |
| |
| return a; |
| } |
| |
| static Oid |
| interpret_func_support(DefElem *defel) |
| { |
| List *procName = defGetQualifiedName(defel); |
| Oid procOid; |
| Oid argList[1]; |
| |
| /* |
| * Support functions always take one INTERNAL argument and return |
| * INTERNAL. |
| */ |
| argList[0] = INTERNALOID; |
| |
| procOid = LookupFuncName(procName, 1, argList, true); |
| if (!OidIsValid(procOid)) |
| ereport(ERROR, |
| (errcode(ERRCODE_UNDEFINED_FUNCTION), |
| errmsg("function %s does not exist", |
| func_signature_string(procName, 1, NIL, argList)))); |
| |
| if (get_func_rettype(procOid) != INTERNALOID) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), |
| errmsg("support function %s must return type %s", |
| NameListToString(procName), "internal"))); |
| |
| /* |
| * Someday we might want an ACL check here; but for now, we insist that |
| * you be superuser to specify a support function, so privilege on the |
| * support function is moot. |
| */ |
| if (!superuser()) |
| ereport(ERROR, |
| (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), |
| errmsg("must be superuser to specify a support function"))); |
| |
| return procOid; |
| } |
| |
| |
| /* |
| * Dissect the list of options assembled in gram.y into function |
| * attributes. |
| */ |
| static void |
| compute_function_attributes(ParseState *pstate, |
| bool is_procedure, |
| List *options, |
| List **as, |
| char **language, |
| Node **transform, |
| bool *windowfunc_p, |
| char *volatility_p, |
| bool *strict_p, |
| bool *security_definer, |
| bool *leakproof_p, |
| ArrayType **proconfig, |
| float4 *procost, |
| float4 *prorows, |
| Oid *prosupport, |
| char *parallel_p, |
| List **describeQualName_p, |
| char *data_access_p, |
| char *exec_location_p) |
| { |
| ListCell *option; |
| DefElem *as_item = NULL; |
| DefElem *language_item = NULL; |
| DefElem *transform_item = NULL; |
| DefElem *windowfunc_item = NULL; |
| DefElem *volatility_item = NULL; |
| DefElem *strict_item = NULL; |
| DefElem *security_item = NULL; |
| DefElem *leakproof_item = NULL; |
| List *set_items = NIL; |
| DefElem *cost_item = NULL; |
| DefElem *rows_item = NULL; |
| DefElem *support_item = NULL; |
| DefElem *parallel_item = NULL; |
| DefElem *describe_item = NULL; |
| DefElem *data_access_item = NULL; |
| DefElem *exec_location_item = NULL; |
| |
| foreach(option, options) |
| { |
| DefElem *defel = (DefElem *) lfirst(option); |
| |
| if (strcmp(defel->defname, "as") == 0) |
| { |
| if (as_item) |
| ereport(ERROR, |
| (errcode(ERRCODE_SYNTAX_ERROR), |
| errmsg("conflicting or redundant options"), |
| parser_errposition(pstate, defel->location))); |
| as_item = defel; |
| } |
| else if (strcmp(defel->defname, "language") == 0) |
| { |
| if (language_item) |
| ereport(ERROR, |
| (errcode(ERRCODE_SYNTAX_ERROR), |
| errmsg("conflicting or redundant options"), |
| parser_errposition(pstate, defel->location))); |
| language_item = defel; |
| } |
| else if (strcmp(defel->defname, "transform") == 0) |
| { |
| if (transform_item) |
| ereport(ERROR, |
| (errcode(ERRCODE_SYNTAX_ERROR), |
| errmsg("conflicting or redundant options"), |
| parser_errposition(pstate, defel->location))); |
| transform_item = defel; |
| } |
| else if (strcmp(defel->defname, "window") == 0) |
| { |
| if (windowfunc_item) |
| ereport(ERROR, |
| (errcode(ERRCODE_SYNTAX_ERROR), |
| errmsg("conflicting or redundant options"), |
| parser_errposition(pstate, defel->location))); |
| if (is_procedure) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), |
| errmsg("invalid attribute in procedure definition"), |
| parser_errposition(pstate, defel->location))); |
| windowfunc_item = defel; |
| } |
| else if (compute_common_attribute(pstate, |
| is_procedure, |
| defel, |
| &volatility_item, |
| &strict_item, |
| &security_item, |
| &leakproof_item, |
| &set_items, |
| &cost_item, |
| &rows_item, |
| &support_item, |
| ¶llel_item, |
| &describe_item, |
| &data_access_item, |
| &exec_location_item)) |
| { |
| /* recognized common option */ |
| continue; |
| } |
| else |
| elog(ERROR, "option \"%s\" not recognized", |
| defel->defname); |
| } |
| |
| if (as_item) |
| *as = (List *) as_item->arg; |
| if (language_item) |
| *language = strVal(language_item->arg); |
| if (transform_item) |
| *transform = transform_item->arg; |
| if (windowfunc_item) |
| *windowfunc_p = intVal(windowfunc_item->arg); |
| if (volatility_item) |
| *volatility_p = interpret_func_volatility(volatility_item); |
| if (strict_item) |
| *strict_p = intVal(strict_item->arg); |
| if (security_item) |
| *security_definer = intVal(security_item->arg); |
| if (leakproof_item) |
| *leakproof_p = intVal(leakproof_item->arg); |
| if (set_items) |
| *proconfig = update_proconfig_value(NULL, set_items); |
| if (cost_item) |
| { |
| *procost = defGetNumeric(cost_item); |
| if (*procost <= 0) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
| errmsg("COST must be positive"))); |
| } |
| if (rows_item) |
| { |
| *prorows = defGetNumeric(rows_item); |
| if (*prorows <= 0) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
| errmsg("ROWS must be positive"))); |
| } |
| if (support_item) |
| *prosupport = interpret_func_support(support_item); |
| if (parallel_item) |
| *parallel_p = interpret_func_parallel(parallel_item); |
| if (describe_item) |
| *describeQualName_p = defGetQualifiedName(describe_item); |
| if (data_access_item) |
| *data_access_p = interpret_data_access(data_access_item); |
| if (exec_location_item) |
| *exec_location_p = interpret_exec_location(exec_location_item); |
| } |
| |
| |
| /* |
| * For a dynamically linked C language object, the form of the clause is |
| * |
| * AS <object file name> [, <link symbol name> ] |
| * |
| * In all other cases |
| * |
| * AS <object reference, or sql code> |
| */ |
| static void |
| interpret_AS_clause(Oid languageOid, const char *languageName, |
| char *funcname, List *as, Node *sql_body_in, |
| List *parameterTypes, List *inParameterNames, |
| char **prosrc_str_p, char **probin_str_p, |
| Node **sql_body_out, |
| const char *queryString) |
| { |
| if (!sql_body_in && !as) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), |
| errmsg("no function body specified"))); |
| |
| if (sql_body_in && as) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), |
| errmsg("duplicate function body specified"))); |
| |
| if (sql_body_in && languageOid != SQLlanguageId) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), |
| errmsg("inline SQL function body only valid for language SQL"))); |
| |
| *sql_body_out = NULL; |
| |
| if (languageOid == ClanguageId) |
| { |
| /* |
| * For "C" language, store the file name in probin and, when given, |
| * the link symbol name in prosrc. If link symbol is omitted, |
| * substitute procedure name. We also allow link symbol to be |
| * specified as "-", since that was the habit in PG versions before |
| * 8.4, and there might be dump files out there that don't translate |
| * that back to "omitted". |
| */ |
| *probin_str_p = strVal(linitial(as)); |
| if (list_length(as) == 1) |
| *prosrc_str_p = funcname; |
| else |
| { |
| *prosrc_str_p = strVal(lsecond(as)); |
| if (strcmp(*prosrc_str_p, "-") == 0) |
| *prosrc_str_p = funcname; |
| } |
| } |
| else if (sql_body_in) |
| { |
| SQLFunctionParseInfoPtr pinfo; |
| |
| pinfo = (SQLFunctionParseInfoPtr) palloc0(sizeof(SQLFunctionParseInfo)); |
| |
| pinfo->fname = funcname; |
| pinfo->nargs = list_length(parameterTypes); |
| pinfo->argtypes = (Oid *) palloc(pinfo->nargs * sizeof(Oid)); |
| pinfo->argnames = (char **) palloc(pinfo->nargs * sizeof(char *)); |
| for (int i = 0; i < list_length(parameterTypes); i++) |
| { |
| char *s = strVal(list_nth(inParameterNames, i)); |
| |
| pinfo->argtypes[i] = list_nth_oid(parameterTypes, i); |
| if (IsPolymorphicType(pinfo->argtypes[i])) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), |
| errmsg("SQL function with unquoted function body cannot have polymorphic arguments"))); |
| |
| if (s[0] != '\0') |
| pinfo->argnames[i] = s; |
| else |
| pinfo->argnames[i] = NULL; |
| } |
| |
| if (IsA(sql_body_in, List)) |
| { |
| List *stmts = linitial_node(List, castNode(List, sql_body_in)); |
| ListCell *lc; |
| List *transformed_stmts = NIL; |
| |
| foreach(lc, stmts) |
| { |
| Node *stmt = lfirst(lc); |
| Query *q; |
| ParseState *pstate = make_parsestate(NULL); |
| |
| pstate->p_sourcetext = queryString; |
| sql_fn_parser_setup(pstate, pinfo); |
| q = transformStmt(pstate, stmt); |
| if (q->commandType == CMD_UTILITY) |
| ereport(ERROR, |
| errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| errmsg("%s is not yet supported in unquoted SQL function body", |
| GetCommandTagName(CreateCommandTag(q->utilityStmt)))); |
| transformed_stmts = lappend(transformed_stmts, q); |
| free_parsestate(pstate); |
| } |
| |
| *sql_body_out = (Node *) list_make1(transformed_stmts); |
| } |
| else |
| { |
| Query *q; |
| ParseState *pstate = make_parsestate(NULL); |
| |
| pstate->p_sourcetext = queryString; |
| sql_fn_parser_setup(pstate, pinfo); |
| q = transformStmt(pstate, sql_body_in); |
| if (q->commandType == CMD_UTILITY) |
| ereport(ERROR, |
| errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| errmsg("%s is not yet supported in unquoted SQL function body", |
| GetCommandTagName(CreateCommandTag(q->utilityStmt)))); |
| free_parsestate(pstate); |
| |
| *sql_body_out = (Node *) q; |
| } |
| |
| /* |
| * We must put something in prosrc. For the moment, just record an |
| * empty string. It might be useful to store the original text of the |
| * CREATE FUNCTION statement --- but to make actual use of that in |
| * error reports, we'd also have to adjust readfuncs.c to not throw |
| * away node location fields when reading prosqlbody. |
| */ |
| *prosrc_str_p = pstrdup(""); |
| |
| /* But we definitely don't need probin. */ |
| *probin_str_p = NULL; |
| } |
| else |
| { |
| /* Everything else wants the given string in prosrc. */ |
| *prosrc_str_p = strVal(linitial(as)); |
| *probin_str_p = NULL; |
| |
| if (list_length(as) != 1) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), |
| errmsg("only one AS item needed for language \"%s\"", |
| languageName))); |
| |
| if (languageOid == INTERNALlanguageId) |
| { |
| /* |
| * In PostgreSQL versions before 6.5, the SQL name of the created |
| * function could not be different from the internal name, and |
| * "prosrc" wasn't used. So there is code out there that does |
| * CREATE FUNCTION xyz AS '' LANGUAGE internal. To preserve some |
| * modicum of backwards compatibility, accept an empty "prosrc" |
| * value as meaning the supplied SQL function name. |
| */ |
| if (strlen(*prosrc_str_p) == 0) |
| *prosrc_str_p = funcname; |
| } |
| } |
| |
| if (languageOid == INTERNALlanguageId) |
| { |
| /* |
| * In PostgreSQL versions before 6.5, the SQL name of the created |
| * function could not be different from the internal name, and |
| * "prosrc" wasn't used. So there is code out there that does |
| * CREATE FUNCTION xyz AS '' LANGUAGE internal. To preserve some |
| * modicum of backwards compatibility, accept an empty "prosrc" |
| * value as meaning the supplied SQL function name. |
| */ |
| if (strlen(*prosrc_str_p) == 0) |
| *prosrc_str_p = funcname; |
| } |
| } |
| |
| |
| /* |
| * Handle functions that try to define a "DESCRIBE" callback. |
| */ |
| static Oid |
| validate_describe_callback(List *describeQualName, |
| Oid returnTypeOid, |
| ArrayType *parameterModes) |
| { |
| int nargs = 1; |
| Oid inputTypeOids[1] = {INTERNALOID}; |
| Oid *actualInputTypeOids; |
| Oid describeReturnTypeOid; |
| Oid describeFuncOid; |
| bool describeReturnsSet; |
| FuncDetailCode fdResult; |
| AclResult aclresult; |
| int i; |
| |
| if (describeQualName == NIL) |
| return InvalidOid; |
| |
| /* |
| * describe callbacks only supported for functions that return either |
| * a pseudotype or a generic record. |
| */ |
| if (!TypeSupportsDescribe(returnTypeOid)) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), |
| errmsg("DESCRIBE only supported for functions returning \"record\""))); |
| } |
| if (parameterModes) |
| { |
| int len = ARR_DIMS(parameterModes)[0]; |
| char *modes = ARR_DATA_PTR(parameterModes); |
| |
| Assert(ARR_NDIM(parameterModes) == 1); |
| for (i = 0; i < len; i++) |
| { |
| switch (modes[i]) |
| { |
| case FUNC_PARAM_IN: |
| case FUNC_PARAM_VARIADIC: |
| break; |
| |
| case FUNC_PARAM_INOUT: |
| case FUNC_PARAM_OUT: |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), |
| errmsg("DESCRIBE is not supported for functions " |
| "with OUT parameters"))); |
| break; |
| |
| case FUNC_PARAM_TABLE: |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), |
| errmsg("DESCRIBE is not supported for functions " |
| "that return TABLE"))); |
| break; |
| |
| /* above list should be exhaustive */ |
| default: |
| elog(ERROR, "unrecognized function parameter mode: %c", modes[i]); |
| break; |
| } |
| } |
| } |
| int nvargs; |
| Oid vatype; |
| /* Lookup the function in the catalog */ |
| fdResult = func_get_detail(describeQualName, |
| NIL, /* argument expressions */ |
| NIL, /* argument names */ |
| nargs, |
| inputTypeOids, |
| false, /* expand_variadic */ |
| false, /* expand_defaults */ |
| false, /* GPDB_14_MERGE_FIXME: new added */ |
| &describeFuncOid, |
| &describeReturnTypeOid, |
| &describeReturnsSet, |
| &nvargs, |
| &vatype, |
| &actualInputTypeOids, |
| NULL); |
| |
| if (fdResult != FUNCDETAIL_NORMAL || !OidIsValid(describeFuncOid)) |
| { |
| /* Should we try to create the function when not found? */ |
| ereport(ERROR, |
| (errcode(ERRCODE_UNDEFINED_FUNCTION), |
| errmsg("function %s does not exist", |
| func_signature_string(describeQualName, nargs, NIL, inputTypeOids)))); |
| } |
| if (describeReturnTypeOid != INTERNALOID) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), |
| errmsg("return type of function %s is not \"internal\"", |
| func_signature_string(describeQualName, nargs, NIL, inputTypeOids)))); |
| } |
| if (describeReturnsSet) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), |
| errmsg("function %s returns a set", |
| func_signature_string(describeQualName, nargs, NIL, inputTypeOids)))); |
| } |
| |
| if (OidIsValid(vatype)) |
| ereport(ERROR, |
| (errcode(ERRCODE_DATATYPE_MISMATCH), |
| errmsg("describe function cannot be variadic"))); |
| |
| /* Check that the creator has permission to call the function */ |
| aclresult = pg_proc_aclcheck(describeFuncOid, GetUserId(), ACL_EXECUTE); |
| if (aclresult != ACLCHECK_OK) |
| aclcheck_error(aclresult, OBJECT_PROCEDURE, get_func_name(describeFuncOid)); |
| |
| /* Looks reasonable */ |
| return describeFuncOid; |
| } |
| |
| |
| /* |
| * CreateFunction |
| * Execute a CREATE FUNCTION (or CREATE PROCEDURE) utility statement. |
| */ |
| ObjectAddress |
| CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt) |
| { |
| char *probin_str; |
| char *prosrc_str; |
| Node *prosqlbody; |
| Oid prorettype; |
| bool returnsSet; |
| char *language; |
| Oid languageOid; |
| Oid languageValidator; |
| Node *transformDefElem = NULL; |
| char *funcname; |
| Oid namespaceId; |
| AclResult aclresult; |
| oidvector *parameterTypes; |
| List *parameterTypes_list = NIL; |
| ArrayType *allParameterTypes; |
| ArrayType *parameterModes; |
| ArrayType *parameterNames; |
| List *inParameterNames_list = NIL; |
| List *parameterDefaults; |
| Oid variadicArgType; |
| List *trftypes_list = NIL; |
| ArrayType *trftypes; |
| Oid requiredResultType; |
| bool isWindowFunc, |
| isStrict, |
| security, |
| isLeakProof; |
| char volatility; |
| ArrayType *proconfig; |
| float4 procost; |
| float4 prorows; |
| Oid prosupport; |
| HeapTuple languageTuple; |
| Form_pg_language languageStruct; |
| List *as_clause; |
| char parallel; |
| List *describeQualName = NIL; |
| Oid describeFuncOid = InvalidOid; |
| char dataAccess; |
| char execLocation; |
| ObjectAddress objAddr; |
| CreateFunctionStmt *original_stmt = copyObject(stmt); |
| |
| SIMPLE_FAULT_INJECTOR("create_function_fail"); |
| /* Convert list of names to a name and namespace */ |
| namespaceId = QualifiedNameGetCreationNamespace(stmt->funcname, |
| &funcname); |
| |
| /* Check we have creation rights in target namespace */ |
| aclresult = pg_namespace_aclcheck(namespaceId, GetUserId(), ACL_CREATE); |
| if (aclresult != ACLCHECK_OK) |
| aclcheck_error(aclresult, OBJECT_SCHEMA, |
| get_namespace_name(namespaceId)); |
| |
| /* Set default attributes */ |
| as_clause = NIL; |
| language = NULL; |
| isWindowFunc = false; |
| isStrict = false; |
| security = false; |
| isLeakProof = false; |
| volatility = PROVOLATILE_VOLATILE; |
| proconfig = NULL; |
| procost = -1; /* indicates not set */ |
| prorows = -1; /* indicates not set */ |
| prosupport = InvalidOid; |
| parallel = PROPARALLEL_UNSAFE; |
| dataAccess = '\0'; /* indicates not set */ |
| execLocation = '\0'; /* indicates not set */ |
| |
| /* Extract non-default attributes from stmt->options list */ |
| compute_function_attributes(pstate, |
| stmt->is_procedure, |
| stmt->options, |
| &as_clause, &language, &transformDefElem, |
| &isWindowFunc, &volatility, |
| &isStrict, &security, &isLeakProof, |
| &proconfig, &procost, &prorows, |
| &prosupport, ¶llel, |
| &describeQualName, |
| &dataAccess, &execLocation); |
| |
| if (!language) |
| { |
| if (stmt->sql_body) |
| language = "sql"; |
| else |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), |
| errmsg("no language specified"))); |
| } |
| |
| /* Look up the language and validate permissions */ |
| languageTuple = SearchSysCache1(LANGNAME, PointerGetDatum(language)); |
| if (!HeapTupleIsValid(languageTuple)) |
| ereport(ERROR, |
| (errcode(ERRCODE_UNDEFINED_OBJECT), |
| errmsg("language \"%s\" does not exist", language), |
| (extension_file_exists(language) ? |
| errhint("Use CREATE EXTENSION to load the language into the database.") : 0))); |
| |
| languageStruct = (Form_pg_language) GETSTRUCT(languageTuple); |
| languageOid = languageStruct->oid; |
| |
| /* If prodataaccess indicator not specified, fill in default. */ |
| if (dataAccess == '\0') |
| dataAccess = getDefaultDataAccess(languageOid); |
| |
| /* If proexeclocation indicator not specified, fill in default. */ |
| if (execLocation == '\0') |
| execLocation = PROEXECLOCATION_ANY; |
| |
| if (languageStruct->lanpltrusted) |
| { |
| /* if trusted language, need USAGE privilege */ |
| AclResult aclresult; |
| |
| aclresult = pg_language_aclcheck(languageOid, GetUserId(), ACL_USAGE); |
| if (aclresult != ACLCHECK_OK) |
| aclcheck_error(aclresult, OBJECT_LANGUAGE, |
| NameStr(languageStruct->lanname)); |
| } |
| else |
| { |
| /* if untrusted language, must be superuser */ |
| if (!superuser()) |
| aclcheck_error(ACLCHECK_NO_PRIV, OBJECT_LANGUAGE, |
| NameStr(languageStruct->lanname)); |
| } |
| |
| languageValidator = languageStruct->lanvalidator; |
| |
| ReleaseSysCache(languageTuple); |
| |
| /* |
| * Check consistency for data access. Note this comes after the language |
| * tuple lookup, as we need language oid. |
| */ |
| validate_sql_data_access(dataAccess, volatility, languageOid); |
| |
| /* |
| * Only superuser is allowed to create leakproof functions because |
| * leakproof functions can see tuples which have not yet been filtered out |
| * by security barrier views or row-level security policies. |
| */ |
| if (isLeakProof && !superuser()) |
| { |
| Oid role = get_role_oid("mdb_admin", true /*if nodoby created mdb_admin role in this database*/); |
| if (!is_member_of_role(GetUserId(), role)) |
| ereport(ERROR, |
| (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), |
| errmsg("only superuser or mdb_admin can define a leakproof function"))); |
| } |
| |
| if (transformDefElem) |
| { |
| ListCell *lc; |
| |
| foreach(lc, castNode(List, transformDefElem)) |
| { |
| Oid typeid = typenameTypeId(NULL, |
| lfirst_node(TypeName, lc)); |
| Oid elt = get_base_element_type(typeid); |
| |
| typeid = elt ? elt : typeid; |
| |
| get_transform_oid(typeid, languageOid, false); |
| trftypes_list = lappend_oid(trftypes_list, typeid); |
| } |
| } |
| |
| /* |
| * Convert remaining parameters of CREATE to form wanted by |
| * ProcedureCreate. |
| */ |
| interpret_function_parameter_list(pstate, |
| stmt->parameters, |
| languageOid, |
| stmt->is_procedure ? OBJECT_PROCEDURE : OBJECT_FUNCTION, |
| ¶meterTypes, |
| ¶meterTypes_list, |
| &allParameterTypes, |
| ¶meterModes, |
| ¶meterNames, |
| &inParameterNames_list, |
| ¶meterDefaults, |
| &variadicArgType, |
| &requiredResultType); |
| |
| if (stmt->is_procedure) |
| { |
| Assert(!stmt->returnType); |
| prorettype = requiredResultType ? requiredResultType : VOIDOID; |
| returnsSet = false; |
| } |
| else if (stmt->returnType) |
| { |
| /* explicit RETURNS clause */ |
| compute_return_type(stmt->returnType, languageOid, |
| &prorettype, &returnsSet); |
| if (OidIsValid(requiredResultType) && prorettype != requiredResultType) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), |
| errmsg("function result type must be %s because of OUT parameters", |
| format_type_be(requiredResultType)))); |
| } |
| else if (OidIsValid(requiredResultType)) |
| { |
| /* default RETURNS clause from OUT parameters */ |
| prorettype = requiredResultType; |
| returnsSet = false; |
| } |
| else |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), |
| errmsg("function result type must be specified"))); |
| /* Alternative possibility: default to RETURNS VOID */ |
| prorettype = VOIDOID; |
| returnsSet = false; |
| } |
| |
| validate_sql_exec_location(execLocation, returnsSet); |
| |
| if (list_length(trftypes_list) > 0) |
| { |
| ListCell *lc; |
| Datum *arr; |
| int i; |
| |
| arr = palloc(list_length(trftypes_list) * sizeof(Datum)); |
| i = 0; |
| foreach(lc, trftypes_list) |
| arr[i++] = ObjectIdGetDatum(lfirst_oid(lc)); |
| trftypes = construct_array(arr, list_length(trftypes_list), |
| OIDOID, sizeof(Oid), true, TYPALIGN_INT); |
| } |
| else |
| { |
| /* store SQL NULL instead of empty array */ |
| trftypes = NULL; |
| } |
| |
| interpret_AS_clause(languageOid, language, funcname, as_clause, stmt->sql_body, |
| parameterTypes_list, inParameterNames_list, |
| &prosrc_str, &probin_str, &prosqlbody, |
| pstate->p_sourcetext); |
| |
| /* double check that we really have a function body */ |
| /* prosrc_str doesn't point to a palloc()'d string in interpret_AS_clause() */ |
| if (prosrc_str == NULL) |
| prosrc_str = ""; |
| |
| /* Handle the describe callback, if any */ |
| if (describeQualName != NIL) |
| describeFuncOid = validate_describe_callback(describeQualName, |
| prorettype, |
| parameterModes); |
| |
| /* |
| * Set default values for COST and ROWS depending on other parameters; |
| * reject ROWS if it's not returnsSet. NB: pg_dump knows these default |
| * values, keep it in sync if you change them. |
| */ |
| if (procost < 0) |
| { |
| /* SQL and PL-language functions are assumed more expensive */ |
| if (languageOid == INTERNALlanguageId || |
| languageOid == ClanguageId) |
| procost = 1; |
| else |
| procost = 100; |
| } |
| if (prorows < 0) |
| { |
| if (returnsSet) |
| prorows = 1000; |
| else |
| prorows = 0; /* dummy value if not returnsSet */ |
| } |
| else if (!returnsSet) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
| errmsg("ROWS is not applicable when function does not return a set"))); |
| |
| /* |
| * And now that we have all the parameters, and know we're permitted to do |
| * so, go ahead and create the function. |
| */ |
| objAddr = ProcedureCreate(funcname, |
| namespaceId, |
| stmt->replace, |
| returnsSet, |
| prorettype, |
| GetUserId(), |
| languageOid, |
| languageValidator, |
| describeFuncOid, |
| prosrc_str, /* converted to text later */ |
| probin_str, /* converted to text later */ |
| prosqlbody, |
| stmt->is_procedure ? PROKIND_PROCEDURE : (isWindowFunc ? PROKIND_WINDOW : PROKIND_FUNCTION), |
| security, |
| isLeakProof, |
| isStrict, |
| volatility, |
| parallel, |
| parameterTypes, |
| PointerGetDatum(allParameterTypes), |
| PointerGetDatum(parameterModes), |
| PointerGetDatum(parameterNames), |
| parameterDefaults, |
| PointerGetDatum(trftypes), |
| PointerGetDatum(proconfig), |
| prosupport, |
| procost, |
| prorows, |
| dataAccess, |
| execLocation); |
| |
| if (Gp_role == GP_ROLE_DISPATCH) |
| { |
| CdbDispatchUtilityStatement((Node *) original_stmt, |
| DF_CANCEL_ON_ERROR| |
| DF_WITH_SNAPSHOT| |
| DF_NEED_TWO_PHASE, |
| GetAssignedOidsForDispatch(), |
| NULL); |
| } |
| |
| return objAddr; |
| } |
| |
| /* |
| * Guts of function deletion. |
| * |
| * Note: this is also used for aggregate deletion, since the OIDs of |
| * both functions and aggregates point to pg_proc. |
| */ |
| void |
| RemoveFunctionById(Oid funcOid) |
| { |
| Relation relation; |
| HeapTuple tup; |
| char prokind; |
| |
| /* |
| * Delete the pg_proc tuple. |
| */ |
| relation = table_open(ProcedureRelationId, RowExclusiveLock); |
| |
| tup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcOid)); |
| if (!HeapTupleIsValid(tup)) /* should not happen */ |
| elog(ERROR, "cache lookup failed for function %u", funcOid); |
| |
| prokind = ((Form_pg_proc) GETSTRUCT(tup))->prokind; |
| |
| CatalogTupleDelete(relation, &tup->t_self); |
| |
| ReleaseSysCache(tup); |
| |
| table_close(relation, RowExclusiveLock); |
| |
| /* Remove anything in pg_proc_callback for this function */ |
| deleteProcCallbacks(funcOid); |
| |
| /* |
| * If there's a pg_aggregate tuple, delete that too. |
| */ |
| if (prokind == PROKIND_AGGREGATE) |
| { |
| relation = table_open(AggregateRelationId, RowExclusiveLock); |
| |
| tup = SearchSysCache1(AGGFNOID, ObjectIdGetDatum(funcOid)); |
| if (!HeapTupleIsValid(tup)) /* should not happen */ |
| elog(ERROR, "cache lookup failed for pg_aggregate tuple for function %u", funcOid); |
| |
| CatalogTupleDelete(relation, &tup->t_self); |
| |
| ReleaseSysCache(tup); |
| |
| table_close(relation, RowExclusiveLock); |
| } |
| } |
| |
| /* |
| * Implements the ALTER FUNCTION utility command (except for the |
| * RENAME and OWNER clauses, which are handled as part of the generic |
| * ALTER framework). |
| */ |
| ObjectAddress |
| AlterFunction(ParseState *pstate, AlterFunctionStmt *stmt) |
| { |
| HeapTuple tup; |
| Oid funcOid; |
| Form_pg_proc procForm; |
| bool is_procedure; |
| Relation rel; |
| ListCell *l; |
| DefElem *volatility_item = NULL; |
| DefElem *strict_item = NULL; |
| DefElem *security_def_item = NULL; |
| DefElem *leakproof_item = NULL; |
| List *set_items = NIL; |
| DefElem *cost_item = NULL; |
| DefElem *rows_item = NULL; |
| DefElem *support_item = NULL; |
| DefElem *parallel_item = NULL; |
| ObjectAddress address; |
| DefElem *describe_item = NULL; |
| DefElem *data_access_item = NULL; |
| DefElem *exec_location_item = NULL; |
| bool isnull; |
| char data_access; |
| char exec_location; |
| |
| rel = table_open(ProcedureRelationId, RowExclusiveLock); |
| |
| funcOid = LookupFuncWithArgs(stmt->objtype, stmt->func, false); |
| |
| ObjectAddressSet(address, ProcedureRelationId, funcOid); |
| |
| tup = SearchSysCacheCopy1(PROCOID, ObjectIdGetDatum(funcOid)); |
| if (!HeapTupleIsValid(tup)) /* should not happen */ |
| elog(ERROR, "cache lookup failed for function %u", funcOid); |
| |
| procForm = (Form_pg_proc) GETSTRUCT(tup); |
| |
| /* Permission check: must own function */ |
| if (!pg_proc_ownercheck(funcOid, GetUserId())) |
| aclcheck_error(ACLCHECK_NOT_OWNER, stmt->objtype, |
| NameListToString(stmt->func->objname)); |
| |
| /* check bootstrap object */ |
| CheckForModifySystemFunc(funcOid, stmt->func->objname); |
| |
| if (procForm->prokind == PROKIND_AGGREGATE) |
| ereport(ERROR, |
| (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
| errmsg("\"%s\" is an aggregate function", |
| NameListToString(stmt->func->objname)))); |
| |
| is_procedure = (procForm->prokind == PROKIND_PROCEDURE); |
| |
| /* Examine requested actions. */ |
| foreach(l, stmt->actions) |
| { |
| DefElem *defel = (DefElem *) lfirst(l); |
| |
| if (compute_common_attribute(pstate, |
| is_procedure, |
| defel, |
| &volatility_item, |
| &strict_item, |
| &security_def_item, |
| &leakproof_item, |
| &set_items, |
| &cost_item, |
| &rows_item, |
| &support_item, |
| ¶llel_item, |
| &describe_item, |
| &data_access_item, |
| &exec_location_item) == false) |
| elog(ERROR, "option \"%s\" not recognized", defel->defname); |
| } |
| |
| if (volatility_item) |
| procForm->provolatile = interpret_func_volatility(volatility_item); |
| if (strict_item) |
| procForm->proisstrict = intVal(strict_item->arg); |
| if (security_def_item) |
| procForm->prosecdef = intVal(security_def_item->arg); |
| if (leakproof_item) |
| { |
| procForm->proleakproof = intVal(leakproof_item->arg); |
| if (procForm->proleakproof && !superuser()) |
| { |
| Oid role = get_role_oid("mdb_admin", true /*if nodoby created mdb_admin role in this database*/); |
| if (!is_member_of_role(GetUserId(), role)) |
| ereport(ERROR, |
| (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), |
| errmsg("only superuser or mdb_admin can define a leakproof function"))); |
| } |
| } |
| if (cost_item) |
| { |
| procForm->procost = defGetNumeric(cost_item); |
| if (procForm->procost <= 0) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
| errmsg("COST must be positive"))); |
| } |
| if (rows_item) |
| { |
| procForm->prorows = defGetNumeric(rows_item); |
| if (procForm->prorows <= 0) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
| errmsg("ROWS must be positive"))); |
| if (!procForm->proretset) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
| errmsg("ROWS is not applicable when function does not return a set"))); |
| } |
| if (support_item) |
| { |
| /* interpret_func_support handles the privilege check */ |
| Oid newsupport = interpret_func_support(support_item); |
| |
| /* Add or replace dependency on support function */ |
| if (OidIsValid(procForm->prosupport)) |
| changeDependencyFor(ProcedureRelationId, funcOid, |
| ProcedureRelationId, procForm->prosupport, |
| newsupport); |
| else |
| { |
| ObjectAddress referenced; |
| |
| referenced.classId = ProcedureRelationId; |
| referenced.objectId = newsupport; |
| referenced.objectSubId = 0; |
| recordDependencyOn(&address, &referenced, DEPENDENCY_NORMAL); |
| } |
| |
| procForm->prosupport = newsupport; |
| } |
| if (parallel_item) |
| procForm->proparallel = interpret_func_parallel(parallel_item); |
| if (set_items) |
| { |
| Datum datum; |
| bool isnull; |
| ArrayType *a; |
| Datum repl_val[Natts_pg_proc]; |
| bool repl_null[Natts_pg_proc]; |
| bool repl_repl[Natts_pg_proc]; |
| |
| /* extract existing proconfig setting */ |
| datum = SysCacheGetAttr(PROCOID, tup, Anum_pg_proc_proconfig, &isnull); |
| a = isnull ? NULL : DatumGetArrayTypeP(datum); |
| |
| /* update according to each SET or RESET item, left to right */ |
| a = update_proconfig_value(a, set_items); |
| |
| /* update the tuple */ |
| memset(repl_repl, false, sizeof(repl_repl)); |
| repl_repl[Anum_pg_proc_proconfig - 1] = true; |
| |
| if (a == NULL) |
| { |
| repl_val[Anum_pg_proc_proconfig - 1] = (Datum) 0; |
| repl_null[Anum_pg_proc_proconfig - 1] = true; |
| } |
| else |
| { |
| repl_val[Anum_pg_proc_proconfig - 1] = PointerGetDatum(a); |
| repl_null[Anum_pg_proc_proconfig - 1] = false; |
| } |
| |
| tup = heap_modify_tuple(tup, RelationGetDescr(rel), |
| repl_val, repl_null, repl_repl); |
| } |
| /* DO NOT put more touches of procForm below here; it's now dangling. */ |
| /* GPDB_14_MERGE_FIXME: except refetch procForm from new tup? */ |
| |
| if (describe_item) |
| { |
| elog(ERROR, "cannot change DESCRIBE function"); |
| } |
| if (data_access_item) |
| { |
| Datum repl_val[Natts_pg_proc]; |
| bool repl_null[Natts_pg_proc]; |
| bool repl_repl[Natts_pg_proc]; |
| |
| MemSet(repl_null, 0, sizeof(repl_null)); |
| MemSet(repl_repl, 0, sizeof(repl_repl)); |
| repl_repl[Anum_pg_proc_prodataaccess - 1] = true; |
| repl_val[Anum_pg_proc_prodataaccess - 1] = |
| CharGetDatum(interpret_data_access(data_access_item)); |
| |
| tup = heap_modify_tuple(tup, RelationGetDescr(rel), |
| repl_val, repl_null, repl_repl); |
| } |
| if (exec_location_item) |
| { |
| Datum repl_val[Natts_pg_proc]; |
| bool repl_null[Natts_pg_proc]; |
| bool repl_repl[Natts_pg_proc]; |
| |
| MemSet(repl_null, 0, sizeof(repl_null)); |
| MemSet(repl_repl, 0, sizeof(repl_repl)); |
| repl_repl[Anum_pg_proc_proexeclocation - 1] = true; |
| repl_val[Anum_pg_proc_proexeclocation - 1] = |
| CharGetDatum(interpret_exec_location(exec_location_item)); |
| |
| tup = heap_modify_tuple(tup, RelationGetDescr(rel), |
| repl_val, repl_null, repl_repl); |
| } |
| |
| data_access = DatumGetChar( |
| heap_getattr(tup, Anum_pg_proc_prodataaccess, |
| RelationGetDescr(rel), &isnull)); |
| Assert(!isnull); |
| exec_location = DatumGetChar( |
| heap_getattr(tup, Anum_pg_proc_proexeclocation, |
| RelationGetDescr(rel), &isnull)); |
| Assert(!isnull); |
| |
| /* |
| * GPDB_14_MERGE_FIXME: we need to check the new procForm as the tup is updated. |
| * The issue also exists in GPDB7. |
| */ |
| procForm = (Form_pg_proc) GETSTRUCT(tup); |
| |
| /* Cross check for various properties. */ |
| validate_sql_data_access(data_access, |
| procForm->provolatile, |
| procForm->prolang); |
| validate_sql_exec_location(exec_location, |
| procForm->proretset); |
| |
| /* Do the update */ |
| CatalogTupleUpdate(rel, &tup->t_self, tup); |
| |
| InvokeObjectPostAlterHook(ProcedureRelationId, funcOid, 0); |
| |
| table_close(rel, NoLock); |
| heap_freetuple(tup); |
| |
| if (Gp_role == GP_ROLE_DISPATCH) |
| { |
| CdbDispatchUtilityStatement((Node *) stmt, |
| DF_CANCEL_ON_ERROR| |
| DF_WITH_SNAPSHOT| |
| DF_NEED_TWO_PHASE, |
| NIL, |
| NULL); |
| } |
| |
| return address; |
| } |
| |
| |
| /* |
| * CREATE CAST |
| */ |
| ObjectAddress |
| CreateCast(CreateCastStmt *stmt) |
| { |
| Oid sourcetypeid; |
| Oid targettypeid; |
| char sourcetyptype; |
| char targettyptype; |
| Oid funcid; |
| int nargs; |
| char castcontext; |
| char castmethod; |
| HeapTuple tuple; |
| AclResult aclresult; |
| ObjectAddress myself; |
| |
| sourcetypeid = typenameTypeId(NULL, stmt->sourcetype); |
| targettypeid = typenameTypeId(NULL, stmt->targettype); |
| sourcetyptype = get_typtype(sourcetypeid); |
| targettyptype = get_typtype(targettypeid); |
| |
| /* No pseudo-types allowed */ |
| if (sourcetyptype == TYPTYPE_PSEUDO) |
| ereport(ERROR, |
| (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
| errmsg("source data type %s is a pseudo-type", |
| TypeNameToString(stmt->sourcetype)))); |
| |
| if (targettyptype == TYPTYPE_PSEUDO) |
| ereport(ERROR, |
| (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
| errmsg("target data type %s is a pseudo-type", |
| TypeNameToString(stmt->targettype)))); |
| |
| /* Permission check */ |
| if (!pg_type_ownercheck(sourcetypeid, GetUserId()) |
| && !pg_type_ownercheck(targettypeid, GetUserId())) |
| ereport(ERROR, |
| (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), |
| errmsg("must be owner of type %s or type %s", |
| format_type_be(sourcetypeid), |
| format_type_be(targettypeid)))); |
| |
| aclresult = pg_type_aclcheck(sourcetypeid, GetUserId(), ACL_USAGE); |
| if (aclresult != ACLCHECK_OK) |
| aclcheck_error_type(aclresult, sourcetypeid); |
| |
| aclresult = pg_type_aclcheck(targettypeid, GetUserId(), ACL_USAGE); |
| if (aclresult != ACLCHECK_OK) |
| aclcheck_error_type(aclresult, targettypeid); |
| |
| /* Domains are allowed for historical reasons, but we warn */ |
| if (sourcetyptype == TYPTYPE_DOMAIN) |
| ereport(WARNING, |
| (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
| errmsg("cast will be ignored because the source data type is a domain"))); |
| |
| else if (targettyptype == TYPTYPE_DOMAIN) |
| ereport(WARNING, |
| (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
| errmsg("cast will be ignored because the target data type is a domain"))); |
| |
| /* Determine the cast method */ |
| if (stmt->func != NULL) |
| castmethod = COERCION_METHOD_FUNCTION; |
| else if (stmt->inout) |
| castmethod = COERCION_METHOD_INOUT; |
| else |
| castmethod = COERCION_METHOD_BINARY; |
| |
| if (castmethod == COERCION_METHOD_FUNCTION) |
| { |
| Form_pg_proc procstruct; |
| |
| funcid = LookupFuncWithArgs(OBJECT_FUNCTION, stmt->func, false); |
| |
| tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid)); |
| if (!HeapTupleIsValid(tuple)) |
| elog(ERROR, "cache lookup failed for function %u", funcid); |
| |
| procstruct = (Form_pg_proc) GETSTRUCT(tuple); |
| nargs = procstruct->pronargs; |
| if (nargs < 1 || nargs > 3) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), |
| errmsg("cast function must take one to three arguments"))); |
| if (!IsBinaryCoercible(sourcetypeid, procstruct->proargtypes.values[0])) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), |
| errmsg("argument of cast function must match or be binary-coercible from source data type"))); |
| if (nargs > 1 && procstruct->proargtypes.values[1] != INT4OID) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), |
| errmsg("second argument of cast function must be type %s", |
| "integer"))); |
| if (nargs > 2 && procstruct->proargtypes.values[2] != BOOLOID) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), |
| errmsg("third argument of cast function must be type %s", |
| "boolean"))); |
| if (!IsBinaryCoercible(procstruct->prorettype, targettypeid)) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), |
| errmsg("return data type of cast function must match or be binary-coercible to target data type"))); |
| |
| /* |
| * Restricting the volatility of a cast function may or may not be a |
| * good idea in the abstract, but it definitely breaks many old |
| * user-defined types. Disable this check --- tgl 2/1/03 |
| */ |
| #ifdef NOT_USED |
| if (procstruct->provolatile == PROVOLATILE_VOLATILE) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), |
| errmsg("cast function must not be volatile"))); |
| #endif |
| if (procstruct->prokind != PROKIND_FUNCTION) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), |
| errmsg("cast function must be a normal function"))); |
| if (procstruct->proretset) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), |
| errmsg("cast function must not return a set"))); |
| |
| ReleaseSysCache(tuple); |
| } |
| else |
| { |
| funcid = InvalidOid; |
| nargs = 0; |
| } |
| |
| if (castmethod == COERCION_METHOD_BINARY) |
| { |
| int16 typ1len; |
| int16 typ2len; |
| bool typ1byval; |
| bool typ2byval; |
| char typ1align; |
| char typ2align; |
| |
| /* |
| * Must be superuser to create binary-compatible casts, since |
| * erroneous casts can easily crash the backend. |
| */ |
| if (!superuser()) |
| ereport(ERROR, |
| (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), |
| errmsg("must be superuser to create a cast WITHOUT FUNCTION"))); |
| |
| /* |
| * Also, insist that the types match as to size, alignment, and |
| * pass-by-value attributes; this provides at least a crude check that |
| * they have similar representations. A pair of types that fail this |
| * test should certainly not be equated. |
| */ |
| get_typlenbyvalalign(sourcetypeid, &typ1len, &typ1byval, &typ1align); |
| get_typlenbyvalalign(targettypeid, &typ2len, &typ2byval, &typ2align); |
| if (typ1len != typ2len || |
| typ1byval != typ2byval || |
| typ1align != typ2align) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), |
| errmsg("source and target data types are not physically compatible"))); |
| |
| /* |
| * We know that composite, enum and array types are never binary- |
| * compatible with each other. They all have OIDs embedded in them. |
| * |
| * Theoretically you could build a user-defined base type that is |
| * binary-compatible with a composite, enum, or array type. But we |
| * disallow that too, as in practice such a cast is surely a mistake. |
| * You can always work around that by writing a cast function. |
| */ |
| if (sourcetyptype == TYPTYPE_COMPOSITE || |
| targettyptype == TYPTYPE_COMPOSITE) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), |
| errmsg("composite data types are not binary-compatible"))); |
| |
| if (sourcetyptype == TYPTYPE_ENUM || |
| targettyptype == TYPTYPE_ENUM) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), |
| errmsg("enum data types are not binary-compatible"))); |
| |
| if (OidIsValid(get_element_type(sourcetypeid)) || |
| OidIsValid(get_element_type(targettypeid))) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), |
| errmsg("array data types are not binary-compatible"))); |
| |
| /* |
| * We also disallow creating binary-compatibility casts involving |
| * domains. Casting from a domain to its base type is already |
| * allowed, and casting the other way ought to go through domain |
| * coercion to permit constraint checking. Again, if you're intent on |
| * having your own semantics for that, create a no-op cast function. |
| * |
| * NOTE: if we were to relax this, the above checks for composites |
| * etc. would have to be modified to look through domains to their |
| * base types. |
| */ |
| if (sourcetyptype == TYPTYPE_DOMAIN || |
| targettyptype == TYPTYPE_DOMAIN) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), |
| errmsg("domain data types must not be marked binary-compatible"))); |
| } |
| |
| /* |
| * Allow source and target types to be same only for length coercion |
| * functions. We assume a multi-arg function does length coercion. |
| */ |
| if (sourcetypeid == targettypeid && nargs < 2) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), |
| errmsg("source data type and target data type are the same"))); |
| |
| /* convert CoercionContext enum to char value for castcontext */ |
| switch (stmt->context) |
| { |
| case COERCION_IMPLICIT: |
| castcontext = COERCION_CODE_IMPLICIT; |
| break; |
| case COERCION_ASSIGNMENT: |
| castcontext = COERCION_CODE_ASSIGNMENT; |
| break; |
| /* COERCION_PLPGSQL is intentionally not covered here */ |
| case COERCION_EXPLICIT: |
| castcontext = COERCION_CODE_EXPLICIT; |
| break; |
| default: |
| elog(ERROR, "unrecognized CoercionContext: %d", stmt->context); |
| castcontext = 0; /* keep compiler quiet */ |
| break; |
| } |
| |
| myself = CastCreate(sourcetypeid, targettypeid, funcid, castcontext, |
| castmethod, DEPENDENCY_NORMAL); |
| |
| if (Gp_role == GP_ROLE_DISPATCH) |
| { |
| CdbDispatchUtilityStatement((Node *) stmt, |
| DF_CANCEL_ON_ERROR| |
| DF_WITH_SNAPSHOT| |
| DF_NEED_TWO_PHASE, |
| GetAssignedOidsForDispatch(), |
| NULL); |
| } |
| |
| return myself; |
| } |
| |
| |
| static void |
| check_transform_function(Form_pg_proc procstruct) |
| { |
| if (procstruct->provolatile == PROVOLATILE_VOLATILE) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), |
| errmsg("transform function must not be volatile"))); |
| if (procstruct->prokind != PROKIND_FUNCTION) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), |
| errmsg("transform function must be a normal function"))); |
| if (procstruct->proretset) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), |
| errmsg("transform function must not return a set"))); |
| if (procstruct->pronargs != 1) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), |
| errmsg("transform function must take one argument"))); |
| if (procstruct->proargtypes.values[0] != INTERNALOID) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), |
| errmsg("first argument of transform function must be type %s", |
| "internal"))); |
| } |
| |
| |
| /* |
| * CREATE TRANSFORM |
| */ |
| ObjectAddress |
| CreateTransform(CreateTransformStmt *stmt) |
| { |
| Oid typeid; |
| char typtype; |
| Oid langid; |
| Oid fromsqlfuncid; |
| Oid tosqlfuncid; |
| AclResult aclresult; |
| Form_pg_proc procstruct; |
| Datum values[Natts_pg_transform]; |
| bool nulls[Natts_pg_transform]; |
| bool replaces[Natts_pg_transform]; |
| Oid transformid; |
| HeapTuple tuple; |
| HeapTuple newtuple; |
| Relation relation; |
| ObjectAddress myself, |
| referenced; |
| ObjectAddresses *addrs; |
| bool is_replace; |
| |
| /* |
| * Get the type |
| */ |
| typeid = typenameTypeId(NULL, stmt->type_name); |
| typtype = get_typtype(typeid); |
| |
| if (typtype == TYPTYPE_PSEUDO) |
| ereport(ERROR, |
| (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
| errmsg("data type %s is a pseudo-type", |
| TypeNameToString(stmt->type_name)))); |
| |
| if (typtype == TYPTYPE_DOMAIN) |
| ereport(ERROR, |
| (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
| errmsg("data type %s is a domain", |
| TypeNameToString(stmt->type_name)))); |
| |
| if (!pg_type_ownercheck(typeid, GetUserId())) |
| aclcheck_error_type(ACLCHECK_NOT_OWNER, typeid); |
| |
| aclresult = pg_type_aclcheck(typeid, GetUserId(), ACL_USAGE); |
| if (aclresult != ACLCHECK_OK) |
| aclcheck_error_type(aclresult, typeid); |
| |
| /* |
| * Get the language |
| */ |
| langid = get_language_oid(stmt->lang, false); |
| |
| aclresult = pg_language_aclcheck(langid, GetUserId(), ACL_USAGE); |
| if (aclresult != ACLCHECK_OK) |
| aclcheck_error(aclresult, OBJECT_LANGUAGE, stmt->lang); |
| |
| /* |
| * Get the functions |
| */ |
| if (stmt->fromsql) |
| { |
| fromsqlfuncid = LookupFuncWithArgs(OBJECT_FUNCTION, stmt->fromsql, false); |
| |
| if (!pg_proc_ownercheck(fromsqlfuncid, GetUserId())) |
| aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_FUNCTION, NameListToString(stmt->fromsql->objname)); |
| |
| aclresult = pg_proc_aclcheck(fromsqlfuncid, GetUserId(), ACL_EXECUTE); |
| if (aclresult != ACLCHECK_OK) |
| aclcheck_error(aclresult, OBJECT_FUNCTION, NameListToString(stmt->fromsql->objname)); |
| |
| tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(fromsqlfuncid)); |
| if (!HeapTupleIsValid(tuple)) |
| elog(ERROR, "cache lookup failed for function %u", fromsqlfuncid); |
| procstruct = (Form_pg_proc) GETSTRUCT(tuple); |
| if (procstruct->prorettype != INTERNALOID) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), |
| errmsg("return data type of FROM SQL function must be %s", |
| "internal"))); |
| check_transform_function(procstruct); |
| ReleaseSysCache(tuple); |
| } |
| else |
| fromsqlfuncid = InvalidOid; |
| |
| if (stmt->tosql) |
| { |
| tosqlfuncid = LookupFuncWithArgs(OBJECT_FUNCTION, stmt->tosql, false); |
| |
| if (!pg_proc_ownercheck(tosqlfuncid, GetUserId())) |
| aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_FUNCTION, NameListToString(stmt->tosql->objname)); |
| |
| aclresult = pg_proc_aclcheck(tosqlfuncid, GetUserId(), ACL_EXECUTE); |
| if (aclresult != ACLCHECK_OK) |
| aclcheck_error(aclresult, OBJECT_FUNCTION, NameListToString(stmt->tosql->objname)); |
| |
| tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(tosqlfuncid)); |
| if (!HeapTupleIsValid(tuple)) |
| elog(ERROR, "cache lookup failed for function %u", tosqlfuncid); |
| procstruct = (Form_pg_proc) GETSTRUCT(tuple); |
| if (procstruct->prorettype != typeid) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), |
| errmsg("return data type of TO SQL function must be the transform data type"))); |
| check_transform_function(procstruct); |
| ReleaseSysCache(tuple); |
| } |
| else |
| tosqlfuncid = InvalidOid; |
| |
| /* |
| * Ready to go |
| */ |
| values[Anum_pg_transform_trftype - 1] = ObjectIdGetDatum(typeid); |
| values[Anum_pg_transform_trflang - 1] = ObjectIdGetDatum(langid); |
| values[Anum_pg_transform_trffromsql - 1] = ObjectIdGetDatum(fromsqlfuncid); |
| values[Anum_pg_transform_trftosql - 1] = ObjectIdGetDatum(tosqlfuncid); |
| |
| MemSet(nulls, false, sizeof(nulls)); |
| |
| relation = table_open(TransformRelationId, RowExclusiveLock); |
| |
| tuple = SearchSysCache2(TRFTYPELANG, |
| ObjectIdGetDatum(typeid), |
| ObjectIdGetDatum(langid)); |
| if (HeapTupleIsValid(tuple)) |
| { |
| Form_pg_transform form = (Form_pg_transform) GETSTRUCT(tuple); |
| |
| if (!stmt->replace) |
| ereport(ERROR, |
| (errcode(ERRCODE_DUPLICATE_OBJECT), |
| errmsg("transform for type %s language \"%s\" already exists", |
| format_type_be(typeid), |
| stmt->lang))); |
| |
| MemSet(replaces, false, sizeof(replaces)); |
| replaces[Anum_pg_transform_trffromsql - 1] = true; |
| replaces[Anum_pg_transform_trftosql - 1] = true; |
| |
| newtuple = heap_modify_tuple(tuple, RelationGetDescr(relation), values, nulls, replaces); |
| CatalogTupleUpdate(relation, &newtuple->t_self, newtuple); |
| |
| transformid = form->oid; |
| ReleaseSysCache(tuple); |
| is_replace = true; |
| } |
| else |
| { |
| transformid = GetNewOidForTransform(relation, TransformOidIndexId, |
| Anum_pg_transform_oid, |
| typeid, langid); |
| values[Anum_pg_transform_oid - 1] = ObjectIdGetDatum(transformid); |
| newtuple = heap_form_tuple(RelationGetDescr(relation), values, nulls); |
| CatalogTupleInsert(relation, newtuple); |
| is_replace = false; |
| } |
| |
| if (is_replace) |
| deleteDependencyRecordsFor(TransformRelationId, transformid, true); |
| |
| addrs = new_object_addresses(); |
| |
| /* make dependency entries */ |
| ObjectAddressSet(myself, TransformRelationId, transformid); |
| |
| /* dependency on language */ |
| ObjectAddressSet(referenced, LanguageRelationId, langid); |
| add_exact_object_address(&referenced, addrs); |
| |
| /* dependency on type */ |
| ObjectAddressSet(referenced, TypeRelationId, typeid); |
| add_exact_object_address(&referenced, addrs); |
| |
| /* dependencies on functions */ |
| if (OidIsValid(fromsqlfuncid)) |
| { |
| ObjectAddressSet(referenced, ProcedureRelationId, fromsqlfuncid); |
| add_exact_object_address(&referenced, addrs); |
| } |
| if (OidIsValid(tosqlfuncid)) |
| { |
| ObjectAddressSet(referenced, ProcedureRelationId, tosqlfuncid); |
| add_exact_object_address(&referenced, addrs); |
| } |
| |
| record_object_address_dependencies(&myself, addrs, DEPENDENCY_NORMAL); |
| free_object_addresses(addrs); |
| |
| /* dependency on extension */ |
| recordDependencyOnCurrentExtension(&myself, is_replace); |
| |
| /* Post creation hook for new transform */ |
| InvokeObjectPostCreateHook(TransformRelationId, transformid, 0); |
| |
| heap_freetuple(newtuple); |
| |
| table_close(relation, RowExclusiveLock); |
| |
| if (Gp_role == GP_ROLE_DISPATCH) |
| { |
| Assert(stmt->type == T_CreateTransformStmt); |
| Assert(stmt->type < 1000); |
| CdbDispatchUtilityStatement((Node *) stmt, |
| DF_CANCEL_ON_ERROR| |
| DF_WITH_SNAPSHOT| |
| DF_NEED_TWO_PHASE, |
| GetAssignedOidsForDispatch(), |
| NULL); |
| if (is_replace) |
| { |
| MetaTrackUpdObject(TransformRelationId, |
| myself.objectId, |
| GetUserId(), |
| "ALTER", "TRANSFORM"); |
| } else { |
| /* MPP-6929: metadata tracking */ |
| MetaTrackAddObject(TransformRelationId, |
| myself.objectId, |
| GetUserId(), |
| "CREATE", "TRANSFORM"); |
| } |
| } |
| |
| return myself; |
| } |
| |
| |
| /* |
| * get_transform_oid - given type OID and language OID, look up a transform OID |
| * |
| * If missing_ok is false, throw an error if the transform is not found. If |
| * true, just return InvalidOid. |
| */ |
| Oid |
| get_transform_oid(Oid type_id, Oid lang_id, bool missing_ok) |
| { |
| Oid oid; |
| |
| oid = GetSysCacheOid2(TRFTYPELANG, Anum_pg_transform_oid, |
| ObjectIdGetDatum(type_id), |
| ObjectIdGetDatum(lang_id)); |
| if (!OidIsValid(oid) && !missing_ok) |
| ereport(ERROR, |
| (errcode(ERRCODE_UNDEFINED_OBJECT), |
| errmsg("transform for type %s language \"%s\" does not exist", |
| format_type_be(type_id), |
| get_language_name(lang_id, false)))); |
| return oid; |
| } |
| |
| |
| /* |
| * Subroutine for ALTER FUNCTION/AGGREGATE SET SCHEMA/RENAME |
| * |
| * Is there a function with the given name and signature already in the given |
| * namespace? If so, raise an appropriate error message. |
| */ |
| void |
| IsThereFunctionInNamespace(const char *proname, int pronargs, |
| oidvector *proargtypes, Oid nspOid) |
| { |
| /* check for duplicate name (more friendly than unique-index failure) */ |
| if (SearchSysCacheExists3(PROCNAMEARGSNSP, |
| CStringGetDatum(proname), |
| PointerGetDatum(proargtypes), |
| ObjectIdGetDatum(nspOid))) |
| ereport(ERROR, |
| (errcode(ERRCODE_DUPLICATE_FUNCTION), |
| errmsg("function %s already exists in schema \"%s\"", |
| funcname_signature_string(proname, pronargs, |
| NIL, proargtypes->values), |
| get_namespace_name(nspOid)))); |
| } |
| |
| /* |
| * ExecuteDoStmt |
| * Execute inline procedural-language code |
| * |
| * See at ExecuteCallStmt() about the atomic argument. |
| */ |
| void |
| ExecuteDoStmt(DoStmt *stmt, bool atomic) |
| { |
| InlineCodeBlock *codeblock = makeNode(InlineCodeBlock); |
| ListCell *arg; |
| DefElem *as_item = NULL; |
| DefElem *language_item = NULL; |
| char *language; |
| Oid laninline; |
| HeapTuple languageTuple; |
| Form_pg_language languageStruct; |
| |
| /* Process options we got from gram.y */ |
| foreach(arg, stmt->args) |
| { |
| DefElem *defel = (DefElem *) lfirst(arg); |
| |
| if (strcmp(defel->defname, "as") == 0) |
| { |
| if (as_item) |
| ereport(ERROR, |
| (errcode(ERRCODE_SYNTAX_ERROR), |
| errmsg("conflicting or redundant options"))); |
| as_item = defel; |
| } |
| else if (strcmp(defel->defname, "language") == 0) |
| { |
| if (language_item) |
| ereport(ERROR, |
| (errcode(ERRCODE_SYNTAX_ERROR), |
| errmsg("conflicting or redundant options"))); |
| language_item = defel; |
| } |
| else |
| elog(ERROR, "option \"%s\" not recognized", |
| defel->defname); |
| } |
| |
| if (as_item) |
| codeblock->source_text = strVal(as_item->arg); |
| else |
| ereport(ERROR, |
| (errcode(ERRCODE_SYNTAX_ERROR), |
| errmsg("no inline code specified"))); |
| |
| /* if LANGUAGE option wasn't specified, use the default */ |
| if (language_item) |
| language = strVal(language_item->arg); |
| else |
| language = "plpgsql"; |
| |
| /* Look up the language and validate permissions */ |
| languageTuple = SearchSysCache1(LANGNAME, PointerGetDatum(language)); |
| if (!HeapTupleIsValid(languageTuple)) |
| ereport(ERROR, |
| (errcode(ERRCODE_UNDEFINED_OBJECT), |
| errmsg("language \"%s\" does not exist", language), |
| (extension_file_exists(language) ? |
| errhint("Use CREATE EXTENSION to load the language into the database.") : 0))); |
| |
| languageStruct = (Form_pg_language) GETSTRUCT(languageTuple); |
| codeblock->langOid = languageStruct->oid; |
| codeblock->langIsTrusted = languageStruct->lanpltrusted; |
| codeblock->atomic = atomic; |
| |
| if (languageStruct->lanpltrusted) |
| { |
| /* if trusted language, need USAGE privilege */ |
| AclResult aclresult; |
| |
| aclresult = pg_language_aclcheck(codeblock->langOid, GetUserId(), |
| ACL_USAGE); |
| if (aclresult != ACLCHECK_OK) |
| aclcheck_error(aclresult, OBJECT_LANGUAGE, |
| NameStr(languageStruct->lanname)); |
| } |
| else |
| { |
| /* if untrusted language, must be superuser */ |
| if (!superuser()) |
| aclcheck_error(ACLCHECK_NO_PRIV, OBJECT_LANGUAGE, |
| NameStr(languageStruct->lanname)); |
| } |
| |
| /* get the handler function's OID */ |
| laninline = languageStruct->laninline; |
| if (!OidIsValid(laninline)) |
| ereport(ERROR, |
| (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| errmsg("language \"%s\" does not support inline code execution", |
| NameStr(languageStruct->lanname)))); |
| |
| ReleaseSysCache(languageTuple); |
| |
| /* execute the inline handler */ |
| OidFunctionCall1(laninline, PointerGetDatum(codeblock)); |
| } |
| |
| static void |
| CheckForModifySystemFunc(Oid funcOid, List *funcName) |
| { |
| if (!allowSystemTableMods && funcOid < FirstBootstrapObjectId) |
| ereport(ERROR, |
| (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), |
| errmsg("permission defined: \"%s\" is a system function", |
| NameListToString(funcName)))); |
| } |
| |
| /* |
| * Execute CALL statement |
| * |
| * Inside a top-level CALL statement, transaction-terminating commands such as |
| * COMMIT or a PL-specific equivalent are allowed. The terminology in the SQL |
| * standard is that CALL establishes a non-atomic execution context. Most |
| * other commands establish an atomic execution context, in which transaction |
| * control actions are not allowed. If there are nested executions of CALL, |
| * we want to track the execution context recursively, so that the nested |
| * CALLs can also do transaction control. Note, however, that for example in |
| * CALL -> SELECT -> CALL, the second call cannot do transaction control, |
| * because the SELECT in between establishes an atomic execution context. |
| * |
| * So when ExecuteCallStmt() is called from the top level, we pass in atomic = |
| * false (recall that that means transactions = yes). We then create a |
| * CallContext node with content atomic = false, which is passed in the |
| * fcinfo->context field to the procedure invocation. The language |
| * implementation should then take appropriate measures to allow or prevent |
| * transaction commands based on that information, e.g., call |
| * SPI_connect_ext(SPI_OPT_NONATOMIC). The language should also pass on the |
| * atomic flag to any nested invocations to CALL. |
| * |
| * The expression data structures and execution context that we create |
| * within this function are children of the portalContext of the Portal |
| * that the CALL utility statement runs in. Therefore, any pass-by-ref |
| * values that we're passing to the procedure will survive transaction |
| * commits that might occur inside the procedure. |
| */ |
| void |
| ExecuteCallStmt(CallStmt *stmt, ParamListInfo params, bool atomic, DestReceiver *dest) |
| { |
| LOCAL_FCINFO(fcinfo, FUNC_MAX_ARGS); |
| ListCell *lc; |
| FuncExpr *fexpr; |
| int nargs; |
| int i; |
| AclResult aclresult; |
| FmgrInfo flinfo; |
| CallContext *callcontext; |
| EState *estate; |
| ExprContext *econtext; |
| HeapTuple tp; |
| PgStat_FunctionCallUsage fcusage; |
| Datum retval; |
| |
| fexpr = stmt->funcexpr; |
| Assert(fexpr); |
| Assert(IsA(fexpr, FuncExpr)); |
| |
| aclresult = pg_proc_aclcheck(fexpr->funcid, GetUserId(), ACL_EXECUTE); |
| if (aclresult != ACLCHECK_OK) |
| aclcheck_error(aclresult, OBJECT_PROCEDURE, get_func_name(fexpr->funcid)); |
| |
| /* Prep the context object we'll pass to the procedure */ |
| callcontext = makeNode(CallContext); |
| callcontext->atomic = atomic; |
| |
| tp = SearchSysCache1(PROCOID, ObjectIdGetDatum(fexpr->funcid)); |
| if (!HeapTupleIsValid(tp)) |
| elog(ERROR, "cache lookup failed for function %u", fexpr->funcid); |
| |
| /* |
| * If proconfig is set we can't allow transaction commands because of the |
| * way the GUC stacking works: The transaction boundary would have to pop |
| * the proconfig setting off the stack. That restriction could be lifted |
| * by redesigning the GUC nesting mechanism a bit. |
| */ |
| if (!heap_attisnull(tp, Anum_pg_proc_proconfig, NULL)) |
| callcontext->atomic = true; |
| |
| /* |
| * In security definer procedures, we can't allow transaction commands. |
| * StartTransaction() insists that the security context stack is empty, |
| * and AbortTransaction() resets the security context. This could be |
| * reorganized, but right now it doesn't work. |
| */ |
| if (((Form_pg_proc) GETSTRUCT(tp))->prosecdef) |
| callcontext->atomic = true; |
| |
| ReleaseSysCache(tp); |
| |
| /* safety check; see ExecInitFunc() */ |
| nargs = list_length(fexpr->args); |
| if (nargs > FUNC_MAX_ARGS) |
| ereport(ERROR, |
| (errcode(ERRCODE_TOO_MANY_ARGUMENTS), |
| errmsg_plural("cannot pass more than %d argument to a procedure", |
| "cannot pass more than %d arguments to a procedure", |
| FUNC_MAX_ARGS, |
| FUNC_MAX_ARGS))); |
| |
| /* Initialize function call structure */ |
| InvokeFunctionExecuteHook(fexpr->funcid); |
| fmgr_info(fexpr->funcid, &flinfo); |
| fmgr_info_set_expr((Node *) fexpr, &flinfo); |
| InitFunctionCallInfoData(*fcinfo, &flinfo, nargs, fexpr->inputcollid, |
| (Node *) callcontext, NULL); |
| |
| /* |
| * Evaluate procedure arguments inside a suitable execution context. Note |
| * we can't free this context till the procedure returns. |
| */ |
| estate = CreateExecutorState(); |
| estate->es_param_list_info = params; |
| econtext = CreateExprContext(estate); |
| |
| /* |
| * If we're called in non-atomic context, we also have to ensure that the |
| * argument expressions run with an up-to-date snapshot. Our caller will |
| * have provided a current snapshot in atomic contexts, but not in |
| * non-atomic contexts, because the possibility of a COMMIT/ROLLBACK |
| * destroying the snapshot makes higher-level management too complicated. |
| */ |
| if (!atomic) |
| PushActiveSnapshot(GetTransactionSnapshot()); |
| |
| i = 0; |
| foreach(lc, fexpr->args) |
| { |
| ExprState *exprstate; |
| Datum val; |
| bool isnull; |
| |
| exprstate = ExecPrepareExpr(lfirst(lc), estate); |
| |
| val = ExecEvalExprSwitchContext(exprstate, econtext, &isnull); |
| |
| fcinfo->args[i].value = val; |
| fcinfo->args[i].isnull = isnull; |
| |
| i++; |
| } |
| |
| /* Get rid of temporary snapshot for arguments, if we made one */ |
| if (!atomic) |
| PopActiveSnapshot(); |
| |
| /* Here we actually call the procedure */ |
| pgstat_init_function_usage(fcinfo, &fcusage); |
| retval = FunctionCallInvoke(fcinfo); |
| pgstat_end_function_usage(&fcusage, true); |
| |
| /* Handle the procedure's outputs */ |
| if (fexpr->funcresulttype == VOIDOID) |
| { |
| /* do nothing */ |
| } |
| else if (fexpr->funcresulttype == RECORDOID) |
| { |
| /* send tuple to client */ |
| HeapTupleHeader td; |
| Oid tupType; |
| int32 tupTypmod; |
| TupleDesc retdesc; |
| HeapTupleData rettupdata; |
| TupOutputState *tstate; |
| TupleTableSlot *slot; |
| |
| if (fcinfo->isnull) |
| elog(ERROR, "procedure returned null record"); |
| |
| /* |
| * Ensure there's an active snapshot whilst we execute whatever's |
| * involved here. Note that this is *not* sufficient to make the |
| * world safe for TOAST pointers to be included in the returned data: |
| * the referenced data could have gone away while we didn't hold a |
| * snapshot. Hence, it's incumbent on PLs that can do COMMIT/ROLLBACK |
| * to not return TOAST pointers, unless those pointers were fetched |
| * after the last COMMIT/ROLLBACK in the procedure. |
| * |
| * XXX that is a really nasty, hard-to-test requirement. Is there a |
| * way to remove it? |
| */ |
| EnsurePortalSnapshotExists(); |
| |
| td = DatumGetHeapTupleHeader(retval); |
| tupType = HeapTupleHeaderGetTypeId(td); |
| tupTypmod = HeapTupleHeaderGetTypMod(td); |
| retdesc = lookup_rowtype_tupdesc(tupType, tupTypmod); |
| |
| tstate = begin_tup_output_tupdesc(dest, retdesc, |
| &TTSOpsHeapTuple); |
| |
| rettupdata.t_len = HeapTupleHeaderGetDatumLength(td); |
| ItemPointerSetInvalid(&(rettupdata.t_self)); |
| rettupdata.t_tableOid = InvalidOid; |
| rettupdata.t_data = td; |
| |
| slot = ExecStoreHeapTuple(&rettupdata, tstate->slot, false); |
| tstate->dest->receiveSlot(slot, tstate->dest); |
| |
| end_tup_output(tstate); |
| |
| ReleaseTupleDesc(retdesc); |
| } |
| else |
| elog(ERROR, "unexpected result type for procedure: %u", |
| fexpr->funcresulttype); |
| |
| FreeExecutorState(estate); |
| } |
| |
| /* |
| * Construct the tuple descriptor for a CALL statement return |
| */ |
| TupleDesc |
| CallStmtResultDesc(CallStmt *stmt) |
| { |
| FuncExpr *fexpr; |
| HeapTuple tuple; |
| TupleDesc tupdesc; |
| |
| fexpr = stmt->funcexpr; |
| |
| tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(fexpr->funcid)); |
| if (!HeapTupleIsValid(tuple)) |
| elog(ERROR, "cache lookup failed for procedure %u", fexpr->funcid); |
| |
| tupdesc = build_function_result_tupdesc_t(tuple); |
| |
| ReleaseSysCache(tuple); |
| |
| return tupdesc; |
| } |