@@ -930,6 +930,8 @@ do_compile(FunctionCallInfo fcinfo,
930930
931931 if (cached_result )
932932 {
933+ int ci ;
934+
933935 elog (LOG , "do_compile: Using cached parse result (ndatums=%d), skipping ANTLR parsing" , cached_result -> ndatums );
934936 pltsql_parse_result = cached_result -> parse_tree ;
935937
@@ -939,12 +941,110 @@ do_compile(FunctionCallInfo fcinfo,
939941
940942 /* Mark that this function was loaded from persistent cache */
941943 function -> from_cache = true;
942-
944+
945+ /*
946+ * Re-derive found_varno and fetch_status_varno from cached
947+ * datums by scanning refnames.
948+ *
949+ * The cache was serialized from the validator-compiled function
950+ * which may have a different datum layout than what do_compile
951+ * built above (e.g., for ITVFs the validator serializes BEFORE
952+ * pg_proc is updated with TABLE-mode columns, so the cache has
953+ * fewer datums than the runtime parameter loop creates).
954+ * Scanning by refname makes us layout-independent.
955+ *
956+ * in_arg_varnos is NOT re-derived here — the parameter loop
957+ * above already set it correctly from pg_proc metadata, and
958+ * scanning '@'-prefixed refnames would also match local
959+ * variables, overflowing the pronargs-sized buffer.
960+ */
961+ function -> found_varno = -1 ;
962+ function -> fetch_status_varno = -1 ;
963+
964+ for (ci = 0 ; ci < cached_result -> ndatums ; ci ++ )
965+ {
966+ PLtsql_datum * d = cached_result -> datums [ci ];
967+
968+ if (d -> dtype == PLTSQL_DTYPE_VAR )
969+ {
970+ PLtsql_var * v = (PLtsql_var * ) d ;
971+
972+ if (v -> refname && strcmp (v -> refname , "found" ) == 0 )
973+ function -> found_varno = d -> dno ;
974+ else if (v -> refname && strcmp (v -> refname , "@@fetch_status" ) == 0 )
975+ function -> fetch_status_varno = d -> dno ;
976+ }
977+ }
978+
979+ /*
980+ * Do NOT re-derive in_arg_varnos from cached datums.
981+ * The parameter loop above already populated in_arg_varnos
982+ * correctly from pg_proc metadata. Re-scanning cached datums
983+ * for '@'-prefixed refnames would also match local variables
984+ * (e.g. @sql, @precision), overflowing the in_arg_varnos
985+ * buffer which is sized for pronargs only.
986+ */
987+
988+ Assert (function -> found_varno >= 0 );
989+ Assert (function -> fetch_status_varno >= 0 );
990+
991+ /*
992+ * Re-derive out_param_varno from cached datums.
993+ *
994+ * The validator already built the OUT row (PLtsql_row) and it was
995+ * serialized into the cache. The cached parse tree's RETURN nodes
996+ * reference it by dno. We just need to find it and set
997+ * function->out_param_varno so the runtime can use it.
998+ *
999+ * For multiple OUT args (or procedure with any OUT): find the
1000+ * PLtsql_row datum in the cache, then rebuild its rowtupdesc
1001+ * (which is not serialized — marked read_write_ignore).
1002+ * For single OUT arg (non-procedure function): point directly
1003+ * to the matching cached datum using the dno from the parameter
1004+ * loop (which assigned the same sequential dnos as the validator).
1005+ */
1006+ if (num_out_args > 1 ||
1007+ (num_out_args == 1 && function -> fn_prokind == PROKIND_PROCEDURE ))
1008+ {
1009+ for (ci = 0 ; ci < cached_result -> ndatums ; ci ++ )
1010+ {
1011+ if (cached_result -> datums [ci ]-> dtype == PLTSQL_DTYPE_ROW )
1012+ {
1013+ PLtsql_row * row = (PLtsql_row * ) cached_result -> datums [ci ];
1014+ int fi ;
1015+
1016+ function -> out_param_varno = row -> dno ;
1017+
1018+ /*
1019+ * Rebuild rowtupdesc — it is NULL after deserialization
1020+ * (TupleDesc is not serializable). Walk the ROW's varnos
1021+ * to find each member VAR's type info, same as
1022+ * build_row_from_vars does.
1023+ */
1024+ row -> rowtupdesc = CreateTemplateTupleDesc (row -> nfields );
1025+ for (fi = 0 ; fi < row -> nfields ; fi ++ )
1026+ {
1027+ PLtsql_var * fvar = (PLtsql_var * ) cached_result -> datums [row -> varnos [fi ]];
1028+
1029+ TupleDescInitEntry (row -> rowtupdesc , fi + 1 ,
1030+ row -> fieldnames [fi ],
1031+ fvar -> datatype -> typoid ,
1032+ fvar -> datatype -> atttypmod ,
1033+ 0 );
1034+ TupleDescInitEntryCollation (row -> rowtupdesc , fi + 1 ,
1035+ fvar -> datatype -> collation );
1036+ }
1037+ break ;
1038+ }
1039+ }
1040+ }
1041+ else if (num_out_args == 1 )
1042+ function -> out_param_varno = out_arg_variables [0 ]-> dno ;
1043+
9431044 /* Free the wrapper structure (but not the data we transferred) */
9441045 pfree (cached_result );
9451046
9461047 parse_rc = 0 ;
947- // [TODO}: uncomment to execute deserialized parse result
9481048 goto skip_antlr_parsing ;
9491049 }
9501050 }
@@ -976,8 +1076,9 @@ do_compile(FunctionCallInfo fcinfo,
9761076 /*
9771077 * Multi-Statement Table-Valued Function: 1) Add a declare table statement
9781078 * to the beginning 2) Add a return table statement to the end
1079+ * Skip when restoring from cache — the cached parse tree already contains these.
9791080 */
980- if (function -> is_mstvf )
1081+ if (function -> is_mstvf && ! function -> from_cache )
9811082 {
9821083 /*
9831084 * ANTLR parser would return a stmt list like INIT->BLOCK, where BLOCK
@@ -994,9 +1095,11 @@ do_compile(FunctionCallInfo fcinfo,
9941095 * control to fall off the end without an explicit RETURN statement. The
9951096 * easiest way to implement this is to add a RETURN statement to the end
9961097 * of the statement list during parsing.
1098+ * Skip when restoring from cache — the cached parse tree already has the dummy return.
9971099 */
998- if (num_out_args > 0 || function -> fn_rettype == VOIDOID ||
999- function -> fn_retset )
1100+ if (!function -> from_cache &&
1101+ (num_out_args > 0 || function -> fn_rettype == VOIDOID ||
1102+ function -> fn_retset ))
10001103 add_dummy_return (function );
10011104
10021105 /*
@@ -1031,7 +1134,7 @@ do_compile(FunctionCallInfo fcinfo,
10311134 * handles cache writes for DDL. Calling both in the same transaction causes
10321135 * "tuple already updated by self" errors.
10331136 */
1034- if (!forValidator && pltsql_enable_procedure_parse_cache && !function -> from_cache )
1137+ if (!forValidator && pltsql_enable_routine_parse_cache && !function -> from_cache )
10351138 pltsql_update_func_cache_entry (procTup , function );
10361139
10371140 /* Debug dump for completed functions */
@@ -3220,7 +3323,7 @@ pltsql_HashTableLookup(PLtsql_func_hashkey *func_key)
32203323 if (hentry )
32213324 {
32223325 /* If GUC is disabled and function was loaded from cache, return NULL to force re-parse */
3223- if (!pltsql_enable_procedure_parse_cache && hentry -> function -> from_cache )
3326+ if (!pltsql_enable_routine_parse_cache && hentry -> function -> from_cache )
32243327 {
32253328 elog (DEBUG1 , "pltsql_HashTableLookup: GUC disabled, invalidating cached function %u" , (unsigned int ) func_key -> funcOid );
32263329 return NULL ;
0 commit comments