support function/procedure in WITH clause#1320
Conversation
📝 WalkthroughWalkthroughThis PR adds Oracle PL/SQL WITH FUNCTION/WITH PROCEDURE support, enabling inline function and procedure definitions in SQL statements. Changes span the parser (Oracle grammar and analysis), executor (function initialization), optimizer (plan propagation), and PL/iSQL runtime (function compilation and invocation), with comprehensive test coverage. ChangesOracle WITH FUNCTION/WITH PROCEDURE Support
Sequence DiagramsequenceDiagram
participant Parser as Parser (ora_gram.y)
participant Analyze as Parse Analyze (parse_cte.c)
participant WithFunc as With Func (parse_with_plsql.c)
participant ParseFunc as Parse Func (parse_func.c)
participant Planner as Optimizer (planner.c)
participant Exec as Executor (execExpr.c)
participant PlISql as PL/iSQL (pl_handler.c)
Parser->>Parser: Parse WITH FUNCTION/PROCEDURE
Parser->>Analyze: WithClause.plsql_defs
Analyze->>WithFunc: transformWithFuncDefs()
WithFunc->>WithFunc: Resolve arg types, register
WithFunc->>ParseFunc: Install lookup hook
ParseFunc->>ParseFunc: Function call resolution
ParseFunc->>ParseFunc: Set FUNC_FROM_WITH_CLAUSE
Analyze->>Planner: Query.withFuncDefs
Planner->>Planner: Copy to PlannedStmt
Exec->>Exec: ExecInitFunc detects WITH
Exec->>PlISql: Dispatch via EState
PlISql->>PlISql: Compile/execute inline
PlISql-->>Exec: Return result
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Tip 💬 Introducing Slack Agent: The best way for teams to turn conversations into code.Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.
Built for teams:
One agent for your entire SDLC. Right inside Slack. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 11
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
src/backend/executor/execSRF.c (1)
739-776:⚠️ Potential issue | 🟠 Major | ⚡ Quick win
init_sexprroutes WITH clause functions tofmgr_subproc_info_cxtafter the macro changeLines 739–747 classify any non-pg_proc function as
is_subproc_func:if (!FUNC_EXPR_FROM_PG_PROC(funcexpr->function_from)) is_subproc_func = true; // ← true for FUNC_FROM_WITH_CLAUSE after macro changeLines 773–776 then call
fmgr_subproc_info_cxtfor all such functions:if (is_subproc_func) fmgr_subproc_info_cxt(foid, &(sexpr->func), sexprCxt); // ← wrong for WITH clauseIf a WITH clause function is used as a set-returning function in the SELECT list (
ExecInitFunctionResultSet → init_sexpr) or in a FROM clause (ExecInitTableFunctionResult → init_sexpr), this path will be reached.fmgr_subproc_info_cxtexpects a PLiSQL subproc structure (with a validparent_func/nsubprocfuncs) that WITH clause functions don't provide, which will crash or corrupt state.Apply the same pattern already used in
execExpr.c(context snippet 1):🛡️ Proposed fix for
init_sexpr- bool is_subproc_func = false; + bool is_subproc_func = false; + bool is_with_func = false; if (node != NULL && nodeTag(node) == T_FuncExpr) { FuncExpr *funcexpr = (FuncExpr *) node; - if (!FUNC_EXPR_FROM_PG_PROC(funcexpr->function_from)) + if (funcexpr->function_from == FUNC_FROM_WITH_CLAUSE) + is_with_func = true; + else if (!FUNC_EXPR_FROM_PG_PROC(funcexpr->function_from)) is_subproc_func = true; } - if (!is_subproc_func) + if (!is_subproc_func && !is_with_func) { /* Verify EXECUTE privilege before invoking the function. */And in the
fmgrlookup block, add appropriate handling foris_with_func(either a dedicated lookup or a fallback tofmgr_info_cxt, depending on how WITH clause function entries are registered).🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/backend/executor/execSRF.c` around lines 739 - 776, init_sexpr currently treats any FuncExpr that is not from pg_proc as a "subproc" (using FUNC_EXPR_FROM_PG_PROC), which after the macro change will mark WITH-clause functions as subprocs and cause fmgr_subproc_info_cxt to be called incorrectly; update init_sexpr to detect FUNC_FROM_WITH_CLAUSE (e.g. add an is_with_func boolean alongside is_subproc_func) and follow the same pattern used in execExpr.c: if is_with_func call the regular fmgr lookup (fmgr_info_cxt) or a dedicated WITH-clause fmgr lookup instead of fmgr_subproc_info_cxt, and only call fmgr_subproc_info_cxt when the FuncExpr truly represents a PL/pgSQL subproc (is_subproc_func).src/oracle_fe_utils/ora_psqlscan.l (2)
1138-1142:⚠️ Potential issue | 🟠 Major | ⚡ Quick win
END label;in a WITH FUNCTION body permanently suppresses semicolon terminationLine 1139 resets
ora_plsql_expect_end_symboltoEND_SYMBOL_INVALIDfor any identifier token. WhenEND foo_func;(labeled end) is used inside a WITH FUNCTION body:
ENDsetsEND_SYMBOL_SEMICOLONand decrementsbegin_depthto 0.foo_func(identifier) hits line 1139 and resets the state toEND_SYMBOL_INVALID.- The
;handler never enters theEND_SYMBOL_SEMICOLONbranch, soskip_this_semistays false andcancel_semicolon_terminatoris never cleared.- The subsequent
SELECT …;is suppressed bycancel_semicolon_terminator=true, and the statement never terminates via;. The scanner waits for more input indefinitely.For
CREATE FUNCTION/CREATE PROCEDUREthis is benign (the/slash terminates the statement), but forWITH FUNCTION— which relies on the;path — this is a correctness gap that breaks any function body usingEND func_name;, which is valid Oracle PL/SQL syntax.A minimal fix is to detect the labeled-end pattern (identifier immediately after
ENDwhenEND_SYMBOL_SEMICOLONis already set) and avoid resetting the symbol:🐛 Proposed fix
/* Not final End state .This state needs to be cleared */ - if (cur_state->ora_plsql_expect_end_symbol) + if (cur_state->ora_plsql_expect_end_symbol && + /* Preserve SEMICOLON state for "END label;" — the label + * must not clear the symbol set by the preceding END. */ + !(cur_state->ora_plsql_expect_end_symbol == END_SYMBOL_SEMICOLON && + (cur_state->identifiers[0] == 'w' || + cur_state->identifiers[0] == 'e'))) cur_state->ora_plsql_expect_end_symbol = END_SYMBOL_INVALID;🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/oracle_fe_utils/ora_psqlscan.l` around lines 1138 - 1142, The code unconditionally clears cur_state->ora_plsql_expect_end_symbol to END_SYMBOL_INVALID on any identifier which breaks labeled END (e.g., "END foo;") for WITH FUNCTION; change the logic so that when cur_state->ora_plsql_expect_end_symbol == END_SYMBOL_SEMICOLON and the identifier immediately follows an END token (i.e., the labeled-end pattern), you do NOT reset ora_plsql_expect_end_symbol (leave END_SYMBOL_SEMICOLON so the subsequent ';' clears cancel_semicolon_terminator and terminates the statement); otherwise continue to clear to END_SYMBOL_INVALID as before—use the existing parser state (previous token marker / begin_depth / token-type checks) to detect the "identifier immediately after END" case and keep END_SYMBOL_SEMICOLON intact.
798-866:⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift
skip_this_semifires on innerEND;inside nestedBEGIN…ENDblocks, permanently breaking scanner stateThe
skip_this_semi=truebranch (line 839) is entered wheneverora_plsql_expect_end_symbol == END_SYMBOL_SEMICOLONandidentifiers[0]is'w'/'e', with no check againstbegin_depth. Because everyENDkeyword (including those closing innerBEGIN…ENDblocks) unconditionally setsEND_SYMBOL_SEMICOLON(line 1191) and decrementsbegin_depth, the first innerEND;inside a WITH FUNCTION body wrongly triggers the state-reset path.Consider:
WITH FUNCTION foo RETURN NUMBER IS BEGIN -- begin_depth: 0→1 <<nested>> BEGIN -- begin_depth: 1→2 RETURN 1; END; -- ← fires skip_this_semi prematurely! RETURN 2; END; -- scanner never sees this as function-end SELECT foo FROM DUAL;After the inner
END;:
begin_depthdecrements 2 → 1.skip_this_semi=truefires:cancel_semicolon_terminator→false,identifiers[1..3]→0,identifier_count→1.- With
identifiers[1]=0, the big condition in the identifier rule is now false, so the outerENDnever decrementsbegin_depth(it stays at 1) and never setsEND_SYMBOL_SEMICOLON.- The final
;seesbegin_depth=1 ≠ 0→ the statement never terminates; the scanner blocks waiting for more input.The analogous
CREATE FUNCTIONpath is immune because it terminates via/, not;.The fix requires a three-way split: distinguish the outermost
END;(depth==0 →skip_this_semi), innerEND;in a WITH FUNCTION body (depth>0 → clear symbol only), and all other contexts (→END_SYMBOL_SLASH):🐛 Proposed fix
- if (cur_state->identifiers[0] == 'w' || - cur_state->identifiers[0] == 'e') - { + if ((cur_state->identifiers[0] == 'w' || + cur_state->identifiers[0] == 'e') && + cur_state->begin_depth == 0) + { /* * WITH FUNCTION/PROCEDURE (or EXPLAIN WITH FUNCTION/PROCEDURE): - * this END; closes one function/procedure body. The statement + * this END; closes the outermost function/procedure body. The + * statement continues — either with another FUNCTION/PROCEDURE + * definition or with the actual SELECT/INSERT/etc. */ cur_state->cancel_semicolon_terminator = false; cur_state->ora_plsql_expect_end_symbol = END_SYMBOL_INVALID; if (cur_state->identifiers[0] == 'e') { cur_state->identifiers[2] = 0; cur_state->identifiers[3] = 0; cur_state->identifier_count = 2; } else { cur_state->identifiers[1] = 0; cur_state->identifiers[2] = 0; cur_state->identifiers[3] = 0; cur_state->identifier_count = 1; } skip_this_semi = true; } + else if (cur_state->identifiers[0] == 'w' || + cur_state->identifiers[0] == 'e') + { + /* + * Nested END; within a WITH FUNCTION/PROCEDURE body. + * Cancel the symbol; keep cancel_semicolon_terminator and + * begin_depth intact so the outer END; is handled correctly. + */ + cur_state->ora_plsql_expect_end_symbol = END_SYMBOL_INVALID; + } else cur_state->ora_plsql_expect_end_symbol = END_SYMBOL_SLASH;🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/oracle_fe_utils/ora_psqlscan.l` around lines 798 - 866, The current ';' handler resets identifiers and sets skip_this_semi whenever ora_plsql_expect_end_symbol == END_SYMBOL_SEMICOLON and identifiers[0] == 'w'/'e', which incorrectly fires for inner END; inside nested BEGIN…END; blocks; change the branch to three-way based on cur_state->begin_depth: if begin_depth == 0 keep the existing skip_this_semi behavior (reset cancel_semicolon_terminator, clear ora_plsql_expect_end_symbol and reset identifiers/identifier_count), if begin_depth > 0 DO NOT reset identifiers or identifier_count—only clear ora_plsql_expect_end_symbol and cancel_semicolon_terminator so inner END; doesn't dismantle the WITH/FUNCTION tracking, and otherwise (the existing else) set ora_plsql_expect_end_symbol = END_SYMBOL_SLASH; update the code around the ';' case that references cur_state->ora_plsql_expect_end_symbol, cur_state->identifiers, cur_state->identifier_count, cur_state->cancel_semicolon_terminator, and cur_state->begin_depth accordingly.
🧹 Nitpick comments (9)
src/include/nodes/primnodes.h (1)
818-818: 💤 Low valueStale comment on
function_fromfield — WITH clause not mentionedThe inline comment still reads
/* proc func, subproc func, package func */. It should be updated to include WITH clause inline functions.📝 Proposed fix
- char function_from; /* proc func, subproc func, package func */ + char function_from; /* proc func, subproc func, package func, WITH clause inline func */🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/include/nodes/primnodes.h` at line 818, Update the stale inline comment for the struct field function_from in primnodes.h to mention WITH clause inline functions as well as proc/subproc/package funcs; locate the field named function_from in the struct (char function_from) and change its comment text to something like "/* proc func, subproc func, package func, WITH-clause inline func */" so the comment accurately documents all possible sources.src/backend/parser/parse_with_plsql.c (2)
287-310: 💤 Low valueDefault-expression append loop relies on trailing-defaults invariant.
The
Assert(defexpr != NULL)at line 306 will trip ifanalyzeddefaultsever contains aNULLslot beyond positionnargs(i.e., non-trailing defaults). Grammar likely enforces trailing defaults today, but a defensive check that produces a real error would be more robust than an assert in a production build (where the assert is compiled out and aNULLwould be inserted into*fargs).🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/backend/parser/parse_with_plsql.c` around lines 287 - 310, The loop that appends default expressions uses Assert(defexpr != NULL) which can be compiled out, so replace the assert with a runtime check that raises an error when a NULL default is encountered beyond nargs: inside the foreach over entry->analyzeddefaults, if defexpr == NULL and skip > nargs then call the PostgreSQL error reporting (ereport/errmsg) to raise a clear error (e.g., invalid function header/defaults) rather than allowing a NULL to be appended to *fargs; keep the rest of the logic intact so provided args are still skipped and non-NULL defaults are copied.
92-114: ⚡ Quick winPass
pstatetoparser_errpositionso duplicate-definition errors point at the source location.
parser_errposition(NULL, …)returns 0 (no position) regardless ofnewentry->def->location, so the error message loses caret positioning. The caller already has aParseState; threading it throughcheckDuplicateWithFunckeeps the diagnostic informative.♻️ Proposed change
static void -checkDuplicateWithFunc(List *func_list, WithFuncEntry * newentry) +checkDuplicateWithFunc(ParseState *pstate, List *func_list, WithFuncEntry * newentry) { @@ ereport(ERROR, (errcode(ERRCODE_DUPLICATE_FUNCTION), errmsg("WITH clause function \"%s\" is defined more than once with the same argument types", newentry->funcname), - parser_errposition(NULL, newentry->def->location))); + parser_errposition(pstate, newentry->def->location)));And update the call site at line 189 accordingly.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/backend/parser/parse_with_plsql.c` around lines 92 - 114, The function checkDuplicateWithFunc currently calls parser_errposition(NULL, newentry->def->location) which yields no caret; modify checkDuplicateWithFunc to accept a ParseState *pstate parameter, and pass pstate into parser_errposition(pstate, newentry->def->location) when reporting the duplicate-function error; update the caller that builds func_list (the site that invokes checkDuplicateWithFunc) to forward its ParseState pointer to the new function signature so diagnostics point at the correct source location; ensure function prototype and any declarations use the new ParseState *pstate parameter.src/backend/parser/parse_func.c (1)
319-406: ⚖️ Poor tradeoffThree near-duplicate WITH-hook call sites — consider consolidating.
The hook is invoked with the same large argument block in three separate spots: as the registered
p_subprocfunc_hook(line 321), in the parent-chain retry loop (line 373), and in the recursive-PL/iSQL fallback (line 397). The argument list is identical aside frompstate/ancestor. Extracting a small static helper (try_with_func_lookup(pstate, …)) would reduce duplication, make future signature changes safer, and remove the symbol-equality coupling onwithFuncLookupHook. No behavioral change required.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/backend/parser/parse_func.c` around lines 319 - 406, There are three near-duplicate calls to withFuncLookupHook (via p_subprocfunc_hook, a parent-chain loop, and a PL/iSQL fallback) that pass the same large argument list; create a small static helper like try_with_func_lookup(ParseState *state, List *funcname, List **fargs, List *argnames, int nargs, Oid *actual_arg_types, bool func_variadic, bool proc_call, Oid *funcid, Oid *rettype, bool *retset, int *nvargs, Oid *vatype, List **declared_arg_types, List **argdefaults, void **pfunc) which encapsulates calling withFuncLookupHook(state, ...) and returns the FUNCDETAIL_* result (and when it finds a match sets function_from to FUNC_FROM_WITH_CLAUSE). Replace the three direct invocations (the p_subprocfunc_hook branch that compares to withFuncLookupHook, the ancestor loop call, and the p_with_func_list fallback) with calls to this helper using the appropriate ParseState (pstate or ancestor); remove the symbol-equality check against withFuncLookupHook and use the helper’s return value to decide setting function_from, preserving all semantics and argument order.src/backend/commands/explain.c (2)
770-881: ⚡ Quick winHeavy duplication between TEXT and non-TEXT branches.
The two branches contain near-identical logic for building the
name(mode argname type, ...) RETURN rettypesignature (lines 788‑817 vs 842‑873), including the parameter mode switch and rettype handling. Extracting a small helper such asformat_inline_function_signature(StringInfo, InlineFunctionDef *)would remove ~40 lines of copy/paste and prevent the two branches from drifting (e.g., one already drifted on body output — see the related comment).♻️ Sketch of helper extraction
static void format_inline_function_signature(StringInfo buf, InlineFunctionDef *ifd) { ListCell *alc; bool first_arg = true; appendStringInfo(buf, "%s(", ifd->funcname); foreach(alc, ifd->args) { FunctionParameter *fp = (FunctionParameter *) lfirst(alc); if (!first_arg) appendStringInfoString(buf, ", "); first_arg = false; switch (fp->mode) { case FUNC_PARAM_OUT: appendStringInfoString(buf, "OUT "); break; case FUNC_PARAM_INOUT: appendStringInfoString(buf, "IN OUT "); break; default: break; } if (fp->name) appendStringInfo(buf, "%s ", fp->name); appendStringInfoString(buf, TypeNameToString(fp->argType)); } appendStringInfoChar(buf, ')'); if (!ifd->is_proc && ifd->rettype != NULL) appendStringInfo(buf, " RETURN %s", TypeNameToString(ifd->rettype)); }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/backend/commands/explain.c` around lines 770 - 881, The code duplicates signature-building logic in the TEXT and non-TEXT branches when formatting InlineFunctionDef signatures; extract that repeated logic into a helper (e.g., format_inline_function_signature(StringInfo buf, InlineFunctionDef *ifd)) and replace the blocks in both branches with calls to that helper; ensure the helper handles argument modes, names, types and rettype exactly as in the original (use InlineFunctionDef, FunctionParameter, fp->mode, fp->name, fp->argType, ifd->is_proc, ifd->rettype and TypeNameToString), keep existing surrounding behavior (in the non-TEXT branch call ExplainPropertyText("Signature", buf->data, es); in the TEXT branch write buf->data into es->str after ExplainIndentText) and free any temporary buffer as before.
776-776: 💤 Low valueUse
lfirst_nodefor consistent node extraction.
InlineFunctionDefis a node-tagged struct (withNodeTagtype field). Extract it usinglfirst_node(InlineFunctionDef, lc)at lines 776 and 835 to match the established convention used elsewhere in this file for similar node types (e.g.,lfirst_node(Query, ...)at line 229,lfirst_node(RangeTblEntry, ...)at line 913). This addsIsAassertions in assert-enabled builds for better runtime safety.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/backend/commands/explain.c` at line 776, Replace the raw cast usage of lfirst(lc) to extract InlineFunctionDef nodes with the node-safe helper: use lfirst_node(InlineFunctionDef, lc) wherever InlineFunctionDef is being extracted (the two occurrences around the InlineFunctionDef handling, previously using "InlineFunctionDef *ifd = (InlineFunctionDef *) lfirst(lc)"). This follows the established pattern (e.g., lfirst_node(Query, ...) usage) and ensures an IsA check in assert builds for runtime safety.src/backend/oracle_parser/ora_gram.y (1)
997-999: ⚡ Quick winAdd regression tests for the DML-vs-CTE disambiguation path.
This precedence-based resolution is subtle; please lock it down with parser tests for cases like
WITH FUNCTION ... INSERT/UPDATE/DELETE/MERGE ...andWITH FUNCTION ... <cte_name> AS (...) ....Also applies to: 14393-14404
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/backend/oracle_parser/ora_gram.y` around lines 997 - 999, Add regression tests to lock down the precedence-based DML vs CTE disambiguation implemented around the tokens DELETE_P, INSERT, UPDATE, MERGE and the PLSQL_DEFS_END handling in ora_gram.y: create parser tests that cover "WITH FUNCTION ... INSERT/UPDATE/DELETE/MERGE ..." (ensuring those are parsed as DML following a WITH FUNCTION) and also tests for "WITH FUNCTION ... <cte_name> AS (...) ..." (ensuring the name is parsed as a CTE), include one test per DML token plus positive and negative CTE cases, run them against the parser so the ambiguity is exercised and fails if precedence regresses.src/include/nodes/parsenodes.h (1)
1662-1663: ⚡ Quick winUpdate the
WithClausenote to reflectwithFuncDefspropagation.The note says
WithClausedoes not propagate intoQuery, but inline defs now do viaQuery.withFuncDefs. Please update the comment to avoid future confusion.Proposed comment update
- * Note: WithClause does not propagate into the Query representation; - * but CommonTableExpr does. + * Note: WithClause itself does not propagate into Query representation. + * CommonTableExprs propagate via Query.cteList, and ORA_PARSER inline + * function/procedure definitions propagate via Query.withFuncDefs.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/include/nodes/parsenodes.h` around lines 1662 - 1663, The comment should be updated to state that while WithClause historically did not propagate into Query, function definitions contained in WithClause now do propagate via Query.withFuncDefs; change the note referencing WithClause and CommonTableExpr in parsenodes.h so it mentions that WithClause itself still isn't copied into Query but its inline function definitions are carried over through the Query.withFuncDefs field (referencing the WithClause struct and the Query.withFuncDefs member and keeping CommonTableExpr behavior noted).src/oracle_test/regress/sql/with_function.sql (1)
8-13: ⚡ Quick winT02 does not actually validate WITH PROCEDURE execution.
The
SELECT 'procedure ok'result is independent ofdo_nothing, so this only proves parsing, not runtime behavior. Please make the assertion depend on the procedure path.Proposed test adjustment
-- T02: WITH PROCEDURE (void — verified via side-effect SELECT) WITH PROCEDURE do_nothing IS BEGIN NULL; END; -SELECT 'procedure ok' AS result FROM dual; +WITH + PROCEDURE do_nothing IS + BEGIN + NULL; + END; + FUNCTION call_proc RETURN VARCHAR2 IS + BEGIN + do_nothing; + RETURN 'procedure ok'; + END; +SELECT call_proc() AS result FROM dual;As per coding guidelines
**/sql/*.sql: Test SQL files. Ensure comprehensive coverage of features.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/oracle_test/regress/sql/with_function.sql` around lines 8 - 13, The test currently only parses WITH PROCEDURE do_nothing but the SELECT 'procedure ok' result is independent; update the test so the SELECT outcome depends on a side-effect of the WITH PROCEDURE named do_nothing (e.g., have do_nothing perform a visible side-effect such as inserting/updating a value or setting a local variable reachable by the SELECT, then assert the SELECT reads that value), so execution of do_nothing is required to produce the expected result.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/backend/commands/explain.c`:
- Around line 815-880: The TEXT branch that iterates pstmt->withFuncDefs should
mirror the non-TEXT verbose behavior: before the foreach loop emit a header line
like "WITH Functions:" (use appendStringInfo(es->str, "WITH Functions:\n"); and
ExplainIndentText/es->str for indentation), and inside the loop after printing
the signature (where sig is appended and printed) add a check if (es->verbose &&
ifd->src) to emit the body (e.g. call ExplainIndentText(es);
appendStringInfo(es->str, "Body: %s\n", ifd->src);) so the inline function body
(ifd->src) is shown in TEXT VERBOSE; use the existing symbols ifd->src,
es->verbose, ExplainIndentText and appendStringInfo(es->str, ...) so the output
matches the non-TEXT branch and provides context for each function.
In `@src/backend/parser/parse_clause.c`:
- Around line 4066-4070: Replace the silent "continue" for
FUNC_FROM_WITH_CLAUSE: instead of skipping validation, explicitly handle
WITH-clause routines by either (A) rejecting them if they contain OUT or INOUT
parameters or (B) resolving their argument modes and letting the existing
validation path run; implement (A) by checking the function's arg-mode array
(e.g. proargmodes / argmodes) when func->function_from == FUNC_FROM_WITH_CLAUSE
and raising an error if any mode is OUT or INOUT, or implement (B) by removing
the continue and falling through so the existing argument-mode
resolution/validation code executes for FUNC_FROM_WITH_CLAUSE as it does for
catalog PL/pgsql functions and subprocs. Ensure you reference
FUNC_FROM_WITH_CLAUSE and func->function_from in your change and preserve
existing validation logic for arg modes.
In `@src/backend/parser/parse_cte.c`:
- Around line 126-134: The error hint currently references internal symbols
compatible_db and ORA_PARSER; update the errmsg/errhint in the WITH
FUNCTION/PROCEDURE check (the block that tests withClause->plsql_defs and calls
transformWithFuncDefs(pstate, withClause->plsql_defs)) so the user-facing hint
instructs how to set the GUC: e.g., tell users to set ivorysql.compatible_mode
to "oracle" (or use the equivalent ALTER SYSTEM/SET command) instead of
mentioning compatible_db or ORA_PARSER.
In `@src/backend/parser/parse_func.c`:
- Around line 906-923: The code that syncs actual_arg_types from fargs can write
past the fixed-size actual_arg_types[FUNC_MAX_ARGS] if withFuncLookupHook
appended defaults; add a defensive bound check when computing new_nargs (use
FUNC_MAX_ARGS) and reject or truncate with the same error path used elsewhere
(e.g., increment nargs handling that checks FUNC_MAX_ARGS and reports an error
via ereport/elog). Specifically, in the block that sets new_nargs and loops over
fargs (the code that updates actual_arg_types and variables
nargs/nargsplusdefs), ensure new_nargs <= FUNC_MAX_ARGS before writing into
actual_arg_types (and raise the same error when exceeded), mirroring the guard
used in the surrounding default-handling block that references FUNC_MAX_ARGS.
In `@src/backend/parser/parse_with_plsql.c`:
- Line 145: The bug is that funcindex is initialized to 0 in
transformWithFuncDefs so the first inline WITH function gets FuncExpr.funcid ==
0 (InvalidOid); change the initialization in transformWithFuncDefs so funcindex
starts at 1 (or alternatively add and use a dedicated field for the
inline-function index instead of casting a zero-based index to Oid) so that
entry->funcindex and subsequently FuncExpr.funcid are never 0; update any code
that assigns or casts entry->funcindex to Oid (e.g. where *funcid is set) to
rely on the new 1-based scheme.
In `@src/include/nodes/primnodes.h`:
- Around line 780-783: The change to FUNC_EXPR_FROM_PG_PROC causes
!FUNC_EXPR_FROM_PG_PROC to be true for FUNC_FROM_WITH_CLAUSE, so update
init_sexpr to first check for FUNC_FROM_WITH_CLAUSE before treating a function
as a subprocedure: in init_sexpr, replace the unguarded "if
(!FUNC_EXPR_FROM_PG_PROC(funcexpr->function_from)) is_subproc_func = true" with
a two-way check that sets is_with_func when funcexpr->function_from ==
FUNC_FROM_WITH_CLAUSE else sets is_subproc_func when
!FUNC_EXPR_FROM_PG_PROC(...); ensure callers that invoke
fmgr_subproc_info_cxt(foid, &(sexpr->func), sexprCxt) only run when
is_subproc_func is true, and audit other unguarded uses of
!FUNC_EXPR_FROM_PG_PROC (e.g., dependency.c) to apply the same guard pattern.
In `@src/oracle_test/regress/sql/with_function.sql`:
- Around line 131-133: The test currently calls _wf_scope_test without ever
defining it, producing a false-positive; update the test to first declare the
WITH FUNCTION _wf_scope_test(...) and use it inside the same statement (a
positive in-statement call to _wf_scope_test) and then, in a separate statement
afterwards, call _wf_scope_test(1) from outside to assert the out-of-scope
failure; ensure the in-statement call and the external call are separate
statements so the second triggers the expected scope error.
In `@src/pl/plisql/src/pl_comp.c`:
- Around line 1627-1634: Only assign pstate->p_with_func_list when the current
ParseState belongs to the same WITH-function execution context: check that
plisql_active_with_func_entries is non-NIL AND that the current pstate's
execution context matches the saved plisql_active_with_func_estate (e.g. compare
pstate->p_estate or equivalent field against plisql_active_with_func_estate)
before setting p_with_func_list; this ensures only parses running in the owning
WITH-call can see those WITH-function entries (references: p_with_func_list,
plisql_active_with_func_entries, plisql_active_with_func_estate, pstate).
In `@src/pl/plisql/src/pl_handler.c`:
- Around line 317-322: The WITH-function path only copies the parameter type OID
into ParamExternData (paramLI->params[i].ptype) but not the parsed typmod, so
declarations like varchar2(10) lose their modifier; update the code that fills
ParamListInfo in pl_handler.c to also copy the parsed typmod into each
ParamExternData (set paramLI->params[i].ptypmod = fp->argtypmod or the
corresponding parsed typmod field on the formal parameter), since
plisql_compile_inline() expects both param->ptype and param->ptypmod when
rebuilding parameter variables.
- Around line 473-489: The error-context callback (errcallback) is pushed onto
error_context_stack before calling buildParamListForFunc, typenameTypeId, and
plisql_compile_inline, but if any of those functions throws the final restore
(error_context_stack = errcallback.previous) is skipped; wrap the compilation
block that sets paramLI, rettype, calls plisql_compile_inline and
fixupCompiledReturnType in a PG_TRY / PG_FINALLY pair and in the PG_FINALLY
ensure error_context_stack is restored (pop errcallback) so the
with_func_compile_error_callback / errctx frame is always removed even on
errors.
---
Outside diff comments:
In `@src/backend/executor/execSRF.c`:
- Around line 739-776: init_sexpr currently treats any FuncExpr that is not from
pg_proc as a "subproc" (using FUNC_EXPR_FROM_PG_PROC), which after the macro
change will mark WITH-clause functions as subprocs and cause
fmgr_subproc_info_cxt to be called incorrectly; update init_sexpr to detect
FUNC_FROM_WITH_CLAUSE (e.g. add an is_with_func boolean alongside
is_subproc_func) and follow the same pattern used in execExpr.c: if is_with_func
call the regular fmgr lookup (fmgr_info_cxt) or a dedicated WITH-clause fmgr
lookup instead of fmgr_subproc_info_cxt, and only call fmgr_subproc_info_cxt
when the FuncExpr truly represents a PL/pgSQL subproc (is_subproc_func).
In `@src/oracle_fe_utils/ora_psqlscan.l`:
- Around line 1138-1142: The code unconditionally clears
cur_state->ora_plsql_expect_end_symbol to END_SYMBOL_INVALID on any identifier
which breaks labeled END (e.g., "END foo;") for WITH FUNCTION; change the logic
so that when cur_state->ora_plsql_expect_end_symbol == END_SYMBOL_SEMICOLON and
the identifier immediately follows an END token (i.e., the labeled-end pattern),
you do NOT reset ora_plsql_expect_end_symbol (leave END_SYMBOL_SEMICOLON so the
subsequent ';' clears cancel_semicolon_terminator and terminates the statement);
otherwise continue to clear to END_SYMBOL_INVALID as before—use the existing
parser state (previous token marker / begin_depth / token-type checks) to detect
the "identifier immediately after END" case and keep END_SYMBOL_SEMICOLON
intact.
- Around line 798-866: The current ';' handler resets identifiers and sets
skip_this_semi whenever ora_plsql_expect_end_symbol == END_SYMBOL_SEMICOLON and
identifiers[0] == 'w'/'e', which incorrectly fires for inner END; inside nested
BEGIN…END; blocks; change the branch to three-way based on
cur_state->begin_depth: if begin_depth == 0 keep the existing skip_this_semi
behavior (reset cancel_semicolon_terminator, clear ora_plsql_expect_end_symbol
and reset identifiers/identifier_count), if begin_depth > 0 DO NOT reset
identifiers or identifier_count—only clear ora_plsql_expect_end_symbol and
cancel_semicolon_terminator so inner END; doesn't dismantle the WITH/FUNCTION
tracking, and otherwise (the existing else) set ora_plsql_expect_end_symbol =
END_SYMBOL_SLASH; update the code around the ';' case that references
cur_state->ora_plsql_expect_end_symbol, cur_state->identifiers,
cur_state->identifier_count, cur_state->cancel_semicolon_terminator, and
cur_state->begin_depth accordingly.
---
Nitpick comments:
In `@src/backend/commands/explain.c`:
- Around line 770-881: The code duplicates signature-building logic in the TEXT
and non-TEXT branches when formatting InlineFunctionDef signatures; extract that
repeated logic into a helper (e.g., format_inline_function_signature(StringInfo
buf, InlineFunctionDef *ifd)) and replace the blocks in both branches with calls
to that helper; ensure the helper handles argument modes, names, types and
rettype exactly as in the original (use InlineFunctionDef, FunctionParameter,
fp->mode, fp->name, fp->argType, ifd->is_proc, ifd->rettype and
TypeNameToString), keep existing surrounding behavior (in the non-TEXT branch
call ExplainPropertyText("Signature", buf->data, es); in the TEXT branch write
buf->data into es->str after ExplainIndentText) and free any temporary buffer as
before.
- Line 776: Replace the raw cast usage of lfirst(lc) to extract
InlineFunctionDef nodes with the node-safe helper: use
lfirst_node(InlineFunctionDef, lc) wherever InlineFunctionDef is being extracted
(the two occurrences around the InlineFunctionDef handling, previously using
"InlineFunctionDef *ifd = (InlineFunctionDef *) lfirst(lc)"). This follows the
established pattern (e.g., lfirst_node(Query, ...) usage) and ensures an IsA
check in assert builds for runtime safety.
In `@src/backend/oracle_parser/ora_gram.y`:
- Around line 997-999: Add regression tests to lock down the precedence-based
DML vs CTE disambiguation implemented around the tokens DELETE_P, INSERT,
UPDATE, MERGE and the PLSQL_DEFS_END handling in ora_gram.y: create parser tests
that cover "WITH FUNCTION ... INSERT/UPDATE/DELETE/MERGE ..." (ensuring those
are parsed as DML following a WITH FUNCTION) and also tests for "WITH FUNCTION
... <cte_name> AS (...) ..." (ensuring the name is parsed as a CTE), include one
test per DML token plus positive and negative CTE cases, run them against the
parser so the ambiguity is exercised and fails if precedence regresses.
In `@src/backend/parser/parse_func.c`:
- Around line 319-406: There are three near-duplicate calls to
withFuncLookupHook (via p_subprocfunc_hook, a parent-chain loop, and a PL/iSQL
fallback) that pass the same large argument list; create a small static helper
like try_with_func_lookup(ParseState *state, List *funcname, List **fargs, List
*argnames, int nargs, Oid *actual_arg_types, bool func_variadic, bool proc_call,
Oid *funcid, Oid *rettype, bool *retset, int *nvargs, Oid *vatype, List
**declared_arg_types, List **argdefaults, void **pfunc) which encapsulates
calling withFuncLookupHook(state, ...) and returns the FUNCDETAIL_* result (and
when it finds a match sets function_from to FUNC_FROM_WITH_CLAUSE). Replace the
three direct invocations (the p_subprocfunc_hook branch that compares to
withFuncLookupHook, the ancestor loop call, and the p_with_func_list fallback)
with calls to this helper using the appropriate ParseState (pstate or ancestor);
remove the symbol-equality check against withFuncLookupHook and use the helper’s
return value to decide setting function_from, preserving all semantics and
argument order.
In `@src/backend/parser/parse_with_plsql.c`:
- Around line 287-310: The loop that appends default expressions uses
Assert(defexpr != NULL) which can be compiled out, so replace the assert with a
runtime check that raises an error when a NULL default is encountered beyond
nargs: inside the foreach over entry->analyzeddefaults, if defexpr == NULL and
skip > nargs then call the PostgreSQL error reporting (ereport/errmsg) to raise
a clear error (e.g., invalid function header/defaults) rather than allowing a
NULL to be appended to *fargs; keep the rest of the logic intact so provided
args are still skipped and non-NULL defaults are copied.
- Around line 92-114: The function checkDuplicateWithFunc currently calls
parser_errposition(NULL, newentry->def->location) which yields no caret; modify
checkDuplicateWithFunc to accept a ParseState *pstate parameter, and pass pstate
into parser_errposition(pstate, newentry->def->location) when reporting the
duplicate-function error; update the caller that builds func_list (the site that
invokes checkDuplicateWithFunc) to forward its ParseState pointer to the new
function signature so diagnostics point at the correct source location; ensure
function prototype and any declarations use the new ParseState *pstate
parameter.
In `@src/include/nodes/parsenodes.h`:
- Around line 1662-1663: The comment should be updated to state that while
WithClause historically did not propagate into Query, function definitions
contained in WithClause now do propagate via Query.withFuncDefs; change the note
referencing WithClause and CommonTableExpr in parsenodes.h so it mentions that
WithClause itself still isn't copied into Query but its inline function
definitions are carried over through the Query.withFuncDefs field (referencing
the WithClause struct and the Query.withFuncDefs member and keeping
CommonTableExpr behavior noted).
In `@src/include/nodes/primnodes.h`:
- Line 818: Update the stale inline comment for the struct field function_from
in primnodes.h to mention WITH clause inline functions as well as
proc/subproc/package funcs; locate the field named function_from in the struct
(char function_from) and change its comment text to something like "/* proc
func, subproc func, package func, WITH-clause inline func */" so the comment
accurately documents all possible sources.
In `@src/oracle_test/regress/sql/with_function.sql`:
- Around line 8-13: The test currently only parses WITH PROCEDURE do_nothing but
the SELECT 'procedure ok' result is independent; update the test so the SELECT
outcome depends on a side-effect of the WITH PROCEDURE named do_nothing (e.g.,
have do_nothing perform a visible side-effect such as inserting/updating a value
or setting a local variable reachable by the SELECT, then assert the SELECT
reads that value), so execution of do_nothing is required to produce the
expected result.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 68afd7be-f244-4751-a057-fa2c4e2943b6
📒 Files selected for processing (27)
contrib/ivorysql_ora/src/merge/ora_merge.csrc/backend/commands/explain.csrc/backend/executor/execExpr.csrc/backend/executor/execSRF.csrc/backend/executor/execTuples.csrc/backend/optimizer/plan/planner.csrc/backend/oracle_parser/ora_gram.ysrc/backend/parser/Makefilesrc/backend/parser/analyze.csrc/backend/parser/meson.buildsrc/backend/parser/parse_clause.csrc/backend/parser/parse_cte.csrc/backend/parser/parse_func.csrc/backend/parser/parse_with_plsql.csrc/include/nodes/execnodes.hsrc/include/nodes/parsenodes.hsrc/include/nodes/plannodes.hsrc/include/nodes/primnodes.hsrc/include/oracle_parser/ora_with_function.hsrc/include/parser/parse_node.hsrc/oracle_fe_utils/ora_psqlscan.lsrc/oracle_test/regress/expected/with_function.outsrc/oracle_test/regress/parallel_schedulesrc/oracle_test/regress/sql/with_function.sqlsrc/pl/plisql/src/pl_comp.csrc/pl/plisql/src/pl_handler.csrc/pl/plisql/src/plisql.h
| if (!ifd->is_proc && ifd->rettype != NULL) | ||
| appendStringInfo(&sig, " RETURN %s", | ||
| TypeNameToString(ifd->rettype)); | ||
|
|
||
| ExplainPropertyText("Signature", sig.data, es); | ||
|
|
||
| if (es->verbose && ifd->src) | ||
| ExplainPropertyText("Body", ifd->src, es); | ||
|
|
||
| pfree(sig.data); | ||
|
|
||
| ExplainCloseGroup("WITH Function", NULL, true, es); | ||
| } | ||
|
|
||
| ExplainCloseGroup("WITH Functions", "WITH Functions", false, es); | ||
| } | ||
| else | ||
| { | ||
| foreach(lc, pstmt->withFuncDefs) | ||
| { | ||
| InlineFunctionDef *ifd = (InlineFunctionDef *) lfirst(lc); | ||
| StringInfoData sig; | ||
| ListCell *alc; | ||
| bool first_arg = true; | ||
|
|
||
| initStringInfo(&sig); | ||
|
|
||
| appendStringInfo(&sig, "WITH %s: %s(", | ||
| ifd->is_proc ? "Procedure" : "Function", | ||
| ifd->funcname); | ||
|
|
||
| foreach(alc, ifd->args) | ||
| { | ||
| FunctionParameter *fp = (FunctionParameter *) lfirst(alc); | ||
|
|
||
| if (!first_arg) | ||
| appendStringInfoString(&sig, ", "); | ||
| first_arg = false; | ||
|
|
||
| switch (fp->mode) | ||
| { | ||
| case FUNC_PARAM_OUT: | ||
| appendStringInfoString(&sig, "OUT "); | ||
| break; | ||
| case FUNC_PARAM_INOUT: | ||
| appendStringInfoString(&sig, "IN OUT "); | ||
| break; | ||
| default: | ||
| break; | ||
| } | ||
|
|
||
| if (fp->name) | ||
| appendStringInfo(&sig, "%s ", fp->name); | ||
| appendStringInfoString(&sig, TypeNameToString(fp->argType)); | ||
| } | ||
| appendStringInfoChar(&sig, ')'); | ||
| if (!ifd->is_proc && ifd->rettype != NULL) | ||
| appendStringInfo(&sig, " RETURN %s", | ||
| TypeNameToString(ifd->rettype)); | ||
|
|
||
| ExplainIndentText(es); | ||
| appendStringInfo(es->str, "%s\n", sig.data); | ||
|
|
||
| pfree(sig.data); | ||
| } | ||
| } |
There was a problem hiding this comment.
Body output inconsistent between TEXT and non-TEXT formats.
In the non-TEXT branch the function body is emitted under EXPLAIN VERBOSE (line 821‑822), but the TEXT branch (lines 832‑880) never emits ifd->src, even with VERBOSE. Users running EXPLAIN (VERBOSE) in the default text format will silently lose the inline subprogram body, which is the most useful piece of information for these inline definitions. Also, unlike ExplainPrintJIT / ExplainPrintSettings, the TEXT branch does not emit a section header (e.g. WITH Functions:) to delimit the block, so the per-function lines appear without context after the plan tree.
Consider mirroring the verbose-body behavior (and an optional header) in the TEXT branch.
📝 Suggested addition for the TEXT branch
else
{
+ if (pstmt->withFuncDefs != NIL)
+ {
+ ExplainIndentText(es);
+ appendStringInfoString(es->str, "WITH Functions:\n");
+ es->indent++;
+ }
foreach(lc, pstmt->withFuncDefs)
{
@@
ExplainIndentText(es);
appendStringInfo(es->str, "%s\n", sig.data);
+ if (es->verbose && ifd->src)
+ {
+ ExplainIndentText(es);
+ appendStringInfo(es->str, "Body: %s\n", ifd->src);
+ }
+
pfree(sig.data);
}
+ if (pstmt->withFuncDefs != NIL)
+ es->indent--;
}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/backend/commands/explain.c` around lines 815 - 880, The TEXT branch that
iterates pstmt->withFuncDefs should mirror the non-TEXT verbose behavior: before
the foreach loop emit a header line like "WITH Functions:" (use
appendStringInfo(es->str, "WITH Functions:\n"); and ExplainIndentText/es->str
for indentation), and inside the loop after printing the signature (where sig is
appended and printed) add a check if (es->verbose && ifd->src) to emit the body
(e.g. call ExplainIndentText(es); appendStringInfo(es->str, "Body: %s\n",
ifd->src);) so the inline function body (ifd->src) is shown in TEXT VERBOSE; use
the existing symbols ifd->src, es->verbose, ExplainIndentText and
appendStringInfo(es->str, ...) so the output matches the non-TEXT branch and
provides context for each function.
| FUNCTION ora_func_name opt_ora_func_args_with_defaults | ||
| RETURN func_return | ||
| ora_func_is_or_as Sconst ';' | ||
| { | ||
| InlineFunctionDef *n = makeNode(InlineFunctionDef); | ||
| /* ora_func_name returns a list; take the last element */ | ||
| n->funcname = strVal(llast($2)); | ||
| n->args = $3; | ||
| n->rettype = $5; | ||
| n->is_proc = false; | ||
| n->src = $7; | ||
| n->location = @2; | ||
| $$ = (Node *) n; |
There was a problem hiding this comment.
Reject qualified names in inline declarations instead of silently truncating them.
Line 14429 and Line 14441 take strVal(llast($2)), which drops leading qualifiers from ora_func_name. That can parse a qualified name but bind/store the wrong identifier.
💡 Proposed fix
FUNCTION ora_func_name opt_ora_func_args_with_defaults
RETURN func_return
ora_func_is_or_as Sconst ';'
{
InlineFunctionDef *n = makeNode(InlineFunctionDef);
- /* ora_func_name returns a list; take the last element */
- n->funcname = strVal(llast($2));
+ if (list_length($2) != 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("qualified name is not allowed in WITH FUNCTION declaration"),
+ parser_errposition(`@2`)));
+ n->funcname = strVal(linitial($2));
n->args = $3;
n->rettype = $5;
n->is_proc = false;
n->src = $7;
n->location = `@2`;
@@
| PROCEDURE ora_func_name opt_procedure_args_with_defaults
ora_func_is_or_as Sconst ';'
{
InlineFunctionDef *n = makeNode(InlineFunctionDef);
- n->funcname = strVal(llast($2));
+ if (list_length($2) != 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("qualified name is not allowed in WITH PROCEDURE declaration"),
+ parser_errposition(`@2`)));
+ n->funcname = strVal(linitial($2));
n->args = $3;
n->rettype = NULL;
n->is_proc = true;
n->src = $5;
n->location = `@2`;Also applies to: 14437-14447
| else if (func->function_from == FUNC_FROM_WITH_CLAUSE) | ||
| { | ||
| /* WITH-clause functions have no OUT params */ | ||
| continue; | ||
| } |
There was a problem hiding this comment.
Don't silently skip OUT/IN OUT validation for WITH-clause routines.
This continue makes FUNC_FROM_WITH_CLAUSE calls bypass the same argument-mode checks applied to catalog PL/iSQL functions and subprocs. If WITH-clause routines are supposed to forbid OUT/IN OUT params here, reject them explicitly; otherwise resolve their arg modes and run the existing validation path instead of skipping it.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/backend/parser/parse_clause.c` around lines 4066 - 4070, Replace the
silent "continue" for FUNC_FROM_WITH_CLAUSE: instead of skipping validation,
explicitly handle WITH-clause routines by either (A) rejecting them if they
contain OUT or INOUT parameters or (B) resolving their argument modes and
letting the existing validation path run; implement (A) by checking the
function's arg-mode array (e.g. proargmodes / argmodes) when func->function_from
== FUNC_FROM_WITH_CLAUSE and raising an error if any mode is OUT or INOUT, or
implement (B) by removing the continue and falling through so the existing
argument-mode resolution/validation code executes for FUNC_FROM_WITH_CLAUSE as
it does for catalog PL/pgsql functions and subprocs. Ensure you reference
FUNC_FROM_WITH_CLAUSE and func->function_from in your change and preserve
existing validation logic for arg modes.
| if (withClause->plsql_defs != NIL) | ||
| { | ||
| if (compatible_db != ORA_PARSER) | ||
| ereport(ERROR, | ||
| (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), | ||
| errmsg("WITH FUNCTION/PROCEDURE is only supported in Oracle compatibility mode"), | ||
| errhint("Set compatible_db = ORA_PARSER to enable this feature."))); | ||
| transformWithFuncDefs(pstate, withClause->plsql_defs); | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Confirm the user-facing GUC name and accepted value(s).
rg -nP -C2 'DefineCustom\w+Variable\([^)]*"ivorysql\.compatible_mode"' --type=c
rg -nP -C2 'compatible_mode' -g '*.sgml' -g '*.md'Repository: IvorySQL/IvorySQL
Length of output: 43
🏁 Script executed:
# Search for compatible_db variable definition and usage
rg -n 'compatible_db' --type=c | head -20Repository: IvorySQL/IvorySQL
Length of output: 2416
🏁 Script executed:
# Search for any GUC variable related to compatibility or oracle
rg -n 'compatible' --type=c | grep -i 'define\|variable\|guc' | head -20Repository: IvorySQL/IvorySQL
Length of output: 1899
🏁 Script executed:
# Look for configuration-related files that might define GUC
fd -e 'conf.d' -o -e 'postgresql.conf' -o 'guc.c' -o 'variables.c' | head -20Repository: IvorySQL/IvorySQL
Length of output: 178
🏁 Script executed:
# Search for how this setting is documented in code or comments
rg -n 'compatible.*mode\|oracle.*compat' --type=c --type=h | head -30Repository: IvorySQL/IvorySQL
Length of output: 43
🏁 Script executed:
# Look at the full GUC definition for ivorysql.compatible_mode
sed -n '401,430p' src/backend/utils/misc/ivy_guc.cRepository: IvorySQL/IvorySQL
Length of output: 932
🏁 Script executed:
# Find the enum definition for compatible_db values
rg -n 'enum.*compatible|ORA_PARSER|PG_PARSER' --type=c --type=h | grep -E '(enum|ORA_PARSER|PG_PARSER)' | head -20Repository: IvorySQL/IvorySQL
Length of output: 1678
🏁 Script executed:
# Look at the check_compatible_mode function
sed -n '457,480p' src/backend/utils/misc/ivy_guc.cRepository: IvorySQL/IvorySQL
Length of output: 919
🏁 Script executed:
# Find the db_parser_options definition
rg -n 'db_parser_options' --type=c -A 5Repository: IvorySQL/IvorySQL
Length of output: 813
Update errhint to reference the user-facing GUC instead of internal C symbols.
The current hint "Set compatible_db = ORA_PARSER to enable this feature." references compatible_db (a C variable) and ORA_PARSER (an enum constant), neither of which users can set directly. Users interact with the GUC ivorysql.compatible_mode, which accepts the value "oracle".
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("WITH FUNCTION/PROCEDURE is only supported in Oracle compatibility mode"),
- errhint("Set compatible_db = ORA_PARSER to enable this feature.")));
+ errhint("Set ivorysql.compatible_mode = oracle to enable this feature.")));🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/backend/parser/parse_cte.c` around lines 126 - 134, The error hint
currently references internal symbols compatible_db and ORA_PARSER; update the
errmsg/errhint in the WITH FUNCTION/PROCEDURE check (the block that tests
withClause->plsql_defs and calls transformWithFuncDefs(pstate,
withClause->plsql_defs)) so the user-facing hint instructs how to set the GUC:
e.g., tell users to set ivorysql.compatible_mode to "oracle" (or use the
equivalent ALTER SYSTEM/SET command) instead of mentioning compatible_db or
ORA_PARSER.
| { | ||
| int new_nargs = list_length(fargs); | ||
|
|
||
| if (new_nargs > nargs) | ||
| { | ||
| ListCell *lc; | ||
| int j = 0; | ||
|
|
||
| foreach(lc, fargs) | ||
| { | ||
| if (j >= nargs) | ||
| actual_arg_types[j] = exprType((Node *) lfirst(lc)); | ||
| j++; | ||
| } | ||
| nargs = new_nargs; | ||
| nargsplusdefs = nargs; | ||
| } | ||
| } |
There was a problem hiding this comment.
Add a FUNC_MAX_ARGS bound check when syncing argument types.
actual_arg_types[] is a fixed-size FUNC_MAX_ARGS array (line 120). The earlier check at line 159 only validates the original fargs length; after withFuncLookupHook appends pre-analyzed default expressions to *fargs (parse_with_plsql.c line 308), new_nargs could in theory exceed FUNC_MAX_ARGS, leading to an out-of-bounds write at line 917. Mirroring the guard used in the surrounding default-handling block would close the gap.
🛡️ Proposed defensive bound check
{
int new_nargs = list_length(fargs);
if (new_nargs > nargs)
{
ListCell *lc;
int j = 0;
+ if (new_nargs > FUNC_MAX_ARGS)
+ ereport(ERROR,
+ (errcode(ERRCODE_TOO_MANY_ARGUMENTS),
+ errmsg_plural("cannot pass more than %d argument to a function",
+ "cannot pass more than %d arguments to a function",
+ FUNC_MAX_ARGS,
+ FUNC_MAX_ARGS),
+ parser_errposition(pstate, location)));
foreach(lc, fargs)
{
if (j >= nargs)
actual_arg_types[j] = exprType((Node *) lfirst(lc));
j++;
}
nargs = new_nargs;
nargsplusdefs = nargs;
}
}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/backend/parser/parse_func.c` around lines 906 - 923, The code that syncs
actual_arg_types from fargs can write past the fixed-size
actual_arg_types[FUNC_MAX_ARGS] if withFuncLookupHook appended defaults; add a
defensive bound check when computing new_nargs (use FUNC_MAX_ARGS) and reject or
truncate with the same error path used elsewhere (e.g., increment nargs handling
that checks FUNC_MAX_ARGS and reports an error via ereport/elog). Specifically,
in the block that sets new_nargs and loops over fargs (the code that updates
actual_arg_types and variables nargs/nargsplusdefs), ensure new_nargs <=
FUNC_MAX_ARGS before writing into actual_arg_types (and raise the same error
when exceeded), mirroring the guard used in the surrounding default-handling
block that references FUNC_MAX_ARGS.
| #define FUNC_EXPR_FROM_PG_PROC(function_from) \ | ||
| (function_from != FUNC_FROM_SUBPROCFUNC && function_from != FUNC_FROM_PACKAGE \ | ||
| && function_from != FUNC_FROM_PACKGE_INITBODY) | ||
| && function_from != FUNC_FROM_PACKGE_INITBODY \ | ||
| && function_from != FUNC_FROM_WITH_CLAUSE) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Find all files using FUNC_EXPR_FROM_PG_PROC so they can be audited
# for missing FUNC_FROM_WITH_CLAUSE guards.
rg -n "FUNC_EXPR_FROM_PG_PROC" --type=c -A2 -B2Repository: IvorySQL/IvorySQL
Length of output: 16197
🏁 Script executed:
# Get context around the problematic line in execSRF.c (lines 740-780)
sed -n '740,780p' src/backend/executor/execSRF.c | cat -nRepository: IvorySQL/IvorySQL
Length of output: 1715
🏁 Script executed:
# Check the full init_sexpr function to understand scope
grep -n "init_sexpr" src/backend/executor/execSRF.c | head -5Repository: IvorySQL/IvorySQL
Length of output: 408
🏁 Script executed:
# Verify the fmgr_subproc_info_cxt function signature and where it's called
rg -n "fmgr_subproc_info_cxt" --type=c | head -20Repository: IvorySQL/IvorySQL
Length of output: 664
🏁 Script executed:
# Find all negated uses of FUNC_EXPR_FROM_PG_PROC (the risky pattern)
rg -n "!FUNC_EXPR_FROM_PG_PROC" --type=c -A3 -B1Repository: IvorySQL/IvorySQL
Length of output: 5695
🏁 Script executed:
# Get the fmgr_subproc_info_cxt implementation
sed -n '141,200p' src/backend/utils/fmgr/fmgr.c | cat -nRepository: IvorySQL/IvorySQL
Length of output: 2323
🏁 Script executed:
# Check if WITH clause functions could have parent_func == NULL
rg -n "FUNC_FROM_WITH_CLAUSE" --type=c -B2 -A2 | head -40Repository: IvorySQL/IvorySQL
Length of output: 2553
🏁 Script executed:
# Check what happens in the actual call path - examine subproc_should_change_return_type
rg -n "subproc_should_change_return_type" --type=c | head -10Repository: IvorySQL/IvorySQL
Length of output: 987
🏁 Script executed:
# Get context on execSRF.c around the problem area (lines 730-780)
sed -n '730,780p' src/backend/executor/execSRF.c | cat -nRepository: IvorySQL/IvorySQL
Length of output: 2067
🏁 Script executed:
# Check the actual implementation of plisql_subproc_should_change_return_type
sed -n '3594,3650p' src/pl/plisql/src/pl_package.c | cat -nRepository: IvorySQL/IvorySQL
Length of output: 2088
🏁 Script executed:
# Verify the macro definition one more time to be absolutely clear
cat src/include/nodes/primnodes.h | grep -A5 "define FUNC_EXPR_FROM_PG_PROC"Repository: IvorySQL/IvorySQL
Length of output: 332
🏁 Script executed:
# Check if fmgr_subproc_info_cxt is the only concern or if there are callers that validate parent_func
rg -n "parent_func" --type=c src/pl/plisql/src/pl_package.c | head -20Repository: IvorySQL/IvorySQL
Length of output: 559
🏁 Script executed:
# Check funcapi.c context around line 455
sed -n '450,465p' src/backend/utils/fmgr/funcapi.c | cat -nRepository: IvorySQL/IvorySQL
Length of output: 700
🏁 Script executed:
# Check functioncmds.c at lines 3013 and 3065
sed -n '3010,3020p' src/backend/commands/functioncmds.c | cat -nRepository: IvorySQL/IvorySQL
Length of output: 344
🏁 Script executed:
# Check what calls init_sexpr to understand the WITH clause function flow
rg -n "init_sexpr" src/backend/executor/execSRF.c -B5 -A5Repository: IvorySQL/IvorySQL
Length of output: 1919
🏁 Script executed:
# Verify the macro one more time: when WITH clause, FUNC_EXPR_FROM_PG_PROC should be false
python3 << 'EOF'
# Test the macro logic
FUNC_FROM_SUBPROCFUNC = 's'
FUNC_FROM_PACKAGE = 'p'
FUNC_FROM_PACKGE_INITBODY = 'b'
FUNC_FROM_WITH_CLAUSE = 'w'
FUNC_FROM_PG_PROC = 'n' # normal
def FUNC_EXPR_FROM_PG_PROC(function_from):
return (function_from != FUNC_FROM_SUBPROCFUNC and
function_from != FUNC_FROM_PACKAGE and
function_from != FUNC_FROM_PACKGE_INITBODY and
function_from != FUNC_FROM_WITH_CLAUSE)
# Test cases
test_cases = [
(FUNC_FROM_WITH_CLAUSE, "WITH clause"),
(FUNC_FROM_SUBPROCFUNC, "SUBPROCFUNC"),
(FUNC_FROM_PACKAGE, "PACKAGE"),
(FUNC_FROM_PG_PROC, "PG_PROC"),
]
for func_from, name in test_cases:
result = FUNC_EXPR_FROM_PG_PROC(func_from)
negated = not result
print(f"{name:20} -> FUNC_EXPR_FROM_PG_PROC={result}, !FUNC_EXPR_FROM_PG_PROC={negated}")
EOFRepository: IvorySQL/IvorySQL
Length of output: 393
🏁 Script executed:
# Check dependency.c more carefully - verify if it uses positive form (safe)
sed -n '2230,2240p' src/backend/catalog/dependency.c | cat -nRepository: IvorySQL/IvorySQL
Length of output: 404
🏁 Script executed:
# Check if WITH clause functions set parent_func properly
rg -n "parent_func" --type=c src/backend/parser/ | head -20Repository: IvorySQL/IvorySQL
Length of output: 131
🏁 Script executed:
# Verify the crash scenario: check if subproc_should_change_return_type is actually called in the init_sexpr path
sed -n '730,800p' src/backend/executor/execSRF.c | cat -nRepository: IvorySQL/IvorySQL
Length of output: 3000
🏁 Script executed:
# Check if parent_func is set for WITH clause functions specifically
rg -n "parent_func" --type=c src/backend/parser/parse_func.c -B3 -A3Repository: IvorySQL/IvorySQL
Length of output: 368
🏁 Script executed:
# Verify: does fmgr_subproc_info_cxt itself fail or just set up incorrectly?
# And check if WITH clause functions would have correct parent_func
sed -n '330,390p' src/backend/parser/parse_func.c | cat -nRepository: IvorySQL/IvorySQL
Length of output: 2704
🏁 Script executed:
# Look for where WITH clause functions would be created/handled during parse
rg -n "plisql_get_with_func\|FUNC_FROM_WITH_CLAUSE" --type=c src/pl/plisql/src/pl_handler.c -B2 -A2Repository: IvorySQL/IvorySQL
Length of output: 43
🏁 Script executed:
# Find where is_subproc_func is actually used or if there are downstream effects
rg -n "is_subproc_func" src/backend/executor/execSRF.cRepository: IvorySQL/IvorySQL
Length of output: 181
🏁 Script executed:
# Check if the WITH clause lookup actually sets parent_func to NULL
sed -n '500,580p' src/pl/plisql/src/pl_handler.c | cat -nRepository: IvorySQL/IvorySQL
Length of output: 2704
🏁 Script executed:
# Verify the exact behavior: execSRF.c lines 140-180 to see complete context
sed -n '140,180p' src/backend/executor/execSRF.c | cat -nRepository: IvorySQL/IvorySQL
Length of output: 1620
!FUNC_EXPR_FROM_PG_PROC now returns true for FUNC_FROM_WITH_CLAUSE; unguarded call sites risk treating WITH functions as subproc functions
After this macro update, code that gates subproc/package-specific logic with !FUNC_EXPR_FROM_PG_PROC without a prior check for FUNC_FROM_WITH_CLAUSE will incorrectly route WITH clause functions through subproc code paths.
Key risk in init_sexpr (execSRF.c, lines 745–746):
// Unguarded — treats WITH clause functions as subproc:
if (!FUNC_EXPR_FROM_PG_PROC(funcexpr->function_from))
is_subproc_func = true;
// Later (line 774), calls subproc-only handler:
if (is_subproc_func)
fmgr_subproc_info_cxt(foid, &(sexpr->func), sexprCxt);fmgr_subproc_info_cxt is designed for PLiSQL subprocedures and expects different function metadata than WITH clause functions provide. This path is triggered whenever a WITH clause function is used as a set-returning function or in a FROM clause context.
Correct pattern already in place (execExpr.c, lines 2784–2787):
if (funcexpr->function_from == FUNC_FROM_WITH_CLAUSE)
is_with_func = true;
else if (!FUNC_EXPR_FROM_PG_PROC(funcexpr->function_from))
is_subproc_func = true;Apply this same guard to init_sexpr. Also audit dependency.c (line 2233) and other unguarded !FUNC_EXPR_FROM_PG_PROC call sites for similar issues.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/include/nodes/primnodes.h` around lines 780 - 783, The change to
FUNC_EXPR_FROM_PG_PROC causes !FUNC_EXPR_FROM_PG_PROC to be true for
FUNC_FROM_WITH_CLAUSE, so update init_sexpr to first check for
FUNC_FROM_WITH_CLAUSE before treating a function as a subprocedure: in
init_sexpr, replace the unguarded "if
(!FUNC_EXPR_FROM_PG_PROC(funcexpr->function_from)) is_subproc_func = true" with
a two-way check that sets is_with_func when funcexpr->function_from ==
FUNC_FROM_WITH_CLAUSE else sets is_subproc_func when
!FUNC_EXPR_FROM_PG_PROC(...); ensure callers that invoke
fmgr_subproc_info_cxt(foid, &(sexpr->func), sexprCxt) only run when
is_subproc_func is true, and audit other unguarded uses of
!FUNC_EXPR_FROM_PG_PROC (e.g., dependency.c) to apply the same guard pattern.
| -- T17: WITH FUNCTION scope — function is not visible outside its statement | ||
| SELECT _wf_scope_test(1) FROM dual; | ||
|
|
There was a problem hiding this comment.
T17 is currently a false-positive scope test.
This case never defines _wf_scope_test beforehand, so the error is expected even if scope handling is broken. Add a positive in-statement use first, then the out-of-scope failure check.
Proposed test fix
-- T17: WITH FUNCTION scope — function is not visible outside its statement
+WITH FUNCTION _wf_scope_test(n NUMBER) RETURN NUMBER IS
+BEGIN RETURN n + 1; END;
+SELECT _wf_scope_test(1) FROM dual;
SELECT _wf_scope_test(1) FROM dual;As per coding guidelines **/sql/*.sql: Test SQL files. Ensure comprehensive coverage of features.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| -- T17: WITH FUNCTION scope — function is not visible outside its statement | |
| SELECT _wf_scope_test(1) FROM dual; | |
| -- T17: WITH FUNCTION scope — function is not visible outside its statement | |
| WITH FUNCTION _wf_scope_test(n NUMBER) RETURN NUMBER IS | |
| BEGIN RETURN n + 1; END; | |
| SELECT _wf_scope_test(1) FROM dual; | |
| SELECT _wf_scope_test(1) FROM dual; | |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/oracle_test/regress/sql/with_function.sql` around lines 131 - 133, The
test currently calls _wf_scope_test without ever defining it, producing a
false-positive; update the test to first declare the WITH FUNCTION
_wf_scope_test(...) and use it inside the same statement (a positive
in-statement call to _wf_scope_test) and then, in a separate statement
afterwards, call _wf_scope_test(1) from outside to assert the out-of-scope
failure; ensure the in-statement call and the external call are separate
statements so the second triggers the expected scope error.
| /* | ||
| * If we are currently executing a WITH-clause function, expose the WITH | ||
| * function entries on this ParseState so that parse_func.c can resolve | ||
| * recursive calls (and calls to sibling WITH functions) even though the | ||
| * hook slot is occupied by plisql_subprocfunc_ref. | ||
| */ | ||
| if (plisql_active_with_func_entries != NIL) | ||
| pstate->p_with_func_list = plisql_active_with_func_entries; |
There was a problem hiding this comment.
Gate p_with_func_list by the owning WITH-call context.
This publishes the outer query's WITH-function entries to any PL/iSQL parse done while a WITH function is on the stack. If a WITH function calls a normal stored PL/iSQL function, SQL parsed in that callee can also see the caller's WITH entries and mis-resolve an ordinary function name against the outer WITH clause. The handler now saves plisql_active_with_func_estate, but this path never consults it. Please restrict the exposure to the matching WITH-function execution context instead of checking only plisql_active_with_func_entries.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/pl/plisql/src/pl_comp.c` around lines 1627 - 1634, Only assign
pstate->p_with_func_list when the current ParseState belongs to the same
WITH-function execution context: check that plisql_active_with_func_entries is
non-NIL AND that the current pstate's execution context matches the saved
plisql_active_with_func_estate (e.g. compare pstate->p_estate or equivalent
field against plisql_active_with_func_estate) before setting p_with_func_list;
this ensures only parses running in the owning WITH-call can see those
WITH-function entries (references: p_with_func_list,
plisql_active_with_func_entries, plisql_active_with_func_estate, pstate).
| paramLI->paramnames[i] = fp->name; | ||
| paramLI->params[i].value = (Datum) 0; | ||
| paramLI->params[i].isnull = true; | ||
| paramLI->params[i].pflags = 0; | ||
| paramLI->params[i].ptype = typenameTypeId(NULL, fp->argType); | ||
| paramLI->params[i].pmode = (char) fp->mode; |
There was a problem hiding this comment.
Preserve declared typmods in WITH-function parameters.
Only the type OID is copied into ParamListInfo here. plisql_compile_inline() later builds each parameter variable from param->ptype and param->ptypmod, so declarations like varchar2(10) or numeric(8,2) lose their modifier when compiled through the WITH-function path. Please carry the parsed typmod into each ParamExternData.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/pl/plisql/src/pl_handler.c` around lines 317 - 322, The WITH-function
path only copies the parameter type OID into ParamExternData
(paramLI->params[i].ptype) but not the parsed typmod, so declarations like
varchar2(10) lose their modifier; update the code that fills ParamListInfo in
pl_handler.c to also copy the parsed typmod into each ParamExternData (set
paramLI->params[i].ptypmod = fp->argtypmod or the corresponding parsed typmod
field on the formal parameter), since plisql_compile_inline() expects both
param->ptype and param->ptypmod when rebuilding parameter variables.
| /* Set up error context so compilation errors name the function */ | ||
| errctx.funcname = ifd->funcname; | ||
| errctx.is_proc = ifd->is_proc; | ||
| errcallback.callback = with_func_compile_error_callback; | ||
| errcallback.arg = &errctx; | ||
| errcallback.previous = error_context_stack; | ||
| error_context_stack = &errcallback; | ||
|
|
||
| paramLI = buildParamListForFunc(ifd->args); | ||
| rettype = (ifd->rettype != NULL) | ||
| ? typenameTypeId(NULL, ifd->rettype) | ||
| : InvalidOid; | ||
| compiled = plisql_compile_inline(ifd->src, paramLI, false, | ||
| rettype, ifd->is_proc); | ||
| fixupCompiledReturnType(compiled, rettype, ifd->is_proc); | ||
|
|
||
| error_context_stack = errcallback.previous; |
There was a problem hiding this comment.
Always pop the error-context callback on compilation failure.
If buildParamListForFunc(), typenameTypeId(), or plisql_compile_inline() throws, the restore at Line 489 is skipped and error_context_stack keeps a pointer to this stack-local errcallback. The next error can then walk a dead frame. Wrap the compile block in PG_TRY/PG_FINALLY so the callback is removed unconditionally.
🩹 Suggested structure
errctx.funcname = ifd->funcname;
errctx.is_proc = ifd->is_proc;
errcallback.callback = with_func_compile_error_callback;
errcallback.arg = &errctx;
errcallback.previous = error_context_stack;
error_context_stack = &errcallback;
-
- paramLI = buildParamListForFunc(ifd->args);
- rettype = (ifd->rettype != NULL)
- ? typenameTypeId(NULL, ifd->rettype)
- : InvalidOid;
- compiled = plisql_compile_inline(ifd->src, paramLI, false,
- rettype, ifd->is_proc);
- fixupCompiledReturnType(compiled, rettype, ifd->is_proc);
-
- error_context_stack = errcallback.previous;
+ PG_TRY();
+ {
+ paramLI = buildParamListForFunc(ifd->args);
+ rettype = (ifd->rettype != NULL)
+ ? typenameTypeId(NULL, ifd->rettype)
+ : InvalidOid;
+ compiled = plisql_compile_inline(ifd->src, paramLI, false,
+ rettype, ifd->is_proc);
+ fixupCompiledReturnType(compiled, rettype, ifd->is_proc);
+ }
+ PG_FINALLY();
+ {
+ error_context_stack = errcallback.previous;
+ }
+ PG_END_TRY();📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| /* Set up error context so compilation errors name the function */ | |
| errctx.funcname = ifd->funcname; | |
| errctx.is_proc = ifd->is_proc; | |
| errcallback.callback = with_func_compile_error_callback; | |
| errcallback.arg = &errctx; | |
| errcallback.previous = error_context_stack; | |
| error_context_stack = &errcallback; | |
| paramLI = buildParamListForFunc(ifd->args); | |
| rettype = (ifd->rettype != NULL) | |
| ? typenameTypeId(NULL, ifd->rettype) | |
| : InvalidOid; | |
| compiled = plisql_compile_inline(ifd->src, paramLI, false, | |
| rettype, ifd->is_proc); | |
| fixupCompiledReturnType(compiled, rettype, ifd->is_proc); | |
| error_context_stack = errcallback.previous; | |
| /* Set up error context so compilation errors name the function */ | |
| errctx.funcname = ifd->funcname; | |
| errctx.is_proc = ifd->is_proc; | |
| errcallback.callback = with_func_compile_error_callback; | |
| errcallback.arg = &errctx; | |
| errcallback.previous = error_context_stack; | |
| error_context_stack = &errcallback; | |
| PG_TRY(); | |
| { | |
| paramLI = buildParamListForFunc(ifd->args); | |
| rettype = (ifd->rettype != NULL) | |
| ? typenameTypeId(NULL, ifd->rettype) | |
| : InvalidOid; | |
| compiled = plisql_compile_inline(ifd->src, paramLI, false, | |
| rettype, ifd->is_proc); | |
| fixupCompiledReturnType(compiled, rettype, ifd->is_proc); | |
| } | |
| PG_FINALLY(); | |
| { | |
| error_context_stack = errcallback.previous; | |
| } | |
| PG_END_TRY(); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/pl/plisql/src/pl_handler.c` around lines 473 - 489, The error-context
callback (errcallback) is pushed onto error_context_stack before calling
buildParamListForFunc, typenameTypeId, and plisql_compile_inline, but if any of
those functions throws the final restore (error_context_stack =
errcallback.previous) is skipped; wrap the compilation block that sets paramLI,
rettype, calls plisql_compile_inline and fixupCompiledReturnType in a PG_TRY /
PG_FINALLY pair and in the PG_FINALLY ensure error_context_stack is restored
(pop errcallback) so the with_func_compile_error_callback / errctx frame is
always removed even on errors.
support function/procedure in WITH clause
fix #1067
Summary by CodeRabbit
Release Notes
New Features
Tests