diff --git a/src/common/backend/parser/parse_func.cpp b/src/common/backend/parser/parse_func.cpp index 2bec3ceeb..60b4ce425 100644 --- a/src/common/backend/parser/parse_func.cpp +++ b/src/common/backend/parser/parse_func.cpp @@ -39,8 +39,10 @@ static Oid FuncNameAsType(List* funcname); static Node* ParseComplexProjection(ParseState* pstate, char* funcname, Node* first_arg, int location); -static List* GetDefaultVale(Oid funcoid, const int* argnumbers, int ndargs); +static List* GetDefaultVale(Oid funcoid, const int* argnumbers, int ndargs, bool* defaultValid); static Oid cl_get_input_param_original_type(Oid func_oid, int argno); +static bool CheckDefaultArgsPosition(int2vector* defaultargpos, int pronargdefaults, int ndargs, + int pronallargs, int pronargs, HeapTuple procTup); /* * Parse a function call @@ -1820,7 +1822,14 @@ FuncDetailCode func_get_detail(List* funcname, List* fargs, List* fargnames, int ereport(ERROR, (errcode(ERRCODE_UNDEFINED_FUNCTION), errmodule(MOD_OPT), errmsg("not enough default arguments"))); - *argdefaults = GetDefaultVale(*funcid, best_candidate->argnumbers, best_candidate->ndargs); + bool defaultValid = true; + *argdefaults = GetDefaultVale(*funcid, best_candidate->argnumbers, best_candidate->ndargs, &defaultValid); + if (!defaultValid) { + ereport(DEBUG3, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmodule(MOD_OPT), errmsg("default arguments pos is not right."))); + return FUNCDETAIL_NOTFOUND; + } } if (pform->proisagg) result = FUNCDETAIL_AGGREGATE; @@ -2260,7 +2269,7 @@ Oid LookupAggNameTypeNames(List* aggname, List* argtypes, bool noError) } // fetch default args if caller wants 'em -static List* GetDefaultVale(Oid funcoid, const int* argnumbers, int ndargs) +static List* GetDefaultVale(Oid funcoid, const int* argnumbers, int ndargs, bool* defaultValid) { HeapTuple tuple; Form_pg_proc formproc; @@ -2357,6 +2366,9 @@ static List* GetDefaultVale(Oid funcoid, const int* argnumbers, int ndargs) while (ndelete-- > 0) { defaults = list_delete_first(defaults); } + + *defaultValid = CheckDefaultArgsPosition(defaultargpos, pronargdefaults, ndargs, + pronallargs, pronargs, tuple); } if (argtypes != NULL) @@ -2376,6 +2388,49 @@ static List* GetDefaultVale(Oid funcoid, const int* argnumbers, int ndargs) return defaults; } +static bool CheckDefaultArgs(int2vector* defaultargpos, int pronargdefaults, int pronallargs, + int pronargs, HeapTuple proctup) +{ + if (u_sess->attr.attr_sql.sql_compatibility != A_FORMAT || PROC_UNCHECK_DEFAULT_PARAM) { + return true; + } + // if the func has out param, but did not enable_out_param_override, we don't check the defaultpos + if (pronallargs > pronargs && !enable_out_param_override()) { + return true; + } + // the pg_catalog's func can omit out param, so we don't check the defaultpos + bool isnull = false; + Datum namespaceDatum = SysCacheGetAttr(PROCOID, proctup, Anum_pg_proc_pronamespace, &isnull); + if (!isnull && IsAformatStyleFunctionOid(DatumGetObjectId(namespaceDatum))) { + return true; + } + // if the func has the default args without position, we consider the position is at the end, no error + if (defaultargpos->dim1 == 0 && pronargdefaults > 0) { + return true; + } + return false; +} + +/* + * Check whether the parameter is in the appropriate position with default values + */ +static bool CheckDefaultArgsPosition(int2vector* defaultargpos, int pronargdefaults, int ndargs, + int pronallargs, int pronargs, HeapTuple proctup) +{ + if (CheckDefaultArgs(defaultargpos, pronargdefaults, pronallargs, pronargs, proctup)) { + return true; + } + // Check whether the defaultpos are at the end of the parameter list from back to front + int argIndex = pronallargs - 1; + int defaultposIndex = pronargdefaults - 1; + for (; argIndex >= pronallargs - ndargs; argIndex--, defaultposIndex--) { + if (defaultargpos->values[defaultposIndex] != argIndex) { + return false; + } + } + return true; +} + /* * Replace column managed type with original type * to identify overloaded functions diff --git a/src/common/backend/utils/misc/guc/guc_sql.cpp b/src/common/backend/utils/misc/guc/guc_sql.cpp index 530bec8cd..1ce287c7e 100755 --- a/src/common/backend/utils/misc/guc/guc_sql.cpp +++ b/src/common/backend/utils/misc/guc/guc_sql.cpp @@ -388,7 +388,8 @@ static const struct behavior_compat_entry behavior_compat_options[OPT_MAX] = { {"allow_orderby_undistinct_column", OPT_ALLOW_ORDERBY_UNDISTINCT_COLUMN}, {"select_into_return_null", OPT_SELECT_INTO_RETURN_NULL}, {"accept_empty_str", OPT_ACCEPT_EMPTY_STR}, - {"plpgsql_dependency", OPT_PLPGSQL_DEPENDENCY} + {"plpgsql_dependency", OPT_PLPGSQL_DEPENDENCY}, + {"proc_uncheck_default_param", OPT_PROC_UNCHECK_DEFAULT_PARAM} }; // increase SQL_IGNORE_STRATEGY_NUM if we need more strategy diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h index 3d3f414ce..d6e695799 100644 --- a/src/include/miscadmin.h +++ b/src/include/miscadmin.h @@ -203,7 +203,8 @@ extern bool contain_backend_version(uint32 version_number); #define OPT_SELECT_INTO_RETURN_NULL 67108864 #define OPT_ACCEPT_EMPTY_STR 134217728 #define OPT_PLPGSQL_DEPENDENCY 268435456 -#define OPT_MAX 29 +#define OPT_PROC_UNCHECK_DEFAULT_PARAM 536870912 +#define OPT_MAX 30 #define PLPSQL_OPT_FOR_LOOP 1 #define PLPSQL_OPT_OUTPARAM 2 @@ -246,6 +247,7 @@ extern bool contain_backend_version(uint32 version_number); #define SELECT_INTO_RETURN_NULL (u_sess->utils_cxt.behavior_compat_flags & OPT_SELECT_INTO_RETURN_NULL) #define PLPGSQL_DEPENDENCY (u_sess->utils_cxt.behavior_compat_flags & OPT_PLPGSQL_DEPENDENCY) +#define PROC_UNCHECK_DEFAULT_PARAM (u_sess->utils_cxt.behavior_compat_flags & OPT_PROC_UNCHECK_DEFAULT_PARAM) /* define database compatibility Attribute */ typedef struct { diff --git a/src/test/regress/expected/function_default_test.out b/src/test/regress/expected/function_default_test.out new file mode 100644 index 000000000..797d6bc9c --- /dev/null +++ b/src/test/regress/expected/function_default_test.out @@ -0,0 +1,47 @@ +drop schema if exists function_default cascade; +NOTICE: schema "function_default" does not exist, skipping +create schema function_default; +set current_schema = function_default; +show behavior_compat_options; + behavior_compat_options +------------------------- + +(1 row) + + +-- 创建存储过程 +create or replace procedure pro_default(p1 text,p2 int default 123,p3 int) +as +begin +raise info 'p1:%',p1; +raise info 'p2:%',p2; +raise info 'p3:%',p3; +end; +/ +-- 调用存储过程,预期报错 +call pro_default('test',1); +ERROR: function pro_default(unknown, integer) does not exist +HINT: No function matches the given name and argument types. You might need to add explicit type casts. + +-- 开启逃生参数,保持原本表现 +set behavior_compat_options="proc_uncheck_default_param"; +show behavior_compat_options; + behavior_compat_options +---------------------------- + proc_uncheck_default_param +(1 row) + +call pro_default('test',1); +INFO: p1:test +INFO: p2:1 +INFO: p3:123 + pro_default +------------- + +(1 row) + + +-- 清理环境 +drop schema function_default cascade; +NOTICE: drop cascades to function pro_default(text,integer,integer) +reset behavior_compat_options; diff --git a/src/test/regress/parallel_schedule0 b/src/test/regress/parallel_schedule0 index a3656c89c..d4119f85a 100644 --- a/src/test/regress/parallel_schedule0 +++ b/src/test/regress/parallel_schedule0 @@ -760,7 +760,7 @@ test: plpgsql_assign_value_to_array_attribute test: plpgsql_array_of_record #test: plpgsql_nest_compile test: arrayinterface_ted -test: plpgsql_inout_param record_slow_sql_in_proc +test: function_default_test plpgsql_inout_param record_slow_sql_in_proc test: plpgsql_cursor_rowtype test: plpgsql_assign_list test: plpgsql_package_type plpgsql_package_param @@ -1107,4 +1107,4 @@ test: enable_expr_fusion_flatten # test for on update timestamp and generated column test: on_update_session1 on_update_session2 -test: ts_gb18030_utf8 \ No newline at end of file +test: ts_gb18030_utf8 diff --git a/src/test/regress/parallel_schedule0B b/src/test/regress/parallel_schedule0B index eff44a944..d191a418c 100644 --- a/src/test/regress/parallel_schedule0B +++ b/src/test/regress/parallel_schedule0B @@ -277,7 +277,7 @@ test: plpgsql_assign_value_to_array_attribute test: plpgsql_array_of_record #test: plpgsql_nest_compile test: arrayinterface_ted -test: plpgsql_inout_param +test: function_default_test plpgsql_inout_param test: plpgsql_cursor_rowtype test: plpgsql_assign_list test: plpgsql_package_type plpgsql_package_param diff --git a/src/test/regress/sql/function_default_test.sql b/src/test/regress/sql/function_default_test.sql new file mode 100755 index 000000000..73eca37cf --- /dev/null +++ b/src/test/regress/sql/function_default_test.sql @@ -0,0 +1,25 @@ +drop schema if exists function_default cascade; +create schema function_default; +set current_schema = function_default; +show behavior_compat_options; + +-- 创建存储过程 +create or replace procedure pro_default(p1 text,p2 int default 123,p3 int) +as +begin +raise info 'p1:%',p1; +raise info 'p2:%',p2; +raise info 'p3:%',p3; +end; +/ +-- 调用存储过程,预期报错 +call pro_default('test',1); + +-- 开启逃生参数,保持原本表现 +set behavior_compat_options="proc_uncheck_default_param"; +show behavior_compat_options; +call pro_default('test',1); + +-- 清理环境 +drop schema function_default cascade; +reset behavior_compat_options;