From 87e446405e2bd07df4afbec9926696a125bd6e9e Mon Sep 17 00:00:00 2001 From: ab2020c Date: Wed, 19 Feb 2025 05:25:28 -0500 Subject: [PATCH] function result cache --- doc/src/sgml/ref/alter_function.sgmlin | 1 + doc/src/sgml/ref/create_function.sgmlin | 2 + src/bin/gs_guc/cluster_guc.conf | 1 + src/bin/pg_dump/pg_dump.cpp | 12 + src/bin/psql/po/zh_CN.po | 3 + src/common/backend/catalog/pg_proc.cpp | 3 +- src/common/backend/parser/gram.y | 11 +- src/common/backend/utils/adt/ruleutils.cpp | 4 + src/common/backend/utils/cache/catcache.cpp | 1 + src/common/backend/utils/fmgr/fmgr.cpp | 5 + src/common/backend/utils/init/globals.cpp | 2 +- src/common/backend/utils/misc/guc.cpp | 3 +- src/common/backend/utils/misc/guc/guc_sql.cpp | 12 + src/common/backend/utils/mmgr/portalmem.cpp | 15 + .../interfaces/libpq/frontend_parser/gram.y | 11 +- .../optimizer/commands/functioncmds.cpp | 35 +- .../optimizer/commands/typecmds.cpp | 3 +- src/gausskernel/optimizer/plan/createplan.cpp | 233 ++ src/gausskernel/runtime/executor/execQual.cpp | 35 +- .../runtime/executor/execUtils.cpp | 1287 +++++++++++ src/include/catalog/pg_class.h | 2 +- src/include/catalog/pg_proc.h | 4 +- src/include/catalog/pg_proc_fn.h | 3 +- src/include/commands/defrem.h | 2 +- src/include/fmgr.h | 5 + .../knl/knl_guc/knl_session_attr_sql.h | 1 + src/include/nodes/execnodes.h | 8 + src/include/nodes/primnodes.h | 1 + src/include/parser/kwlist.h | 1 + src/include/utils/guc.h | 3 + src/include/utils/portal.h | 3 + .../expected/function_result_cache.out | 1947 +++++++++++++++++ src/test/regress/parallel_schedule0 | 1 + .../regress/sql/function_result_cache.sql | 948 ++++++++ 34 files changed, 4586 insertions(+), 22 deletions(-) create mode 100644 src/test/regress/expected/function_result_cache.out create mode 100644 src/test/regress/sql/function_result_cache.sql diff --git a/doc/src/sgml/ref/alter_function.sgmlin b/doc/src/sgml/ref/alter_function.sgmlin index fc7c42beca..89607012b4 100644 --- a/doc/src/sgml/ref/alter_function.sgmlin +++ b/doc/src/sgml/ref/alter_function.sgmlin @@ -30,6 +30,7 @@ where action can be: | ROWS result_rows | SET configuration_parameter {{ TO | = } { value | DEFAULT }| FROM CURRENT} | RESET {configuration_parameter| ALL} +| {RESULT_CACHE | NOT RESULT_CACHE} diff --git a/doc/src/sgml/ref/create_function.sgmlin b/doc/src/sgml/ref/create_function.sgmlin index f8135eb8d8..fbbaab8488 100755 --- a/doc/src/sgml/ref/create_function.sgmlin +++ b/doc/src/sgml/ref/create_function.sgmlin @@ -27,6 +27,7 @@ CREATE [ OR REPLACE ] [DEFINER = user] FUNCTION function_name | COST execution_cost | ROWS result_rows | SET configuration_parameter { {TO | =} value | FROM CURRENT } + | {RESULT_CACHE | NOT RESULT_CACHE} ] [...] { AS 'definition' @@ -46,6 +47,7 @@ CREATE [ OR REPLACE ] [DEFINER = user] FUNCTION function_name | COST execution_cost | ROWS result_rows | SET configuration_parameter { {TO | =} value | FROM CURRENT } + | {RESULT_CACHE | NOT RESULT_CACHE} ][...] { IS | AS diff --git a/src/bin/gs_guc/cluster_guc.conf b/src/bin/gs_guc/cluster_guc.conf index 03d9361219..48d9a459a4 100755 --- a/src/bin/gs_guc/cluster_guc.conf +++ b/src/bin/gs_guc/cluster_guc.conf @@ -662,6 +662,7 @@ checkpoint_wait_timeout|int|2,3600|s|NULL| sql_use_spacelimit|int|-1,2147483647|kB|the max space limit query can used on single DN.| enable_hadoop_env|bool|0,0|NULL|NULL| enable_upgrade_merge_lock_mode|bool|0,0|NULL|NULL| +enable_func_cache|bool|0,0|NULL|NULL| job_queue_processes|int|0,1000|NULL|NULL| enable_absolute_tablespace|bool|0,0|NULL|NULL| enable_orc_cache|bool|0,0|NULL|NULL| diff --git a/src/bin/pg_dump/pg_dump.cpp b/src/bin/pg_dump/pg_dump.cpp index 1f7ac4709b..3ba79ee130 100644 --- a/src/bin/pg_dump/pg_dump.cpp +++ b/src/bin/pg_dump/pg_dump.cpp @@ -14233,6 +14233,7 @@ static void dumpFunc(Archive* fout, FuncInfo* finfo) char* proshippable = NULL; char* propackage = NULL; char* protypeid = NULL; + char* result_cache = NULL; char* rettypename = NULL; char* propackageid = NULL; char* definer = NULL; @@ -14252,6 +14253,7 @@ static void dumpFunc(Archive* fout, FuncInfo* finfo) bool isNullProargsrc = false; bool addDelimiter = false; bool isNullSelfloop = false; + bool hasResultCache = false; const char *funcKind; char* parallelCursorName = NULL; char* parallelCursorStrategy = NULL; @@ -14282,6 +14284,7 @@ static void dumpFunc(Archive* fout, FuncInfo* finfo) isHasPropackage = is_column_exists(AH->connection, ProcedureRelationId, "propackage"); hasProKindAttr = is_column_exists(AH->connection, ProcedureRelationId, "prokind"); hasProargsrc = is_column_exists(AH->connection, ProcedureRelationId, "proargsrc"); + hasResultCache = is_column_exists(AH->connection, ProcedureRelationId, "result_cache"); /* * proleakproof was added at v9.2 @@ -14299,6 +14302,7 @@ static void dumpFunc(Archive* fout, FuncInfo* finfo) "%s, " "(SELECT lanname FROM pg_catalog.pg_language WHERE oid = prolang) AS lanname, " "%s, " + "%s, " "(SELECT 1 FROM pg_depend WHERE objid = p.oid AND objid = refobjid AND refclassid = 1255 LIMIT 1) AS selfloop, " "proargnames[o.parallel_cursor_seq + 1] AS parallelcursorname, o.parallel_cursor_strategy AS parallelcursorstrategy, " "pg_catalog.array_to_string(o.parallel_cursor_partkey, ', ') AS parallelcursorpartkey " @@ -14309,6 +14313,7 @@ static void dumpFunc(Archive* fout, FuncInfo* finfo) isHasPropackage ? "propackage" : "NULL AS propackage", hasProKindAttr?"prokind":"'f' as prokind", hasProargsrc ? "proargsrc" : "NULL AS proargsrc", + hasResultCache ? "result_cache" : "NULL as result_cache", finfo->dobj.catId.oid); res = ExecuteSqlQueryForSingleRow(fout, query->data); @@ -14337,6 +14342,7 @@ static void dumpFunc(Archive* fout, FuncInfo* finfo) parallelCursorName = PQgetvalue(res, 0, PQfnumber(res, "parallelcursorname")); parallelCursorStrategy = PQgetvalue(res, 0, PQfnumber(res, "parallelcursorstrategy")); parallelCursorPartKey = PQgetvalue(res, 0, PQfnumber(res, "parallelcursorpartkey")); + result_cache = PQgetvalue(res, 0, PQfnumber(res, "result_cache")); if ((gdatcompatibility != NULL) && strcmp(gdatcompatibility, B_FORMAT) == 0) { /* get definer user name */ @@ -14501,6 +14507,12 @@ static void dumpFunc(Archive* fout, FuncInfo* finfo) } } + if (hasResultCache) { + if (result_cache != NULL && result_cache[0] == 't') { + appendPQExpBuffer(q, " RESULT_CACHE"); + } + } + if (addDelimiter && IsPlainFormat()) { appendPQExpBuffer(headWithDefault, "delimiter //\n"); } diff --git a/src/bin/psql/po/zh_CN.po b/src/bin/psql/po/zh_CN.po index 1628f4462c..586225b5c5 100644 --- a/src/bin/psql/po/zh_CN.po +++ b/src/bin/psql/po/zh_CN.po @@ -1046,6 +1046,9 @@ msgstr "不稳定性" msgid "Volatility" msgstr "挥发性" +msgid "Result_cache" +msgstr "函数缓存" + # describe.c:186 #: describe.cpp:386 msgid "Language" diff --git a/src/common/backend/catalog/pg_proc.cpp b/src/common/backend/catalog/pg_proc.cpp index 3ac06cbd27..bc950f1663 100644 --- a/src/common/backend/catalog/pg_proc.cpp +++ b/src/common/backend/catalog/pg_proc.cpp @@ -1078,7 +1078,7 @@ ObjectAddress ProcedureCreate(const char* procedureName, Oid procNamespace, Oid List* parameterDefaults, Datum proconfig, float4 procost, float4 prorows, int2vector* prodefaultargpos, bool fenced, bool shippable, bool package, bool proIsProcedure, const char *proargsrc, bool isPrivate, TypeDependExtend* paramTypDependExt, TypeDependExtend* retTypDependExt, CreateFunctionStmt* stmt, bool isPipelined, - FunctionPartitionInfo* partInfo, Oid protypeid, char typefunckind, bool isfinal, Oid profuncid) + FunctionPartitionInfo* partInfo, Oid protypeid, char typefunckind, bool isfinal, Oid profuncid, bool result_cache) { Oid retval; int parameterCount; @@ -1368,6 +1368,7 @@ ObjectAddress ProcedureCreate(const char* procedureName, Oid procNamespace, Oid values[Anum_pg_proc_pronargs - 1] = UInt16GetDatum(parameterCount); values[Anum_pg_proc_pronargdefaults - 1] = UInt16GetDatum(list_length(parameterDefaults)); values[Anum_pg_proc_prorettype - 1] = ObjectIdGetDatum(returnType); + values[Anum_pg_proc_result_cache - 1] = BoolGetDatum(result_cache); if (parameterCount <= FUNC_MAX_ARGS_INROW) { nulls[Anum_pg_proc_proargtypesext - 1] = true; values[Anum_pg_proc_proargtypes - 1] = PointerGetDatum(parameterTypes); diff --git a/src/common/backend/parser/gram.y b/src/common/backend/parser/gram.y index 17892f22d3..a755910c50 100644 --- a/src/common/backend/parser/gram.y +++ b/src/common/backend/parser/gram.y @@ -1000,7 +1000,7 @@ static char* IdentResolveToChar(char *ident, core_yyscan_t yyscanner); RANDOMIZED RANGE RATIO RAW READ REAL REASSIGN REBUILD RECHECK RECURSIVE RECYCLEBIN REDISANYVALUE REF REFERENCES REFRESH REINDEX REJECT_P RELATIVE_P RELEASE RELOPTIONS REMOTE_P REMOVE RENAME REPEAT REPEATABLE REPLACE REPLICA - RESET RESIZE RESOURCE RESPECT_P RESTART RESTRICT RETURN RETURNED_SQLSTATE RETURNING RETURNS REUSE REVOKE RIGHT ROLE ROLES ROLLBACK ROLLUP ROTATE + RESET RESIZE RESOURCE RESPECT_P RESTART RESTRICT RESULT_CACHE RETURN RETURNED_SQLSTATE RETURNING RETURNS REUSE REVOKE RIGHT ROLE ROLES ROLLBACK ROLLUP ROTATE ROTATION ROW ROW_COUNT ROWNUM ROWS ROWTYPE_P RULE SAMPLE SAVEPOINT SCHEDULE SCHEMA SCHEMA_NAME SCROLL SEARCH SECOND_P SECURITY SELECT SEPARATOR_P SEQUENCE SEQUENCES @@ -18263,6 +18263,14 @@ common_func_opt_item: BCompatibilityOptionSupportCheck($1); $$ = makeDefElem("comment", (Node *)makeString($2)); } + | RESULT_CACHE + { + $$ = makeDefElem("result_cache", (Node*)makeInteger(TRUE)); + } + | NOT RESULT_CACHE + { + $$ = makeDefElem("result_cache", (Node*)makeInteger(FALSE)); + } ; parallel_partition_opt: @@ -32052,6 +32060,7 @@ unreserved_keyword: | RESPECT_P | RESTART | RESTRICT + | RESULT_CACHE | RESULT | RETURN | RETURNED_SQLSTATE diff --git a/src/common/backend/utils/adt/ruleutils.cpp b/src/common/backend/utils/adt/ruleutils.cpp index 6aa2dfbdd9..fb847ec32a 100644 --- a/src/common/backend/utils/adt/ruleutils.cpp +++ b/src/common/backend/utils/adt/ruleutils.cpp @@ -4939,6 +4939,10 @@ char* pg_get_functiondef_worker(Oid funcid, int* headerlines) else if (!proIsProcedure) appendStringInfoString(&buf, " NOT SHIPPABLE"); + Datum result_cache = SysCacheGetAttr(PROCOID, proctup, Anum_pg_proc_result_cache, &isnull); + if (!isnull && DatumGetBool(result_cache)) + appendStringInfoString(&buf, " RESULT_CACHE"); + int2 parallelCursorSeq = GetParallelCursorSeq(funcid); if (parallelCursorSeq != -1) { print_parallel_enable(&buf, proctup, parallelCursorSeq, funcid); diff --git a/src/common/backend/utils/cache/catcache.cpp b/src/common/backend/utils/cache/catcache.cpp index fa8423430d..2104ae9e77 100644 --- a/src/common/backend/utils/cache/catcache.cpp +++ b/src/common/backend/utils/cache/catcache.cpp @@ -2100,6 +2100,7 @@ HeapTuple CreateHeapTuple4BuiltinFunc(const Builtin_func* func, TupleDesc desc) values[Anum_pg_proc_pronargs - 1] = UInt16GetDatum(parameterCount); values[Anum_pg_proc_pronargdefaults - 1] = UInt16GetDatum(func->pronargdefaults); values[Anum_pg_proc_prorettype - 1] = ObjectIdGetDatum(func->rettype); + values[Anum_pg_proc_result_cache - 1] = BoolGetDatum(false); if (parameterCount <= FUNC_MAX_ARGS_INROW) { nulls[Anum_pg_proc_proargtypesext - 1] = true; values[Anum_pg_proc_proargtypes - 1] = PointerGetDatum(parameterTypes); diff --git a/src/common/backend/utils/fmgr/fmgr.cpp b/src/common/backend/utils/fmgr/fmgr.cpp index 615bfc67e1..9dec973924 100755 --- a/src/common/backend/utils/fmgr/fmgr.cpp +++ b/src/common/backend/utils/fmgr/fmgr.cpp @@ -1116,6 +1116,11 @@ struct fmgr_security_definer_cache { Datum arg; /* passthrough argument for plugin modules */ }; +PLpgSQL_function* get_security_function(FunctionCallInfo fcinfo) +{ + return (PLpgSQL_function*)(((fmgr_security_definer_cache*)fcinfo->flinfo->fn_extra)->flinfo.fn_extra); +} + /* * Function handler for security-definer/proconfig/plugin-hooked functions. * We extract the OID of the actual function and do a fmgr lookup again. diff --git a/src/common/backend/utils/init/globals.cpp b/src/common/backend/utils/init/globals.cpp index fd8eeb2193..3c73c773f7 100644 --- a/src/common/backend/utils/init/globals.cpp +++ b/src/common/backend/utils/init/globals.cpp @@ -77,7 +77,7 @@ bool will_shutdown = false; * ********************************************/ -const uint32 GRAND_VERSION_NUM = 93038; +const uint32 GRAND_VERSION_NUM = 93039; /******************************************** * 2.VERSION NUM FOR EACH FEATURE diff --git a/src/common/backend/utils/misc/guc.cpp b/src/common/backend/utils/misc/guc.cpp index 40a3100e19..31d1c05d98 100755 --- a/src/common/backend/utils/misc/guc.cpp +++ b/src/common/backend/utils/misc/guc.cpp @@ -457,7 +457,8 @@ const char* sync_guc_variable_namelist[] = {"work_mem", "max_error_count", "enable_expr_fusion", "heap_bulk_read_size", - "restrict_nonsystem_relation_kind" + "restrict_nonsystem_relation_kind", + "enable_func_cache" }; static void set_config_sourcefile(const char* name, char* sourcefile, int sourceline); diff --git a/src/common/backend/utils/misc/guc/guc_sql.cpp b/src/common/backend/utils/misc/guc/guc_sql.cpp index 831a6032c4..a851d826c4 100755 --- a/src/common/backend/utils/misc/guc/guc_sql.cpp +++ b/src/common/backend/utils/misc/guc/guc_sql.cpp @@ -1893,6 +1893,18 @@ static void InitSqlConfigureNamesBool() NULL, NULL }, + {{"enable_func_cache", + PGC_USERSET, + NODE_ALL, + QUERY_TUNING_OTHER, + gettext_noop("enable function cache."), + NULL}, + &u_sess->attr.attr_sql.enable_func_cache, + false, + NULL, + NULL, + NULL + }, /* End-of-list marker */ {{NULL, (GucContext)0, diff --git a/src/common/backend/utils/mmgr/portalmem.cpp b/src/common/backend/utils/mmgr/portalmem.cpp index f50a73a0b5..b16478338e 100755 --- a/src/common/backend/utils/mmgr/portalmem.cpp +++ b/src/common/backend/utils/mmgr/portalmem.cpp @@ -269,6 +269,21 @@ Portal CreatePortal(const char* name, bool allowDup, bool dupSilent, bool is_fro portal->funcUseCount = 0; portal->hasStreamForPlpgsql = false; portal->have_rollback_transaction = false; + if (ENABLE_FUNCTION_RESULT_CACHE()) { + if (is_from_spi && ActivePortal && ActivePortal->top_estate != NULL) { + portal->func_retcache_cxt = ActivePortal->func_retcache_cxt; + portal->top_estate = ActivePortal->top_estate; + portal->func_retcache_slot_count = ActivePortal->func_retcache_slot_count; + } else { + portal->func_retcache_cxt = AllocSetContextCreate(PortalGetHeapMemory(portal), + "Function Result Cache Context", + ALLOCSET_DEFAULT_SIZES); + MemoryContext oldcxt = MemoryContextSwitchTo(portal->func_retcache_cxt); + portal->top_estate = (EState*)palloc0(sizeof(EState)); + MemoryContextSwitchTo(oldcxt); + portal->func_retcache_slot_count = 0; + } + } #ifndef ENABLE_MULTIPLE_NODES portal->streamInfo.Reset(); diff --git a/src/common/interfaces/libpq/frontend_parser/gram.y b/src/common/interfaces/libpq/frontend_parser/gram.y index 39cbff3d35..d705fcea39 100755 --- a/src/common/interfaces/libpq/frontend_parser/gram.y +++ b/src/common/interfaces/libpq/frontend_parser/gram.y @@ -597,7 +597,7 @@ extern THR_LOCAL bool stmt_contains_operator_plus; RANDOMIZED RANGE RATIO RAW READ REAL REASSIGN REBUILD RECHECK RECURSIVE RECYCLEBIN REDISANYVALUE REF REFERENCES REFRESH REINDEX REJECT_P RELATIVE_P RELEASE RELOPTIONS REMOTE_P REMOVE RENAME REPEAT REPEATABLE REPLACE REPLICA - RESET RESIZE RESOURCE RESPECT_P RESTART RESTRICT RETURN RETURNED_SQLSTATE RETURNING RETURNS REUSE REVOKE RIGHT ROLE ROLES ROLLBACK ROLLUP ROTATE + RESET RESIZE RESOURCE RESPECT_P RESTART RESTRICT RESULT_CACHE RETURN RETURNED_SQLSTATE RETURNING RETURNS REUSE REVOKE RIGHT ROLE ROLES ROLLBACK ROLLUP ROTATE ROTATION ROW ROW_COUNT ROWNUM ROWS ROWTYPE_P RULE SAMPLE SAVEPOINT SCHEDULE SCHEMA SCHEMA_NAME SCROLL SEARCH SECOND_P SECURITY SELECT SEPARATOR_P SEQUENCE SEQUENCES @@ -3283,6 +3283,14 @@ common_func_opt_item: { $$ = makeDefElem("package", (Node *)makeInteger(true)); } + | RESULT_CACHE + { + $$ = makeDefElem("result_cache", (Node*)makeInteger(TRUE)); + } + | NOT RESULT_CACHE + { + $$ = makeDefElem("result_cache", (Node*)makeInteger(FALSE)); + } ; @@ -12126,6 +12134,7 @@ unreserved_keyword: | RESPECT_P | RESTART | RESTRICT + | RESULT_CACHE | RETURNS | REUSE | REVOKE diff --git a/src/gausskernel/optimizer/commands/functioncmds.cpp b/src/gausskernel/optimizer/commands/functioncmds.cpp index a5080d4a89..f75cb54358 100644 --- a/src/gausskernel/optimizer/commands/functioncmds.cpp +++ b/src/gausskernel/optimizer/commands/functioncmds.cpp @@ -596,7 +596,7 @@ void examine_parameter_list(List* parameters, Oid languageOid, const char* query static bool compute_common_attribute(DefElem* defel, DefElem** volatility_item, DefElem** strict_item, DefElem** security_item, DefElem** leakproof_item, List** set_items, DefElem** cost_item, DefElem** rows_item, DefElem** fencedItem, DefElem** shippable_item, DefElem** package_item, DefElem** pipelined_item, - DefElem** parallel_enable_item) + DefElem** parallel_enable_item, DefElem** result_cache_item) { if (strcmp(defel->defname, "volatility") == 0) { if (*volatility_item) @@ -655,6 +655,11 @@ static bool compute_common_attribute(DefElem* defel, DefElem** volatility_item, goto duplicate_error; *parallel_enable_item = defel; + } else if (strcmp(defel->defname, "result_cache") == 0) { + if (*result_cache_item) + goto duplicate_error; + + *result_cache_item = defel; } else return false; @@ -725,7 +730,7 @@ static bool compute_b_attribute(DefElem* defel) List* compute_attributes_sql_style(const List* options, List** as, char** language, bool* windowfunc_p, char* volatility_p, bool* strict_p, bool* security_definer, bool* leakproof_p, ArrayType** proconfig, float4* procost, float4* prorows, bool* fenced, bool* shippable, bool* package, bool* is_pipelined, - FunctionPartitionInfo** partInfo) + FunctionPartitionInfo** partInfo, bool* result_cache) { ListCell* option = NULL; DefElem* as_item = NULL; @@ -743,6 +748,7 @@ List* compute_attributes_sql_style(const List* options, List** as, char** langua DefElem* package_item = NULL; DefElem* pipelined_item = NULL; DefElem* parallel_enable_item = NULL; + DefElem* result_cache_item = NULL; List* bCompatibilities = NIL; foreach (option, options) { DefElem* defel = (DefElem*)lfirst(option); @@ -771,7 +777,8 @@ List* compute_attributes_sql_style(const List* options, List** as, char** langua &shippable_item, &package_item, &pipelined_item, - ¶llel_enable_item)) { + ¶llel_enable_item, + &result_cache_item)) { /* recognized common option */ continue; } else if (compute_b_attribute(defel)) { @@ -865,6 +872,10 @@ List* compute_attributes_sql_style(const List* options, List** as, char** langua } *partInfo = (FunctionPartitionInfo*)parallel_enable_item->arg; } + + if (result_cache_item != NULL) { + *result_cache = intVal(result_cache_item->arg); + } list_free(set_items); return bCompatibilities; } @@ -1075,6 +1086,7 @@ ObjectAddress CreateFunction(CreateFunctionStmt* stmt, const char* queryString, bool fenced = IS_SINGLE_NODE ? false : true; bool shippable = false; bool package = false; + bool result_cache = false; FunctionPartitionInfo* partInfo = NULL; bool proIsProcedure = stmt->isProcedure; if (!OidIsValid(pkg_oid)) { @@ -1207,7 +1219,7 @@ ObjectAddress CreateFunction(CreateFunctionStmt* stmt, const char* queryString, List *functionOptions = compute_attributes_sql_style((const List *)stmt->options, &as_clause, &language, &isWindowFunc, &volatility, &isStrict, &security, &isLeakProof, &proconfig, &procost, &prorows, &fenced, &shippable, &package, - &isPipelined, &partInfo); + &isPipelined, &partInfo, &result_cache); pipelined_function_sanity_check(stmt, isPipelined); @@ -1436,7 +1448,8 @@ ObjectAddress CreateFunction(CreateFunctionStmt* stmt, const char* queryString, type_oid, stmt->typfunckind, stmt->isfinal, - func_oid); + func_oid, + result_cache); CreateFunctionComment(address.objectId, functionOptions); pfree_ext(param_type_depend_ext); @@ -2609,6 +2622,7 @@ ObjectAddress AlterFunction(AlterFunctionStmt* stmt) DefElem* shippable_item = NULL; DefElem* package_item = NULL; DefElem* pipelined_item = NULL; + DefElem* result_cache_item = NULL; ObjectAddress address; bool isNull = false; @@ -2691,7 +2705,8 @@ ObjectAddress AlterFunction(AlterFunctionStmt* stmt) &shippable_item, &package_item, &pipelined_item, - NULL)) { + NULL, + &result_cache_item)) { continue; } else if (compute_b_attribute(defel)) { /* recognized b compatibility options */ @@ -2740,7 +2755,7 @@ ObjectAddress AlterFunction(AlterFunctionStmt* stmt) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("Do not support package for ALTER FUNCTION."))); } - if (set_items != NULL || fencedItem != NULL || shippable_item != NULL) { + if (set_items != NULL || fencedItem != NULL || shippable_item != NULL || result_cache_item != NULL) { Datum datum; bool isnull = false; Datum repl_val[Natts_pg_proc]; @@ -2795,6 +2810,12 @@ ObjectAddress AlterFunction(AlterFunctionStmt* stmt) elog(NOTICE, "Immutable function will be shippable anyway."); } } + + if (result_cache_item != NULL) { + repl_repl[Anum_pg_proc_result_cache - 1] = true; + repl_val[Anum_pg_proc_result_cache - 1] = BoolGetDatum(intVal(result_cache_item->arg)); + repl_null[Anum_pg_proc_result_cache - 1] = false; + } tup = (HeapTuple) tableam_tops_modify_tuple(tup, RelationGetDescr(rel), repl_val, repl_null, repl_repl); } diff --git a/src/gausskernel/optimizer/commands/typecmds.cpp b/src/gausskernel/optimizer/commands/typecmds.cpp index 8e2fdf7e73..375ca474bf 100644 --- a/src/gausskernel/optimizer/commands/typecmds.cpp +++ b/src/gausskernel/optimizer/commands/typecmds.cpp @@ -3226,6 +3226,7 @@ static bool is_same_function(HeapTuple proctup, TupleDesc tupdesc, CreateFunctio bool shippable = false; bool package = false; bool isPipelined = false; + bool result_cache = false; HeapTuple langtup; Form_pg_proc proc = (Form_pg_proc)GETSTRUCT(proctup); Form_pg_language lang; @@ -3244,7 +3245,7 @@ static bool is_same_function(HeapTuple proctup, TupleDesc tupdesc, CreateFunctio /* Compare option */ compute_attributes_sql_style((const List*)method->options, &as_clause, &language, &isWindowFunc, &volatility, &isStrict, &security, &isLeakProof, &proconfig, &procost, &prorows, - &fenced, &shippable, &package, &isPipelined, &partInfo); + &fenced, &shippable, &package, &isPipelined, &partInfo, &result_cache); /* Validate language */ langtup = SearchSysCache1(LANGOID, ObjectIdGetDatum(proc->prolang)); diff --git a/src/gausskernel/optimizer/plan/createplan.cpp b/src/gausskernel/optimizer/plan/createplan.cpp index b6da7d0274..df3128ba25 100755 --- a/src/gausskernel/optimizer/plan/createplan.cpp +++ b/src/gausskernel/optimizer/plan/createplan.cpp @@ -308,6 +308,200 @@ void set_plan_rows_from_plan(Plan* plan, double localRows, double multiple) plan->plan_rows = get_global_rows(localRows, plan->multiple, ng_get_dest_num_data_nodes(plan)); } +extern void set_func_checked(FuncExpr *fexpr); +extern void estimate_func_retcache(FuncExpr *fexpr, PlannerInfo *root); + +typedef struct FuncRetcacheKey +{ + Oid funcid; + Oid funccoll; +} FuncRetcacheKey; + +typedef struct FuncRetcacheEntry +{ + FuncRetcacheKey funckey; + uint32 funcflags; +} FuncRetcacheEntry; + +typedef struct FuncRetCacheWalkerContext +{ + PlannerInfo *root; + HTAB *hashp; + bool is_supported_cmd; +} FuncRetCacheWalkerContext; + +static void +func_retcache_expr(FuncExpr *fexpr, FuncRetCacheWalkerContext *wcxt) +{ + FuncRetcacheKey funckey; + FuncRetcacheEntry* funcentry; + + if (!wcxt->is_supported_cmd) { + /* + * SQL that does not support function result caching, + * mark the function as checked and exit directly. + */ + set_func_checked(fexpr); + return; + } + + funckey.funcid = fexpr->funcid; + funckey.funccoll = fexpr->inputcollid; + + funcentry = (FuncRetcacheEntry *)hash_search(wcxt->hashp, &funckey, HASH_FIND, NULL); + if (funcentry) { + /* + * If the same function appears multiple times, syscache will be called multiple times, + * resulting in waste. Using hash tables, when a function appears multiple times, the + * last hit result will be tried first to improve performance. + */ + Assert(funcentry->funcflags != 0); + fexpr->funcflags = funcentry->funcflags; + + return; + } + + Assert(fexpr->funcflags == 0); + estimate_func_retcache(fexpr, wcxt->root); + Assert(fexpr->funcflags != 0); + + funcentry = (FuncRetcacheEntry *)hash_search(wcxt->hashp, &funckey, HASH_ENTER, NULL); + + Assert(funcentry); + funcentry->funcflags = fexpr->funcflags; +} + +static bool +func_retcache_walker(Node *node, FuncRetCacheWalkerContext *wcxt) +{ + if (node == NULL) + return false; + + /* Guard against stack overflow due to overly complex plans */ + check_stack_depth(); + + if (IsA(node, RestrictInfo)) { + RestrictInfo *rinfo = (RestrictInfo *)node; + + if (rinfo->clause) + (void)expression_tree_walker((Node *)rinfo->clause, (bool (*)())func_retcache_walker, + (void *) wcxt); + + if (rinfo->left_em && rinfo->left_em->em_expr) + (void)expression_tree_walker((Node *)rinfo->left_em->em_expr, (bool (*)())func_retcache_walker, + (void *) wcxt); + + if (rinfo->right_em && rinfo->right_em->em_expr) + (void)expression_tree_walker((Node *)rinfo->right_em->em_expr, (bool (*)())func_retcache_walker, + (void *) wcxt); + + return false; + } else if (IsA(node, FuncExpr)) { + FuncExpr *fexpr = (FuncExpr *)node; + + func_retcache_expr(fexpr, wcxt); + + if (expression_tree_walker((Node *)fexpr->args, (bool (*)())func_retcache_walker, + (void *) wcxt)) + return true; + + return false; + } + + return expression_tree_walker(node, (bool (*)())func_retcache_walker, + (void *) wcxt); +} + +static void +scan_func_rescache_recurse(Path *path, FuncRetCacheWalkerContext *wcxt) +{ + /* Guard against stack overflow due to overly complex plans */ + check_stack_depth(); + + switch (path->pathtype) { + case T_IndexScan: + case T_IndexOnlyScan: + { + List *scan_clauses = castNode(IndexPath, path)->indexinfo->indrestrictinfo; + + if (expression_tree_walker((Node *)scan_clauses, (bool (*)())func_retcache_walker, + (void *) wcxt)) + break; + + break; + } + case T_SeqScan: + case T_BitmapHeapScan: + case T_TidScan: + case T_SubqueryScan: + case T_FunctionScan: + case T_ValuesScan: + case T_CteScan: + case T_WorkTableScan: + case T_ForeignScan: + { + RelOptInfo *rel = path->parent; + List *scan_clauses = rel->baserestrictinfo; + + if (expression_tree_walker((Node *)scan_clauses, (bool (*)())func_retcache_walker, + (void *) wcxt)) + break; + + break; + } + case T_HashJoin: + case T_MergeJoin: + case T_NestLoop: + { + JoinPath *jpath = (JoinPath *) path; + + scan_func_rescache_recurse(jpath->outerjoinpath, wcxt); + scan_func_rescache_recurse(jpath->innerjoinpath, wcxt); + + if (expression_tree_walker((Node *)jpath->joinrestrictinfo, (bool (*)())func_retcache_walker, + (void *) wcxt)) + break; + + break; + } + case T_BaseResult: + { + if (IsA(path, ProjectionPath)) + { + ProjectionPath *ppath = (ProjectionPath *)path; + scan_func_rescache_recurse(ppath->subpath, wcxt); + } + + break; + } + case T_Material: + { + MaterialPath *mpath = (MaterialPath *)path; + scan_func_rescache_recurse(mpath->subpath, wcxt); + + break; + } + case T_Append: + { + AppendPath *apath = (AppendPath *)path; + ListCell *subpaths; + + foreach(subpaths, apath->subpaths) + { + scan_func_rescache_recurse((Path *) lfirst(subpaths), wcxt); + } + } + case T_WindowAgg: + default: + break; + } + + if (wcxt->root->origin_tlist && + expression_tree_walker((Node *)wcxt->root->origin_tlist, (bool (*)())func_retcache_walker, + (void *) wcxt)) + {} +} + /* * create_plan * Creates the access plan for a query by recursively processing the @@ -335,6 +529,45 @@ Plan* create_plan(PlannerInfo* root, Path* best_path) root->curOuterParams = NIL; u_sess->opt_cxt.is_under_append_plan = false; + /* + * Find all called functions from the best_path and save their properties. + * strictly limit it to read-only SELECT. + */ + if (ENABLE_FUNCTION_RESULT_CACHE() && root->parse) { + FuncRetCacheWalkerContext wcxt; + HASHCTL wcxt_hash_ctl; + PlannerInfo* parent_root = root->parent_root; + + wcxt.root = root; + wcxt.is_supported_cmd = root->parse->commandType == CMD_SELECT && !root->parse->hasForUpdate; + while (parent_root != NULL) { + if (parent_root->parent_root == NULL) { + break; + } + parent_root = parent_root->parent_root; + } + if (parent_root) { + wcxt.is_supported_cmd &= parent_root->parse->commandType == CMD_SELECT && !root->parse->hasForUpdate; + } + + wcxt_hash_ctl.keysize = sizeof(FuncRetcacheKey); + wcxt_hash_ctl.entrysize = sizeof(FuncRetcacheEntry); + wcxt_hash_ctl.hash = tag_hash; + wcxt_hash_ctl.hcxt = AllocSetContextCreate(root->planner_cxt, + "FuncRetCacheWalkerMemoryContext", + ALLOCSET_DEFAULT_MINSIZE, + ALLOCSET_DEFAULT_INITSIZE, + ALLOCSET_DEFAULT_MAXSIZE); + + wcxt.hashp = hash_create("func retcache flag hash", 32, + &wcxt_hash_ctl, + HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT); + + scan_func_rescache_recurse(best_path, &wcxt); + + MemoryContextDelete(wcxt_hash_ctl.hcxt); + } + /* Recursively process the path tree */ plan = create_plan_recurse(root, best_path); diff --git a/src/gausskernel/runtime/executor/execQual.cpp b/src/gausskernel/runtime/executor/execQual.cpp index fec02e774b..543ce3e910 100644 --- a/src/gausskernel/runtime/executor/execQual.cpp +++ b/src/gausskernel/runtime/executor/execQual.cpp @@ -165,6 +165,10 @@ static inline Datum GetResultByType(char* result, Oid typOid, int typmod); THR_LOCAL PLpgSQL_execstate* plpgsql_estate = NULL; +extern void EStateFuncAssignCache(ExprState *state, ExprContext *econtext, FuncExpr *fe, FunctionCallInfo fcinfo); +extern bool EStateFuncGetRetCache(FunctionCallInfo fcinfo, Datum *retvalue, bool *retnull); +extern bool EStateFuncPutRetCache(FunctionCallInfo fcinfo, Datum ret); + /* ---------------------------------------------------------------- * ExecEvalExpr routines * @@ -2935,12 +2939,30 @@ static Datum ExecMakeFunctionResultNoSets( fcinfo->arg[1] = fetch_lob_value_from_tuple(lob_pointer, InvalidOid, &is_null); } } - if (func_encoding != db_encoding) { - DB_ENCODING_SWITCH_TO(func_encoding); - result = FunctionCallInvoke(fcinfo); - DB_ENCODING_SWITCH_BACK(db_encoding); + + /* Function result cache */ + if (!has_refcursor && !has_cursor_return && + ENABLE_FUNCTION_RESULT_CACHE() && + fcinfo->fncache) { + if (!EStateFuncGetRetCache(fcinfo, &result, &fcinfo->isnull)) { + if (func_encoding != db_encoding) { + DB_ENCODING_SWITCH_TO(func_encoding); + result = FunctionCallInvoke(fcinfo); + DB_ENCODING_SWITCH_BACK(db_encoding); + } else { + result = FunctionCallInvoke(fcinfo); + } + if (fcinfo->fncache) + EStateFuncPutRetCache(fcinfo, result); + } } else { - result = FunctionCallInvoke(fcinfo); + if (func_encoding != db_encoding) { + DB_ENCODING_SWITCH_TO(func_encoding); + result = FunctionCallInvoke(fcinfo); + DB_ENCODING_SWITCH_BACK(db_encoding); + } else { + result = FunctionCallInvoke(fcinfo); + } } } *isNull = fcinfo->isnull; @@ -3683,6 +3705,9 @@ static Datum ExecEvalFunc(FuncExprState *fcache, ExprContext *econtext, bool *is fcache->xprstate.evalfunc = (ExprStateEvalFunc)ExecMakeFunctionResultNoSets; return ExecMakeFunctionResultNoSets(fcache, econtext, isNull, isDone); } else { + if (ENABLE_FUNCTION_RESULT_CACHE()) + EStateFuncAssignCache(NULL, econtext, func, &fcache->fcinfo_data); + fcache->xprstate.evalfunc = (ExprStateEvalFunc)ExecMakeFunctionResultNoSets; return ExecMakeFunctionResultNoSets(fcache, econtext, isNull, isDone); } diff --git a/src/gausskernel/runtime/executor/execUtils.cpp b/src/gausskernel/runtime/executor/execUtils.cpp index c157b3b9c8..b3296833e4 100644 --- a/src/gausskernel/runtime/executor/execUtils.cpp +++ b/src/gausskernel/runtime/executor/execUtils.cpp @@ -79,6 +79,98 @@ #include "catalog/pg_proc_fn.h" #include "funcapi.h" +/* Function Cached Result */ + +#define FNCACHE_NUM_ARGS (0x00FF) +#define FNCACHE_NUMARGS(fflags) ((short)((fflags) & FNCACHE_NUM_ARGS)) + +#define FNCACHE_REF_COUNT (0xFF00) +#define FNCACHE_REFCOUNT(fflags) ((fflags) & FNCACHE_REF_COUNT) +#define FNCACHE_REF_MAX (0xFF00) +#define FNCACHE_REF_ONE (0x0100) + +#define FNCACHE_CHECKED (1 << 24) /* function is checked */ +#define FNCACHE_ENABLE_CACHE (1 << 25) /* function can use cache */ +#define FNCACHE_ARG_UNIQUE (1 << 26) /* function has unique arg */ + +#define FCR_REFCOUNT_ONE 1 +#define FCR_REFCOUNT_MASK ((1U << 4) - 1) + +#define FCR_USAGECOUNT_MASK 0x003C0000U +#define FCR_USAGECOUNT_ONE (1U << 18) +#define FCR_USAGECOUNT_SHIFT 18 +#define FCR_FLAG_MASK 0xFFC00000U + +#define FCR_STATE_GET_REFCOUNT(state) ((state) & FCR_REFCOUNT_MASK) +#define FCR_STATE_GET_USAGECOUNT(state) (((state) & FCR_USAGECOUNT_MASK) >> FCR_USAGECOUNT_SHIFT) + +/* Function Result Flags */ + +#define FR_VALID (1U << 24) /* cache is valid */ +#define FR_RETNULL (1U << 25) /* result is null */ +#define FR_HITTED (1U << 26) /* cache has hitted */ + +#define FR_STATE_IS_VALID(state) (((state) & FR_VALID) == FR_VALID) +#define FR_STATE_RET_NULL(state) (((state) & FR_RETNULL) == FR_RETNULL) +#define FR_STATE_HITTED(state) (((state) & FR_HITTED) == FR_HITTED) + +#define FR_MAX_REF_COUNT FCR_REFCOUNT_MASK +#define FR_MAX_USAGE_COUNT 5 + +#define FR_CACHE_NUM_BUCKETS (32) +#define FR_CACHE_SIZE_SECTION (4) +#define FR_CACHE_NUM_SECTIONS (4) +#define FR_CACHE_SIZE_BUCKET (FR_CACHE_SIZE_SECTION * FR_CACHE_NUM_SECTIONS) +#define FR_CACHE_MAX_SIZE ((FR_CACHE_NUM_BUCKETS) * (FR_CACHE_SIZE_BUCKET)) + +#define FR_CACHE_SEG_RANGE ((UINT32_MAX + 1) / FR_CACHE_NUM_BUCKETS) + +#if FUNC_MAX_ARGS > 16 +#define FCR_MAX_ARGS (16) +#else +#define FCR_MAX_ARGS (FUNC_MAX_ARGS) +#endif + +#if FNCACHE_NUM_ARGS < FCR_MAX_ARGS +#error FCR_MAX_ARGS too large +#endif + +typedef struct FuncRetCache +{ + uint32 state; + uint32 argisnull; + Datum* args; + Datum retval; +} FuncRetCache; + +typedef struct FuncRetBucket +{ + short nextvict; + pg_crc32c* argcrcs; + FuncRetCache** retcache; +} FuncRetBucket; + +typedef struct FuncCacheData +{ + Oid fnoid; + Oid fncoll; + uint32 fcflags; + bool security; + short prevhit; + Oid *argtypes; + Oid rettype; + int usagecount; + int hitcount; + union + { + FuncRetBucket** retbuckets; + FuncRetCache* retcache; + } cacheptr; +} FuncCacheData; + +static FuncCache EStateFuncGetCache(EState *es, Oid fid, Oid fncoll); +static FuncCache EStateFuncPutCache(EState *es, Oid fid, Oid fncoll); + static bool get_last_attnums(Node* node, ProjectionInfo* projInfo); static bool index_recheck_constraint( Relation index, Oid* constr_procs, Datum* existing_values, const bool* existing_isnull, Datum* new_values); @@ -3204,3 +3296,1198 @@ void set_result_for_plpgsql_language_function_with_outparam_by_flatten(Datum *re pfree(values); pfree(nulls); } + +/* + * Result cache of procedural language functions + * + * Currently, function inputs are difficult to predict because they often contain expressions + * or even the output of other functions. + * + * Some scenarios of relation scan operators can estimate the number of calls more accurately, + * but the distribution of parameters is still difficult to predict. Therefore, the design + * considers fragmenting the cache in order to create as small a cache as possible. + * + * In practice, the cost of cache management in the PG environment is still considerable. For + * the simplest function that contains only one native type parameter and directly returns the + * result, the hit rate is less than about 15%, which will result in negative returns. + * + * The function cost is closely related to the management of the result cache, but unfortunately, + * there are problems with the current function cost system and it is difficult to solve it for + * the time being. The first phase of implementation uses a rough empirical coefficient. + * + * Fragmented cache, divided into 32 buckets, each with a capacity of 16, and a total cache of 512. + * 1. Bucket slots are allocated at once + * 2. There are many buckets, and some buckets may not be used when the result set is small, + * reducing memory allocation + * 3. Four positions are allocated in the bucket each time, and the bucket can be not full when + * the result set is small, reducing memory allocation + * 4. The bucket size is small, and the matching process is faster + * 5. The parameters use the instruction set to calculate the CRC matching bucket, in order to be faster + * Extreme scenarios, such as all input parameters are constants, only one bucket is used, 4 blocks. + */ +static bool +func_cache_support_type(Oid typ) +{ + if (typ >= FirstBootstrapObjectId) { + Oid basetyp; + + basetyp = getBaseType(typ); + + if (!OidIsValid(basetyp)) + return false; + + if (basetyp >= FirstBootstrapObjectId) + return false; + + return func_cache_support_type(basetyp); + } + + switch(typ) { + case BOOLOID: + case CHAROID: + case INT2OID: + case INT4OID: + case INT8OID: + case FLOAT4OID: + case FLOAT8OID: + case TIMEOID: + case DATEOID: + case TIMESTAMPOID: + case TIMESTAMPTZOID: + case TEXTOID: + case BPCHAROID: + case VARCHAROID: + case NVARCHAR2OID: + case NUMERICOID: + return true; + default: + return false; + } +} + +static bool +func_cache_support_proc(FuncExpr *fexpr, HeapTuple proctup) +{ + short i, nargs; + bool isNull; + Datum tmp; + + Form_pg_proc procform = (Form_pg_proc) GETSTRUCT(proctup); + + Assert(!procform->proretset); + + if (procform->prolang < FirstBootstrapObjectId || + procform->provolatile == PROVOLATILE_VOLATILE || + procform->proisagg || procform->proiswindow || + list_length(fexpr->args) != procform->pronargs) + return false; + + tmp = SysCacheGetAttr(PROCOID, proctup, Anum_pg_proc_prokind, &isNull); + if (isNull) { + return false; + } + + if (DatumGetChar(tmp) != PROKIND_FUNCTION) + return false; + + (void)SysCacheGetAttr(PROCNAMEARGSNSP, proctup, + Anum_pg_proc_proallargtypes, + &isNull); + /* Containing none-IN args */ + if (!isNull) + return false; + + nargs = procform->pronargs; + + /* Too many args */ + if (nargs > FCR_MAX_ARGS) + return false; + + for (i = 0; i < nargs; i++) { + if (!func_cache_support_type(procform->proargtypes.values[i])) + return false; + } + + fexpr->funcflags |= nargs; + + return true; +} + +bool +check_func_need_rescache(FuncExpr *fexpr) +{ + HeapTuple proctup; + Relation relation; + Datum datum; + bool isNull = false; + bool hasResultCacheFlag = false; + + /* + * builtin functions not support. + */ + if (fexpr->funcid < FirstNormalObjectId) + return false; + + if (fexpr->funcretset || fexpr->funcvariadic || + !func_cache_support_type(fexpr->funcresulttype)) { + return false; + } + + proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(fexpr->funcid)); + if (!HeapTupleIsValid(proctup)) + return false; + + /* Function's RESULT_CACHE property is false, not support */ + relation = heap_open(ProcedureRelationId, NoLock); + datum = heap_getattr(proctup, Anum_pg_proc_result_cache, RelationGetDescr(relation), &isNull); + hasResultCacheFlag = isNull ? false : DatumGetBool(datum); + heap_close(relation, NoLock); + + if (!hasResultCacheFlag) { + ReleaseSysCache(proctup); + return false; + } + + if (!func_cache_support_proc(fexpr, proctup)) { + ReleaseSysCache(proctup); + return false; + } + + ReleaseSysCache(proctup); + + return true; +} + +void +set_func_checked(FuncExpr *fexpr) +{ + if (fexpr) { + fexpr->funcflags = FNCACHE_CHECKED; + } +} + +void +estimate_func_retcache(FuncExpr *fexpr, PlannerInfo *root) +{ + ListCell *lc; + Node *arg; + VariableStatData rdata; + + Assert(IsA(fexpr, FuncExpr)); + Assert((fexpr->funcflags & FNCACHE_CHECKED) == 0); + + fexpr->funcflags = FNCACHE_CHECKED; + + if (!check_func_need_rescache(fexpr)) { + return; + } + + fexpr->funcflags |= FNCACHE_ENABLE_CACHE; + + if (root == NULL) + return; + + foreach(lc, fexpr->args) { + arg = (Node *)lfirst(lc); + + if (IsA(arg, Var)) { + examine_variable(root, arg, 0, &rdata); + + if (rdata.isunique) + fexpr->funcflags |= FNCACHE_ARG_UNIQUE; + + ReleaseVariableStats(rdata); + } + } +} + +void +EStateFuncAssignCache(ExprState *state, ExprContext *ectx, FuncExpr *fe, FunctionCallInfo fcinfo) +{ + Oid fnid, fncoll; + EState *es_top; + FmgrInfo *flinfo; + FuncCache fncache; + + Assert(!(state && ectx)); + Assert(state != NULL || ectx != NULL); + + if ((state == NULL && ectx == NULL) || fe == NULL || fcinfo == NULL) { + return; + } + + if (!ActivePortal || ActivePortal->func_retcache_cxt == NULL) { + return; + } + + if (!IsA(fe, FuncExpr)) { + return; + } + + if (fe->funcid < FirstNormalObjectId) + return; + + /* smp not support */ + if (StreamThreadAmI() || StreamTopConsumerAmI()) { + return; + } + + if (state) { + /* From ExecInitFunc */ + return; + } else if (ectx && ActivePortal->top_estate) { + /* From ExecEvalFunc */ + es_top = ActivePortal->top_estate; + } else { + /* EState not found */ + return; + } + + if (es_top->es_topstate) + es_top = es_top->es_topstate; + + if (es_top->es_topstate) + return; + + flinfo = fcinfo->flinfo; + + Assert(flinfo->fn_oid >= FirstNormalObjectId); + Assert(!flinfo->fn_retset); + + if (flinfo->fn_nargs > FCR_MAX_ARGS) + return; + + fnid = flinfo->fn_oid; + fncoll = fcinfo->fncollation; + + /* + * Save top EState to function. + */ + fcinfo->top_estate = es_top; + + Assert(fcinfo->fncache == NULL); + + fncache = EStateFuncGetCache(es_top, fnid, fncoll); + + if (fncache) { + /* Just in case. Variable arguments and default arguments. */ + if (FNCACHE_NUMARGS(fncache->fcflags) != fcinfo->nargs) + return; + + if ((fncache->fcflags & FNCACHE_ENABLE_CACHE) == 0) + /* Function has checked, not support result cache. */ + return; + + if (FNCACHE_REFCOUNT(fncache->fcflags) >= FNCACHE_REF_MAX) + /* * Cache can only be used for the first 255 calls to the same function in same plan. */ + return; + + if ((fe->funcflags & FNCACHE_ARG_UNIQUE) && + (fncache->fcflags & FNCACHE_ARG_UNIQUE) == 0) + fncache->fcflags |= FNCACHE_ARG_UNIQUE; + + fncache->fcflags += FNCACHE_REF_ONE; + fcinfo->fncache = fncache; + + return; + } + + /* Function has not yet created a cache. */ + + /* + * Function has not yet checked? Check if support function result cache. + */ + if ((fe->funcflags & FNCACHE_CHECKED) == 0) + estimate_func_retcache(fe, NULL); + + fncache = EStateFuncPutCache(es_top, fnid, fncoll); + if (fncache == NULL) { + return; + } + + Assert(fncache); + + /* Function result can not cached, return. */ + if ((fe->funcflags & FNCACHE_ENABLE_CACHE) == 0) { + fncache->fcflags = FNCACHE_CHECKED; + return; + } + + fncache->fcflags = fe->funcflags; + Assert(FNCACHE_REFCOUNT(fncache->fcflags) == 0); + + fncache->prevhit = -1; + + fncache->fcflags += FNCACHE_REF_ONE; + + Assert(fncache->argtypes == NULL); + Assert(!OidIsValid(fncache->rettype)); + + fcinfo->fncache = fncache; + + /* + * Save function args and return type, found their base types. + */ + if (flinfo->fn_nargs > 0) { + HeapTuple proctup; + Form_pg_proc procform; + short i, nargs; + Oid* argtypes; + + proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(fnid)); + Assert(HeapTupleIsValid(proctup)); + + procform = (Form_pg_proc) GETSTRUCT(proctup); + Assert(procform->pronargs == fcinfo->flinfo->fn_nargs); + + nargs = procform->pronargs; + argtypes = procform->proargtypes.values; + + fncache->security = procform->prosecdef; + fncache->argtypes = (Oid *)MemoryContextAllocZero(ActivePortal->func_retcache_cxt, + sizeof(Oid) * nargs); + + /* transform to base types */ + for (i = 0; i < nargs; i++) { + if (argtypes[i] < FirstBootstrapObjectId) { + fncache->argtypes[i] = argtypes[i]; + continue; + } + + fncache->argtypes[i] = getBaseType(argtypes[i]); + Assert(OidIsValid(fncache->argtypes[i])); + Assert(fncache->argtypes[i] < FirstBootstrapObjectId); + } + + ReleaseSysCache(proctup); + } else if (flinfo->fn_nargs == 0) { + HeapTuple proctup; + Form_pg_proc procform; + + proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(fnid)); + Assert(HeapTupleIsValid(proctup)); + + procform = (Form_pg_proc) GETSTRUCT(proctup); + fncache->security = procform->prosecdef; + ReleaseSysCache(proctup); + } + + fncache->rettype = fe->funcresulttype; + + /* transform to base types */ + if (fncache->rettype >= FirstBootstrapObjectId) { + fncache->rettype = getBaseType(fncache->rettype); + Assert(OidIsValid(fncache->rettype)); + Assert(fncache->rettype < FirstBootstrapObjectId); + } +} + +#define FNCACHE_ARRAY_SIZE (8) +#define MAX_FNCACHE_SLOT (16) /* max slot: 14 */ + +/* + * Before all functions in the plan are initialized, we cannot predict how + * many functions need to be processed, including other functions called in + * PL/pgSQL functions. + * + * Each function's own result cache is associated with a pointer after allocation, + * so the location cannot be changed, and space needs to be saved as much as possible. + * This cache only needs to be associated during the executor initialization phase, + * and there is no need to worry about performance. + * + * When allocating function cache, an array of 8 pointers is used for management: + * 1. When more space is needed, an array of pointers of 8 elements is allocated; + * 2. The last element of the array points to the next array; + * 3. Any function expression is initialized only once, so sequential traversal is tolerable. + * + * If a plan involves a lot of function calls, this may need to be reconsidered. + * Currently limited to MAX_FNCACHE_SLOT. + * + * Use function oid and collation as cache key. + */ +static FuncCache +EStateFuncGetCache(EState *es, Oid fnid, Oid fncoll) +{ + short i; + FuncCache *fncaches; + + Assert(es); + Assert(OidIsValid(fnid)); + Assert(fnid >= FirstNormalObjectId); + Assert(es->es_topstate == NULL); + + if (es->es_topstate) + return NULL; + + if (es->es_funcache.fncaches == NULL) + return NULL; + + fncaches = es->es_funcache.fncaches; + + for (i = 0;;) { + if (i < (FNCACHE_ARRAY_SIZE - 1)) { + if (!OidIsValid(fncaches[i]->fnoid)) + break; + + if (fncaches[i]->fnoid != fnid || fncaches[i]->fncoll != fncoll) { + i++; + continue; + } + + return fncaches[i]; + } + + if (fncaches[FNCACHE_ARRAY_SIZE - 1] == NULL) + break; + + i = 0; + fncaches = (FuncCache *)fncaches[FNCACHE_ARRAY_SIZE - 1]; + } + + return NULL; +} + +/* + * Create function result cache. + */ +static FuncCache +EStateFuncPutCache(EState *es, Oid fnid, Oid fncoll) +{ + short i; + FuncCache *fncaches, fncache; + + Assert(es); + Assert(OidIsValid(fnid)); + Assert(fnid >= FirstNormalObjectId); + Assert(es->es_topstate == NULL); + + if (es->es_topstate) + return NULL; + + Assert(EStateFuncGetCache(es, fnid, fncoll) == NULL); + + if (es->es_funcache.fncaches == NULL) { + fncaches = (FuncCache *)MemoryContextAllocZero(ActivePortal->func_retcache_cxt, + sizeof(FuncCache) * FNCACHE_ARRAY_SIZE); + + fncache = (FuncCache)MemoryContextAllocZero(ActivePortal->func_retcache_cxt, + sizeof(FuncCacheData) * (FNCACHE_ARRAY_SIZE - 1)); + + for (i = 0; i < FNCACHE_ARRAY_SIZE - 1; i++) + fncaches[i] = fncache + i; + + es->es_funcache.fncaches = fncaches; + ActivePortal->func_retcache_slot_count += FNCACHE_ARRAY_SIZE; + } else { + i = 0; + fncaches = es->es_funcache.fncaches; + + for (;;) { + if (i < (FNCACHE_ARRAY_SIZE - 1)) { + if (OidIsValid(fncaches[i]->fnoid)) { + i++; + continue; + } + + fncache = fncaches[i]; + break; + } + + if (fncaches[FNCACHE_ARRAY_SIZE - 1]) { + i = 0; + fncaches = (FuncCache *)fncaches[FNCACHE_ARRAY_SIZE - 1]; + + continue; + } + + if (ActivePortal->func_retcache_slot_count < MAX_FNCACHE_SLOT) { + fncaches[FNCACHE_ARRAY_SIZE - 1] = (FuncCache)MemoryContextAllocZero(ActivePortal->func_retcache_cxt, + sizeof(FuncCache) * FNCACHE_ARRAY_SIZE); + + fncache = (FuncCache)MemoryContextAllocZero(ActivePortal->func_retcache_cxt, + sizeof(FuncCacheData) * (FNCACHE_ARRAY_SIZE - 1)); + + fncaches = (FuncCache *)fncaches[FNCACHE_ARRAY_SIZE - 1]; + for (i = 0; i < FNCACHE_ARRAY_SIZE - 1; i++) + fncaches[i] = fncache + i; + + fncache = fncaches[0]; + ActivePortal->func_retcache_slot_count += FNCACHE_ARRAY_SIZE; + } else { + return NULL; + } + break; + } + } + + Assert(fncache); + + fncache->fnoid = fnid; + fncache->fncoll = fncoll; + + return fncache; +} + +/* + * Recycling cache is mainly used for: + * 1. The plan is called only once and uses unique parameters. The parameters + * are different each time and it is impossible to hit. + * 2. There is no hit after several calls during the execution process. + * + * Although the cost of the result cache itself is much lower than the cost of + * UDF calls, it is still not negligible, so it is best to stop using it at the + * right time. + */ +static void +EStateFuncReclaimCache(FuncCache fncache) +{ + Oid dtmtype; + short i, j, k, nargs; + FuncRetBucket *retbucket; + FuncRetCache *retcache; + + if (FNCACHE_REFCOUNT(fncache->fcflags) > FNCACHE_REF_ONE) { + fncache->fcflags -= FNCACHE_REF_ONE; + + return; + } + + if (fncache->cacheptr.retbuckets == NULL) { + return; + } + + nargs = FNCACHE_NUMARGS(fncache->fcflags); + + Assert(fncache->cacheptr.retbuckets); + for (i = 0; i < FR_CACHE_NUM_BUCKETS; i++) { + retbucket = fncache->cacheptr.retbuckets[i]; + + if (retbucket == NULL) + continue; + + for (j = FR_CACHE_SIZE_BUCKET - 1; j >= 0; j--) { + if (retbucket->retcache[j] == NULL) + continue; + + retcache = retbucket->retcache[j]; + if (!FR_STATE_IS_VALID(retcache->state)) + continue; + + for (k = 0; k < nargs; k++) { + dtmtype = fncache->argtypes[k]; + + if (dtmtype == TEXTOID || dtmtype == VARCHAROID || + dtmtype == NVARCHAR2OID || dtmtype == BPCHAROID || + dtmtype == NUMERICOID) + pfree(DatumGetPointer(retcache->args[k])); + } + + dtmtype = fncache->rettype; + if (dtmtype == TEXTOID || dtmtype == VARCHAROID || + dtmtype == NVARCHAR2OID || dtmtype == BPCHAROID || + dtmtype == NUMERICOID) + pfree(DatumGetPointer(retcache->retval)); + + if ((j % FR_CACHE_SIZE_SECTION) == 0) { + pfree(retbucket->retcache[j]->args); + pfree(retbucket->retcache[j]); + } + } + + pfree(retbucket->retcache); + } + + pfree(fncache->cacheptr.retbuckets); +} + +/* Strings that are too long are difficult to match and are not cached. */ +#define FCR_DATUM_LENGTH_THRESHOLD (127) + +/* + * Check Datum if not support cache. + */ +static inline bool +FunctionDatumToolong(Datum d, Oid dtype) +{ + if (dtype == TEXTOID || dtype == VARCHAROID || + dtype == NVARCHAR2OID || dtype == BPCHAROID) { + if (VARSIZE_ANY(DatumGetPointer(d)) > FCR_DATUM_LENGTH_THRESHOLD) + return true; + } + + return false; +} + +/* + * Check whether the function args used in this call cannot be cached. + */ +static bool +FunctionArgCannotCache(FuncCache fncache, FunctionCallInfo fcinfo) +{ + short i; + short nargs = fcinfo->nargs; + Datum *fargs = fcinfo->arg; + bool *argnull = fcinfo->argnull; + + if (nargs != fcinfo->flinfo->fn_nargs) + return true; + + for (i = 0; i < nargs; i++) { + if (argnull[i]) + continue; + + if (FunctionDatumToolong(fargs[i], fncache->argtypes[i])) + return true; + } + + return false; +} + +/* + * Copy Datum to the current context according to type. + * + * Datum has some judgments before getting the cache, see FunctionDatumToolong, + * and blocks the parts that are unclear and worry about problems. Here you can + * directly use datumcopy. + * + * In the future, when adding new types or making adjustments, you should also + * pay attention to the differences in operations of different types. + */ +static inline Datum +FunctionDatumCopy(Datum d, Oid dtype) +{ + if (dtype == NUMERICOID) { + return NumericGetDatum(DatumGetNumericCopy(d)); + } else if (dtype == TEXTOID || dtype == VARCHAROID || + dtype == NVARCHAR2OID || dtype == BPCHAROID) { + return datumCopy(d, false, -1); + } else + return d; +} + +#define INVALID_ARG_CRC32C (0u) +#define FISRT_ARG_CRC32C (1u) +#define ARG_CRC32C_IS_VALID(c) (!EQ_CRC32C(c, INVALID_ARG_CRC32C)) + +#if ((UINT32_MAX + 1) % FR_CACHE_MAX_SIZE != 0) +#error Wrong FR_CACHE_MAX_SIZE, must be divisible by UINT32_MAX + 1. +#endif + +/* + * Because not too many results are cached, the instruction set CRC operation + * is faster and the hash range is sufficient. + * + * FunctionArgCannotCache first filters the parameters, so there is no need to + * worry about data validity issues here. + * + * Pay attention to this when adjusting or adding supported types in the future. + */ +static inline pg_crc32c +EStateFuncHashArgs(Oid *argtypes, Datum *fargs, bool *isnull, short nargs) +{ + struct varlena *vl; + pg_crc32c tmpcrc; + pg_crc32c argcrc; + short i; + Oid argtype; + + INIT_CRC32C(argcrc); + + for (i = 0; i < nargs; i++) { + if (isnull[i]) + continue; + + argtype = argtypes[i]; + + if (argtype == NUMERICOID) { + struct varlena *vl; + Size len; + + vl = (struct varlena*)DatumGetPointer(fargs[i]); + + Assert(!VARATT_IS_EXTENDED(vl) || !VARATT_IS_HUGE_TOAST_POINTER(vl)); + + len = VARATT_IS_HUGE_TOAST_POINTER(vl) ? VARSIZE_EXTERNAL(vl) : VARSIZE(vl); + + INIT_CRC32C(tmpcrc); + COMP_CRC32C(tmpcrc, vl, VARSIZE_ANY(vl)); + FIN_CRC32C(tmpcrc); + + COMP_CRC32C(argcrc, &tmpcrc, sizeof(pg_crc32c)); + } else if (argtype == TEXTOID || argtype == VARCHAROID || + argtype == NVARCHAR2OID || argtype == BPCHAROID) { + INIT_CRC32C(tmpcrc); + vl = (struct varlena *) DatumGetPointer(fargs[i]); + COMP_CRC32C(tmpcrc, vl, VARSIZE_ANY(vl)); + FIN_CRC32C(tmpcrc); + + COMP_CRC32C(argcrc, &tmpcrc, sizeof(pg_crc32c)); + } else + COMP_CRC32C(argcrc, fargs + i, sizeof(Datum)); + } + + FIN_CRC32C(argcrc); + + if EQ_CRC32C(argcrc, INVALID_ARG_CRC32C) + argcrc = FISRT_ARG_CRC32C; + + return argcrc; +} + +/* + * Compare the parameters Dautm one by one according to type for equality. + */ +static inline bool +EStateFuncCompareArgs(Oid *argtypes, FuncRetCache *retcache, Datum *fargs, bool *isnull, short nargs) +{ + short i; + Oid argtype; + Datum *args = retcache->args; + uint32 argisnull = retcache->argisnull; + + for (i = 0; i < nargs; i++) { + /* Function arg input is NULL */ + if (isnull[i]) { + if (argisnull & (1 << i)) + continue; + else + break; + } + /* Function arg input is not NULL but cached arg is NULL */ + if (argisnull & (1 << i)) + break; + + argtype = argtypes[i]; + + if (argtype == NUMERICOID) { + if (!DatumGetBool(DirectFunctionCall2(numeric_eq, fargs[i], args[i]))) + break; + } else if (argtype == TEXTOID || argtype == VARCHAROID || + argtype == NVARCHAR2OID || argtype == BPCHAROID) { + if (!datumIsEqual(fargs[i], args[i], false, -1)) + break; + } else if (fargs[i] != args[i]) + break; + } + + if (i == nargs) + return true; + + return false; +} + +/* + * Get function's result cache. + */ +bool +EStateFuncGetRetCache(FunctionCallInfo fcinfo, Datum *retvalue, bool *retnull) +{ + short i, nargs, prevhit, buck, offs; + Datum *fargs; + bool *argnull; + FuncCache fncache; + FuncRetBucket *retbucket; + FuncRetCache *retcache, **retcachea; + pg_crc32c argcrc; + pg_crc32c *argcrcs; + + Assert(fcinfo->fncache); + Assert(fcinfo->top_estate); + + fncache = fcinfo->fncache; + + /* + * Contains only one parameter and is called only once, disabling caching. + * It should be determined when deciding whether to use caching (before create_plan), + * but the current framework is difficult to code. + * It is necessary to observe the number of function calls and whether the function + * parameters are consistent, which cannot be done with one recursion. + * + * When it contains only one parameter, it is determined and closed in the first call. + * Only the allocated internal elements are recycled, and the structures themselves are + * retained because their pointers are allocated to each call. If recycled, it will + * affect all other functions. + */ + if (unlikely((fncache->fcflags & FNCACHE_ARG_UNIQUE) && + (fncache->fcflags & FNCACHE_REF_COUNT) == 1)) { + Assert(fncache->cacheptr.retbuckets == NULL); + + if (fncache->argtypes) + pfree(fncache->argtypes); + + fcinfo->fncache = NULL; + + return false; + } + + /* + * The number of times used reaches the cache number, and the hit rate is less than 15%. + */ + if (unlikely(fncache->usagecount >= FR_MAX_USAGE_COUNT * FR_CACHE_MAX_SIZE && + ((fncache->hitcount / 0.15) < fncache->usagecount) )) { + /* A function without parameters will hit all of them after being called once. */ + Assert(FNCACHE_NUMARGS(fncache->fcflags) > 0); + + EStateFuncReclaimCache(fncache); + + fcinfo->fncache = NULL; + + return false; + } + + if (fncache->usagecount < INT32_MAX) + fncache->usagecount++; + + nargs = fcinfo->nargs; + + if (nargs == 0) { + /* Never match when function first call. */ + if (unlikely(fncache->cacheptr.retcache == NULL)) + return false; + + retcache = fncache->cacheptr.retcache; + + Assert(FR_STATE_IS_VALID(retcache->state)); + + if (fncache->usagecount < INT32_MAX) + fncache->hitcount++; + + /* Found cached results, no need to call function to return directly. */ + *retvalue = retcache->retval; + *retnull = FR_STATE_RET_NULL(retcache->state); + + return true; + } + + /* + * Calculate CRC of function parameters for bucket matching. + */ + fcinfo->arghash = INVALID_ARG_CRC32C; + + /* Check whether the function args used in this call cannot be cached. */ + if (FunctionArgCannotCache(fncache, fcinfo)) + return false; + + fargs = fcinfo->arg; + argnull = fcinfo->argnull; + + argcrc = EStateFuncHashArgs(fncache->argtypes, fargs, argnull, nargs); + Assert(ARG_CRC32C_IS_VALID(argcrc)); + + fcinfo->arghash = argcrc; + + /* No cache yet */ + if (fncache->cacheptr.retbuckets == NULL) + return false; + + /* + * When called multiple times, the next call may use the same parameters, + * and the cache that hit the last time will be tried first. + */ + if (fncache->prevhit >= 0) { + prevhit = fncache->prevhit; + + buck = prevhit / FR_CACHE_SIZE_BUCKET; + offs = prevhit % FR_CACHE_SIZE_BUCKET; + + Assert(fncache->cacheptr.retbuckets); + retbucket = fncache->cacheptr.retbuckets[buck]; + + Assert(retbucket); + Assert(retbucket->retcache); + + Assert(ARG_CRC32C_IS_VALID(retbucket->argcrcs[offs])); + + retcache = retbucket->retcache[offs]; + Assert(retcache); + Assert(FR_STATE_IS_VALID(retcache->state)); + + if (EQ_CRC32C(argcrc, retbucket->argcrcs[offs]) && + EStateFuncCompareArgs(fncache->argtypes, retcache, fargs, argnull, nargs)) { + if (fncache->usagecount < INT32_MAX) + fncache->hitcount++; + + /* Matches the previous call and returns directly. */ + *retvalue = retcache->retval; + *retnull = FR_STATE_RET_NULL(retcache->state); + + return true; + } else + fncache->prevhit = -1; + } + + /* Locate the bucket where the cache for this call is located. */ + buck = argcrc % FR_CACHE_NUM_BUCKETS; + retbucket = fncache->cacheptr.retbuckets[buck]; + if (retbucket == NULL) + return false; + + retcachea = retbucket->retcache; + argcrcs = retbucket->argcrcs; + + for (i = 0; i < FR_CACHE_SIZE_BUCKET; i++) { + /* The last valid cache in the bucket has been reached. */ + if (!ARG_CRC32C_IS_VALID(argcrcs[i])) + break; + + /* If the CRC is different, the parameters will also be different. Continue to compare the next. */ + if (!EQ_CRC32C(argcrc, argcrcs[i])) + continue; + + /* CRC is the same, check if the parameters are the same. */ + if (!EStateFuncCompareArgs(fncache->argtypes, retcachea[i], fargs, argnull, nargs)) + continue; + + if (fncache->usagecount < INT32_MAX) + fncache->hitcount++; + + if (FCR_STATE_GET_USAGECOUNT(retcachea[i]->state) < FR_MAX_USAGE_COUNT) + retcachea[i]->state += FCR_USAGECOUNT_ONE; + + if ((retcachea[i]->state & FR_HITTED) == 0) + retcachea[i]->state |= FR_HITTED; + + /* + * The function appears multiple times in the plan, and the next call may use the + * same parameters, for example: + * SELECT f1(col1), f1(col1) ... FROM t1; + * Record the hit position this time, and expect to hit it directly next time. + */ + if ((fncache->fcflags & FNCACHE_REF_COUNT) > 1) { + /* + * Note that the way the last hit is saved here is different from the bucket + * positioning method. + */ + fncache->prevhit = buck * FR_CACHE_SIZE_BUCKET + i; + } + + /* Found cached results, no need to call the function to return directly. */ + *retvalue = retcachea[i]->retval; + *retnull = FR_STATE_RET_NULL(retcachea[i]->state); + + return true; + } + + return false; +} + +extern PLpgSQL_function* get_security_function(FunctionCallInfo fcinfo); + +/* + * Add the function call result to the cache. + * Before calling, call EStateFuncGetRetCache to confirm that it is not a hit. + */ +bool +EStateFuncPutRetCache(FunctionCallInfo fcinfo, Datum ret) +{ + short i, nargs, buck, nextvict; + Oid datumtype; + pg_crc32c argcrc, *argcrcs; + int usagecount; + Datum *fargs; + bool *argnull; + Datum *args; + EState *es; + FuncCache fncache; + FuncRetBucket **retbuckets, *retbucket; + FuncRetCache **retcache, *buckcache, *cret, *rret; + MemoryContext oldctx; + + Assert(fcinfo->fncache); + Assert(fcinfo->top_estate); + Assert(FNCACHE_NUMARGS(fcinfo->fncache->fcflags) == fcinfo->nargs); + + /* Autonomous transaction functions and subroutines do not support caching. */ + if (fcinfo->flinfo && fcinfo->flinfo->fn_extra) { + + PLpgSQL_function* func; + if (fcinfo->fncache->security) { + func = get_security_function(fcinfo); + } else { + func = (PLpgSQL_function*)fcinfo->flinfo->fn_extra; + } + + if (func->action->isAutonomous || func->proc_list != NIL || OidIsValid(func->parent_oid)) { + EStateFuncReclaimCache(fcinfo->fncache); + + fcinfo->fncache = NULL; + return false; + } + } + + fncache = fcinfo->fncache; + es = fcinfo->top_estate; + nargs = fcinfo->nargs; + + if (nargs == 0) { + /* No arguments, only cached one result. */ + FuncRetCache *retcache; + + Assert(fncache->cacheptr.retcache == NULL); + + oldctx = MemoryContextSwitchTo(ActivePortal->func_retcache_cxt); + + retcache = (FuncRetCache *)palloc0(sizeof(FuncRetCache)); + + retcache->state = FR_VALID; + + if (fcinfo->isnull) + retcache->state |= FR_RETNULL; + else + retcache->retval = FunctionDatumCopy(ret, fncache->rettype); + + MemoryContextSwitchTo(oldctx); + + fncache->cacheptr.retcache = retcache; + + return true; + } + + /* CRC is invalid, FunctionArgCannotCache check considers the arguments to be uncached */ + if (!ARG_CRC32C_IS_VALID(fcinfo->arghash)) + return false; + + argcrc = fcinfo->arghash; + + Assert(!FunctionArgCannotCache(fncache, fcinfo)); + + /* Check if the function result can be cached. */ + if (!fcinfo->isnull && FunctionDatumToolong(ret, fncache->rettype)) + return true; + + buck = argcrc % FR_CACHE_NUM_BUCKETS; + + oldctx = MemoryContextSwitchTo(ActivePortal->func_retcache_cxt); + + if (fncache->cacheptr.retbuckets) + retbuckets = fncache->cacheptr.retbuckets; + else { + /* cache initial */ + retbuckets = (FuncRetBucket **)palloc0(sizeof(FuncRetBucket *) * FR_CACHE_NUM_BUCKETS); + fncache->cacheptr.retbuckets = retbuckets; + } + + if (retbuckets[buck]) + retbucket = retbuckets[buck]; + else { + /* bucket initial */ + retbucket = (FuncRetBucket *)palloc0(sizeof(FuncRetBucket)); + retbuckets[buck] = retbucket; + + retbucket->argcrcs = (pg_crc32c *)palloc0(sizeof(pg_crc32c) * FR_CACHE_SIZE_BUCKET); + retbucket->retcache = (FuncRetCache **)palloc0(sizeof(FuncRetCache *) * FR_CACHE_SIZE_BUCKET); + + buckcache = (FuncRetCache *)palloc0(sizeof(FuncRetCache) * FR_CACHE_SIZE_SECTION); + args = (Datum *)palloc0(sizeof(Datum) * FR_CACHE_SIZE_SECTION * nargs); + + for (i = 0; i < FR_CACHE_SIZE_SECTION; i++) { + buckcache[i].args = args + nargs * i; + retbucket->retcache[i] = buckcache + i; + } + } + + rret = NULL; + nextvict = retbucket->nextvict; + argcrcs = retbucket->argcrcs; + retcache = retbucket->retcache; + + for (;;) { + cret = retcache[nextvict]; + + if (unlikely(cret == NULL)) { + /* Initialize the next segment */ + Assert(!ARG_CRC32C_IS_VALID(argcrcs[nextvict])); + Assert((nextvict % FR_CACHE_SIZE_SECTION) == 0); + + buckcache = (FuncRetCache *)palloc0(sizeof(FuncRetCache) * FR_CACHE_SIZE_SECTION); + args = (Datum *)palloc0(sizeof(Datum) * FR_CACHE_SIZE_SECTION * nargs); + + for (i = 0; i < FR_CACHE_SIZE_SECTION; i++) { + buckcache[i].args = args + nargs * i; + retbucket->retcache[i + nextvict] = buckcache + i; + } + + cret = buckcache; + } + + /* Free to use */ + if (!FR_STATE_IS_VALID(cret->state)) { + rret = cret; + break; + } + + usagecount = FCR_STATE_GET_USAGECOUNT(cret->state); + + /* If have a hit result, it will have one more chance to stay before being kicked out. */ + if (FR_STATE_HITTED(cret->state)) + cret->state &= ~FR_HITTED; + else if (usagecount > 0) + cret->state -= FCR_USAGECOUNT_ONE; + else { + rret = cret; + break; + } + + nextvict++; + if (nextvict >= FR_CACHE_SIZE_BUCKET) + nextvict = 0; + } + + if (rret == NULL) { + MemoryContextSwitchTo(oldctx); + + return false; + } + + argcrcs[nextvict] = argcrc; + + nextvict++; + if (nextvict >= FR_CACHE_SIZE_BUCKET) + nextvict = 0; + retbucket->nextvict = nextvict; + + rret->state = FR_VALID; + rret->state += FCR_USAGECOUNT_ONE; + + if (fcinfo->isnull) + rret->state |= FR_RETNULL; + else { + datumtype = fncache->rettype; + + if (datumtype == NUMERICOID || datumtype == TEXTOID || + datumtype == VARCHAROID || datumtype == NVARCHAR2OID || + datumtype == BPCHAROID) { + if (rret->retval) + pfree(DatumGetPointer(rret->retval)); + } + + rret->retval = FunctionDatumCopy(ret, datumtype); + } + + fargs = fcinfo->arg; + argnull = fcinfo->argnull; + + rret->argisnull = 0; + + for (i = 0; i < nargs; i++) { + if (argnull[i]) { + rret->argisnull |= (1 << i); + continue; + } + + datumtype = fncache->argtypes[i]; + + if (datumtype == NUMERICOID || datumtype == TEXTOID || + datumtype == VARCHAROID || datumtype == NVARCHAR2OID || + datumtype == BPCHAROID) { + if (rret->args[i]) + pfree(DatumGetPointer(rret->args[i])); + } + + rret->args[i] = FunctionDatumCopy(fargs[i], datumtype); + } + + MemoryContextSwitchTo(oldctx); + + return true; +} diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h index ec72ee01b5..df08bcdd27 100644 --- a/src/include/catalog/pg_class.h +++ b/src/include/catalog/pg_class.h @@ -165,7 +165,7 @@ DATA(insert OID = 1247 ( pg_type PGNSP 71 0 PGUID 0 0 0 0 0 0 0 0 0 0 0 0 DESCR(""); DATA(insert OID = 1249 ( pg_attribute PGNSP 75 0 PGUID 0 0 0 0 0 0 0 0 0 0 0 0 f f p r 25 0 f f f f f 0 f f n 3 _null_ _null_ n 3 _null_ _null_ 1)); DESCR(""); -DATA(insert OID = 1255 ( pg_proc PGNSP 81 0 PGUID 0 0 0 0 0 0 0 0 0 0 0 0 f f p r 39 0 t f f f f 0 f f n 3 _null_ _null_ n 3 _null_ _null_ 1)); +DATA(insert OID = 1255 ( pg_proc PGNSP 81 0 PGUID 0 0 0 0 0 0 0 0 0 0 0 0 f f p r 40 0 t f f f f 0 f f n 3 _null_ _null_ n 3 _null_ _null_ 1)); DESCR(""); DATA(insert OID = 7815 ( gs_package PGNSP 9745 0 PGUID 0 0 0 0 0 0 0 0 0 0 0 0 f f p r 8 0 t f f f f 0 f f n 3 _null_ _null_ n 3 _null_ _null_ 1)); DESCR(""); diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h index c30bd9b6d4..1546fcf6ce 100644 --- a/src/include/catalog/pg_proc.h +++ b/src/include/catalog/pg_proc.h @@ -87,6 +87,7 @@ CATALOG(pg_proc,1255) BKI_BOOTSTRAP BKI_ROWTYPE_OID(81) BKI_SCHEMA_MACRO oidvector allargtypes; /* all parameter types */ oidvector_extend allargtypesext; + bool result_cache; /* if function can cache result */ #endif } FormData_pg_proc; @@ -101,7 +102,7 @@ typedef FormData_pg_proc *Form_pg_proc; * compiler constants for pg_proc * ---------------- */ -#define Natts_pg_proc 39 +#define Natts_pg_proc 40 #define Anum_pg_proc_proname 1 #define Anum_pg_proc_pronamespace 2 #define Anum_pg_proc_proowner 3 @@ -141,6 +142,7 @@ typedef FormData_pg_proc *Form_pg_proc; #define Anum_pg_proc_prodefaultargposext 37 #define Anum_pg_proc_allargtypes 38 #define Anum_pg_proc_allargtypesext 39 +#define Anum_pg_proc_result_cache 40 /* proc_oid is only for builitin * func view shouldn't be included in Natts_pg_proc diff --git a/src/include/catalog/pg_proc_fn.h b/src/include/catalog/pg_proc_fn.h index f1ae15c2a7..81ad1502f3 100644 --- a/src/include/catalog/pg_proc_fn.h +++ b/src/include/catalog/pg_proc_fn.h @@ -59,7 +59,8 @@ extern ObjectAddress ProcedureCreate(const char *procedureName, Oid protypeid = InvalidOid, char typefunckind = OBJECTTYPE_NULL_PROC, bool isfinal = false, - Oid profuncoid = InvalidOid + Oid profuncoid = InvalidOid, + bool result_cache = false ); extern bool function_parse_error_transpose(const char *prosrc); diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index 5586805bf3..841dfe5d8b 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -211,7 +211,7 @@ extern Oid GetFunctionNodeGroup(CreateFunctionStmt* stmt, bool* multi_group); extern List* compute_attributes_sql_style(const List* options, List** as, char** language, bool* windowfunc_p, char* volatility_p, bool* strict_p, bool* security_definer, bool* leakproof_p, ArrayType** proconfig, float* procost, float4* prorows, bool* fenced, - bool* shippable, bool* package, bool* is_pipelined, FunctionPartitionInfo** partInfo); + bool* shippable, bool* package, bool* is_pipelined, FunctionPartitionInfo** partInfo, bool* result_cache); extern Oid GetFunctionNodeGroupByFuncid(Oid funcid); extern Oid GetFunctionNodeGroup(AlterFunctionStmt* stmt); diff --git a/src/include/fmgr.h b/src/include/fmgr.h index eededd2085..150bc57eb6 100644 --- a/src/include/fmgr.h +++ b/src/include/fmgr.h @@ -31,6 +31,8 @@ #ifndef FRONTEND_PARSER +typedef struct FuncCacheData *FuncCache; + typedef ScalarVector* (*VectorFunction)(FunctionCallInfo fcinfo); typedef Datum (*GenericArgExtract)(Datum* data); @@ -162,6 +164,9 @@ typedef struct FunctionCallInfoData { Datum* arg; /* Arguments passed to function */ bool* argnull; /* T if arg[i] is actually NULL */ Oid* argTypes; /* Argument type */ + uint32 arghash; /* Argument hash */ + struct EState *top_estate; + FuncCache fncache; Datum prealloc_arg[FUNC_PREALLOCED_ARGS]; /* prealloced arguments.*/ bool prealloc_argnull[FUNC_PREALLOCED_ARGS]; /* prealloced argument null flags.*/ Oid prealloc_argTypes[FUNC_PREALLOCED_ARGS] = {InvalidOid}; /* prealloced argument type */ diff --git a/src/include/knl/knl_guc/knl_session_attr_sql.h b/src/include/knl/knl_guc/knl_session_attr_sql.h index 5775df6d9a..c0da56284f 100644 --- a/src/include/knl/knl_guc/knl_session_attr_sql.h +++ b/src/include/knl/knl_guc/knl_session_attr_sql.h @@ -264,6 +264,7 @@ typedef struct knl_session_attr_sql { bool uppercase_attribute_name; #endif bool var_eq_const_selectivity; + bool enable_func_cache; int vectorEngineStrategy; int gms_stats_history_retention; #if (!defined(ENABLE_MULTIPLE_NODES)) && (!defined(ENABLE_PRIVATEGAUSS)) diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index 3fe83d2136..99a52c1572 100755 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -608,6 +608,11 @@ typedef struct BloomFilterControl { #define InvalidBktId (-1) /* invalid hash-bucket id */ +typedef struct EStateFuncCache +{ + FuncCache *fncaches; +} EStateFuncCache; + /* ---------------- * EState information * @@ -694,6 +699,9 @@ typedef struct EState { */ ExprContext* es_per_tuple_exprcontext; + struct EState* es_topstate; + EStateFuncCache es_funcache; + /* * These fields are for re-evaluating plan quals when an updated tuple is * substituted in READ COMMITTED mode. es_epqTuple[] contains tuples that diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h index 7262a38ffa..5c1a59c2cc 100644 --- a/src/include/nodes/primnodes.h +++ b/src/include/nodes/primnodes.h @@ -467,6 +467,7 @@ typedef struct FuncExpr { CoercionForm funcformat; /* how to display this function call */ Oid funccollid; /* OID of collation of result */ Oid inputcollid; /* OID of collation that function should use */ + uint32 funcflags; char* fmtstr; /* fmt string */ char* nlsfmtstr; /* nls fmt string */ List* args; /* arguments to the function */ diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h index 47fae50961..9c375afa79 100644 --- a/src/include/parser/kwlist.h +++ b/src/include/parser/kwlist.h @@ -556,6 +556,7 @@ PG_KEYWORD("respect", RESPECT_P, UNRESERVED_KEYWORD) PG_KEYWORD("restart", RESTART, UNRESERVED_KEYWORD) PG_KEYWORD("restrict", RESTRICT, UNRESERVED_KEYWORD) PG_KEYWORD("result", RESULT, UNRESERVED_KEYWORD) +PG_KEYWORD("result_cache", RESULT_CACHE, UNRESERVED_KEYWORD) PG_KEYWORD("return", RETURN, UNRESERVED_KEYWORD) PG_KEYWORD("returned_sqlstate", RETURNED_SQLSTATE, UNRESERVED_KEYWORD) PG_KEYWORD("returning", RETURNING, RESERVED_KEYWORD) diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h index c884de5a0b..87fbee1882 100755 --- a/src/include/utils/guc.h +++ b/src/include/utils/guc.h @@ -483,6 +483,9 @@ typedef enum { #define FORCE_VALIDATE_PLANCACHE_RESULT \ ((bool)g_instance.attr.attr_sql.plan_cache_type_validation) +#define ENABLE_FUNCTION_RESULT_CACHE() \ + (u_sess->attr.attr_sql.enable_func_cache && u_sess->attr.attr_sql.query_dop_tmp == 1) + typedef enum { SUMMARY = 0, /* not collect multi column statistics info */ DETAIL = 1, /* collect multi column statistics info */ diff --git a/src/include/utils/portal.h b/src/include/utils/portal.h index b6f532332b..d785836694 100644 --- a/src/include/utils/portal.h +++ b/src/include/utils/portal.h @@ -268,6 +268,9 @@ typedef struct PortalData { * A specific data link list hung under the portal. * The special data mounted in the portal can be used during cleaning,such as utl_tcp. */ + MemoryContext func_retcache_cxt; + EState* top_estate; + int func_retcache_slot_count; } PortalData; /* diff --git a/src/test/regress/expected/function_result_cache.out b/src/test/regress/expected/function_result_cache.out new file mode 100644 index 0000000000..dafe787833 --- /dev/null +++ b/src/test/regress/expected/function_result_cache.out @@ -0,0 +1,1947 @@ +create schema func_result_cache; +set search_path to func_result_cache; +set enable_func_cache=on; +set datestyle='ISO, MDY'; +\set VERBOSITY terse +-- all support datatype +create table all_support_type( + id int, + col_bool bool, + col_smallint smallint, + col_int int, + col_bigint bigint, + col_float float, + col_double_precision double precision, + col_char char(20), + col_varchar varchar(20), + col_varchar2 varchar2(20), + col_time time, + col_timetz timetz, + col_date date, + col_timestamp timestamp, + col_timestamptz timestamptz, + col_numeric numeric, + col_number number, + col_text text +); +insert into all_support_type values( + generate_series(1,1000), + generate_series(1,1000) % 2, + generate_series(1,1000) % 5, + generate_series(1,1000) % 5, + generate_series(1,1000) % 5, + generate_series(1,1000) % 5, + generate_series(1,1000) % 5, + generate_series(1,1000) % 10 || '_haha', + generate_series(1,1000) % 10 || '_haha', + generate_series(1,1000) % 10 || '_haha', + '18:13:49.579082', + '18:13:49.579082', + '2024-11-22', + '2024-11-22 16:36:45', + '2024-11-22 16:36:45', + generate_series(1,1000) % 5, + generate_series(1,1000) % 5, + generate_series(1,1000) % 10 || '_haha' +); +insert into all_support_type values(1001,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null); +insert into all_support_type values(1002,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null); +-- 无参函数 +create or replace function func_no_param() returns int as $$ +begin + return 1; +end; +$$ language plpgsql stable result_cache; +explain (costs off, analyze, timing off) select func_no_param() from all_support_type; + QUERY PLAN +--------------------------------------------------------- + Seq Scan on all_support_type (actual rows=1002 loops=1) +--? Total runtime: .* +(2 rows) + +create or replace function func_bool(p bool) returns bool as $$ +begin + raise notice 'param:%', p; + return p; +end; +$$ language plpgsql stable result_cache; +create or replace function func_smallint(p smallint) returns smallint as $$ +begin + if p = 1 then + raise notice 'param:%', p; + end if; + return p+1; +end; +$$ language plpgsql stable result_cache; +create or replace function func_int(p int) returns int as $$ +begin + if p = 1 then + raise notice 'param:%', p; + end if; + return p*2; +end; +$$ language plpgsql stable result_cache; +create or replace function func_bigint(p bigint) returns bigint as $$ +begin + if p = 1 then + raise notice 'param:%', p; + end if; + return p*2+1; +end; +$$ language plpgsql stable result_cache; +create or replace function func_float(p float) returns float as $$ +begin + if p = 1 then + raise notice 'param:%', p; + end if; + return p*2+1; +end; +$$ language plpgsql stable result_cache; +create or replace function func_double_precision(p double precision) returns double precision as $$ +begin + if p = 1 then + raise notice 'param:%', p; + end if; + return p*2-1; +end; +$$ language plpgsql stable result_cache; +create or replace function func_char(p char) returns char as $$ +begin + if p = '1_haha ' then + raise notice 'param:%', p; + end if; + return p||'_char'; +end; +$$ language plpgsql stable result_cache; +create or replace function func_varchar(p varchar) returns varchar as $$ +begin + if p = '1_haha' then + raise notice 'param:%', p; + end if; + return p||'_varchar'; +end; +$$ language plpgsql stable result_cache; +create or replace function func_varchar2(p varchar2) returns varchar2 as $$ +begin + if p = '1_haha' then + raise notice 'param:%', p; + end if; + return p||'_varchar2'; +end; +$$ language plpgsql stable result_cache; +create or replace function func_time(p time) returns time as $$ +begin + raise notice 'param:%', p; + return p + interval '1 hour'; +end; +$$ language plpgsql stable result_cache; +create or replace function func_timetz(p timetz) returns timetz as $$ +begin + raise notice 'param:%', p; + return p + interval '1 hour'; +end; +$$ language plpgsql stable result_cache; +create or replace function func_date(p date) returns date as $$ +begin + raise notice 'param:%', p; + return p + interval '1 day'; +end; +$$ language plpgsql stable result_cache; +create or replace function func_timestamp(p timestamp) returns timestamp as $$ +begin + raise notice 'param:%', p; + return p + interval '1 day'; +end; +$$ language plpgsql stable result_cache; +create or replace function func_timestamptz(p timestamptz) returns timestamptz as $$ +begin + raise notice 'param:%', p; + return p + interval '1 day'; +end; +$$ language plpgsql stable result_cache; +create or replace function func_numeric(p numeric) returns numeric as $$ +begin + if p = 1 then + raise notice 'param:%', p; + end if; + return p - 1; +end; +$$ language plpgsql stable result_cache; +create or replace function func_number(p number) returns number as $$ +begin + if p = 1 then + raise notice 'param:%', p; + end if; + return p * 4; +end; +$$ language plpgsql stable result_cache; +create or replace function func_text(p text) returns text as $$ +begin + if p = '1_haha' then + raise notice 'param:%', p; + end if; + return p||'_text'; +end; +$$ language plpgsql stable result_cache; +-- targetlist存在单个函数 +explain (costs off, analyze, timing off) select col_bool, func_bool(col_bool) from all_support_type; +NOTICE: param:t +NOTICE: param:f +NOTICE: param: + QUERY PLAN +--------------------------------------------------------- + Seq Scan on all_support_type (actual rows=1002 loops=1) +--? Total runtime: .* +(2 rows) + +explain (costs off, analyze, timing off) select col_smallint, func_smallint(col_smallint) from all_support_type; +NOTICE: param:1 + QUERY PLAN +--------------------------------------------------------- + Seq Scan on all_support_type (actual rows=1002 loops=1) +--? Total runtime: .* +(2 rows) + +explain (costs off, analyze, timing off) select col_int, func_int(col_int) from all_support_type; +NOTICE: param:1 + QUERY PLAN +--------------------------------------------------------- + Seq Scan on all_support_type (actual rows=1002 loops=1) +--? Total runtime: .* +(2 rows) + +explain (costs off, analyze, timing off) select col_bigint, func_bigint(col_bigint) from all_support_type; +NOTICE: param:1 + QUERY PLAN +--------------------------------------------------------- + Seq Scan on all_support_type (actual rows=1002 loops=1) +--? Total runtime: .* +(2 rows) + +explain (costs off, analyze, timing off) select col_float, func_float(col_float) from all_support_type; +NOTICE: param:1 + QUERY PLAN +--------------------------------------------------------- + Seq Scan on all_support_type (actual rows=1002 loops=1) +--? Total runtime: .* +(2 rows) + +explain (costs off, analyze, timing off) select col_double_precision, func_double_precision(col_double_precision) from all_support_type; +NOTICE: param:1 + QUERY PLAN +--------------------------------------------------------- + Seq Scan on all_support_type (actual rows=1002 loops=1) +--? Total runtime: .* +(2 rows) + +explain (costs off, analyze, timing off) select col_char, func_char(col_char) from all_support_type; +NOTICE: param:1_haha + QUERY PLAN +--------------------------------------------------------- + Seq Scan on all_support_type (actual rows=1002 loops=1) +--? Total runtime: .* +(2 rows) + +explain (costs off, analyze, timing off) select col_varchar, func_varchar(col_varchar) from all_support_type; +NOTICE: param:1_haha + QUERY PLAN +--------------------------------------------------------- + Seq Scan on all_support_type (actual rows=1002 loops=1) +--? Total runtime: .* +(2 rows) + +explain (costs off, analyze, timing off) select col_varchar2, func_varchar2(col_varchar2) from all_support_type; +NOTICE: param:1_haha + QUERY PLAN +--------------------------------------------------------- + Seq Scan on all_support_type (actual rows=1002 loops=1) +--? Total runtime: .* +(2 rows) + +explain (costs off, analyze, timing off) select col_time, func_time(col_time) from all_support_type; +NOTICE: param:18:13:49.579082 +NOTICE: param: + QUERY PLAN +--------------------------------------------------------- + Seq Scan on all_support_type (actual rows=1002 loops=1) +--? Total runtime: .* +(2 rows) + +explain (costs off, analyze, timing off) select col_timetz, func_timetz(col_timetz) from all_support_type limit 10; --not support +NOTICE: param:18:13:49.579082-08 +NOTICE: param:18:13:49.579082-08 +NOTICE: param:18:13:49.579082-08 +NOTICE: param:18:13:49.579082-08 +NOTICE: param:18:13:49.579082-08 +NOTICE: param:18:13:49.579082-08 +NOTICE: param:18:13:49.579082-08 +NOTICE: param:18:13:49.579082-08 +NOTICE: param:18:13:49.579082-08 +NOTICE: param:18:13:49.579082-08 + QUERY PLAN +------------------------------------------------------------- + Limit (actual rows=10 loops=1) + -> Seq Scan on all_support_type (actual rows=10 loops=1) +--? Total runtime: .* +(3 rows) + +explain (costs off, analyze, timing off) select col_date, func_date(col_date) from all_support_type; +NOTICE: param:2024-11-22 00:00:00 +NOTICE: param: + QUERY PLAN +--------------------------------------------------------- + Seq Scan on all_support_type (actual rows=1002 loops=1) +--? Total runtime: .* +(2 rows) + +explain (costs off, analyze, timing off) select col_timestamp, func_timestamp(col_timestamp) from all_support_type; +NOTICE: param:2024-11-22 16:36:45 +NOTICE: param: + QUERY PLAN +--------------------------------------------------------- + Seq Scan on all_support_type (actual rows=1002 loops=1) +--? Total runtime: .* +(2 rows) + +explain (costs off, analyze, timing off) select col_timestamptz, func_timestamptz(col_timestamptz) from all_support_type; +NOTICE: param:2024-11-22 16:36:45-08 +NOTICE: param: + QUERY PLAN +--------------------------------------------------------- + Seq Scan on all_support_type (actual rows=1002 loops=1) +--? Total runtime: .* +(2 rows) + +explain (costs off, analyze, timing off) select col_numeric, func_numeric(col_numeric) from all_support_type; +NOTICE: param:1 + QUERY PLAN +--------------------------------------------------------- + Seq Scan on all_support_type (actual rows=1002 loops=1) +--? Total runtime: .* +(2 rows) + +explain (costs off, analyze, timing off) select col_number, func_number(col_number) from all_support_type; +NOTICE: param:1 + QUERY PLAN +--------------------------------------------------------- + Seq Scan on all_support_type (actual rows=1002 loops=1) +--? Total runtime: .* +(2 rows) + +explain (costs off, analyze, timing off) select col_text, func_text(col_text) from all_support_type; +NOTICE: param:1_haha + QUERY PLAN +--------------------------------------------------------- + Seq Scan on all_support_type (actual rows=1002 loops=1) +--? Total runtime: .* +(2 rows) + +-- targetlist存在多个函数 +-- 2个 +explain (costs off, analyze, timing off) +select + col_bool, func_bool(col_bool), + col_smallint, func_smallint(col_smallint) +from all_support_type; +NOTICE: param:t +NOTICE: param:1 +NOTICE: param:f +NOTICE: param: + QUERY PLAN +--------------------------------------------------------- + Seq Scan on all_support_type (actual rows=1002 loops=1) +--? Total runtime: .* +(2 rows) + +-- 4个 +explain (costs off, analyze, timing off) +select + col_int, func_int(col_int), + col_bigint, func_bigint(col_bigint), + col_float, func_float(col_float), + col_double_precision, func_double_precision(col_double_precision) +from all_support_type; +NOTICE: param:1 +NOTICE: param:1 +NOTICE: param:1 +NOTICE: param:1 + QUERY PLAN +--------------------------------------------------------- + Seq Scan on all_support_type (actual rows=1002 loops=1) +--? Total runtime: .* +(2 rows) + +-- 8个 +explain (costs off, analyze, timing off) +select + col_int, func_int(col_int), + col_bigint, func_bigint(col_bigint), + col_float, func_float(col_float), + col_double_precision, func_double_precision(col_double_precision), + col_char, func_char(col_char), + col_varchar, func_varchar(col_varchar), + col_varchar2, func_varchar2(col_varchar2), + col_timestamp, func_timestamp(col_timestamp) +from all_support_type; +NOTICE: param:1 +NOTICE: param:1 +NOTICE: param:1 +NOTICE: param:1 +NOTICE: param:1_haha +NOTICE: param:1_haha +NOTICE: param:1_haha +NOTICE: param:2024-11-22 16:36:45 +NOTICE: param: + QUERY PLAN +--------------------------------------------------------- + Seq Scan on all_support_type (actual rows=1002 loops=1) +--? Total runtime: .* +(2 rows) + +-- 11个 +explain (costs off, analyze, timing off) +select + col_int, func_int(col_int), + col_bigint, func_bigint(col_bigint), + col_float, func_float(col_float), + col_double_precision, func_double_precision(col_double_precision), + col_char, func_char(col_char), + col_varchar, func_varchar(col_varchar), + col_varchar2, func_varchar2(col_varchar2), + col_timestamp, func_timestamp(col_timestamp), + col_numeric, func_numeric(col_numeric), + col_number, func_number(col_number), + col_text, func_text(col_text) +from all_support_type; +NOTICE: param:1 +NOTICE: param:1 +NOTICE: param:1 +NOTICE: param:1 +NOTICE: param:1_haha +NOTICE: param:1_haha +NOTICE: param:1_haha +NOTICE: param:2024-11-22 16:36:45 +NOTICE: param:1 +NOTICE: param:1 +NOTICE: param:1_haha +NOTICE: param: + QUERY PLAN +--------------------------------------------------------- + Seq Scan on all_support_type (actual rows=1002 loops=1) +--? Total runtime: .* +(2 rows) + +-- 15个, 限制了最多缓存14个函数的结果 +explain (costs off, analyze, timing off) +select + col_bool, func_bool(col_bool), + col_int, func_int(col_int), + col_bigint, func_bigint(col_bigint), + col_float, func_float(col_float), + col_double_precision, func_double_precision(col_double_precision), + col_char, func_char(col_char), + col_varchar, func_varchar(col_varchar), + col_varchar2, func_varchar2(col_varchar2), + col_time, func_time(col_time), + col_timestamp, func_timestamp(col_timestamp), + col_timestamptz, func_timestamptz(col_timestamptz), + col_numeric, func_numeric(col_numeric), + col_number, func_number(col_number), + col_text, func_text(col_text), + col_date, func_date(col_date) +from all_support_type limit 20; +NOTICE: param:t +NOTICE: param:1 +NOTICE: param:1 +NOTICE: param:1 +NOTICE: param:1 +NOTICE: param:1_haha +NOTICE: param:1_haha +NOTICE: param:1_haha +NOTICE: param:18:13:49.579082 +NOTICE: param:2024-11-22 16:36:45 +NOTICE: param:2024-11-22 16:36:45-08 +NOTICE: param:1 +NOTICE: param:1 +NOTICE: param:1_haha +NOTICE: param:2024-11-22 00:00:00 +NOTICE: param:f +NOTICE: param:2024-11-22 00:00:00 +NOTICE: param:2024-11-22 00:00:00 +NOTICE: param:2024-11-22 00:00:00 +NOTICE: param:2024-11-22 00:00:00 +NOTICE: param:2024-11-22 00:00:00 +NOTICE: param:2024-11-22 00:00:00 +NOTICE: param:2024-11-22 00:00:00 +NOTICE: param:2024-11-22 00:00:00 +NOTICE: param:2024-11-22 00:00:00 +NOTICE: param:2024-11-22 00:00:00 +NOTICE: param:2024-11-22 00:00:00 +NOTICE: param:2024-11-22 00:00:00 +NOTICE: param:2024-11-22 00:00:00 +NOTICE: param:2024-11-22 00:00:00 +NOTICE: param:2024-11-22 00:00:00 +NOTICE: param:2024-11-22 00:00:00 +NOTICE: param:2024-11-22 00:00:00 +NOTICE: param:2024-11-22 00:00:00 +NOTICE: param:2024-11-22 00:00:00 + QUERY PLAN +------------------------------------------------------------- + Limit (actual rows=20 loops=1) + -> Seq Scan on all_support_type (actual rows=20 loops=1) +--? Total runtime: .* +(3 rows) + +-- targetlist存在多个相同的函数 +-- 2个 +explain (costs off, analyze, timing off) +select + col_bool, func_bool(col_bool), + func_bool(col_bool) +from all_support_type; +NOTICE: param:t +NOTICE: param:f +NOTICE: param: + QUERY PLAN +--------------------------------------------------------- + Seq Scan on all_support_type (actual rows=1002 loops=1) +--? Total runtime: .* +(2 rows) + +-- 4个 +explain (costs off, analyze, timing off) +select + col_bool, func_bool(col_bool), + func_bool(col_bool), + func_bool(col_bool), + func_bool(col_bool) +from all_support_type; +NOTICE: param:t +NOTICE: param:f +NOTICE: param: + QUERY PLAN +--------------------------------------------------------- + Seq Scan on all_support_type (actual rows=1002 loops=1) +--? Total runtime: .* +(2 rows) + +-- 8个 +explain (costs off, analyze, timing off) +select + col_bool, func_bool(col_bool), + func_bool(col_bool), + func_bool(col_bool), + func_bool(col_bool), + func_bool(col_bool), + func_bool(col_bool), + func_bool(col_bool), + func_bool(col_bool) +from all_support_type; +NOTICE: param:t +NOTICE: param:f +NOTICE: param: + QUERY PLAN +--------------------------------------------------------- + Seq Scan on all_support_type (actual rows=1002 loops=1) +--? Total runtime: .* +(2 rows) + +-- 大于8个 +explain (costs off, analyze, timing off) +select + col_bool, func_bool(col_bool), + func_bool(col_bool), + func_bool(col_bool), + func_bool(col_bool), + func_bool(col_bool), + func_bool(col_bool), + func_bool(col_bool), + func_bool(col_bool), + func_bool(col_bool), + func_bool(col_bool), + func_bool(col_bool) +from all_support_type; +NOTICE: param:t +NOTICE: param:f +NOTICE: param: + QUERY PLAN +--------------------------------------------------------- + Seq Scan on all_support_type (actual rows=1002 loops=1) +--? Total runtime: .* +(2 rows) + +-- 函数有多个IN参数 +-- 2个 +create or replace function func_bool_multi(p1 bool, p2 bool) returns int as $$ +begin + raise notice 'param:%,%', p1, p2; + if (p1 is null and p2 is null) then + return 1; + elsif (p1 is null and p2 is not null) then + return 2; + elsif (p1 is not null and p2 is null) then + return 3; + end if; + return 4; +end; +$$ language plpgsql stable result_cache; +explain (costs off, analyze, timing off) select col_bool,func_bool_multi(col_bool,col_bool) from all_support_type; +NOTICE: param:t,t +NOTICE: param:f,f +NOTICE: param:, + QUERY PLAN +--------------------------------------------------------- + Seq Scan on all_support_type (actual rows=1002 loops=1) +--? Total runtime: .* +(2 rows) + +create table multi_col( + id int, + col1 int, + col2 int, + col3 int, + col4 int, + col5 int, + col6 int, + col7 int, + col8 int, + col9 int, + col10 int, + col11 int, + col12 int, + col13 int, + col14 int, + col15 int, + col16 int +); +insert into multi_col values( + generate_series(1,20), + generate_series(1,20) % 5, + generate_series(1,20) % 5, + generate_series(1,20) % 5, + generate_series(1,20) % 5, + generate_series(1,20) % 5, + generate_series(1,20) % 5, + generate_series(1,20) % 5, + generate_series(1,20) % 5, + generate_series(1,20) % 5, + generate_series(1,20) % 5, + generate_series(1,20) % 5, + generate_series(1,20) % 5, + generate_series(1,20) % 5, + generate_series(1,20) % 5, + generate_series(1,20) % 5, + generate_series(1,20) % 5 +); +insert into multi_col values(generate_series(21,22),null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null); +-- 3个 +create or replace function func_int_multi(p1 int, p2 int, p3 int) returns int as $$ +begin + raise notice 'param:%,%,%', p1, p2, p3; + return p1+p2+p3; +end; +$$ language plpgsql stable result_cache; +select col1,col2,col3,func_int_multi(col1,col2,col3) from multi_col; +NOTICE: param:1,1,1 +NOTICE: param:2,2,2 +NOTICE: param:3,3,3 +NOTICE: param:4,4,4 +NOTICE: param:0,0,0 +NOTICE: param:,, + col1 | col2 | col3 | func_int_multi +------+------+------+---------------- + 1 | 1 | 1 | 3 + 2 | 2 | 2 | 6 + 3 | 3 | 3 | 9 + 4 | 4 | 4 | 12 + 0 | 0 | 0 | 0 + 1 | 1 | 1 | 3 + 2 | 2 | 2 | 6 + 3 | 3 | 3 | 9 + 4 | 4 | 4 | 12 + 0 | 0 | 0 | 0 + 1 | 1 | 1 | 3 + 2 | 2 | 2 | 6 + 3 | 3 | 3 | 9 + 4 | 4 | 4 | 12 + 0 | 0 | 0 | 0 + 1 | 1 | 1 | 3 + 2 | 2 | 2 | 6 + 3 | 3 | 3 | 9 + 4 | 4 | 4 | 12 + 0 | 0 | 0 | 0 + | | | + | | | +(22 rows) + +-- 7个 +create or replace function func_int_multi1(p1 int, p2 int, p3 int, p4 int, p5 int, p6 int, p7 int) returns int as $$ +begin + raise notice 'param:%,%,%,%,%,%,%', p1, p2, p3, p4, p5, p6, p7; + return p1+p2+p3+p4+p5+p6+p7; +end; +$$ language plpgsql stable result_cache; +select col1,col2,col3,col4,col5,col6,col7,func_int_multi1(col1,col2,col3,col4,col5,col6,col7) from multi_col; +NOTICE: param:1,1,1,1,1,1,1 +NOTICE: param:2,2,2,2,2,2,2 +NOTICE: param:3,3,3,3,3,3,3 +NOTICE: param:4,4,4,4,4,4,4 +NOTICE: param:0,0,0,0,0,0,0 +NOTICE: param:,,,,,, + col1 | col2 | col3 | col4 | col5 | col6 | col7 | func_int_multi1 +------+------+------+------+------+------+------+----------------- + 1 | 1 | 1 | 1 | 1 | 1 | 1 | 7 + 2 | 2 | 2 | 2 | 2 | 2 | 2 | 14 + 3 | 3 | 3 | 3 | 3 | 3 | 3 | 21 + 4 | 4 | 4 | 4 | 4 | 4 | 4 | 28 + 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 + 1 | 1 | 1 | 1 | 1 | 1 | 1 | 7 + 2 | 2 | 2 | 2 | 2 | 2 | 2 | 14 + 3 | 3 | 3 | 3 | 3 | 3 | 3 | 21 + 4 | 4 | 4 | 4 | 4 | 4 | 4 | 28 + 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 + 1 | 1 | 1 | 1 | 1 | 1 | 1 | 7 + 2 | 2 | 2 | 2 | 2 | 2 | 2 | 14 + 3 | 3 | 3 | 3 | 3 | 3 | 3 | 21 + 4 | 4 | 4 | 4 | 4 | 4 | 4 | 28 + 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 + 1 | 1 | 1 | 1 | 1 | 1 | 1 | 7 + 2 | 2 | 2 | 2 | 2 | 2 | 2 | 14 + 3 | 3 | 3 | 3 | 3 | 3 | 3 | 21 + 4 | 4 | 4 | 4 | 4 | 4 | 4 | 28 + 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 + | | | | | | | + | | | | | | | +(22 rows) + +-- 10个 +create or replace function func_int_multi2(p1 int, p2 int, p3 int, p4 int, p5 int, p6 int, p7 int, + p8 int, p9 int, p10 int) +returns int as $$ +begin + raise notice 'param:%,%,%,%,%,%,%,%,%,%', p1, p2, p3, p4, p5, p6, p7, p8, p9, p10; + return p1+p2+p3+p4+p5+p6+p7+p8+p9+p10; +end; +$$ language plpgsql stable result_cache; +select col1,col2,col3,col4,col5,col6,col7,col8,col9,col10, + func_int_multi2(col1,col2,col3,col4,col5,col6,col7,col8,col9,col10) from multi_col; +NOTICE: param:1,1,1,1,1,1,1,1,1,1 +NOTICE: param:2,2,2,2,2,2,2,2,2,2 +NOTICE: param:3,3,3,3,3,3,3,3,3,3 +NOTICE: param:4,4,4,4,4,4,4,4,4,4 +NOTICE: param:0,0,0,0,0,0,0,0,0,0 +NOTICE: param:,,,,,,,,, + col1 | col2 | col3 | col4 | col5 | col6 | col7 | col8 | col9 | col10 | func_int_multi2 +------+------+------+------+------+------+------+------+------+-------+----------------- + 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 10 + 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 20 + 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 30 + 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 40 + 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 + 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 10 + 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 20 + 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 30 + 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 40 + 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 + 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 10 + 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 20 + 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 30 + 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 40 + 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 + 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 10 + 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 20 + 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 30 + 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 40 + 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 + | | | | | | | | | | + | | | | | | | | | | +(22 rows) + +-- 16个 +create or replace function func_int_multi3(p1 int, p2 int, p3 int, p4 int, p5 int, p6 int, p7 int, + p8 int, p9 int, p10 int, p11 int, p12 int, p13 int, p14 int, p15 int, p16 int) +returns int as $$ +begin + raise notice 'param:%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%', p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16; + return p1+p2+p3+p4+p5+p6+p7+p8+p9+p10+p11+p12+p13+p14+p15+p16; +end; +$$ language plpgsql stable result_cache; +select col1,col2,col3,col4,col5,col6,col7,col8,col9,col10,col11,col12,col13,col14,col15,col16, + func_int_multi3(col1,col2,col3,col4,col5,col6,col7,col8,col9,col10,col11,col12,col13,col14,col15,col16) from multi_col; +NOTICE: param:1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 +NOTICE: param:2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2 +NOTICE: param:3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3 +NOTICE: param:4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4 +NOTICE: param:0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +NOTICE: param:,,,,,,,,,,,,,,, + col1 | col2 | col3 | col4 | col5 | col6 | col7 | col8 | col9 | col10 | col11 | col12 | col13 | col14 | col15 | col16 | func_int_multi3 +------+------+------+------+------+------+------+------+------+-------+-------+-------+-------+-------+-------+-------+----------------- + 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 16 + 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 32 + 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 48 + 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 64 + 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 + 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 16 + 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 32 + 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 48 + 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 64 + 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 + 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 16 + 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 32 + 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 48 + 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 64 + 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 + 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 16 + 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 32 + 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 48 + 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 64 + 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 + | | | | | | | | | | | | | | | | + | | | | | | | | | | | | | | | | +(22 rows) + +-- 函数参数大于16, 不支持缓存 +create or replace function func_int_multi4(p1 int, p2 int, p3 int, p4 int, p5 int, p6 int, p7 int, + p8 int, p9 int, p10 int, p11 int, p12 int, p13 int, p14 int, p15 int, p16 int, p17 int) +returns int as $$ +begin + raise notice 'param:%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%', p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16, p17; + return p1+p2+p3+p4+p5+p6+p7+p8+p9+p10+p11+p12+p13+p14+p15+p16; +end; +$$ language plpgsql stable result_cache; +select col1,col2,col3,col4,col5,col6,col7,col8,col9,col10,col11,col12,col13,col14,col15,col16, + func_int_multi4(col1,col2,col3,col4,col5,col6,col7,col8,col9,col10,col11,col12,col13,col14,col15,col16,col16) from multi_col; +NOTICE: param:1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 +NOTICE: param:2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2 +NOTICE: param:3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3 +NOTICE: param:4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4 +NOTICE: param:0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +NOTICE: param:1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 +NOTICE: param:2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2 +NOTICE: param:3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3 +NOTICE: param:4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4 +NOTICE: param:0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +NOTICE: param:1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 +NOTICE: param:2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2 +NOTICE: param:3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3 +NOTICE: param:4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4 +NOTICE: param:0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +NOTICE: param:1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 +NOTICE: param:2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2 +NOTICE: param:3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3 +NOTICE: param:4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4 +NOTICE: param:0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +NOTICE: param:,,,,,,,,,,,,,,,, +NOTICE: param:,,,,,,,,,,,,,,,, + col1 | col2 | col3 | col4 | col5 | col6 | col7 | col8 | col9 | col10 | col11 | col12 | col13 | col14 | col15 | col16 | func_int_multi4 +------+------+------+------+------+------+------+------+------+-------+-------+-------+-------+-------+-------+-------+----------------- + 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 16 + 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 32 + 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 48 + 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 64 + 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 + 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 16 + 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 32 + 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 48 + 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 64 + 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 + 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 16 + 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 32 + 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 48 + 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 64 + 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 + 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 16 + 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 32 + 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 48 + 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 64 + 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 + | | | | | | | | | | | | | | | | + | | | | | | | | | | | | | | | | +(22 rows) + +drop function if exists func_int_multi; +drop function if exists func_int_multi1; +drop function if exists func_int_multi2; +drop function if exists func_int_multi3; +drop function if exists func_int_multi4; +create table testbool(col1 bool,col2 bool); +insert into testbool values(0,0),(0,1),(1,0),(1,1),(null,0),(null,1),(0,null),(1,null),(null,null); +insert into testbool select * from testbool; +-- 输入是常量 +select func_bool(0) from testbool; +NOTICE: param:f + func_bool +----------- + f + f + f + f + f + f + f + f + f + f + f + f + f + f + f + f + f + f +(18 rows) + +select func_bool(1) from testbool; +NOTICE: param:t + func_bool +----------- + t + t + t + t + t + t + t + t + t + t + t + t + t + t + t + t + t + t +(18 rows) + +select func_bool(null) from testbool; +NOTICE: param: + func_bool +----------- + + + + + + + + + + + + + + + + + + +(18 rows) + +select func_bool_multi(0,0) from testbool; +NOTICE: param:f,f + func_bool_multi +----------------- + 4 + 4 + 4 + 4 + 4 + 4 + 4 + 4 + 4 + 4 + 4 + 4 + 4 + 4 + 4 + 4 + 4 + 4 +(18 rows) + +select func_bool_multi(0,1) from testbool; +NOTICE: param:f,t + func_bool_multi +----------------- + 4 + 4 + 4 + 4 + 4 + 4 + 4 + 4 + 4 + 4 + 4 + 4 + 4 + 4 + 4 + 4 + 4 + 4 +(18 rows) + +select func_bool_multi(null,0) from testbool; +NOTICE: param:,f + func_bool_multi +----------------- + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 +(18 rows) + +select func_bool_multi(1,null) from testbool; +NOTICE: param:t, + func_bool_multi +----------------- + 3 + 3 + 3 + 3 + 3 + 3 + 3 + 3 + 3 + 3 + 3 + 3 + 3 + 3 + 3 + 3 + 3 + 3 +(18 rows) + +-- null 值是否缓存 +select col1,func_bool(col1) from testbool; +NOTICE: param:f +NOTICE: param:t +NOTICE: param: + col1 | func_bool +------+----------- + f | f + f | f + t | t + t | t + | + | + f | f + t | t + | + f | f + f | f + t | t + t | t + | + | + f | f + t | t + | +(18 rows) + +select col1,col2,func_bool_multi(col1,col2) from testbool; +NOTICE: param:f,f +NOTICE: param:f,t +NOTICE: param:t,f +NOTICE: param:t,t +NOTICE: param:,f +NOTICE: param:,t +NOTICE: param:f, +NOTICE: param:t, +NOTICE: param:, + col1 | col2 | func_bool_multi +------+------+----------------- + f | f | 4 + f | t | 4 + t | f | 4 + t | t | 4 + | f | 2 + | t | 2 + f | | 3 + t | | 3 + | | 1 + f | f | 4 + f | t | 4 + t | f | 4 + t | t | 4 + | f | 2 + | t | 2 + f | | 3 + t | | 3 + | | 1 +(18 rows) + +drop table testbool; +-- 函数嵌套 +create table testtab(id int); +insert into testtab values(generate_series(1,10) % 2); +-- 1. plsql嵌套调用result_cache函数 +create or replace function func_int_output(p int) returns int as $$ +begin + raise notice 'func_int_output param:%', p; + return p*2; +end; +$$ language plpgsql stable result_cache; +create or replace function func_text_return(p int) returns text +as $$ +begin + raise notice 'func_text_return param:%', p; + return p||'_text'; +end; +$$ language plpgsql stable result_cache; +create or replace function func_top(p int) returns text +as $$ +declare + cursor c1 for select func_int_output(id) as id from testtab where id=p; -- cursor中调用函数 + r1 record; + ret text = 'base_'; +begin + for r1 in c1 loop + ret = ret || func_text_return(r1.id); + end loop; + return ret; +end; +$$ language plpgsql; +select id,func_top(id) from testtab; +NOTICE: func_int_output param:1 +NOTICE: func_text_return param:2 +NOTICE: func_int_output param:0 +NOTICE: func_text_return param:0 + id | func_top +----+------------------------------------- + 1 | base_2_text2_text2_text2_text2_text + 0 | base_0_text0_text0_text0_text0_text + 1 | base_2_text2_text2_text2_text2_text + 0 | base_0_text0_text0_text0_text0_text + 1 | base_2_text2_text2_text2_text2_text + 0 | base_0_text0_text0_text0_text0_text + 1 | base_2_text2_text2_text2_text2_text + 0 | base_0_text0_text0_text0_text0_text + 1 | base_2_text2_text2_text2_text2_text + 0 | base_0_text0_text0_text0_text0_text +(10 rows) + +-- DECLARE CURSOR command +START TRANSACTION; +DECLARE cursor1 CURSOR FOR select id,func_top(id) from testtab; +FETCH FORWARD 1 FROM cursor1; +NOTICE: func_int_output param:1 +NOTICE: func_text_return param:2 + id | func_top +----+------------------------------------- + 1 | base_2_text2_text2_text2_text2_text +(1 row) + +FETCH FORWARD 1 FROM cursor1; +NOTICE: func_int_output param:0 +NOTICE: func_text_return param:0 + id | func_top +----+------------------------------------- + 0 | base_0_text0_text0_text0_text0_text +(1 row) + +FETCH FORWARD 1 FROM cursor1; + id | func_top +----+------------------------------------- + 1 | base_2_text2_text2_text2_text2_text +(1 row) + +FETCH FORWARD 1 FROM cursor1; + id | func_top +----+------------------------------------- + 0 | base_0_text0_text0_text0_text0_text +(1 row) + +FETCH FORWARD 1 FROM cursor1; + id | func_top +----+------------------------------------- + 1 | base_2_text2_text2_text2_text2_text +(1 row) + +FETCH FORWARD 1 FROM cursor1; + id | func_top +----+------------------------------------- + 0 | base_0_text0_text0_text0_text0_text +(1 row) + +FETCH FORWARD 1 FROM cursor1; + id | func_top +----+------------------------------------- + 1 | base_2_text2_text2_text2_text2_text +(1 row) + +FETCH FORWARD 1 FROM cursor1; + id | func_top +----+------------------------------------- + 0 | base_0_text0_text0_text0_text0_text +(1 row) + +FETCH FORWARD 1 FROM cursor1; + id | func_top +----+------------------------------------- + 1 | base_2_text2_text2_text2_text2_text +(1 row) + +FETCH FORWARD 1 FROM cursor1; + id | func_top +----+------------------------------------- + 0 | base_0_text0_text0_text0_text0_text +(1 row) + +CLOSE cursor1; +END; +START TRANSACTION; +DECLARE cursor1 CURSOR FOR select id,func_top(id) from testtab; +FETCH ALL FROM cursor1; +NOTICE: func_int_output param:1 +NOTICE: func_text_return param:2 +NOTICE: func_int_output param:0 +NOTICE: func_text_return param:0 + id | func_top +----+------------------------------------- + 1 | base_2_text2_text2_text2_text2_text + 0 | base_0_text0_text0_text0_text0_text + 1 | base_2_text2_text2_text2_text2_text + 0 | base_0_text0_text0_text0_text0_text + 1 | base_2_text2_text2_text2_text2_text + 0 | base_0_text0_text0_text0_text0_text + 1 | base_2_text2_text2_text2_text2_text + 0 | base_0_text0_text0_text0_text0_text + 1 | base_2_text2_text2_text2_text2_text + 0 | base_0_text0_text0_text0_text0_text +(10 rows) + +CLOSE cursor1; +END; +-- smp/parallel 并行,不支持 +-- opfusion不支持输出列带函数的语句 +create index testtab_id_idx on testtab(id); +set enable_seqscan=off; +set opfusion_debug_mode='log'; +explain (costs off, analyze, timing off) select id,func_top(id) from testtab where id=1; +NOTICE: func_int_output param:1 +NOTICE: func_text_return param:2 + QUERY PLAN +--------------------------------------------------------------------------------------------------------------- + [No Bypass]reason: Bypass not executed because query used the target list which only contains table's column. + Index Only Scan using testtab_id_idx on testtab (actual rows=5 loops=1) + Index Cond: (id = 1) + Heap Fetches: 5 +--? Total runtime: .* +(5 rows) + +reset opfusion_debug_mode; +reset enable_seqscan; +drop index testtab_id_idx; +-- 2. result_cache函数嵌套调用result_cache函数 +create or replace function func_top(p int) returns text +as $$ +declare + cursor c1 for select func_int_output(id) as id from testtab where id=p; -- cursor中调用函数 + r1 record; + ret text = 'base_'; +begin + raise notice 'top param:%', p; + for r1 in c1 loop + ret = ret || func_text_return(r1.id); + end loop; + return ret; +end; +$$ language plpgsql stable result_cache; +select id,func_top(id) from testtab; +NOTICE: top param:1 +NOTICE: func_int_output param:1 +NOTICE: func_text_return param:2 +NOTICE: top param:0 +NOTICE: func_int_output param:0 +NOTICE: func_text_return param:0 + id | func_top +----+------------------------------------- + 1 | base_2_text2_text2_text2_text2_text + 0 | base_0_text0_text0_text0_text0_text + 1 | base_2_text2_text2_text2_text2_text + 0 | base_0_text0_text0_text0_text0_text + 1 | base_2_text2_text2_text2_text2_text + 0 | base_0_text0_text0_text0_text0_text + 1 | base_2_text2_text2_text2_text2_text + 0 | base_0_text0_text0_text0_text0_text + 1 | base_2_text2_text2_text2_text2_text + 0 | base_0_text0_text0_text0_text0_text +(10 rows) + +drop function if exists func_top; +drop function if exists func_int_output; +drop function if exists func_text_return; +-- 3. 函数自嵌套 +CREATE OR REPLACE FUNCTION fibonacci (n bigint) + RETURN bigint + STABLE + RESULT_CACHE +IS +BEGIN + raise notice 'param:%', n; + IF (n =0) OR (n =1) THEN + RETURN 1; + ELSE + RETURN fibonacci(n - 1) + fibonacci(n - 2); + END IF; +END; +/ +select fibonacci(10); +NOTICE: param:10 +NOTICE: param:9 +NOTICE: param:8 +NOTICE: param:7 +NOTICE: param:6 +NOTICE: param:5 +NOTICE: param:4 +NOTICE: param:3 +NOTICE: param:2 +NOTICE: param:1 +NOTICE: param:0 + fibonacci +----------- + 89 +(1 row) + +truncate testtab; +insert into testtab values(10),(0),(1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11); +select id,fibonacci(id) from testtab; +NOTICE: param:10 +NOTICE: param:9 +NOTICE: param:8 +NOTICE: param:7 +NOTICE: param:6 +NOTICE: param:5 +NOTICE: param:4 +NOTICE: param:3 +NOTICE: param:2 +NOTICE: param:1 +NOTICE: param:0 +NOTICE: param:11 + id | fibonacci +----+----------- + 10 | 89 + 0 | 1 + 1 | 1 + 2 | 2 + 3 | 3 + 4 | 5 + 5 | 8 + 6 | 13 + 7 | 21 + 8 | 34 + 9 | 55 + 10 | 89 + 11 | 144 +(13 rows) + +drop function if exists fibonacci; +-- 缓存失效: 模拟数据百分比 +-- 函数调用次数>2560时, 判断缓存命中率是否<15%, 如果小于则失效 +truncate testtab; +insert into testtab values (generate_series(1,10000) % 5); +explain (costs off, analyze, timing off) select func_int(id) from testtab; +NOTICE: param:1 + QUERY PLAN +------------------------------------------------- + Seq Scan on testtab (actual rows=10000 loops=1) +--? Total runtime: .* +(2 rows) + +prepare test as select func_int(id) from testtab; +explain (costs off, analyze, timing off) execute test; +NOTICE: param:1 + QUERY PLAN +------------------------------------------------- + Seq Scan on testtab (actual rows=10000 loops=1) +--? Total runtime: .* +(2 rows) + +explain (costs off, analyze, timing off) execute test; +NOTICE: param:1 + QUERY PLAN +------------------------------------------------- + Seq Scan on testtab (actual rows=10000 loops=1) +--? Total runtime: .* +(2 rows) + +explain (costs off, analyze, timing off) execute test; +NOTICE: param:1 + QUERY PLAN +------------------------------------------------- + Seq Scan on testtab (actual rows=10000 loops=1) +--? Total runtime: .* +(2 rows) + +deallocate test; +truncate testtab; +insert into testtab values (generate_series(1,10000) % 500); +explain (costs off, analyze, timing off) select func_int(id) from testtab; +NOTICE: param:1 + QUERY PLAN +------------------------------------------------- + Seq Scan on testtab (actual rows=10000 loops=1) +--? Total runtime: .* +(2 rows) + +prepare test as select func_int(id) from testtab; +explain (costs off, analyze, timing off) execute test; +NOTICE: param:1 + QUERY PLAN +------------------------------------------------- + Seq Scan on testtab (actual rows=10000 loops=1) +--? Total runtime: .* +(2 rows) + +explain (costs off, analyze, timing off) execute test; +NOTICE: param:1 + QUERY PLAN +------------------------------------------------- + Seq Scan on testtab (actual rows=10000 loops=1) +--? Total runtime: .* +(2 rows) + +explain (costs off, analyze, timing off) execute test; +NOTICE: param:1 + QUERY PLAN +------------------------------------------------- + Seq Scan on testtab (actual rows=10000 loops=1) +--? Total runtime: .* +(2 rows) + +deallocate test; +truncate testtab; +insert into testtab values (generate_series(1,10000) % 1000); +explain (costs off, analyze, timing off) select func_int(id) from testtab; +NOTICE: param:1 +NOTICE: param:1 +NOTICE: param:1 +NOTICE: param:1 +NOTICE: param:1 +NOTICE: param:1 +NOTICE: param:1 +NOTICE: param:1 +NOTICE: param:1 +NOTICE: param:1 + QUERY PLAN +------------------------------------------------- + Seq Scan on testtab (actual rows=10000 loops=1) +--? Total runtime: .* +(2 rows) + +prepare test as select func_int(id) from testtab; +explain (costs off, analyze, timing off) execute test; +NOTICE: param:1 +NOTICE: param:1 +NOTICE: param:1 +NOTICE: param:1 +NOTICE: param:1 +NOTICE: param:1 +NOTICE: param:1 +NOTICE: param:1 +NOTICE: param:1 +NOTICE: param:1 + QUERY PLAN +------------------------------------------------- + Seq Scan on testtab (actual rows=10000 loops=1) +--? Total runtime: .* +(2 rows) + +explain (costs off, analyze, timing off) execute test; +NOTICE: param:1 +NOTICE: param:1 +NOTICE: param:1 +NOTICE: param:1 +NOTICE: param:1 +NOTICE: param:1 +NOTICE: param:1 +NOTICE: param:1 +NOTICE: param:1 +NOTICE: param:1 + QUERY PLAN +------------------------------------------------- + Seq Scan on testtab (actual rows=10000 loops=1) +--? Total runtime: .* +(2 rows) + +explain (costs off, analyze, timing off) execute test; +NOTICE: param:1 +NOTICE: param:1 +NOTICE: param:1 +NOTICE: param:1 +NOTICE: param:1 +NOTICE: param:1 +NOTICE: param:1 +NOTICE: param:1 +NOTICE: param:1 +NOTICE: param:1 + QUERY PLAN +------------------------------------------------- + Seq Scan on testtab (actual rows=10000 loops=1) +--? Total runtime: .* +(2 rows) + +deallocate test; +drop function if exists func_no_param; +drop function if exists func_bool; +drop function if exists func_smallint; +drop function if exists func_int; +drop function if exists func_bigint; +drop function if exists func_float; +drop function if exists func_double_precision; +drop function if exists func_char; +drop function if exists func_varchar; +drop function if exists func_varchar2; +drop function if exists func_time; +drop function if exists func_timetz; +drop function if exists func_date; +drop function if exists func_timestamp; +drop function if exists func_timestamptz; +drop function if exists func_numeric; +drop function if exists func_number; +drop function if exists func_text; +drop function if exists func_bool_multi; +-- 匿名块里调用函数? +-- 数据过长时不支持缓存 (字符串长度>127, numeric/number?) +create table testtab_with_long_value(id int, col_numeric numeric, col_number number, col_text text); +insert into testtab_with_long_value values +(1,1,1,lpad('abc',130,'d')), +(2,1,1,lpad('abc',130,'d')), -- not use cache +(3,2,2,lpad('hahahaha',130,'d')), +(4,2,2,lpad('hahahaha',130,'d')), -- not use cache +(5,3,3,lpad('shortvalue',100,'d')), +(6,3,3,lpad('shortvalue',100,'d')); -- use cache +create or replace function func_text_with_long_value(p text) returns text as $$ +begin + raise notice 'param:%', p; + return p||'_text'; +end; +$$ language plpgsql stable result_cache; +explain (costs off, analyze, timing off) +select col_text, func_text_with_long_value(col_text) from testtab_with_long_value; +NOTICE: param:dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddabc +NOTICE: param:dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddabc +NOTICE: param:ddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddhahahaha +NOTICE: param:ddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddhahahaha +NOTICE: param:ddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddshortvalue + QUERY PLAN +------------------------------------------------------------- + Seq Scan on testtab_with_long_value (actual rows=6 loops=1) +--? Total runtime: .* +(2 rows) + +drop function if exists func_text_with_long_value; +drop table if exists testtab_with_long_value; +truncate testtab; +insert into testtab values(generate_series(1,10) % 2); +-- 不支持内置过程语言: internal, c, SQL +create or replace function func_sql(p int) returns int +as $$ + select p+1; +$$ language sql stable result_cache; +explain (costs off, analyze, timing off) select func_sql(id) from testtab; + QUERY PLAN +---------------------------------------------- + Seq Scan on testtab (actual rows=10 loops=1) +--? Total runtime: .* +(2 rows) + +drop function if exists func_sql; +-- 不支持函数含有OUT, INOUT参数 +create or replace function func_param_with_out(p int, p1 out int) returns int +as $$ +declare + p2 int; +begin + raise notice 'param:%', p; + p1 = p + 2; + p2 = p1 % (p+1) + 10; + return p2; +end; +$$ language plpgsql stable result_cache; +explain (costs off, analyze, timing off) select func_param_with_out(id) from testtab; +NOTICE: param:1 +NOTICE: param:0 +NOTICE: param:1 +NOTICE: param:0 +NOTICE: param:1 +NOTICE: param:0 +NOTICE: param:1 +NOTICE: param:0 +NOTICE: param:1 +NOTICE: param:0 + QUERY PLAN +---------------------------------------------- + Seq Scan on testtab (actual rows=10 loops=1) +--? Total runtime: .* +(2 rows) + +drop function if exists func_param_with_out; +create or replace function func_param_with_inout(p int, p1 inout int) returns int +as $$ +declare + p2 int; +begin + raise notice 'param:%', p; + p1 = p + 2; + p2 = p1 % (p+1) + 10; + return p2; +end; +$$ language plpgsql stable result_cache; +explain (costs off, analyze, timing off) select func_param_with_inout(id, id) from testtab; +NOTICE: param:1 +NOTICE: param:0 +NOTICE: param:1 +NOTICE: param:0 +NOTICE: param:1 +NOTICE: param:0 +NOTICE: param:1 +NOTICE: param:0 +NOTICE: param:1 +NOTICE: param:0 + QUERY PLAN +---------------------------------------------- + Seq Scan on testtab (actual rows=10 loops=1) +--? Total runtime: .* +(2 rows) + +drop function if exists func_param_with_inout; +-- 不支持函数没有返回值 +create or replace function func_param_without_retval(p int, p1 inout int) returns void +as $$ +declare + p2 int; +begin + raise notice 'param:%', p; + p1 = p + 2; + p2 = p1 % (p+1) + 10; +end; +$$ language plpgsql stable result_cache; +explain (costs off, analyze, timing off) select func_param_without_retval(id, id) from testtab; +NOTICE: param:1 +NOTICE: param:0 +NOTICE: param:1 +NOTICE: param:0 +NOTICE: param:1 +NOTICE: param:0 +NOTICE: param:1 +NOTICE: param:0 +NOTICE: param:1 +NOTICE: param:0 + QUERY PLAN +---------------------------------------------- + Seq Scan on testtab (actual rows=10 loops=1) +--? Total runtime: .* +(2 rows) + +drop function if exists func_param_without_retval; +-- 不支持返回结果集的函数 +create or replace function func_setof(p int) +returns setof text +as $$ +declare + row_data record; + query_str text; +begin + raise notice 'param:%', p; + query_str := 'select id from testtab where id='||p; + for row_data in execute(query_str) loop + return next row_data.id; + end loop; + return; +end; +$$ language 'plpgsql' stable result_cache; +explain (costs off, analyze, timing off) select func_setof(id) from testtab; +NOTICE: param:1 +NOTICE: param:0 +NOTICE: param:1 +NOTICE: param:0 +NOTICE: param:1 +NOTICE: param:0 +NOTICE: param:1 +NOTICE: param:0 +NOTICE: param:1 +NOTICE: param:0 + QUERY PLAN +---------------------------------------------- + Seq Scan on testtab (actual rows=50 loops=1) +--? Total runtime: .* +(2 rows) + +drop function if exists func_setof; +-- 不支持VOLATILE、聚集、Window函数 +create or replace function func_int_volatile(p int) returns int as $$ +begin + raise notice 'param:%', p; + return p*2; +end; +$$ language plpgsql result_cache; +explain (costs off, analyze, timing off) select func_int_volatile(id) from testtab; +NOTICE: param:1 +NOTICE: param:0 +NOTICE: param:1 +NOTICE: param:0 +NOTICE: param:1 +NOTICE: param:0 +NOTICE: param:1 +NOTICE: param:0 +NOTICE: param:1 +NOTICE: param:0 + QUERY PLAN +---------------------------------------------- + Seq Scan on testtab (actual rows=10 loops=1) +--? Total runtime: .* +(2 rows) + +drop function if exists func_int_volatile; +-- 不支持自治事务, 子程序 +create table tab_tmp(id int,col int); +insert into tab_tmp values(0,1111),(1,1),(2,1),(3,2),(4,2); +-- function with autonomous transaction +create or replace function func_autonomous(p int) +return int +stable +result_cache +is +sid2 number; +PRAGMA AUTONOMOUS_TRANSACTION; +begin + raise notice 'param:%', p; + if p = 0 then + update tab_tmp set col=0; + commit; + return 0; + end if; + + select id into sid2 from tab_tmp where id = 1 for update; + if sid2 = 1 then + -- no lock or self + update tab_tmp set col=11; + commit; + return 1; + end if; + + rollback; + return -1; + +exception + when others then + rollback; + return -2; +end; +/ +select id,func_autonomous(id) from testtab; -- error +NOTICE: param:1 +CONTEXT: referenced column: func_autonomous + +ERROR: ERROR: commit/rollback/savepoint is not allowed in a non-volatile function +CONTEXT: PL/pgSQL function func_autonomous(integer) line 24 at ROLLBACK +referenced column: func_autonomous + +create or replace procedure proc_autonomous(p int) +is +sid2 number; +PRAGMA AUTONOMOUS_TRANSACTION; +begin + if p = 0 then + update tab_tmp set col=0 where id=0; + commit; + return; + end if; + + select id into sid2 from tab_tmp where id = 1 for update; + if sid2 = 1 then + -- no lock or self + update tab_tmp set col=11; + commit; + return; + end if; + + return; +exception + when others then + rollback; +end; +/ +create or replace function func_with_proc(p int) +return int +stable +result_cache +is +sid2 number; +begin + raise notice 'top param:%', p; + proc_autonomous(p); + return p+1; +end; +/ +select id,func_with_proc(id) from testtab; +NOTICE: top param:1 +NOTICE: top param:0 + id | func_with_proc +----+---------------- + 1 | 2 + 0 | 1 + 1 | 2 + 0 | 1 + 1 | 2 + 0 | 1 + 1 | 2 + 0 | 1 + 1 | 2 + 0 | 1 +(10 rows) + +drop function if exists func_autonomous; +drop function if exists func_with_proc; +drop procedure if exists proc_autonomous; +CREATE OR REPLACE FUNCTION func_add_sql(integer, integer) RETURNS integer + AS 'select $1 + $2;' + LANGUAGE SQL + IMMUTABLE + RESULT_CACHE + RETURNS NULL ON NULL INPUT; +SELECT proname,result_cache FROM PG_PROC WHERE proname = 'func_add_sql'; + proname | result_cache +--------------+-------------- + func_add_sql | t +(1 row) + +SELECT func_add_sql(1,1); + func_add_sql +-------------- + 2 +(1 row) + +drop function if exists func_add_sql; +create table tmp( + bsm VARCHAR(100), + name VARCHAR(100), + num int +); +insert into tmp(bsm,name,num) VALUES('a','苹果',21); +insert into tmp(bsm,name,num) VALUES('b','香蕉',11); +create or replace function getsum(in talename VARCHAR) +RETURNS int as $$ +DECLARE + stmt VARCHAR; + count int; +begin + stmt:=format('select count(1) from %s', talename); + raise notice '%',stmt; + EXECUTE stmt into count; + return count; + + EXCEPTION --捕获异常 + + WHEN OTHERS THEN + RETURN 0; +end; $$ LANGUAGE plpgsql result_cache immutable; +select getsum('tmp'); +NOTICE: select count(1) from tmp + getsum +-------- + 2 +(1 row) + +drop function if exists getsum; +drop table if exists tmp; +DECLARE + x INTEGER; + + FUNCTION f (n INTEGER) + RETURN INTEGER + IS + BEGIN + RETURN (n*n); + END; + +BEGIN + raise notice 'f returns %. Execution returns here (1).', f(2); + + x := f(2); + raise notice 'Execution returns here (2).'; +END; +/ +NOTICE: f returns 4. Execution returns here (1). +NOTICE: Execution returns here (2). +-- package +create package pkg_test as + function func_bool(p bool) return bool stable result_cache; + procedure proc_test(p bool); +end pkg_test; +/ +create or replace package body pkg_test as + function func_bool(p bool) return bool stable result_cache is + begin + raise notice 'param:%', p; + return p; + end func_bool; + + procedure proc_test(p bool) is + cursor cur1 for select func_bool(col_bool) from all_support_type where col_bool=p; + p1 bool; + begin + open cur1; + loop + fetch cur1 into p1; + exit when cur1%notfound; + end loop; + close cur1; + end proc_test; +end pkg_test; +/ +call pkg_test.proc_test('true'); +NOTICE: param:t + proc_test +----------- + +(1 row) + +call pkg_test.proc_test('false'); +NOTICE: param:f + proc_test +----------- + +(1 row) + +explain (costs off, analyze, timing off) select pkg_test.func_bool(col_bool) from all_support_type; +NOTICE: param:t +NOTICE: param:f +NOTICE: param: + QUERY PLAN +--------------------------------------------------------- + Seq Scan on all_support_type (actual rows=1002 loops=1) +--? Total runtime: .* +(2 rows) + +drop package pkg_test; +NOTICE: drop cascades to 2 other objects +drop table if exists testtab,all_support_type,multi_col,tab_tmp; +set search_path to default; +drop schema func_result_cache; diff --git a/src/test/regress/parallel_schedule0 b/src/test/regress/parallel_schedule0 index 5817b18795..3a4ebabdef 100644 --- a/src/test/regress/parallel_schedule0 +++ b/src/test/regress/parallel_schedule0 @@ -283,6 +283,7 @@ test: single_node_bitmapops single_node_combocid #test: single_node_functional_deps single_node_advisory_lock single_node_json single_node_equivclass test: xml xmltype test: subtype +test: function_result_cache # ---------- # Another group of parallel tests diff --git a/src/test/regress/sql/function_result_cache.sql b/src/test/regress/sql/function_result_cache.sql new file mode 100644 index 0000000000..a68ad2415d --- /dev/null +++ b/src/test/regress/sql/function_result_cache.sql @@ -0,0 +1,948 @@ +create schema func_result_cache; +set search_path to func_result_cache; +set enable_func_cache=on; +set datestyle='ISO, MDY'; +\set VERBOSITY terse + +-- all support datatype +create table all_support_type( + id int, + col_bool bool, + col_smallint smallint, + col_int int, + col_bigint bigint, + col_float float, + col_double_precision double precision, + col_char char(20), + col_varchar varchar(20), + col_varchar2 varchar2(20), + col_time time, + col_timetz timetz, + col_date date, + col_timestamp timestamp, + col_timestamptz timestamptz, + col_numeric numeric, + col_number number, + col_text text +); + +insert into all_support_type values( + generate_series(1,1000), + generate_series(1,1000) % 2, + generate_series(1,1000) % 5, + generate_series(1,1000) % 5, + generate_series(1,1000) % 5, + generate_series(1,1000) % 5, + generate_series(1,1000) % 5, + generate_series(1,1000) % 10 || '_haha', + generate_series(1,1000) % 10 || '_haha', + generate_series(1,1000) % 10 || '_haha', + '18:13:49.579082', + '18:13:49.579082', + '2024-11-22', + '2024-11-22 16:36:45', + '2024-11-22 16:36:45', + generate_series(1,1000) % 5, + generate_series(1,1000) % 5, + generate_series(1,1000) % 10 || '_haha' +); + +insert into all_support_type values(1001,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null); +insert into all_support_type values(1002,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null); + +-- 无参函数 +create or replace function func_no_param() returns int as $$ +begin + return 1; +end; +$$ language plpgsql stable result_cache; + +explain (costs off, analyze, timing off) select func_no_param() from all_support_type; + +create or replace function func_bool(p bool) returns bool as $$ +begin + raise notice 'param:%', p; + return p; +end; +$$ language plpgsql stable result_cache; + +create or replace function func_smallint(p smallint) returns smallint as $$ +begin + if p = 1 then + raise notice 'param:%', p; + end if; + return p+1; +end; +$$ language plpgsql stable result_cache; + +create or replace function func_int(p int) returns int as $$ +begin + if p = 1 then + raise notice 'param:%', p; + end if; + return p*2; +end; +$$ language plpgsql stable result_cache; + +create or replace function func_bigint(p bigint) returns bigint as $$ +begin + if p = 1 then + raise notice 'param:%', p; + end if; + return p*2+1; +end; +$$ language plpgsql stable result_cache; + +create or replace function func_float(p float) returns float as $$ +begin + if p = 1 then + raise notice 'param:%', p; + end if; + return p*2+1; +end; +$$ language plpgsql stable result_cache; + +create or replace function func_double_precision(p double precision) returns double precision as $$ +begin + if p = 1 then + raise notice 'param:%', p; + end if; + return p*2-1; +end; +$$ language plpgsql stable result_cache; + +create or replace function func_char(p char) returns char as $$ +begin + if p = '1_haha ' then + raise notice 'param:%', p; + end if; + return p||'_char'; +end; +$$ language plpgsql stable result_cache; + +create or replace function func_varchar(p varchar) returns varchar as $$ +begin + if p = '1_haha' then + raise notice 'param:%', p; + end if; + return p||'_varchar'; +end; +$$ language plpgsql stable result_cache; + +create or replace function func_varchar2(p varchar2) returns varchar2 as $$ +begin + if p = '1_haha' then + raise notice 'param:%', p; + end if; + return p||'_varchar2'; +end; +$$ language plpgsql stable result_cache; + +create or replace function func_time(p time) returns time as $$ +begin + raise notice 'param:%', p; + return p + interval '1 hour'; +end; +$$ language plpgsql stable result_cache; + +create or replace function func_timetz(p timetz) returns timetz as $$ +begin + raise notice 'param:%', p; + return p + interval '1 hour'; +end; +$$ language plpgsql stable result_cache; + +create or replace function func_date(p date) returns date as $$ +begin + raise notice 'param:%', p; + return p + interval '1 day'; +end; +$$ language plpgsql stable result_cache; + +create or replace function func_timestamp(p timestamp) returns timestamp as $$ +begin + raise notice 'param:%', p; + return p + interval '1 day'; +end; +$$ language plpgsql stable result_cache; + +create or replace function func_timestamptz(p timestamptz) returns timestamptz as $$ +begin + raise notice 'param:%', p; + return p + interval '1 day'; +end; +$$ language plpgsql stable result_cache; + +create or replace function func_numeric(p numeric) returns numeric as $$ +begin + if p = 1 then + raise notice 'param:%', p; + end if; + return p - 1; +end; +$$ language plpgsql stable result_cache; + +create or replace function func_number(p number) returns number as $$ +begin + if p = 1 then + raise notice 'param:%', p; + end if; + return p * 4; +end; +$$ language plpgsql stable result_cache; + +create or replace function func_text(p text) returns text as $$ +begin + if p = '1_haha' then + raise notice 'param:%', p; + end if; + return p||'_text'; +end; +$$ language plpgsql stable result_cache; + +-- targetlist存在单个函数 +explain (costs off, analyze, timing off) select col_bool, func_bool(col_bool) from all_support_type; +explain (costs off, analyze, timing off) select col_smallint, func_smallint(col_smallint) from all_support_type; +explain (costs off, analyze, timing off) select col_int, func_int(col_int) from all_support_type; +explain (costs off, analyze, timing off) select col_bigint, func_bigint(col_bigint) from all_support_type; +explain (costs off, analyze, timing off) select col_float, func_float(col_float) from all_support_type; +explain (costs off, analyze, timing off) select col_double_precision, func_double_precision(col_double_precision) from all_support_type; +explain (costs off, analyze, timing off) select col_char, func_char(col_char) from all_support_type; +explain (costs off, analyze, timing off) select col_varchar, func_varchar(col_varchar) from all_support_type; +explain (costs off, analyze, timing off) select col_varchar2, func_varchar2(col_varchar2) from all_support_type; +explain (costs off, analyze, timing off) select col_time, func_time(col_time) from all_support_type; +explain (costs off, analyze, timing off) select col_timetz, func_timetz(col_timetz) from all_support_type limit 10; --not support +explain (costs off, analyze, timing off) select col_date, func_date(col_date) from all_support_type; +explain (costs off, analyze, timing off) select col_timestamp, func_timestamp(col_timestamp) from all_support_type; +explain (costs off, analyze, timing off) select col_timestamptz, func_timestamptz(col_timestamptz) from all_support_type; +explain (costs off, analyze, timing off) select col_numeric, func_numeric(col_numeric) from all_support_type; +explain (costs off, analyze, timing off) select col_number, func_number(col_number) from all_support_type; +explain (costs off, analyze, timing off) select col_text, func_text(col_text) from all_support_type; + +-- targetlist存在多个函数 +-- 2个 +explain (costs off, analyze, timing off) +select + col_bool, func_bool(col_bool), + col_smallint, func_smallint(col_smallint) +from all_support_type; + +-- 4个 +explain (costs off, analyze, timing off) +select + col_int, func_int(col_int), + col_bigint, func_bigint(col_bigint), + col_float, func_float(col_float), + col_double_precision, func_double_precision(col_double_precision) +from all_support_type; + +-- 8个 +explain (costs off, analyze, timing off) +select + col_int, func_int(col_int), + col_bigint, func_bigint(col_bigint), + col_float, func_float(col_float), + col_double_precision, func_double_precision(col_double_precision), + col_char, func_char(col_char), + col_varchar, func_varchar(col_varchar), + col_varchar2, func_varchar2(col_varchar2), + col_timestamp, func_timestamp(col_timestamp) +from all_support_type; + +-- 11个 +explain (costs off, analyze, timing off) +select + col_int, func_int(col_int), + col_bigint, func_bigint(col_bigint), + col_float, func_float(col_float), + col_double_precision, func_double_precision(col_double_precision), + col_char, func_char(col_char), + col_varchar, func_varchar(col_varchar), + col_varchar2, func_varchar2(col_varchar2), + col_timestamp, func_timestamp(col_timestamp), + col_numeric, func_numeric(col_numeric), + col_number, func_number(col_number), + col_text, func_text(col_text) +from all_support_type; + +-- 15个, 限制了最多缓存14个函数的结果 +explain (costs off, analyze, timing off) +select + col_bool, func_bool(col_bool), + col_int, func_int(col_int), + col_bigint, func_bigint(col_bigint), + col_float, func_float(col_float), + col_double_precision, func_double_precision(col_double_precision), + col_char, func_char(col_char), + col_varchar, func_varchar(col_varchar), + col_varchar2, func_varchar2(col_varchar2), + col_time, func_time(col_time), + col_timestamp, func_timestamp(col_timestamp), + col_timestamptz, func_timestamptz(col_timestamptz), + col_numeric, func_numeric(col_numeric), + col_number, func_number(col_number), + col_text, func_text(col_text), + col_date, func_date(col_date) +from all_support_type limit 20; + +-- targetlist存在多个相同的函数 +-- 2个 +explain (costs off, analyze, timing off) +select + col_bool, func_bool(col_bool), + func_bool(col_bool) +from all_support_type; + +-- 4个 +explain (costs off, analyze, timing off) +select + col_bool, func_bool(col_bool), + func_bool(col_bool), + func_bool(col_bool), + func_bool(col_bool) +from all_support_type; + +-- 8个 +explain (costs off, analyze, timing off) +select + col_bool, func_bool(col_bool), + func_bool(col_bool), + func_bool(col_bool), + func_bool(col_bool), + func_bool(col_bool), + func_bool(col_bool), + func_bool(col_bool), + func_bool(col_bool) +from all_support_type; + +-- 大于8个 +explain (costs off, analyze, timing off) +select + col_bool, func_bool(col_bool), + func_bool(col_bool), + func_bool(col_bool), + func_bool(col_bool), + func_bool(col_bool), + func_bool(col_bool), + func_bool(col_bool), + func_bool(col_bool), + func_bool(col_bool), + func_bool(col_bool), + func_bool(col_bool) +from all_support_type; + +-- 函数有多个IN参数 +-- 2个 +create or replace function func_bool_multi(p1 bool, p2 bool) returns int as $$ +begin + raise notice 'param:%,%', p1, p2; + if (p1 is null and p2 is null) then + return 1; + elsif (p1 is null and p2 is not null) then + return 2; + elsif (p1 is not null and p2 is null) then + return 3; + end if; + return 4; +end; +$$ language plpgsql stable result_cache; + +explain (costs off, analyze, timing off) select col_bool,func_bool_multi(col_bool,col_bool) from all_support_type; + +create table multi_col( + id int, + col1 int, + col2 int, + col3 int, + col4 int, + col5 int, + col6 int, + col7 int, + col8 int, + col9 int, + col10 int, + col11 int, + col12 int, + col13 int, + col14 int, + col15 int, + col16 int +); + +insert into multi_col values( + generate_series(1,20), + generate_series(1,20) % 5, + generate_series(1,20) % 5, + generate_series(1,20) % 5, + generate_series(1,20) % 5, + generate_series(1,20) % 5, + generate_series(1,20) % 5, + generate_series(1,20) % 5, + generate_series(1,20) % 5, + generate_series(1,20) % 5, + generate_series(1,20) % 5, + generate_series(1,20) % 5, + generate_series(1,20) % 5, + generate_series(1,20) % 5, + generate_series(1,20) % 5, + generate_series(1,20) % 5, + generate_series(1,20) % 5 +); + +insert into multi_col values(generate_series(21,22),null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null); + +-- 3个 +create or replace function func_int_multi(p1 int, p2 int, p3 int) returns int as $$ +begin + raise notice 'param:%,%,%', p1, p2, p3; + return p1+p2+p3; +end; +$$ language plpgsql stable result_cache; + +select col1,col2,col3,func_int_multi(col1,col2,col3) from multi_col; + +-- 7个 +create or replace function func_int_multi1(p1 int, p2 int, p3 int, p4 int, p5 int, p6 int, p7 int) returns int as $$ +begin + raise notice 'param:%,%,%,%,%,%,%', p1, p2, p3, p4, p5, p6, p7; + return p1+p2+p3+p4+p5+p6+p7; +end; +$$ language plpgsql stable result_cache; + +select col1,col2,col3,col4,col5,col6,col7,func_int_multi1(col1,col2,col3,col4,col5,col6,col7) from multi_col; + +-- 10个 +create or replace function func_int_multi2(p1 int, p2 int, p3 int, p4 int, p5 int, p6 int, p7 int, + p8 int, p9 int, p10 int) +returns int as $$ +begin + raise notice 'param:%,%,%,%,%,%,%,%,%,%', p1, p2, p3, p4, p5, p6, p7, p8, p9, p10; + return p1+p2+p3+p4+p5+p6+p7+p8+p9+p10; +end; +$$ language plpgsql stable result_cache; + +select col1,col2,col3,col4,col5,col6,col7,col8,col9,col10, + func_int_multi2(col1,col2,col3,col4,col5,col6,col7,col8,col9,col10) from multi_col; + +-- 16个 +create or replace function func_int_multi3(p1 int, p2 int, p3 int, p4 int, p5 int, p6 int, p7 int, + p8 int, p9 int, p10 int, p11 int, p12 int, p13 int, p14 int, p15 int, p16 int) +returns int as $$ +begin + raise notice 'param:%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%', p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16; + return p1+p2+p3+p4+p5+p6+p7+p8+p9+p10+p11+p12+p13+p14+p15+p16; +end; +$$ language plpgsql stable result_cache; + +select col1,col2,col3,col4,col5,col6,col7,col8,col9,col10,col11,col12,col13,col14,col15,col16, + func_int_multi3(col1,col2,col3,col4,col5,col6,col7,col8,col9,col10,col11,col12,col13,col14,col15,col16) from multi_col; + +-- 函数参数大于16, 不支持缓存 +create or replace function func_int_multi4(p1 int, p2 int, p3 int, p4 int, p5 int, p6 int, p7 int, + p8 int, p9 int, p10 int, p11 int, p12 int, p13 int, p14 int, p15 int, p16 int, p17 int) +returns int as $$ +begin + raise notice 'param:%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%', p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16, p17; + return p1+p2+p3+p4+p5+p6+p7+p8+p9+p10+p11+p12+p13+p14+p15+p16; +end; +$$ language plpgsql stable result_cache; + +select col1,col2,col3,col4,col5,col6,col7,col8,col9,col10,col11,col12,col13,col14,col15,col16, + func_int_multi4(col1,col2,col3,col4,col5,col6,col7,col8,col9,col10,col11,col12,col13,col14,col15,col16,col16) from multi_col; + +drop function if exists func_int_multi; +drop function if exists func_int_multi1; +drop function if exists func_int_multi2; +drop function if exists func_int_multi3; +drop function if exists func_int_multi4; + +create table testbool(col1 bool,col2 bool); +insert into testbool values(0,0),(0,1),(1,0),(1,1),(null,0),(null,1),(0,null),(1,null),(null,null); +insert into testbool select * from testbool; + +-- 输入是常量 +select func_bool(0) from testbool; +select func_bool(1) from testbool; +select func_bool(null) from testbool; +select func_bool_multi(0,0) from testbool; +select func_bool_multi(0,1) from testbool; +select func_bool_multi(null,0) from testbool; +select func_bool_multi(1,null) from testbool; + +-- null 值是否缓存 +select col1,func_bool(col1) from testbool; +select col1,col2,func_bool_multi(col1,col2) from testbool; +drop table testbool; + +-- 函数嵌套 +create table testtab(id int); +insert into testtab values(generate_series(1,10) % 2); + +-- 1. plsql嵌套调用result_cache函数 +create or replace function func_int_output(p int) returns int as $$ +begin + raise notice 'func_int_output param:%', p; + return p*2; +end; +$$ language plpgsql stable result_cache; + +create or replace function func_text_return(p int) returns text +as $$ +begin + raise notice 'func_text_return param:%', p; + return p||'_text'; +end; +$$ language plpgsql stable result_cache; + +create or replace function func_top(p int) returns text +as $$ +declare + cursor c1 for select func_int_output(id) as id from testtab where id=p; -- cursor中调用函数 + r1 record; + ret text = 'base_'; +begin + for r1 in c1 loop + ret = ret || func_text_return(r1.id); + end loop; + return ret; +end; +$$ language plpgsql; + +select id,func_top(id) from testtab; + +-- DECLARE CURSOR command +START TRANSACTION; +DECLARE cursor1 CURSOR FOR select id,func_top(id) from testtab; +FETCH FORWARD 1 FROM cursor1; +FETCH FORWARD 1 FROM cursor1; +FETCH FORWARD 1 FROM cursor1; +FETCH FORWARD 1 FROM cursor1; +FETCH FORWARD 1 FROM cursor1; +FETCH FORWARD 1 FROM cursor1; +FETCH FORWARD 1 FROM cursor1; +FETCH FORWARD 1 FROM cursor1; +FETCH FORWARD 1 FROM cursor1; +FETCH FORWARD 1 FROM cursor1; +CLOSE cursor1; +END; + +START TRANSACTION; +DECLARE cursor1 CURSOR FOR select id,func_top(id) from testtab; +FETCH ALL FROM cursor1; +CLOSE cursor1; +END; + +-- smp/parallel 并行,不支持 + +-- opfusion不支持输出列带函数的语句 +create index testtab_id_idx on testtab(id); +set enable_seqscan=off; +set opfusion_debug_mode='log'; + +explain (costs off, analyze, timing off) select id,func_top(id) from testtab where id=1; + +reset opfusion_debug_mode; +reset enable_seqscan; +drop index testtab_id_idx; + +-- 2. result_cache函数嵌套调用result_cache函数 +create or replace function func_top(p int) returns text +as $$ +declare + cursor c1 for select func_int_output(id) as id from testtab where id=p; -- cursor中调用函数 + r1 record; + ret text = 'base_'; +begin + raise notice 'top param:%', p; + for r1 in c1 loop + ret = ret || func_text_return(r1.id); + end loop; + return ret; +end; +$$ language plpgsql stable result_cache; + +select id,func_top(id) from testtab; + +drop function if exists func_top; +drop function if exists func_int_output; +drop function if exists func_text_return; + +-- 3. 函数自嵌套 +CREATE OR REPLACE FUNCTION fibonacci (n bigint) + RETURN bigint + STABLE + RESULT_CACHE +IS +BEGIN + raise notice 'param:%', n; + IF (n =0) OR (n =1) THEN + RETURN 1; + ELSE + RETURN fibonacci(n - 1) + fibonacci(n - 2); + END IF; +END; +/ + +select fibonacci(10); + +truncate testtab; +insert into testtab values(10),(0),(1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11); + +select id,fibonacci(id) from testtab; + +drop function if exists fibonacci; + +-- 缓存失效: 模拟数据百分比 +-- 函数调用次数>2560时, 判断缓存命中率是否<15%, 如果小于则失效 +truncate testtab; +insert into testtab values (generate_series(1,10000) % 5); +explain (costs off, analyze, timing off) select func_int(id) from testtab; + +prepare test as select func_int(id) from testtab; +explain (costs off, analyze, timing off) execute test; +explain (costs off, analyze, timing off) execute test; +explain (costs off, analyze, timing off) execute test; +deallocate test; + +truncate testtab; +insert into testtab values (generate_series(1,10000) % 500); +explain (costs off, analyze, timing off) select func_int(id) from testtab; + +prepare test as select func_int(id) from testtab; +explain (costs off, analyze, timing off) execute test; +explain (costs off, analyze, timing off) execute test; +explain (costs off, analyze, timing off) execute test; +deallocate test; + +truncate testtab; +insert into testtab values (generate_series(1,10000) % 1000); +explain (costs off, analyze, timing off) select func_int(id) from testtab; + +prepare test as select func_int(id) from testtab; +explain (costs off, analyze, timing off) execute test; +explain (costs off, analyze, timing off) execute test; +explain (costs off, analyze, timing off) execute test; +deallocate test; + +drop function if exists func_no_param; +drop function if exists func_bool; +drop function if exists func_smallint; +drop function if exists func_int; +drop function if exists func_bigint; +drop function if exists func_float; +drop function if exists func_double_precision; +drop function if exists func_char; +drop function if exists func_varchar; +drop function if exists func_varchar2; +drop function if exists func_time; +drop function if exists func_timetz; +drop function if exists func_date; +drop function if exists func_timestamp; +drop function if exists func_timestamptz; +drop function if exists func_numeric; +drop function if exists func_number; +drop function if exists func_text; +drop function if exists func_bool_multi; + +-- 匿名块里调用函数? + +-- 数据过长时不支持缓存 (字符串长度>127, numeric/number?) +create table testtab_with_long_value(id int, col_numeric numeric, col_number number, col_text text); +insert into testtab_with_long_value values +(1,1,1,lpad('abc',130,'d')), +(2,1,1,lpad('abc',130,'d')), -- not use cache +(3,2,2,lpad('hahahaha',130,'d')), +(4,2,2,lpad('hahahaha',130,'d')), -- not use cache +(5,3,3,lpad('shortvalue',100,'d')), +(6,3,3,lpad('shortvalue',100,'d')); -- use cache + +create or replace function func_text_with_long_value(p text) returns text as $$ +begin + raise notice 'param:%', p; + return p||'_text'; +end; +$$ language plpgsql stable result_cache; + +explain (costs off, analyze, timing off) +select col_text, func_text_with_long_value(col_text) from testtab_with_long_value; + +drop function if exists func_text_with_long_value; +drop table if exists testtab_with_long_value; + +truncate testtab; +insert into testtab values(generate_series(1,10) % 2); + +-- 不支持内置过程语言: internal, c, SQL +create or replace function func_sql(p int) returns int +as $$ + select p+1; +$$ language sql stable result_cache; + +explain (costs off, analyze, timing off) select func_sql(id) from testtab; + +drop function if exists func_sql; + +-- 不支持函数含有OUT, INOUT参数 +create or replace function func_param_with_out(p int, p1 out int) returns int +as $$ +declare + p2 int; +begin + raise notice 'param:%', p; + p1 = p + 2; + p2 = p1 % (p+1) + 10; + return p2; +end; +$$ language plpgsql stable result_cache; + +explain (costs off, analyze, timing off) select func_param_with_out(id) from testtab; + +drop function if exists func_param_with_out; + +create or replace function func_param_with_inout(p int, p1 inout int) returns int +as $$ +declare + p2 int; +begin + raise notice 'param:%', p; + p1 = p + 2; + p2 = p1 % (p+1) + 10; + return p2; +end; +$$ language plpgsql stable result_cache; + +explain (costs off, analyze, timing off) select func_param_with_inout(id, id) from testtab; + +drop function if exists func_param_with_inout; + +-- 不支持函数没有返回值 +create or replace function func_param_without_retval(p int, p1 inout int) returns void +as $$ +declare + p2 int; +begin + raise notice 'param:%', p; + p1 = p + 2; + p2 = p1 % (p+1) + 10; +end; +$$ language plpgsql stable result_cache; + +explain (costs off, analyze, timing off) select func_param_without_retval(id, id) from testtab; + +drop function if exists func_param_without_retval; + +-- 不支持返回结果集的函数 +create or replace function func_setof(p int) +returns setof text +as $$ +declare + row_data record; + query_str text; +begin + raise notice 'param:%', p; + query_str := 'select id from testtab where id='||p; + for row_data in execute(query_str) loop + return next row_data.id; + end loop; + return; +end; +$$ language 'plpgsql' stable result_cache; + +explain (costs off, analyze, timing off) select func_setof(id) from testtab; + +drop function if exists func_setof; + +-- 不支持VOLATILE、聚集、Window函数 +create or replace function func_int_volatile(p int) returns int as $$ +begin + raise notice 'param:%', p; + return p*2; +end; +$$ language plpgsql result_cache; + +explain (costs off, analyze, timing off) select func_int_volatile(id) from testtab; + +drop function if exists func_int_volatile; + +-- 不支持自治事务, 子程序 +create table tab_tmp(id int,col int); +insert into tab_tmp values(0,1111),(1,1),(2,1),(3,2),(4,2); + +-- function with autonomous transaction +create or replace function func_autonomous(p int) +return int +stable +result_cache +is +sid2 number; +PRAGMA AUTONOMOUS_TRANSACTION; +begin + raise notice 'param:%', p; + if p = 0 then + update tab_tmp set col=0; + commit; + return 0; + end if; + + select id into sid2 from tab_tmp where id = 1 for update; + if sid2 = 1 then + -- no lock or self + update tab_tmp set col=11; + commit; + return 1; + end if; + + rollback; + return -1; + +exception + when others then + rollback; + return -2; +end; +/ + +select id,func_autonomous(id) from testtab; -- error + +create or replace procedure proc_autonomous(p int) +is +sid2 number; +PRAGMA AUTONOMOUS_TRANSACTION; +begin + if p = 0 then + update tab_tmp set col=0 where id=0; + commit; + return; + end if; + + select id into sid2 from tab_tmp where id = 1 for update; + if sid2 = 1 then + -- no lock or self + update tab_tmp set col=11; + commit; + return; + end if; + + return; +exception + when others then + rollback; +end; +/ + +create or replace function func_with_proc(p int) +return int +stable +result_cache +is +sid2 number; +begin + raise notice 'top param:%', p; + proc_autonomous(p); + return p+1; +end; +/ + +select id,func_with_proc(id) from testtab; + +drop function if exists func_autonomous; +drop function if exists func_with_proc; +drop procedure if exists proc_autonomous; + +CREATE OR REPLACE FUNCTION func_add_sql(integer, integer) RETURNS integer + AS 'select $1 + $2;' + LANGUAGE SQL + IMMUTABLE + RESULT_CACHE + RETURNS NULL ON NULL INPUT; + +SELECT proname,result_cache FROM PG_PROC WHERE proname = 'func_add_sql'; + +SELECT func_add_sql(1,1); + +drop function if exists func_add_sql; + +create table tmp( + bsm VARCHAR(100), + name VARCHAR(100), + num int +); +insert into tmp(bsm,name,num) VALUES('a','苹果',21); +insert into tmp(bsm,name,num) VALUES('b','香蕉',11); + +create or replace function getsum(in talename VARCHAR) +RETURNS int as $$ +DECLARE + stmt VARCHAR; + count int; +begin + stmt:=format('select count(1) from %s', talename); + raise notice '%',stmt; + EXECUTE stmt into count; + return count; + + EXCEPTION --捕获异常 + + WHEN OTHERS THEN + RETURN 0; +end; $$ LANGUAGE plpgsql result_cache immutable; + +select getsum('tmp'); + +drop function if exists getsum; +drop table if exists tmp; + +DECLARE + x INTEGER; + + FUNCTION f (n INTEGER) + RETURN INTEGER + IS + BEGIN + RETURN (n*n); + END; + +BEGIN + raise notice 'f returns %. Execution returns here (1).', f(2); + + x := f(2); + raise notice 'Execution returns here (2).'; +END; +/ + +-- package +create package pkg_test as + function func_bool(p bool) return bool stable result_cache; + procedure proc_test(p bool); +end pkg_test; +/ + +create or replace package body pkg_test as + function func_bool(p bool) return bool stable result_cache is + begin + raise notice 'param:%', p; + return p; + end func_bool; + + procedure proc_test(p bool) is + cursor cur1 for select func_bool(col_bool) from all_support_type where col_bool=p; + p1 bool; + begin + open cur1; + loop + fetch cur1 into p1; + exit when cur1%notfound; + end loop; + close cur1; + end proc_test; +end pkg_test; +/ + +call pkg_test.proc_test('true'); +call pkg_test.proc_test('false'); +explain (costs off, analyze, timing off) select pkg_test.func_bool(col_bool) from all_support_type; + +drop package pkg_test; +drop table if exists testtab,all_support_type,multi_col,tab_tmp; +set search_path to default; +drop schema func_result_cache; \ No newline at end of file -- Gitee