Skip to content

Support %rowtype and %type#862

Closed
bigplaice wants to merge 4 commits intoIvorySQL:masterfrom
bigplaice:%rowtype
Closed

Support %rowtype and %type#862
bigplaice wants to merge 4 commits intoIvorySQL:masterfrom
bigplaice:%rowtype

Conversation

@bigplaice
Copy link
Copy Markdown
Collaborator

@bigplaice bigplaice commented Aug 28, 2025

When the declaration of the referenced item changes, the declaration of the referencing item would change accordingly.

The referencing item inherits data type, data size, constraints (unless the referenced item is a column) of the referenced item.

Argument datatype of function and prorcedure can be %ROWTYPE. Funtion's returned datatype can be %ROWTYPE.

Summary by CodeRabbit

  • New Features

    • Oracle-style %TYPE and %ROWTYPE for functions, procedures, and packages.
    • Row-based INSERT/UPDATE and VALUES without parentheses.
    • PL/SQL body validation with status tracking; blocks calls to invalid routines.
    • Dependency-aware invalidation when tables/columns change.
  • Enhancements

    • SYS.DBA/ALL/USER_* views show richer argument metadata and improved source text.
    • Expanded package subprocedure metadata.
  • Compatibility

    • PL/SQL parsing modes and error contexts aligned; safeguards in PostgreSQL-compatible mode.
  • Tests

    • Added comprehensive %TYPE/%ROWTYPE regression suite.

If the declaration of the referenced item changes,
then the declaration of the referencing item changes too.

The referencing item inherits data type, data size, constraints
(unless the referenced item is a column) of the referenced item.

Function and prorcedures argument datatype can be %ROWTYPE,
and funtion returned datatype can be %ROWTYPE.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Aug 28, 2025

Walkthrough

Implements Oracle-style %TYPE and %ROWTYPE end-to-end: parsing, type lookup, PL/iSQL compilation, dependency recording/invalidation, and catalog/view exposure. Adds prostatus to pg_proc with subtransaction-based validation for PL/iSQL, expands SYS.* views with arg metadata sourced via a new SRF, integrates new dependency kind (DEPENDENCY_TYPE), and adds ROW-based INSERT/UPDATE handling.

Changes

Cohort / File(s) Summary
SYS views and ARG metadata
contrib/ivorysql_ora/src/sysview/sysview--1.0.sql
Switches TEXT to pg_get_functiondef-derived lines; LEFT JOINs new SRF for %TYPE/%ROWTYPE arg metadata; derives TYPE_NAME/SUBNAME/OBJECT_TYPE, DATA_LENGTH/PRECISION/SCALE, PLS_TYPE dynamically across SYS.DBA/ALL/USER_* views.
Dependency model and invalidation
src/include/catalog/dependency.h, src/backend/catalog/dependency.c, src/backend/commands/tablecmds.c, src/include/commands/tablecmds.h
Adds DEPENDENCY_TYPE and ObjectFunOrPkg; collects TYPE/ROWTYPE dependents; invalidates functions (prostatus NA) and flags packages/bodies; exposes relation/column invalidation APIs; comparator exported.
Catalog: procedures and status
src/include/catalog/pg_proc.h, src/backend/catalog/pg_proc.c
Adds prostatus to pg_proc with VALID/INVALID/NA; PL/iSQL body validation via subtransaction; adjusts type-dependency creation for composite types under PL/iSQL.
Parser core (ROWTYPE, VALUES, modes)
src/include/nodes/parsenodes.h, src/backend/parser/parse_type.c, src/include/parser/parse_type.h, src/backend/parser/gram.y, src/include/parser/parser.h, src/backend/parser/parser.c, src/backend/oracle_parser/ora_gram.y, src/backend/oracle_parser/liboracle_parser.c, src/include/oracle_parser/ora_kwlist.h
Adds TypeName.row_type and SelectStmt.valuesIsrow; introduces LookupOraTypeName*; adds PLISQL raw parse modes; ROWTYPE grammar; VALUES no-parens form; keyword reclass for VALUES; PG-mode guard for PLISQL modes.
Parser resolution and namespace
src/backend/parser/parse_func.c, src/backend/catalog/namespace.c, src/backend/utils/cache/lsyscache.c
Fallback to LookupOraTypeName for args/rettype; block calling INVALID PL/iSQL functions; PG mode forbids PLISQL execution; resolve Ora types by name in namespace lookups.
Target list and ROW expansion
src/backend/parser/parse_target.c, src/include/parser/parse_target.h, src/backend/parser/analyze.c
Adds transformRowExpression/transformRowTargetList; handles INSERT/UPDATE ROW forms; Ivy/Oracle-specific UPDATE target processing.
PL/iSQL engine: deps, types, modes
src/pl/plisql/src/pl_comp.c, src/pl/plisql/src/pl_gram.y, src/pl/plisql/src/pl_handler.c, src/pl/plisql/src/pl_package.c, src/pl/plisql/src/pl_package.h, src/pl/plisql/src/pl_subproc_function.c, src/pl/plisql/src/plisql.h, src/include/funcapi.h
Tracks referenced objects for %TYPE/%ROWTYPE; records DEPENDENCY_TYPE for functions/packages; adds function_free hook; NOT NULL inheritance; switches to PLISQL parse modes; package parse returns basetypmod; exposes globals and APIs.
Ruleutils SRF for ARG refs
src/backend/utils/adt/ruleutils.c
Adds pg_get_function_arg_reference_typerowtype SRF to emit per-arg/return metadata for %TYPE/%ROWTYPE and named/package types.
Function API utils
src/backend/utils/fmgr/funcapi.c
Adds get_func_prostatus/change_func_prostatus; Ora-type fallback in result tupdesc builders.
Package cache view extension
src/backend/utils/cache/packagecache.c
Extends pg_get_subprocs_in_package schema with 5 arrays for type/object-type/length/precision/scale.
Executor/SPI modes
src/backend/executor/spi.c
Error-context mapping extended for PL/iSQL parse modes.
DDL/type parsing guard
src/backend/parser/parse_utilcmd.c
SERIAL detection excludes ROWTYPE.
Tests and SQL
src/pl/plisql/src/Makefile, src/pl/plisql/src/sql/plisql_type_rowtype.sql, src/pl/plisql/src/sql/plisql_out_parameter.sql, src/pl/plisql/src/sql/plisql_nested_subproc2.sql, src/oracle_test/regress/sql/ora_package.sql
Adds comprehensive %TYPE/%ROWTYPE tests; pre-drop in out-parameter test; renames test function; package arg changed to mds%rowtype.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant Parser as Parser (ROWTYPE/PLISQL)
  participant Resolver as Type Resolver
  participant PL as PL/iSQL Compiler
  participant Catalog as pg_proc/pg_depend
  participant Inval as Invalidation

  User->>Parser: CREATE FUNCTION ... (%TYPE/%ROWTYPE)
  Parser->>Resolver: LookupOraTypeName / ROWTYPE reltype
  Resolver-->>Parser: Type OIDs/typmod
  Parser->>PL: Compile PL/iSQL (subxact)
  PL->>Catalog: Insert/Update pg_proc (prostatus)
  alt check_function_bodies ON
    PL->>PL: Validate body in subtransaction
    alt success
      PL->>Catalog: set prostatus = VALID
    else failure
      PL->>Catalog: set prostatus = INVALID
    end
  else OFF
    PL->>Catalog: set prostatus = NA
  end
  PL->>Catalog: Record DEPENDENCY_TYPE to referenced rel/col/pkg
  Note over Catalog,Inval: Dependencies stored for later invalidation
Loading
sequenceDiagram
  autonumber
  actor DDL as DDL (ALTER TABLE ...)
  participant DepScan as pg_depend scan
  participant Proc as Functions/Packages
  participant Catalog as pg_proc/pkg cache

  DDL->>DepScan: Find DEPENDENCY_TYPE on rel/column
  DepScan->>Proc: Collect dependent OIDs
  Proc->>Proc: Sort & deduplicate
  loop For each dependent
    alt Function
      Proc->>Catalog: set prostatus = NA (invalidate)
    else Package
      Proc->>Catalog: set package spec update flag
    else Package Body
      Proc->>Catalog: set package body update flag
    end
  end
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120+ minutes

Possibly related issues

Possibly related PRs

Suggested labels

Community

Suggested reviewers

  • jiaoshuntian

Poem

I nibble on types in the clovery rows,
%TYPE blooms softly, %ROWTYPE grows.
With paws I flip prostatus flags,
And chase dependencies, clearing lags.
In views I line the arguments bright—
Hop! The parser hums, all syntax right. 🐇✨

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbit in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbit in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbit gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbit read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbit help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbit ignore or @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbit summary or @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbit or @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 47

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (5)
src/include/nodes/parsenodes.h (1)

264-277: Regenerate node support to include TypeName.row_type
The node support files (src/backend/nodes/outfuncs.c, readfuncs.c, copyfuncs.c, equalfuncs.c) have no T_TypeName case or handling for the new row_type field (nor even for the existing pct_type), so serialization, deserialization, copying, and equality checks will be broken. Rerun gen_node_support.pl (or manually add branches mirroring pct_type handling) to restore correct behavior.

src/backend/catalog/namespace.c (1)

1536-1574: CRITICAL: wrong index variable and too-strict missing_ok in type-fallback

Inside the named-args branch, the loop uses i instead of j, corrupting indexing and risking OOB access. Also, LookupOraTypeName(..., false) will error on missing types; this fallback should be non-throwing. Fix both:

Apply:

- for (int j in 0..pronargs-1) {
-   newResult->args[j] = proargtypes[argnumbers[j]];
-   if (argtypenames != NULL && strcmp(argtypenames[argnumbers[i]], "") != 0) {
-     TypeName *tname;
-     PkgType *pkgtype;
-     tname = (TypeName *) stringToNode(argtypenames[argnumbers[i]]);
+ for (int j = 0; j < pronargs; j++) {
+   newResult->args[j] = proargtypes[argnumbers[j]];
+   if (argtypenames != NULL && strcmp(argtypenames[argnumbers[j]], "") != 0) {
+     TypeName *tname;
+     PkgType *pkgtype;
+     tname = (TypeName *) stringToNode(argtypenames[argnumbers[j]]);
      pkgtype = LookupPkgTypeByTypename(tname->names, false);
      if (pkgtype != NULL)
      {
-       newResult->args[i] = pkgtype->basetypid;
+       newResult->args[j] = pkgtype->basetypid;
        pfree(pkgtype);
      }
      else
      {
        Type typtup;
-       typtup = LookupOraTypeName(NULL, tname, NULL, false);
+       typtup = LookupOraTypeName(NULL, tname, NULL, true);
        if (typtup)
        {
          if (!((Form_pg_type) GETSTRUCT(typtup))->typisdefined)
            ereport(NOTICE,
                    (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                     errmsg("argument type %s is invalid",
                            TypeNameToString(tname))));
-         newResult->args[i] = typeTypeId(typtup);
+         newResult->args[j] = typeTypeId(typtup);
          ReleaseSysCache(typtup);
        }
      }
      pfree(tname);
    }
  }
src/backend/utils/cache/lsyscache.c (2)

1895-1932: Release syscache before ERROR and use errcode (all-argtypes path).

Repeat the same fix here; also free p_argtypeNames[i] strings and the array after use to avoid leaks.

Apply this diff:

-                if (pkgtype == NULL)
-                {
-                    Type        typtup;
-
-                    typtup = LookupOraTypeName(NULL, tname, NULL, true);
-
-                    if (typtup)
-                    {
-                        if (!((Form_pg_type) GETSTRUCT(typtup))->typisdefined)
-                        {
-                            pfree(tname);
-                            elog(ERROR, "package, relation or view does not exist");
-                        }
-
-                        proargtypes[i] = typeTypeId(typtup);
-                        ReleaseSysCache(typtup);
-                    }
-                    else
-                    {
-                        elog(ERROR, "package, relation or view does not exist");
-                    }
-                }
+                if (pkgtype == NULL)
+                {
+                    Type        typtup;
+
+                    typtup = LookupOraTypeName(NULL, tname, NULL, true);
+
+                    if (typtup)
+                    {
+                        if (!((Form_pg_type) GETSTRUCT(typtup))->typisdefined)
+                        {
+                            ReleaseSysCache(typtup);
+                            pfree(tname);
+                            ereport(ERROR,
+                                    (errcode(ERRCODE_UNDEFINED_OBJECT),
+                                     errmsg("package, relation or view does not exist")));
+                        }
+                        proargtypes[i] = typeTypeId(typtup);
+                        ReleaseSysCache(typtup);
+                    }
+                    else
+                    {
+                        ereport(ERROR,
+                                (errcode(ERRCODE_UNDEFINED_OBJECT),
+                                 errmsg("package, relation or view does not exist")));
+                    }
+                }
@@
-            pfree(p_argtypeNames);
+            for (i = 0; i < numargs; i++)
+                if (p_argtypeNames[i])
+                    pfree(p_argtypeNames[i]);
+            pfree(p_argtypeNames);

1801-1808: Use PROARGMODE_OUT and PROARGMODE_TABLE instead of FUNC_PARAM_ in lsyscache.c*
In src/backend/utils/cache/lsyscache.c (lines 1803–1804), promodes holds pg_proc proargmodes values; replace FUNC_PARAM_OUT/FUNC_PARAM_TABLE with PROARGMODE_OUT/PROARGMODE_TABLE.

src/pl/plisql/src/pl_comp.c (1)

3334-3414: compute_function_hashkey leaks (and may mis-hash triggers).

  • The argtypenames array and elems from deconstruct_array() are never freed.
  • Minor: ensure nelems >= pronargs assumption holds.

Apply:

@@
-        Datum protypenames;
-        bool    isNull;
-        Datum      *elems;
-        int         nelems;
+        Datum protypenames;
+        bool    isNull;
+        Datum      *elems = NULL;
+        int         nelems = 0;
@@
-        if (argtypenames != NULL)
+        if (argtypenames != NULL)
         {
             for (i = 0; i < procStruct->pronargs; i++)
             {
@@
             }
+            /* free nested strings, container, and deconstructed elems */
+            for (i = 0; i < nelems; i++)
+                if (argtypenames[i] != NULL)
+                    pfree(argtypenames[i]);
+            pfree(argtypenames);
+            if (!isNull && elems != NULL)
+                pfree(elems);
         }
         else
             memcpy(hashkey->argtypes, procStruct->proargtypes.values,
                 procStruct->pronargs * sizeof(Oid));

Also consider guarding with:

Assert(nelems == procStruct->pronargs);

or handle shorter arrays defensively.

🧹 Nitpick comments (40)
src/pl/plisql/src/sql/plisql_nested_subproc2.sql (1)

3909-3919: Add teardown for test hygiene

Drop test_f2 after the block to avoid leaking objects into later tests that run in the same database.

Example to append after creation:

DROP FUNCTION test_f2(integer);

Please confirm no other tests rely on test_f2 persisting beyond this script.

src/backend/commands/functioncmds.c (1)

1681-1686: Redundant initialization code.

This loop initializes the arrays that should have been initialized at declaration (as mentioned in the earlier comment). If you add the initialization at declaration, this loop becomes redundant.

If the arrays are initialized at declaration, this loop can be removed to avoid duplicate initialization.

src/include/parser/parser.h (1)

46-50: Enum extended without doc update — clarify PL/iSQL modes.

Please extend the comment above RawParseMode to describe the PL/iSQL variants for parity with PL/pgSQL.

Apply this doc tweak:

  * RAW_PARSE_PLPGSQL_ASSIGNn: parse a PL/pgSQL assignment statement,
  * and return a one-element List containing a RawStmt node.  "n"
  * gives the number of dotted names comprising the target ColumnRef.
+ *
+ * RAW_PARSE_PLISQL_EXPR: parse a PL/iSQL expression; returns a one-element
+ * List containing a RawStmt.
+ *
+ * RAW_PARSE_PLISQL_ASSIGNn: parse a PL/iSQL assignment statement; returns a
+ * one-element List containing a RawStmt. "n" is the dotted target length.
src/include/parser/parse_target.h (1)

58-62: Use extern for consistency with this header’s prototypes

Every other declaration in this header uses extern; keep style consistent.

-List *transformRowExpression(ParseState *pstate, List *exprlist,
-					ParseExprKind exprKind, bool allowDefault);
-List *transformRowTargetList(ParseState *pstate, List *targetlist,
-					ParseExprKind exprKind);
+extern List *transformRowExpression(ParseState *pstate, List *exprlist,
+									ParseExprKind exprKind, bool allowDefault);
+extern List *transformRowTargetList(ParseState *pstate, List *targetlist,
+									ParseExprKind exprKind);
src/include/catalog/dependency.h (2)

152-153: Expose comparator — document order

Comparator returns -1 when a.objectId > b.objectId (descending). Add a brief comment here to record sort order for callers relying on it.


101-111: Rename flags to classId and document the first‐field invariant

File: src/include/catalog/dependency.h (lines 101–111)

  • Change the field
    Oid flags;  /* flags used to identify function, package and package body */
    to
    Oid classId;  /* OID of pg_proc/pg_package/pg_package_body */
    since it isn’t a bitmask but the catalog‐relation OID.
  • Replace the vague “MUST BE FIRST” comment with a clear explanation—or better, add
    static_assert(offsetof(ObjectFunOrPkg, objectId) == 0,
                  "ObjectFunOrPkg.objectId must be first for sort/dedup logic");
    to document why objectId must remain at offset 0 for the comparator and qunique calls.
  • No other code relies on this struct’s layout beyond the palloc/repalloc, pg_qsort and qunique usage.
src/include/catalog/pg_proc.h (2)

202-216: Fix typos and tighten comments for consistency; keep style to single-line summaries.

Current text has typos (“te”), repetition, and verbose phrasing. Suggest:

-#define PROSTATUS_NA		'n'	/* procedure status is unknown,
-					 * must call ALTER FUNCTION COMPILE statement to
-					 * validate te procedure, otherwise failed to
-					 * call the procedure
-					 */
-#define PROSTATUS_VALID		'v'	/* procedure can be called */
-#define PROSTATUS_INVALID	'i'	/* procedure status is invalid,
-					 * must call ALTER FUNCTION COMPILE statement to
-					 * validate te procedure, otherwise failed to
-					 * call the procedure
-					 */
+#define PROSTATUS_NA		'n'	/* unknown/unvalidated; run ALTER FUNCTION ... COMPILE before calls succeed */
+#define PROSTATUS_VALID		'v'	/* procedure can be called */
+#define PROSTATUS_INVALID	'i'	/* invalid; run ALTER FUNCTION ... COMPILE to revalidate before calls succeed */

220-248: Confirm how prostatus is assigned during CREATE/REPLACE and PL/iSQL validation. Consider explicit param only if needed.

If ProcedureCreate does not expose a prostatus parameter, ensure it initializes to PROSTATUS_VALID and PL/iSQL paths subsequently flip to NA/INVALID as appropriate. If multiple callers need different initial states, consider adding a prostatus arg to ProcedureCreate to avoid post-hoc UPDATEs.

src/backend/parser/analyze.c (1)

3828-3929: Row-target dispatcher: polish booleans and fast-paths; leverage transformed TList.

  • Use bool for flags; initialize attrrowtype to InvalidOid.
  • Avoid duplicate transformTargetList calls by transforming once and reusing.
  • Minor correctness: treat ROW identifier case-insensitively consistently (you used pg_strcasecmp here; elsewhere strcmp).
-    int target_has_row = false;
-    bool    cols_has_row = false;
-    int col_is_complex = false;
-    int row_no;
-    Oid attrrowtype;
+    bool target_has_row = false;
+    bool cols_has_row = false;
+    bool col_is_complex = false;
+    int  row_no = 0;
+    Oid  attrrowtype = InvalidOid;

[ suggest_nitpick ]

src/backend/parser/parse_target.c (1)

2069-2093: Star handling messages should be specific.

“unsupported syntax” is vague. Prefer messages similar to existing code (“row expansion via "*" is not supported here” or “invalid user.table.column, table.column, or column specification”) with errposition.

src/backend/catalog/dependency.c (2)

92-95: Remove duplicate include of funcapi.h.

funcapi.h is already included at Line 82. Redundant include increases rebuild churn.

Apply:

 #include "utils/packagecache.h"
-#include "commands/tablecmds.h"
-#include "funcapi.h"
-#include "lib/qunique.h"
+#include "commands/tablecmds.h"
+#include "lib/qunique.h"

927-929: Unnecessary classid==0 check.

pg_depend.classid should always be valid. If you intend to future-proof, add a comment; otherwise remove for clarity.

src/backend/catalog/pg_proc.c (1)

598-610: DRY the prostatus assignment (minor).

The prostatus initialization for plisql/ORA mode is duplicated. Compute a bool is_plisql once and reuse.

Example:

+	bool is_plisql = (ORA_PARSER == compatible_db &&
+					  languageObjectId == get_language_oid("plisql", true));
 ...
-		if (ORA_PARSER == compatible_db &&
-			languageObjectId == get_language_oid("plisql", true))
+		if (is_plisql)
 		{
 			if (check_function_bodies)
 				values[Anum_pg_proc_prostatus - 1] = CharGetDatum(PROSTATUS_VALID);
 			else
 				values[Anum_pg_proc_prostatus - 1] = CharGetDatum(PROSTATUS_NA);
 		}
 ...
-		if (ORA_PARSER == compatible_db &&
-			languageObjectId == get_language_oid("plisql", true))
+		if (is_plisql)

Also applies to: 643-655

src/backend/oracle_parser/ora_gram.y (1)

14592-14614: End-to-end verification checklist for %ROWTYPE and VALUES changes.

Before merge, please validate:

  • %ROWTYPE types flow: arguments, RETURNS, dependency recording, and error positions.
  • SETOF %ROWTYPE (if added) parses and transforms.
  • No bison conflicts; %expect 0 holds.
  • No regression in INSERT … VALUES syntax.
  • Views/rules/util code (ruleutils.c) handle valuesIsrow appropriately.

Would you like me to draft a minimal SQL test matrix to cover these paths?

Also applies to: 9339-9350, 14004-14005, 892-896

src/backend/commands/tablecmds.c (4)

13550-13552: FYI: Watch for unused locals.

ObjectAddress address looks unused in the provided context. If truly unused, drop it to avoid warnings.


13705-13786: Minor fixes and dedup suggestion.

  • Typo: “funciton” → “function”.
  • Error message wording: prefer “failed to lookup package body …” for consistency (also elsewhere).
  • The invalidation switch (function/package/package body) is duplicated here and in check_function_dependency_internal; extract a tiny static helper to reduce duplication and drift.
- * Now find out the dependent funciton which parameters datatype
+ * Now find out the dependent function whose parameters datatype

14022-14066: Error handling branch is fine; keep DEPENDENCY_TYPE guard narrow.

Flow is correct: block AT_AlterColumnType for non-ORA paths; collect dependents only in ORA mode. Keep the elog(ERROR, "unexpected object…") conservative—consider LOG/DEBUG1 instead to avoid hard errors if new dep types appear.


13725-13735: Optional: extract common invalidation into a helper to avoid drift.

Both blocks do the same three-way switch. A static invalidate_fun_or_pkg(ObjectFunOrPkg *entry) reduces duplication.

Also applies to: 21703-21716

contrib/ivorysql_ora/src/sysview/sysview--1.0.sql (3)

464-467: Simplify and harden PL/SQL RECORD detection (case-insensitive, NULL-safe).

Use lower(COALESCE(...)) IN ('table','view') to avoid four equality checks and handle NULLs cleanly.

-       WHEN ARG.TYPE_OBJECT_TYPE::VARCHAR2 = 'table' OR ARG.TYPE_OBJECT_TYPE::VARCHAR2 = 'TABLE'
-         OR ARG.TYPE_OBJECT_TYPE::VARCHAR2 = 'view' OR ARG.TYPE_OBJECT_TYPE::VARCHAR2 = 'VIEW'
-         THEN 'PL/SQL RECORD'
+       WHEN lower(COALESCE(ARG.TYPE_OBJECT_TYPE::text, '')) IN ('table','view')
+         THEN 'PL/SQL RECORD'

679-682: Apply the same PL/SQL RECORD detection simplification in ALL_ARGUMENTS.

Keep logic consistent with DBA_ARGUMENTS.

-       WHEN ARG.TYPE_OBJECT_TYPE::VARCHAR2 = 'table' OR ARG.TYPE_OBJECT_TYPE::VARCHAR2 = 'TABLE'
-         OR ARG.TYPE_OBJECT_TYPE::VARCHAR2 = 'view' OR ARG.TYPE_OBJECT_TYPE::VARCHAR2 = 'VIEW'
-         THEN 'PL/SQL RECORD'
+       WHEN lower(COALESCE(ARG.TYPE_OBJECT_TYPE::text, '')) IN ('table','view')
+         THEN 'PL/SQL RECORD'

759-776: Unify TYPE_ null handling via NULLIF across all argument views*

  • Replace each

       CASE  
         WHEN ARG.TYPE_<X> != '_NULL_'  
           THEN SYS.ORA_CASE_TRANS(ARG.TYPE_<X>::VARCHAR2)::VARCHAR2(128)  
         ELSE NULL  
       END::VARCHAR2(128) AS TYPE_<X>

    with

       SYS.ORA_CASE_TRANS(NULLIF(ARG.TYPE_<X>, '_NULL_')::VARCHAR2)::VARCHAR2(128) AS TYPE_<X>

    for TYPE_NAME, TYPE_SUBNAME and TYPE_OBJECT_TYPE in ALL_ARGUMENTS, USER_ARGUMENTS and DBA_ARGUMENTS.

  • PLS_TYPE logic (WHEN (T.TYPTYPE = 'b' OR 'c') AND ARG.PLS_TYPE IS NOT NULL → use ARG.PLS_TYPE; WHEN TYPTYPE='b' AND ARG.PLS_TYPE IS NULL → fallback to T.OID; ELSE NULL) is consistent and requires no change.

src/pl/plisql/src/plisql.h (1)

1281-1283: No state leak: globals are reset and nested contexts handled. check_referenced_objects and plisql_referenced_objects are set to NIL at the start of each compile (in both pl_comp.c and pl_package.c), freed at the end, and saved/restored for nested calls—no cross-session leaks or thread‐safety issues in the single‐process backend. Optional: encapsulate these fields into a compile‐context struct for clarity.

src/pl/plisql/src/pl_subproc_function.c (1)

1511-1548: Extended reload-check for %TYPE/%ROWTYPE looks correct

The deeper comparison (pctrowtypname presence, pct_type/row_type flags, name list case-insensitive match) makes overload resolution robust for %TYPE/%ROWTYPE. Consider early-continue after name-list length mismatch to skip the loop and save cycles.

src/backend/catalog/namespace.c (1)

1602-1618: Optional: deduplicate Ora-type fallback logic

The positional branch duplicates the package/Ora fallback. Consider extracting a small helper to resolve one arg-type name (handles pkg type, Ora type, validity notice) and call it from both branches. This reduces drift and future bugs.

src/backend/utils/fmgr/funcapi.c (1)

2716-2739: Typos and naming consistency in prostatus helper.

Minor: fix typos in comment (“catalog”), and variable prostaus_datumprostatus_datum for clarity.

Apply this diff:

- * get function prostatus from the pg_proc catatlog
+ * get function prostatus from the pg_proc catalog
@@
-    Datum      prostaus_datum;
+    Datum      prostatus_datum;
@@
-    prostaus_datum = SysCacheGetAttr(PROCOID, procTup, Anum_pg_proc_prostatus,
-                            &isNull);
+    prostatus_datum = SysCacheGetAttr(PROCOID, procTup,
+                                      Anum_pg_proc_prostatus, &isNull);
@@
-        result = DatumGetChar(prostaus_datum);
+        result = DatumGetChar(prostatus_datum);
src/backend/utils/cache/lsyscache.c (2)

1676-1681: Comment typos and clarity.

Fix grammar: “dropped”, “really”. This improves developer comprehension.

Apply this diff:

- * If A function returns a variable in a package, 
- * the variable may be changed with package. If A function's 
- * returned datatype is tablename.columnname%TYPE, the tablename
- * or the columnname can be changed or droped. 
- * so, this function created to get the realy type.
+ * If a function returns a variable in a package,
+ * the variable may change with the package. If a function's
+ * returned datatype is tablename.columnname%TYPE, the table name
+ * or the column name can be changed or dropped.
+ * This function gets the real underlying type.

2005-2018: Free arg type-name strings to avoid leaking memory.

argtypenames entries come from TextDatumGetCString and should be freed after use.

Outside the shown hunk, add before return desc; in build_function_result_tupdesc_d:

if (argtypenames)
{
    for (i = 0; i < numargs; i++)
        if (argtypenames[i])
            pfree(argtypenames[i]);
    pfree(argtypenames);
}
src/pl/plisql/src/sql/plisql_type_rowtype.sql (1)

55-63: Stability: proname LIKE filters can be ambiguous.

SELECTs on pg_proc using proname LIKE may match multiple versions across schemas or after redefinitions, causing flaky counts or statuses.

Use exact match in current schema:

SELECT prostatus
FROM pg_proc p
JOIN pg_namespace n ON n.oid = p.pronamespace
WHERE p.proname = 'fun1' AND n.nspname = current_schema;

Also applies to: 69-76, 154-175, 261-275, 281-292, 324-331, 154-157, 185-187, 298-303, 324-331, 518-522, 686-692, 707-714, 880-885, 896-903, 1556-1611, 2143-2146, 2373-2384, 2395-2415

src/backend/parser/parse_type.c (2)

30-34: Remove unused includes to keep compile tight.

access/table.h and catalog/pg_class.h appear unused in this TU. If not required by macros, drop them.

-#include "catalog/pg_class.h"
-#include "access/table.h"

912-918: Code duplication between LookupTypeNameExtended and LookupOraTypeNameExtended.

The two functions are nearly identical except for the %TYPE notice behavior. Consider factoring into a single internal helper with a “emit_pct_type_notice” boolean to avoid drift.

static Type
LookupTypeNameExtendedInternal(ParseState*, const TypeName*, int32*, bool temp_ok, bool missing_ok, bool emit_pct_notice);

Also applies to: 924-1084

src/pl/plisql/src/pl_comp.c (2)

1149-1149: Nit: fix typo in comment.

“alreay” → “already”.

Apply:

-                /* Check duplicate dependencies that alreay been built in CREATE FUNCTION/PROCEDURE statment */
+                /* Check duplicate dependencies that already were built in CREATE FUNCTION/PROCEDURE statement */

2597-2604: Minor API consistency and immutability notes.

  • pctrowtypname is stored by pointer; ensure it lives in a long-lived context.
  • Keep PLiSQL_type immutable after creation; all per-variable attributes should be on the var.

Also applies to: 2669-2671, 3031-3037

src/pl/plisql/src/pl_package.c (3)

941-998: Dup-dependency scan is repeated and heavy; consider factoring.

The pg_depend scan logic is duplicated in spec/body compiles. Factor into a small helper to DRY and reduce future divergence.

I can factor this into static void record_pct_dependencies(ObjectAddress *myself) shared by both callers.

Also applies to: 1218-1284


3226-3240: Minor: free temporary arrays after tuplestore_putvalues.

The container arrays (oids, paramModes, etc.) can be pfree’d after constructing ArrayTypes to reduce peak memory.

Also applies to: 3270-3280, 3415-3476


454-456: Typos in DEBUG messages.

“realy” → “really”.

Apply similar replacements across the file.

Also applies to: 574-577, 585-587, 469-471, 579-581

src/backend/utils/adt/ruleutils.c (5)

80-80: Dependency layering: parse_package.h include in ruleutils.c

Pulling parser/parse_package.h into ruleutils.c couples deparsing with Oracle package-type lookup. Prefer moving the tiny helper you need behind a utils-facing API (e.g., a small wrapper in utils/ or parser/ gate-kept by ORA_PARSER) to avoid spreading parser headers across utils/adt. If we keep this, add a short comment explaining why the include is safe here.


546-554: Forward-decl and fixed column count: hide magic 9

NUM_RETURN_COLUMNS is fine, but consider an enum for column indexes (OID_COL, POSITION_COL, …) to prevent accidental mismatch between writers and consumers. Also, declare the helper with static inline comments on each column’s meaning to make maintenance easier.


14036-14044: RELKIND coverage for composite types

You only classify RELKIND_RELATION and RELKIND_VIEW/MATVIEW. Composite types created via CREATE TYPE AS (relkind == RELKIND_COMPOSITE_TYPE) will be mislabelled. Consider setting type_object_type = "type" for RELKIND_COMPOSITE_TYPE.


13853-13858: Random-access flag should be boolean

Passing (rsinfo->allowedModes & SFRM_Materialize_Random) to tuplestore_begin_heap works, but is a bitwise mask; wrap it as a boolean cast for clarity: (rsinfo->allowedModes & SFRM_Materialize_Random) != 0.

Apply this small change:

-  tupstore =
-    tuplestore_begin_heap(rsinfo->allowedModes & SFRM_Materialize_Random,
+  tupstore =
+    tuplestore_begin_heap((rsinfo->allowedModes & SFRM_Materialize_Random) != 0,
                           false, work_mem);

13959-14033: Error positioning helper with NULL pstate

parser_errposition(NULL, typeName->location) will always yield 0; if you want a hint without pstate, consider errhint with NameListToString(...) and keep location-less errors, or thread a ParseState only when available. At minimum, drop parser_errposition(NULL, ...) to avoid confusion.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 48d5abf and 8eadeb5.

⛔ Files ignored due to path filters (11)
  • contrib/ivorysql_ora/expected/ora_sysview.out is excluded by !**/*.out
  • src/include/catalog/pg_proc.dat is excluded by !**/*.dat
  • src/oracle_test/regress/expected/ora_package.out is excluded by !**/*.out
  • src/pl/plisql/src/expected/plisql_array.out is excluded by !**/*.out
  • src/pl/plisql/src/expected/plisql_call.out is excluded by !**/*.out
  • src/pl/plisql/src/expected/plisql_control.out is excluded by !**/*.out
  • src/pl/plisql/src/expected/plisql_nested_subproc.out is excluded by !**/*.out
  • src/pl/plisql/src/expected/plisql_nested_subproc2.out is excluded by !**/*.out
  • src/pl/plisql/src/expected/plisql_out_parameter.out is excluded by !**/*.out
  • src/pl/plisql/src/expected/plisql_record.out is excluded by !**/*.out
  • src/pl/plisql/src/expected/plisql_type_rowtype.out is excluded by !**/*.out
📒 Files selected for processing (43)
  • contrib/ivorysql_ora/src/sysview/sysview--1.0.sql (14 hunks)
  • src/backend/catalog/dependency.c (9 hunks)
  • src/backend/catalog/namespace.c (3 hunks)
  • src/backend/catalog/pg_proc.c (6 hunks)
  • src/backend/commands/functioncmds.c (9 hunks)
  • src/backend/commands/tablecmds.c (14 hunks)
  • src/backend/executor/spi.c (1 hunks)
  • src/backend/oracle_parser/liboracle_parser.c (1 hunks)
  • src/backend/oracle_parser/ora_gram.y (7 hunks)
  • src/backend/parser/analyze.c (4 hunks)
  • src/backend/parser/gram.y (1 hunks)
  • src/backend/parser/parse_func.c (4 hunks)
  • src/backend/parser/parse_package.c (6 hunks)
  • src/backend/parser/parse_target.c (1 hunks)
  • src/backend/parser/parse_type.c (5 hunks)
  • src/backend/parser/parse_utilcmd.c (1 hunks)
  • src/backend/parser/parser.c (1 hunks)
  • src/backend/utils/adt/ruleutils.c (3 hunks)
  • src/backend/utils/cache/lsyscache.c (5 hunks)
  • src/backend/utils/cache/packagecache.c (2 hunks)
  • src/backend/utils/fmgr/funcapi.c (4 hunks)
  • src/include/catalog/dependency.h (3 hunks)
  • src/include/catalog/pg_proc.h (2 hunks)
  • src/include/commands/tablecmds.h (1 hunks)
  • src/include/funcapi.h (3 hunks)
  • src/include/nodes/parsenodes.h (3 hunks)
  • src/include/oracle_parser/ora_kwlist.h (1 hunks)
  • src/include/parser/parse_package.h (1 hunks)
  • src/include/parser/parse_target.h (1 hunks)
  • src/include/parser/parse_type.h (1 hunks)
  • src/include/parser/parser.h (1 hunks)
  • src/oracle_test/regress/sql/ora_package.sql (1 hunks)
  • src/pl/plisql/src/Makefile (1 hunks)
  • src/pl/plisql/src/pl_comp.c (23 hunks)
  • src/pl/plisql/src/pl_gram.y (11 hunks)
  • src/pl/plisql/src/pl_handler.c (1 hunks)
  • src/pl/plisql/src/pl_package.c (17 hunks)
  • src/pl/plisql/src/pl_package.h (2 hunks)
  • src/pl/plisql/src/pl_subproc_function.c (3 hunks)
  • src/pl/plisql/src/plisql.h (3 hunks)
  • src/pl/plisql/src/sql/plisql_nested_subproc2.sql (1 hunks)
  • src/pl/plisql/src/sql/plisql_out_parameter.sql (1 hunks)
  • src/pl/plisql/src/sql/plisql_type_rowtype.sql (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (24)
src/backend/utils/cache/packagecache.c (1)
src/backend/access/common/tupdesc.c (2)
  • CreateTemplateTupleDesc (66-102)
  • TupleDescInitEntry (659-728)
src/include/parser/parse_target.h (1)
src/backend/parser/parse_target.c (2)
  • transformRowExpression (2045-2112)
  • transformRowTargetList (2119-2182)
src/backend/catalog/namespace.c (3)
src/backend/parser/parse_type.c (3)
  • LookupOraTypeName (912-918)
  • TypeNameToString (511-519)
  • typeTypeId (675-681)
src/backend/utils/error/elog.c (2)
  • errcode (856-867)
  • errmsg (1069-1085)
src/backend/utils/cache/syscache.c (1)
  • ReleaseSysCache (265-269)
src/include/commands/tablecmds.h (1)
src/backend/commands/tablecmds.c (2)
  • check_function_depend_on_relation (21494-21526)
  • check_function_depend_on_relation_column (21534-21570)
src/backend/parser/parser.c (1)
src/backend/utils/error/elog.c (2)
  • errcode (856-867)
  • errmsg (1069-1085)
src/include/parser/parse_type.h (1)
src/backend/parser/parse_type.c (2)
  • LookupOraTypeName (912-918)
  • LookupOraTypeNameExtended (924-1084)
src/backend/parser/analyze.c (6)
src/include/nodes/pg_list.h (3)
  • list_length (151-155)
  • list_head (127-131)
  • list_nth (298-303)
src/backend/parser/parse_target.c (5)
  • transformRowExpression (2045-2112)
  • transformExpressionList (220-278)
  • transformRowTargetList (2119-2182)
  • updateTargetListEntry (619-644)
  • transformTargetList (121-204)
src/backend/parser/parse_node.c (1)
  • parser_errposition (108-123)
src/backend/nodes/bitmapset.c (1)
  • bms_add_member (814-858)
src/backend/parser/parse_relation.c (2)
  • attnameAttNum (3494-3515)
  • attnumTypeId (3566-3579)
src/backend/nodes/nodeFuncs.c (1)
  • exprType (44-313)
src/include/catalog/dependency.h (1)
src/backend/catalog/dependency.c (1)
  • object_funpkgoid_comparator (2708-2720)
src/pl/plisql/src/plisql.h (1)
src/pl/plisql/src/pl_comp.c (1)
  • plisql_free_function (3799-3887)
src/pl/plisql/src/pl_handler.c (3)
src/pl/plisql/src/pl_comp.c (1)
  • plisql_compile (136-262)
src/backend/executor/spi.c (2)
  • SPI_finish (186-220)
  • SPI_result_code_string (1997-2070)
src/backend/utils/cache/syscache.c (1)
  • ReleaseSysCache (265-269)
src/backend/catalog/pg_proc.c (9)
src/backend/commands/proclang.c (1)
  • get_language_oid (225-237)
src/backend/utils/cache/lsyscache.c (1)
  • get_typtype (2907-2924)
src/backend/commands/functioncmds.c (1)
  • get_transform_oid (2706-2721)
src/backend/access/transam/xact.c (3)
  • BeginInternalSubTransaction (4643-4708)
  • ReleaseCurrentSubTransaction (4717-4736)
  • RollbackAndReleaseCurrentSubTransaction (4745-4802)
src/backend/utils/misc/guc.c (1)
  • AtEOXact_GUC (2268-2545)
src/backend/utils/error/elog.c (5)
  • CopyErrorData (1728-1792)
  • FlushErrorState (1849-1862)
  • errmsg (1069-1085)
  • errhint (1316-1331)
  • errposition (1445-1456)
src/backend/utils/cache/syscache.c (2)
  • SearchSysCache1 (217-226)
  • ReleaseSysCache (265-269)
src/backend/access/common/heaptuple.c (1)
  • heap_modify_tuple (1247-1301)
src/backend/catalog/indexing.c (1)
  • CatalogTupleUpdate (312-326)
src/include/funcapi.h (1)
src/backend/utils/fmgr/funcapi.c (2)
  • get_func_prostatus (2720-2738)
  • change_func_prostatus (2743-2784)
src/backend/parser/parse_target.c (4)
src/include/nodes/pg_list.h (2)
  • list_length (151-155)
  • list_nth (298-303)
src/backend/parser/parse_node.c (1)
  • parser_errposition (108-123)
src/backend/nodes/nodeFuncs.c (1)
  • exprLocation (1402-1834)
src/backend/parser/parse_expr.c (1)
  • transformExpr (143-159)
src/backend/utils/cache/lsyscache.c (5)
src/backend/nodes/read.c (1)
  • stringToNode (89-93)
src/backend/parser/parse_package.c (1)
  • LookupPkgTypeByTypename (352-377)
src/backend/parser/parse_type.c (2)
  • LookupOraTypeName (912-918)
  • typeTypeId (675-681)
src/backend/utils/mmgr/mcxt.c (1)
  • pfree (1519-1533)
src/backend/utils/cache/syscache.c (1)
  • ReleaseSysCache (265-269)
src/backend/parser/parse_func.c (4)
src/backend/utils/cache/syscache.c (2)
  • SearchSysCache1 (217-226)
  • ReleaseSysCache (265-269)
src/backend/nodes/read.c (1)
  • stringToNode (89-93)
src/backend/parser/parse_package.c (1)
  • LookupPkgTypeByTypename (352-377)
src/backend/utils/mmgr/mcxt.c (1)
  • pfree (1519-1533)
src/backend/utils/adt/ruleutils.c (9)
src/backend/access/common/tupdesc.c (2)
  • CreateTemplateTupleDesc (66-102)
  • TupleDescInitEntry (659-728)
src/backend/utils/mmgr/mcxt.c (2)
  • pfree (1519-1533)
  • pstrdup (1694-1698)
src/backend/nodes/read.c (1)
  • stringToNode (89-93)
src/backend/catalog/namespace.c (2)
  • DeconstructQualifiedName (3527-3569)
  • NameListToString (3820-3845)
src/backend/nodes/makefuncs.c (1)
  • makeRangeVar (424-438)
src/backend/parser/parse_package.c (1)
  • LookupPkgTypeByTypename (352-377)
src/backend/utils/cache/lsyscache.c (5)
  • type_is_rowtype (2933-2950)
  • get_rel_name (2205-2222)
  • get_attnum (863-880)
  • get_atttypetypmodcoll (948-967)
  • get_rel_relkind (2280-2297)
src/backend/parser/parse_type.c (1)
  • typeidTypeRelid (753-767)
src/backend/utils/adt/regproc.c (1)
  • regtypeout (1280-1323)
src/backend/commands/functioncmds.c (13)
src/backend/parser/parse_type.c (1)
  • typeTypeId (675-681)
src/backend/nodes/outfuncs.c (1)
  • nodeToString (793-797)
src/backend/nodes/makefuncs.c (1)
  • makeRangeVar (424-438)
src/include/nodes/pg_list.h (1)
  • list_length (151-155)
src/backend/utils/error/elog.c (3)
  • CopyErrorData (1728-1792)
  • FlushErrorState (1849-1862)
  • errposition (1445-1456)
src/backend/catalog/namespace.c (2)
  • NameListToString (3820-3845)
  • DeconstructQualifiedName (3527-3569)
src/backend/parser/parse_node.c (1)
  • parser_errposition (108-123)
src/backend/access/transam/xact.c (3)
  • BeginInternalSubTransaction (4643-4708)
  • ReleaseCurrentSubTransaction (4717-4736)
  • RollbackAndReleaseCurrentSubTransaction (4745-4802)
src/backend/utils/cache/lsyscache.c (3)
  • get_func_real_rettype (1682-1735)
  • repl_func_real_argtype (1742-1847)
  • get_func_real_allargtype (1852-1943)
src/backend/utils/cache/syscache.c (2)
  • SysCacheGetAttr (478-501)
  • ReleaseSysCache (265-269)
src/backend/access/common/heaptuple.c (2)
  • heap_modify_tuple (1247-1301)
  • heap_freetuple (1472-1476)
src/backend/catalog/indexing.c (1)
  • CatalogTupleUpdate (312-326)
src/backend/catalog/pg_depend.c (1)
  • recordDependencyOnCurrentExtension (193-244)
src/pl/plisql/src/pl_subproc_function.c (3)
src/pl/plisql/src/pl_comp.c (1)
  • plisql_free_function (3799-3887)
src/include/nodes/pg_list.h (1)
  • list_length (151-155)
src/backend/parser/parser.c (1)
  • raw_parser (40-50)
src/backend/catalog/dependency.c (4)
src/backend/utils/mmgr/mcxt.c (3)
  • palloc (1315-1343)
  • repalloc (1539-1573)
  • pfree (1519-1533)
src/backend/catalog/indexing.c (1)
  • CatalogTupleDelete (364-368)
src/backend/utils/cache/packagecache.c (4)
  • plisql_internel_funcs_init (393-404)
  • PackageCacheIsValid (87-123)
  • PackageCacheLookup (70-81)
  • PackageHandle (143-149)
src/backend/utils/fmgr/funcapi.c (1)
  • change_func_prostatus (2743-2784)
src/backend/utils/fmgr/funcapi.c (5)
src/backend/parser/parse_type.c (2)
  • LookupOraTypeName (912-918)
  • typeTypeId (675-681)
src/backend/utils/mmgr/mcxt.c (1)
  • pfree (1519-1533)
src/backend/utils/cache/syscache.c (3)
  • ReleaseSysCache (265-269)
  • SysCacheGetAttr (478-501)
  • SearchSysCache1 (217-226)
src/backend/access/common/heaptuple.c (1)
  • heap_modify_tuple (1247-1301)
src/backend/catalog/indexing.c (1)
  • CatalogTupleUpdate (312-326)
src/backend/parser/parse_type.c (4)
src/backend/catalog/namespace.c (4)
  • DeconstructQualifiedName (3527-3569)
  • NameListToString (3820-3845)
  • LookupExplicitNamespace (3611-3642)
  • TypenameGetTypidExtended (1088-1112)
src/backend/nodes/makefuncs.c (1)
  • makeRangeVar (424-438)
src/backend/utils/cache/lsyscache.c (4)
  • get_rel_type_id (2256-2273)
  • get_attnum (863-880)
  • get_atttype (918-937)
  • get_array_type (3129-3142)
src/backend/parser/parse_node.c (3)
  • parser_errposition (108-123)
  • setup_parser_errposition_callback (142-153)
  • cancel_parser_errposition_callback (158-163)
src/pl/plisql/src/pl_package.c (3)
src/backend/catalog/dependency.c (4)
  • new_object_addresses (2728-2742)
  • add_exact_object_address (2774-2792)
  • record_object_address_dependencies (2983-2992)
  • free_object_addresses (3014-3021)
src/backend/utils/cache/lsyscache.c (1)
  • type_is_rowtype (2933-2950)
src/backend/utils/adt/arrayfuncs.c (1)
  • construct_array (3373-3386)
src/pl/plisql/src/pl_comp.c (9)
src/backend/utils/cache/syscache.c (2)
  • ReleaseSysCache (265-269)
  • SearchSysCache1 (217-226)
src/backend/parser/parse_type.c (3)
  • LookupOraTypeName (912-918)
  • TypeNameToString (511-519)
  • typeTypeId (675-681)
src/backend/parser/parse_node.c (1)
  • parser_errposition (108-123)
src/backend/utils/fmgr/funcapi.c (1)
  • get_func_typename_info (1889-1933)
src/backend/catalog/dependency.c (4)
  • new_object_addresses (2728-2742)
  • add_exact_object_address (2774-2792)
  • record_object_address_dependencies (2983-2992)
  • free_object_addresses (3014-3021)
src/backend/nodes/makefuncs.c (2)
  • makeTypeNameFromNameList (458-468)
  • makeTypeName (446-450)
src/backend/parser/parse_package.c (1)
  • LookupPkgTypeByTypename (352-377)
src/include/nodes/pg_list.h (1)
  • list_length (151-155)
src/backend/catalog/namespace.c (2)
  • NameListToString (3820-3845)
  • DeconstructQualifiedName (3527-3569)
src/backend/commands/tablecmds.c (5)
src/backend/utils/mmgr/mcxt.c (3)
  • palloc (1315-1343)
  • pfree (1519-1533)
  • repalloc (1539-1573)
src/backend/catalog/dependency.c (1)
  • object_funpkgoid_comparator (2708-2720)
src/backend/utils/cache/packagecache.c (4)
  • plisql_internel_funcs_init (393-404)
  • PackageCacheIsValid (87-123)
  • PackageCacheLookup (70-81)
  • PackageHandle (143-149)
src/backend/utils/fmgr/funcapi.c (1)
  • change_func_prostatus (2743-2784)
src/backend/utils/cache/syscache.c (2)
  • SearchSysCache1 (217-226)
  • ReleaseSysCache (265-269)

Comment on lines 365 to 367
GENERATE_SERIES(1::INTEGER, SYS._ORA_SRC_COUNT(P.OID))::NUMBER AS LINE,
UNNEST(STRING_TO_ARRAY(P.PROSRC, CHR(10)))::VARCHAR2(4000) AS TEXT,
UNNEST(STRING_TO_ARRAY(substring(pg_get_functiondef(P.OID), length('CREATE OR REPLACE ') + 1), CHR(10)))::VARCHAR2(4000) AS TEXT,
0::VARCHAR2(256) AS ORIGIN_CON_ID
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Fix LINE/TEXT mismatch in ALL_SOURCE (uses pg_get_functiondef for TEXT but PROSRC for LINE).

LINE is derived from SYS._ORA_SRC_COUNT(P.OID) which counts PROSRC lines, while TEXT now comes from pg_get_functiondef(...). This misaligns rows. Align both by switching DBA_SOURCE/ALL_SOURCE/USER_SOURCE to the same source and updating SYS._ORA_SRC_COUNT accordingly.

Apply these diffs:

  1. Use pg_get_functiondef in DBA_SOURCE TEXT and keep LINE in sync:
-   GENERATE_SERIES(1::INTEGER, SYS._ORA_SRC_COUNT(P.OID))::NUMBER AS LINE,
-   UNNEST(STRING_TO_ARRAY(P.PROSRC, CHR(10)))::VARCHAR2(4000) AS TEXT,
+   GENERATE_SERIES(1::INTEGER, SYS._ORA_SRC_COUNT(P.OID))::NUMBER AS LINE,
+   UNNEST(STRING_TO_ARRAY(substring(pg_get_functiondef(P.OID), length('CREATE OR REPLACE ') + 1), CHR(10)))::VARCHAR2(4000) AS TEXT,
  1. Count lines from the same definition text and mark the function STABLE (not IMMUTABLE):
 CREATE OR REPLACE FUNCTION SYS._ORA_SRC_COUNT(PROID OID)
 RETURNS INTEGER AS $$
@@
 BEGIN
-   SELECT
-       COUNT(1)
-   FROM
-   (
-       SELECT
-           UNNEST(STRING_TO_ARRAY(PROSRC, CHR(10)))
-       FROM
-           PG_PROC
-       WHERE
-           OID = $1
-   ) AS SQ
+   SELECT COUNT(*)
+   FROM (
+       SELECT UNNEST(
+         STRING_TO_ARRAY(
+           substring(pg_get_functiondef($1), length('CREATE OR REPLACE ') + 1),
+           CHR(10)
+         )
+       )
+   ) AS sq
    INTO RET;
    RETURN RET;
 END;
-$$ LANGUAGE PLPGSQL IMMUTABLE;
+$$ LANGUAGE PLPGSQL STABLE;

Also update USER_SOURCE to match (see comment below).

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In contrib/ivorysql_ora/src/sysview/sysview--1.0.sql around lines 365 to 367,
the ALL_SOURCE row generation is mismatched: LINE is computed from
SYS._ORA_SRC_COUNT(P.OID) (which counts PROSRC lines) while TEXT comes from
pg_get_functiondef(P.OID). Fix by switching LINE generation to count lines from
the same pg_get_functiondef text (i.e., update SYS._ORA_SRC_COUNT to base its
count on pg_get_functiondef output), change any function declaration using
IMMUTABLE to STABLE for that helper so it can call pg_get_functiondef, and
ensure USER_SOURCE/DBA_SOURCE are updated identically to use pg_get_functiondef
for TEXT and the new line-counting function for LINE so both columns are derived
from the same source.

Comment on lines 394 to 396
GENERATE_SERIES(1::INTEGER, SYS._ORA_SRC_COUNT(P.OID))::NUMBER AS LINE,
UNNEST(STRING_TO_ARRAY(P.PROSRC, CHR(10)))::VARCHAR2(4000) AS TEXT,
UNNEST(STRING_TO_ARRAY(substring(pg_get_functiondef(P.OID), length('CREATE OR REPLACE ') + 1), CHR(10)))::VARCHAR2(4000) AS TEXT,
0::VARCHAR2(256) AS ORIGIN_CON_ID
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

USER_SOURCE has the same LINE/TEXT mismatch.

Mirror the fix used in ALL_SOURCE/DBA_SOURCE so LINE and TEXT derive from the same definition text.

-   GENERATE_SERIES(1::INTEGER, SYS._ORA_SRC_COUNT(P.OID))::NUMBER AS LINE,
-   UNNEST(STRING_TO_ARRAY(substring(pg_get_functiondef(P.OID), length('CREATE OR REPLACE ') + 1), CHR(10)))::VARCHAR2(4000) AS TEXT,
+   GENERATE_SERIES(1::INTEGER, SYS._ORA_SRC_COUNT(P.OID))::NUMBER AS LINE,
+   UNNEST(STRING_TO_ARRAY(substring(pg_get_functiondef(P.OID), length('CREATE OR REPLACE ') + 1), CHR(10)))::VARCHAR2(4000) AS TEXT,

(After applying the prior _ORA_SRC_COUNT change, this view will stay consistent.)

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In contrib/ivorysql_ora/src/sysview/sysview--1.0.sql around lines 394 to 396,
the view uses SYS._ORA_SRC_COUNT(P.OID) for LINE but builds TEXT from
pg_get_functiondef(P.OID) separately, causing LINE/TEXT mismatch; refactor so
both LINE and TEXT come from the same computed definition text (e.g., compute
def := substring(pg_get_functiondef(P.OID), length('CREATE OR REPLACE ') + 1) or
equivalent in a subquery/CTE), use SYS._ORA_SRC_COUNT on that same def for
GENERATE_SERIES bounds and unnest STRING_TO_ARRAY(def, CHR(10)) for TEXT, and
keep ORIGIN_CON_ID as before.

Comment on lines +543 to +561
CASE
WHEN ARG.TYPE_NAME != '_NULL_'
THEN SYS.ORA_CASE_TRANS(ARG.TYPE_NAME::VARCHAR2)::VARCHAR2(128)
ELSE
NULL
END::VARCHAR2(128) AS TYPE_NAME,
CASE
WHEN ARG.TYPE_SUBNAME != '_NULL_'
THEN SYS.ORA_CASE_TRANS(ARG.TYPE_SUBNAME::VARCHAR2)::VARCHAR2(128)
ELSE
NULL
END::VARCHAR2(128) AS TYPE_SUBNAME,
NULL::VARCHAR2(128) AS TYPE_LINK,
NULL::VARCHAR2(128) AS TYPE_OBJECT_TYPE,
CASE
WHEN T.TYPTYPE = 'b'
WHEN ARG.TYPE_OBJECT_TYPE != '_NULL_'
THEN SYS.ORA_CASE_TRANS(ARG.TYPE_OBJECT_TYPE::VARCHAR2)::VARCHAR2(128)
ELSE
NULL
END::VARCHAR2(128) AS TYPE_OBJECT_TYPE,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Avoid sentinel string 'NULL' in TYPE_ columns; use NULLIF for cleaner SQL.*

Sentinel comparison with "!= 'NULL'" is brittle and NULL-unsafe. Prefer NULLIF and a single expression.

-   CASE
-      WHEN ARG.TYPE_NAME != '_NULL_'
-           THEN SYS.ORA_CASE_TRANS(ARG.TYPE_NAME::VARCHAR2)::VARCHAR2(128)
-      ELSE
-           NULL
-   END::VARCHAR2(128) AS TYPE_NAME,
+   SYS.ORA_CASE_TRANS(NULLIF(ARG.TYPE_NAME, '_NULL_')::VARCHAR2)::VARCHAR2(128) AS TYPE_NAME,

-   CASE
-      WHEN ARG.TYPE_SUBNAME != '_NULL_'
-           THEN SYS.ORA_CASE_TRANS(ARG.TYPE_SUBNAME::VARCHAR2)::VARCHAR2(128)
-      ELSE
-           NULL
-   END::VARCHAR2(128) AS TYPE_SUBNAME,
+   SYS.ORA_CASE_TRANS(NULLIF(ARG.TYPE_SUBNAME, '_NULL_')::VARCHAR2)::VARCHAR2(128) AS TYPE_SUBNAME,

-   CASE
-      WHEN ARG.TYPE_OBJECT_TYPE != '_NULL_'
-           THEN SYS.ORA_CASE_TRANS(ARG.TYPE_OBJECT_TYPE::VARCHAR2)::VARCHAR2(128)
-      ELSE
-           NULL
-   END::VARCHAR2(128) AS TYPE_OBJECT_TYPE,
+   SYS.ORA_CASE_TRANS(NULLIF(ARG.TYPE_OBJECT_TYPE, '_NULL_')::VARCHAR2)::VARCHAR2(128) AS TYPE_OBJECT_TYPE,

If possible, also switch the SRF to emit NULL instead of 'NULL'.

Also applies to: 557-561, 563-565

Comment on lines +922 to +1004
if (foundDep->deptype == DEPENDENCY_TYPE &&
(object->classId == RelationRelationId ||
object->classId == PackageRelationId ||
object->classId == PackageBodyRelationId ))
{
if (foundDep->classid == 0)
continue;

switch ((&otherObject)->classId)
{
case ProcedureRelationId:

if (foundDep->deptype == DEPENDENCY_TYPE)
{
/* Add function Oid to the dependancy-oids list */
if (numDependentFuncPkgOids >= maxDependentFuncPkgOids)
{
/* enlarge array if needed */
maxDependentFuncPkgOids *= 2;
dependentFuncPkgOids = (ObjectFunOrPkg *)
repalloc(dependentFuncPkgOids,
maxDependentFuncPkgOids * sizeof(ObjectFunOrPkg));
}

dependentFuncPkgOids[numDependentFuncPkgOids].objectId = foundDep->objid;
dependentFuncPkgOids[numDependentFuncPkgOids].flags = ProcedureRelationId;
numDependentFuncPkgOids++;

/* delete the tuple */
CatalogTupleDelete(*depRel, &tup->t_self);
}
break;

case PackageRelationId:

if (foundDep->deptype == DEPENDENCY_TYPE)
{
/* Add package Oid to the dependancy-oids list */
if (numDependentFuncPkgOids >= maxDependentFuncPkgOids)
{
/* enlarge array if needed */
maxDependentFuncPkgOids *= 2;
dependentFuncPkgOids = (ObjectFunOrPkg *)
repalloc(dependentFuncPkgOids,
maxDependentFuncPkgOids * sizeof(ObjectFunOrPkg));
}

dependentFuncPkgOids[numDependentFuncPkgOids].objectId = foundDep->objid;
dependentFuncPkgOids[numDependentFuncPkgOids].flags = PackageRelationId;
numDependentFuncPkgOids++;

/* delete the tuple */
CatalogTupleDelete(*depRel, &tup->t_self);
}
break;
case PackageBodyRelationId:

if (foundDep->deptype == DEPENDENCY_TYPE)
{
/* Add package body Oid to the dependancy-oids list */
if (numDependentFuncPkgOids >= maxDependentFuncPkgOids)
{
/* enlarge array if needed */
maxDependentFuncPkgOids *= 2;
dependentFuncPkgOids = (ObjectFunOrPkg *)
repalloc(dependentFuncPkgOids,
maxDependentFuncPkgOids * sizeof(ObjectFunOrPkg));
}

dependentFuncPkgOids[numDependentFuncPkgOids].objectId = foundDep->objid;
dependentFuncPkgOids[numDependentFuncPkgOids].flags = PackageBodyRelationId;
numDependentFuncPkgOids++;

/* delete the tuple */
CatalogTupleDelete(*depRel, &tup->t_self);
}
break;
default:
break;
}

continue;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

ReleaseDeletionLock for TYPE dependencies to avoid lock leak.

You acquire a deletion lock on otherObject (Line 872) but skip recursion and return to the loop for DEPENDENCY_TYPE without releasing it. This can hold AccessExclusiveLock indefinitely and cause deadlocks.

Apply:

 		if (foundDep->deptype == DEPENDENCY_TYPE &&
 			(object->classId == RelationRelationId ||
 			 object->classId == PackageRelationId ||
 			 object->classId == PackageBodyRelationId ))
 		{
 			if (foundDep->classid == 0)
 				continue;
@@
 			}
 
-			continue;
+			/* We're not recursing to this object; release the lock we took. */
+			ReleaseDeletionLock(&otherObject);
+			continue;
 		}
📝 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.

Suggested change
if (foundDep->deptype == DEPENDENCY_TYPE &&
(object->classId == RelationRelationId ||
object->classId == PackageRelationId ||
object->classId == PackageBodyRelationId ))
{
if (foundDep->classid == 0)
continue;
switch ((&otherObject)->classId)
{
case ProcedureRelationId:
if (foundDep->deptype == DEPENDENCY_TYPE)
{
/* Add function Oid to the dependancy-oids list */
if (numDependentFuncPkgOids >= maxDependentFuncPkgOids)
{
/* enlarge array if needed */
maxDependentFuncPkgOids *= 2;
dependentFuncPkgOids = (ObjectFunOrPkg *)
repalloc(dependentFuncPkgOids,
maxDependentFuncPkgOids * sizeof(ObjectFunOrPkg));
}
dependentFuncPkgOids[numDependentFuncPkgOids].objectId = foundDep->objid;
dependentFuncPkgOids[numDependentFuncPkgOids].flags = ProcedureRelationId;
numDependentFuncPkgOids++;
/* delete the tuple */
CatalogTupleDelete(*depRel, &tup->t_self);
}
break;
case PackageRelationId:
if (foundDep->deptype == DEPENDENCY_TYPE)
{
/* Add package Oid to the dependancy-oids list */
if (numDependentFuncPkgOids >= maxDependentFuncPkgOids)
{
/* enlarge array if needed */
maxDependentFuncPkgOids *= 2;
dependentFuncPkgOids = (ObjectFunOrPkg *)
repalloc(dependentFuncPkgOids,
maxDependentFuncPkgOids * sizeof(ObjectFunOrPkg));
}
dependentFuncPkgOids[numDependentFuncPkgOids].objectId = foundDep->objid;
dependentFuncPkgOids[numDependentFuncPkgOids].flags = PackageRelationId;
numDependentFuncPkgOids++;
/* delete the tuple */
CatalogTupleDelete(*depRel, &tup->t_self);
}
break;
case PackageBodyRelationId:
if (foundDep->deptype == DEPENDENCY_TYPE)
{
/* Add package body Oid to the dependancy-oids list */
if (numDependentFuncPkgOids >= maxDependentFuncPkgOids)
{
/* enlarge array if needed */
maxDependentFuncPkgOids *= 2;
dependentFuncPkgOids = (ObjectFunOrPkg *)
repalloc(dependentFuncPkgOids,
maxDependentFuncPkgOids * sizeof(ObjectFunOrPkg));
}
dependentFuncPkgOids[numDependentFuncPkgOids].objectId = foundDep->objid;
dependentFuncPkgOids[numDependentFuncPkgOids].flags = PackageBodyRelationId;
numDependentFuncPkgOids++;
/* delete the tuple */
CatalogTupleDelete(*depRel, &tup->t_self);
}
break;
default:
break;
}
continue;
}
if (foundDep->deptype == DEPENDENCY_TYPE &&
(object->classId == RelationRelationId ||
object->classId == PackageRelationId ||
object->classId == PackageBodyRelationId))
{
if (foundDep->classid == 0)
continue;
switch ((&otherObject)->classId)
{
case ProcedureRelationId:
/* existing handling… */
break;
case PackageRelationId:
/* existing handling… */
break;
case PackageBodyRelationId:
/* existing handling… */
break;
default:
break;
}
/* We're not recursing to this object; release the lock we took. */
ReleaseDeletionLock(&otherObject);
continue;
}
🤖 Prompt for AI Agents
In src/backend/catalog/dependency.c around lines 922 to 1004, the code takes a
deletion lock on otherObject (around line 872) but when handling DEPENDENCY_TYPE
it skips recursion and uses multiple continue paths without releasing that lock,
causing a lock leak; fix by calling ReleaseDeletionLock(&otherObject) before any
early continue in this block: add ReleaseDeletionLock(&otherObject) immediately
before the "if (foundDep->classid == 0) continue;" and also add
ReleaseDeletionLock(&otherObject) just before the "continue;" that follows the
switch (i.e., right before the block's ending continue) so the lock is always
released when recursion is skipped.

Comment on lines +1056 to +1065
/* sort to avoid OID counter reverses */
if (numDependentFuncPkgOids > 1)
{
/* Sort and de-dup OID arrays, so we can use binary search. */
pg_qsort(dependentFuncPkgOids, numDependentFuncPkgOids,
sizeof(ObjectFunOrPkg), object_funpkgoid_comparator);

numDependentFuncPkgOids = qunique((void *)dependentFuncPkgOids, numDependentFuncPkgOids,
sizeof(ObjectFunOrPkg), object_funpkgoid_comparator);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix comparator/dedup: don’t collapse different classes with same OID.

object_funpkgoid_comparator compares only objectId. qunique() will drop entries where objectId matches but class (flags) differs (e.g., function OID == package OID), skipping needed invalidations.

Apply:

 int
 object_funpkgoid_comparator(const void *a, const void *b)
 {
 	const ObjectFunOrPkg *obja = (const ObjectFunOrPkg *) a;
 	const ObjectFunOrPkg *objb = (const ObjectFunOrPkg *) b;
 
 	if (obja->objectId > objb->objectId)
 		return -1;
 	if (obja->objectId < objb->objectId)
 		return 1;
-
-	return 0;
+	/* Tie-break on class (stored in flags) to keep function/pkg distinct. */
+	if (obja->flags < objb->flags)
+		return -1;
+	if (obja->flags > objb->flags)
+		return 1;
+	return 0;
 }

Also applies to: 2709-2720

--the datatype returned by function is table
create table test(id integer,name varchar(23));

drop function test;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix DROP FUNCTION syntax: add explicit argument list (zero IN args here).

drop function test; is ambiguous/invalid in PG-compatible syntax. This function has zero IN parameters (only an OUT), so the drop should target test() explicitly.

Apply this diff:

-drop function test;
+drop function if exists test();
🤖 Prompt for AI Agents
In src/pl/plisql/src/sql/plisql_out_parameter.sql around line 946, the DROP
FUNCTION invocation is ambiguous/invalid; replace the bare "drop function test;"
with an explicit zero-argument signature by calling "DROP FUNCTION test();" so
the DB targets the function with no IN parameters (only an OUT) unambiguously.

Comment on lines +1214 to +1223
CREATE OR REPLACE PACKAGE BODY pkg1 is
FUNCTION pfun(v tmpe%ROWTYPE) RETURN tmpe%ROWTYPE AS
r tmpe%ROWTYPE;
BEGIN
var1.phone_number := 'abc';
r.phone_number := var1;
RETURN v;
END;
END;
/
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Bug: assigning a row to a scalar column field.

In pkg1.pfun body, “r.phone_number := var1;” assigns a tmpe%ROWTYPE to a scalar. Use the specific field.

Apply:

-    var1.phone_number := 'abc';
-    r.phone_number :=  var1;
-    RETURN v;
+    var1.phone_number := 'abc';
+    r.phone_number := var1.phone_number;
+    RETURN v;
🤖 Prompt for AI Agents
In src/pl/plisql/src/sql/plisql_type_rowtype.sql around lines 1214 to 1223, the
function body assigns an entire tmpe%ROWTYPE (var1) to the scalar column
r.phone_number which is invalid; change the assignment to use the specific field
(e.g., r.phone_number := var1.phone_number) and ensure the function returns the
intended row (if you meant to return the modified row, return r instead of v).

Comment on lines +1728 to +1749
--Record that variables are allowed in the VALUES clause of an INSERT statement
DECLARE
v1 t1%ROWTYPE;
BEGIN
FOR i IN 1 .. 5 LOOP
v1.id := i;
v1.name := 'a' || i;
INSERT INTO t1 VALUES v1;
END LOOP;
END;
/
SELECT * FROM t1;

--Record that variables are allowed in the INTO subclause of a RETURNING clause
DECLARE
v1 t1%ROWTYPE;
v2 t1%ROWTYPE;
BEGIN
v1.id := 6;
v1.name := 'a6';
INSERT INTO t1 VALUES v1 returning id,name into v2;
raise notice 'v2 = %', v2;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Harness robustness: expected-failure statements may abort test run.

Many blocks are intended to fail (comments say “failed”/“not support”). Unless pg_regress/psql is configured to continue on errors, these will abort the script. Wrap rainy-day cases in an anonymous block with EXCEPTION to assert failure but keep the run going.

Example pattern:

DO $$
BEGIN
  -- expected to fail
  BEGIN
    INSERT INTO t1 VALUES v1, v1;
    RAISE EXCEPTION 'Expected failure did not occur';
  EXCEPTION WHEN others THEN
    RAISE NOTICE 'Expected error: %', SQLERRM;
  END;
END$$;

Also applies to: 1880-1898, 1900-1916, 1960-1985, 1994-2001, 2065-2074, 2081-2093, 2096-2115

🤖 Prompt for AI Agents
In src/pl/plisql/src/sql/plisql_type_rowtype.sql around lines 1728-1749 (and
similarly for ranges 1880-1898, 1900-1916, 1960-1985, 1994-2001, 2065-2074,
2081-2093, 2096-2115), several PL/pgSQL blocks are expected to fail but are
written plainly and will abort the test run; change each of those "expected to
fail" examples to execute inside an anonymous DO block that wraps the failing
statement in its own BEGIN...EXCEPTION WHEN others THEN handler so the error is
caught and the run continues, and inside the inner block raise an explicit
exception if the failing statement does not error (to assert failure), while on
exception raise a NOTICE with SQLERRM to record the expected error; ensure each
replacement preserves the original failing statement and message text.

Comment on lines +2207 to +2213
a t1%ROWTYPE;
BEGIN
RETURN v;
END;
/
SELECT prostatus FROM pg_proc WHERE proname like 'fun9'; --v

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Bug: undefined identifier in return.

fun9() returns t1%ROWTYPE but returns “v”, which is not defined. Should return the local “a”.

-  RETURN v;
+  RETURN a;
🤖 Prompt for AI Agents
In src/pl/plisql/src/sql/plisql_type_rowtype.sql around lines 2207 to 2213, the
function fun9 declares a local row variable (a) and is declared to return
t1%ROWTYPE but the RETURN statement uses an undefined identifier "v"; change the
RETURN to use the local row variable "a" (RETURN a) so the function returns the
correct, defined row variable.

Comment on lines +2221 to +2226
a t1%ROWTYPE;
b t1%ROWTYPE;
BEGIN
RETURN v;
END;
/
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Bug: wrong return in scalar-returning function.

fun10() returns int but returns “v” (undefined). Return a scalar.

-  RETURN v;
+  RETURN 1;
📝 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.

Suggested change
a t1%ROWTYPE;
b t1%ROWTYPE;
BEGIN
RETURN v;
END;
/
a t1%ROWTYPE;
b t1%ROWTYPE;
BEGIN
RETURN 1;
END;
/
🤖 Prompt for AI Agents
In src/pl/plisql/src/sql/plisql_type_rowtype.sql around lines 2221-2226, the
function fun10 is declared to return an int but currently returns an undefined
identifier "v" (and only rowtype variables a and b are declared); replace the
invalid RETURN v with a proper scalar integer return: either return a specific
integer field from one of the rowtype variables (e.g., RETURN a.some_int_column)
or declare and set a local integer variable (e.g., declare i integer := <expr>;
… RETURN i;), ensuring the returned identifier exists and is assigned an integer
value before RETURN.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review continued from previous batch...

Comment on lines +13822 to +13858
/* switch context to build tuplestore */
per_query_ctx = rsinfo->econtext->ecxt_per_query_memory;
oldcontext = MemoryContextSwitchTo(per_query_ctx);

/*
* build tupdesc for result tuples.
*/
tupdesc = CreateTemplateTupleDesc(NUM_RETURN_COLUMNS);
TupleDescInitEntry(tupdesc, (AttrNumber) 1, "oid",
OIDOID, -1, 0);
TupleDescInitEntry(tupdesc, (AttrNumber) 2, "position",
INT4OID, -1, 0);
TupleDescInitEntry(tupdesc, (AttrNumber) 3, "type_name",
TEXTOID, -1, 0);
TupleDescInitEntry(tupdesc, (AttrNumber) 4, "type_subname",
TEXTOID, -1, 0);
TupleDescInitEntry(tupdesc, (AttrNumber) 5, "type_object_type",
TEXTOID, -1, 0);
TupleDescInitEntry(tupdesc, (AttrNumber) 6, "data_length",
INT4OID, -1, 0);
TupleDescInitEntry(tupdesc, (AttrNumber) 7, "data_precision",
INT4OID, -1, 0);
TupleDescInitEntry(tupdesc, (AttrNumber) 8, "data_scale",
INT4OID, -1, 0);
TupleDescInitEntry(tupdesc, (AttrNumber) 9, "pls_type",
TEXTOID, -1, 0);

/*
* put all the tuples into a tuplestore by one scan of the hashtable.
* this way we can avoid the possible issue of the hashtable changing between calls.
*/
tupstore =
tuplestore_begin_heap(rsinfo->allowedModes & SFRM_Materialize_Random,
false, work_mem);

MemoryContextSwitchTo(oldcontext);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

tuplestore and lock handling; finalize and close with matching lock

  • tuplestore_donestoring(tupstore) is commented out; call it before returning to signal completion.
  • table_close(proc_rel, NoLock) should match the lock you acquired (AccessShareLock) per project conventions.

Also free rettypename after use to avoid per-query bloat while scanning pg_proc.

Apply this diff in the SRF:

@@
-    if (rettypename != NULL)
-      pg_get_function_arg_reference_typerowtype_internal(&tupstore, tupdesc,
-                                        rettypename, 0, 
-                                        form_proc->oid, values, nulls);
+    if (rettypename != NULL)
+    {
+      pg_get_function_arg_reference_typerowtype_internal(&tupstore, tupdesc,
+                                        rettypename, 0, 
+                                        form_proc->oid, values, nulls);
+      pfree(rettypename);
+    }
@@
-  /* clean up and return the generated tuplestore */
-  //tuplestore_donestoring(tupstore);
-  table_close(proc_rel, NoLock);
+  /* clean up and return the generated tuplestore */
+  tuplestore_donestoring(tupstore);
+  table_close(proc_rel, AccessShareLock);

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/backend/utils/adt/ruleutils.c around lines 13822-13858, the SRF currently
omits finalization/cleanup steps: call tuplestore_donestoring(tupstore) just
after finishing populating the tuplestore to signal completion, replace
table_close(proc_rel, NoLock) with table_close(proc_rel, AccessShareLock) so the
close matches the AccessShareLock acquired earlier, and pfree(rettypename) (or
the appropriate memory free call used in this codebase) after you finish using
rettypename to avoid per-query memory bloat during the pg_proc scan.

Comment on lines +13866 to +13900
Form_pg_proc form_proc = (Form_pg_proc) GETSTRUCT(proc_tuple);
int numargs = form_proc->pronargs;
char **p_argtypeNames = NULL;
char *rettypename = NULL;
int nth_arg;
Datum values[NUM_RETURN_COLUMNS];
bool nulls[NUM_RETURN_COLUMNS];

get_func_typename_info(proc_tuple, &p_argtypeNames, &rettypename);

if (p_argtypeNames == NULL && rettypename == NULL)
continue;

MemSet(nulls, 0, sizeof(nulls));

if (rettypename != NULL)
pg_get_function_arg_reference_typerowtype_internal(&tupstore, tupdesc,
rettypename, 0,
form_proc->oid, values, nulls);

if (p_argtypeNames!= NULL)
{
for (nth_arg = 0; nth_arg < numargs; nth_arg++)
{
MemSet(nulls, 0, sizeof(nulls));

if (strcmp(p_argtypeNames[nth_arg], "") != 0)
pg_get_function_arg_reference_typerowtype_internal(&tupstore, tupdesc,
p_argtypeNames[nth_arg], nth_arg + 1,
form_proc->oid, values, nulls);
}

pfree(p_argtypeNames);
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Array length vs pronargs; potential out-of-bounds on p_argtypeNames

get_func_typename_info returns a char** but not its length; indexing it with form_pg_proc->pronargs assumes the array length equals pronargs (input-only), which may not hold if proallargtypes is present (OUT/TABLE args). Prefer deriving the count from get_func_arg_info() or extending get_func_typename_info to also return the array length, then iterate up to that length.

Run this to inspect get_func_typename_info’s allocation semantics and whether it guarantees length == pronargs:


🏁 Script executed:

#!/bin/bash
rg -n -C2 "get_func_typename_info\(" src/backend/fmgr/funcapi.c
rg -n -C3 "protypenames|prorettypename" src/backend/fmgr/funcapi.c

Length of output: 268


🏁 Script executed:

#!/bin/bash
# locate get_func_typename_info definition and references across the repo
rg -n "get_func_typename_info" -C3 .

# find where protypenames and prorettypename are populated within that function
rg -n "protypenames" -C3 .
rg -n "prorettypename" -C3 .

Length of output: 13697


Use actual array length from get_func_typename_info instead of pronargs
ruleutils.c indexes p_argtypeNames up to form_proc->pronargs, but get_func_typename_info allocates exactly nelems entries (no guarantee nelems==pronargs), risking out-of-bounds. Change get_func_typename_info to return its element count (or add a NULL terminator) and iterate by that count rather than pronargs.

Comment on lines +14070 to +14075
values[0] = ObjectIdGetDatum(funcOid);
values[1] = Int32GetDatum(position);
values[2] = CStringGetTextDatum("_NULL_");
values[3] = CStringGetTextDatum("_NULL_");
values[4] = CStringGetTextDatum("_NULL_");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Use NULLs instead of sentinel strings "NULL"

Returning "NULL" text for type_name/type_subname/type_object_type conflates NULL with a string value and complicates consumers. Leave those columns NULL when not applicable.

Included in the main helper diff above.

🤖 Prompt for AI Agents
In src/backend/utils/adt/ruleutils.c around lines 14070-14075, the code writes
the sentinel string "_NULL_" into the type_name/type_subname/type_object_type
columns; instead set those columns to SQL NULL instead. Replace the
CStringGetTextDatum("_NULL_") assignments with values[2] = values[3] = values[4]
= (Datum) 0 and set the corresponding nulls[2] = nulls[3] = nulls[4] = true (so
the tuple builder emits SQL NULLs), leaving other value/null handling unchanged.

Comment on lines +14076 to +14111
if (fieldTypeId == ORACHARCHAROID ||
fieldTypeId == ORACHARBYTEOID ||
fieldTypeId == ORAVARCHARCHAROID ||
fieldTypeId == ORAVARCHARBYTEOID ||
fieldTypeId == BPCHAROID ||
fieldTypeId == VARCHAROID)
{
data_length = fieldTypMod - VARHDRSZ;
values[5] = Int32GetDatum(data_length);
nulls[6] = true;
nulls[7] = true;
values[8] = CStringGetTextDatum(DatumGetCString(DirectFunctionCall1(regtypeout, fieldTypeId)));
}
else if (fieldTypeId == NUMERICOID ||
fieldTypeId == NUMBEROID)
{
/* precision (ie, max number of digits) is in upper bits of typmod */
data_precision = Int32GetDatum(((fieldTypMod - VARHDRSZ) >> 16) & 0xffff);
data_scale = Int32GetDatum((fieldTypMod - VARHDRSZ) & 0xffff);
nulls[5] = true;
values[6] = data_precision;
values[7] = data_scale;
values[8] = CStringGetTextDatum("number");
}
else
{
nulls[5] = true;
nulls[6] = true;
nulls[7] = true;
if (fieldTypeId == INT2OID ||
fieldTypeId == INT4OID ||
fieldTypeId == INT8OID)
values[8] = CStringGetTextDatum("number");
else
values[8] = CStringGetTextDatum(DatumGetCString(DirectFunctionCall1(regtypeout, fieldTypeId)));
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Numeric/char typmod handling and INT/Datum confusion

  • Do not assign Datum to int variables (data_precision/data_scale). Compute as ints, wrap with Int32GetDatum when assigning into values[].
  • Guard typmod < 0 to avoid negative lengths/precision; set the corresponding columns NULL when typmod is unknown.

Included in the main helper diff above.

🤖 Prompt for AI Agents
In src/backend/utils/adt/ruleutils.c around lines 14076-14111, the code
incorrectly assigns Datum values into int variables for numeric precision/scale
and fails to guard against negative typmod; change data_precision and data_scale
to be computed as plain ints from (fieldTypMod - VARHDRSZ) only when fieldTypMod
>= 0, then wrap with Int32GetDatum when storing into values[]; similarly, only
compute data_length for char types when fieldTypMod >= 0 and otherwise set the
corresponding values[] entries to NULL (set the matching nulls[] flags); ensure
you do not mix Datum and int types (use int temporaries, then Int32GetDatum(...)
on assignment) and correct the nulls/values indices so unknown typmods produce
NULLs.

Comment on lines +885 to +887
check_referenced_objects = true;
plisql_referenced_objects = NIL;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Re-entrant, exception-safe dependency tracking in package body compile.

Same global-state problem as in pl_comp.c. Save/restore and free deeply.

Apply:

@@
-    check_referenced_objects = true;
-    plisql_referenced_objects = NIL;
+    bool prev_check_referenced_objects = check_referenced_objects;
+    List *prev_plisql_referenced_objects = plisql_referenced_objects;
+    check_referenced_objects = true;
+    plisql_referenced_objects = NIL;
@@
-        list_free(plisql_referenced_objects);
+        list_free_deep(plisql_referenced_objects);
+        plisql_referenced_objects = prev_plisql_referenced_objects;
+        check_referenced_objects = prev_check_referenced_objects;
@@
-    check_referenced_objects = false;
-    list_free(plisql_referenced_objects);
+    list_free_deep(plisql_referenced_objects);
+    plisql_referenced_objects = prev_plisql_referenced_objects;
+    check_referenced_objects = prev_check_referenced_objects;

Also applies to: 1001-1003, 1036-1038

🤖 Prompt for AI Agents
In src/pl/plisql/src/pl_package.c around lines 885-887 (and similarly update
blocks at 1001-1003 and 1036-1038), the function sets global state
check_referenced_objects = true and plisql_referenced_objects = NIL without
saving/restoring or freeing the prior state; modify each block to save the old
values (e.g. bool old_check = check_referenced_objects; List
*old_plisql_referenced_objects = plisql_referenced_objects), then set the new
values, wrap the compile/processing code in a PG_TRY/PG_FINALLY (or equivalent)
so that in the FINALLY you restore check_referenced_objects = old_check and set
plisql_referenced_objects = old_plisql_referenced_objects, and if a new list was
allocated free it with list_free_deep(plisql_referenced_objects) before
restoring; ensure this pattern is applied at all three locations to make
dependency tracking re-entrant and exception-safe.

Comment on lines +1158 to +1160
check_referenced_objects = true;
plisql_referenced_objects = NIL;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Same fix for package spec compile: save/restore referenced-objects tracking.

Apply:

@@
-    check_referenced_objects = true;
-    plisql_referenced_objects = NIL;
+    bool prev_check_referenced_objects = check_referenced_objects;
+    List *prev_plisql_referenced_objects = plisql_referenced_objects;
+    check_referenced_objects = true;
+    plisql_referenced_objects = NIL;
@@
-    check_referenced_objects = false;
-    list_free(plisql_referenced_objects);
+    list_free_deep(plisql_referenced_objects);
+    plisql_referenced_objects = prev_plisql_referenced_objects;
+    check_referenced_objects = prev_check_referenced_objects;

Also applies to: 1322-1324

🤖 Prompt for AI Agents
In src/pl/plisql/src/pl_package.c around lines 1158-1160 (and similarly at
1322-1324), the code unconditionally sets check_referenced_objects = true and
plisql_referenced_objects = NIL for package spec compilation but does not save
and restore the previous tracking state; modify both places to (1) save the
current values of check_referenced_objects and plisql_referenced_objects into
local temporaries, (2) set check_referenced_objects = true and
plisql_referenced_objects = NIL for the spec compile, and (3) after the spec
compile completes (including on error paths), restore the saved values so the
global tracking state is unchanged for callers.

Comment on lines +3296 to 3340

if (oids[i] == ORACHARCHAROID ||
oids[i] == ORACHARBYTEOID ||
oids[i] == ORAVARCHARCHAROID ||
oids[i] == ORAVARCHARBYTEOID ||
oids[i] == BPCHAROID ||
oids[i] == VARCHAROID)
{
paramObjectTypes[i] = CStringGetTextDatum("_NULL_");

if (typmode != -1)
paramDataLengths[i] = Int32GetDatum(typmode - VARHDRSZ);
else
paramDataLengths[i] = Int32GetDatum(0);

paramDataPrecisions[i] = Int32GetDatum(0);
paramDataScales[i] = Int32GetDatum(0);
}
else if (oids[i] == NUMERICOID ||
oids[i] == NUMBEROID)
{
paramObjectTypes[i] = CStringGetTextDatum("_NULL_");
paramDataLengths[i] = Int32GetDatum(0);

/* precision (i.e. max number of digits) is in upper bits of typmod */
paramDataPrecisions[i] = Int32GetDatum(((typmode - VARHDRSZ) >> 16) & 0xffff);
paramDataScales[i] = Int32GetDatum((typmode - VARHDRSZ) & 0xffff);
}
else
{
if (type_is_rowtype(oids[i]))
paramObjectTypes[i] = CStringGetTextDatum("table");
else
paramObjectTypes[i] = CStringGetTextDatum("_NULL_");

paramDataLengths[i] = Int32GetDatum(0);
paramDataPrecisions[i] = Int32GetDatum(0);
paramDataScales[i] = Int32GetDatum(0);
}

i++;
}

foreach (lc, subproc->arg)
{
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Guard NUMERIC typmod decode when typmod = -1.

(typmode - VARHDRSZ) is undefined for -1; leads to bogus precision/scale. Set zeros when typmod < 0.

Apply:

@@
-            else if (oids[i] == NUMERICOID ||
-                    oids[i] == NUMBEROID)
+            else if (oids[i] == NUMERICOID ||
+                    oids[i] == NUMBEROID)
             {
                 paramObjectTypes[i] = CStringGetTextDatum("_NULL_");
                 paramDataLengths[i] = Int32GetDatum(0);
-
-                /* precision (i.e. max number of digits) is in upper bits of typmod */
-                paramDataPrecisions[i] = Int32GetDatum(((typmode - VARHDRSZ) >> 16) & 0xffff);
-                paramDataScales[i] = Int32GetDatum((typmode - VARHDRSZ) & 0xffff);
+                if (typmode >= 0)
+                {
+                    /* precision (max digits) is in upper bits of typmod */
+                    paramDataPrecisions[i] = Int32GetDatum(((typmode - VARHDRSZ) >> 16) & 0xffff);
+                    paramDataScales[i] = Int32GetDatum((typmode - VARHDRSZ) & 0xffff);
+                }
+                else
+                {
+                    paramDataPrecisions[i] = Int32GetDatum(0);
+                    paramDataScales[i] = Int32GetDatum(0);
+                }
             }
@@
-            else if (oids[i] == NUMERICOID ||
-                    oids[i] == NUMBEROID)
+            else if (oids[i] == NUMERICOID ||
+                    oids[i] == NUMBEROID)
             {
                 paramObjectTypes[i] = CStringGetTextDatum("_NULL_");
                 paramDataLengths[i] = Int32GetDatum(0);
-
-                /* precision (i.e. max number of digits) is in upper bits of typmod */
-                paramDataPrecisions[i] = Int32GetDatum(((typmode - VARHDRSZ) >> 16) & 0xffff);
-                paramDataScales[i] = Int32GetDatum((typmode - VARHDRSZ) & 0xffff);
+                if (typmode >= 0)
+                {
+                    /* precision (max digits) is in upper bits of typmod */
+                    paramDataPrecisions[i] = Int32GetDatum(((typmode - VARHDRSZ) >> 16) & 0xffff);
+                    paramDataScales[i] = Int32GetDatum((typmode - VARHDRSZ) & 0xffff);
+                }
+                else
+                {
+                    paramDataPrecisions[i] = Int32GetDatum(0);
+                    paramDataScales[i] = Int32GetDatum(0);
+                }
             }

Also applies to: 3373-3411

@bigplaice bigplaice closed this Aug 29, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant