Skip to content

Oracle Force View Support.#875

Merged
gaoxueyu merged 4 commits intoIvorySQL:masterfrom
NotHimmel:master
Sep 17, 2025
Merged

Oracle Force View Support.#875
gaoxueyu merged 4 commits intoIvorySQL:masterfrom
NotHimmel:master

Conversation

@NotHimmel
Copy link
Copy Markdown
Collaborator

@NotHimmel NotHimmel commented Sep 12, 2025

Summary by CodeRabbit

  • New Features

    • Oracle-compatible FORCE VIEW support: CREATE [OR REPLACE] FORCE VIEW and ALTER VIEW … COMPILE.
    • Automatic invalidation/compilation of dependent views in compatible mode; queries surface stored definitions for force views.
    • Added Oracle-style system views: SYS.DBA_VIEWS, SYS.ALL_VIEWS, SYS.USER_VIEWS.
  • Improvements

    • More permissive DDL with views under Oracle compatibility, reducing hard failures by invalidating and recompiling views when needed.
  • Documentation

    • Clarified comments about DROP EXTENSION behavior relative to FORCE VIEW.
  • Tests

    • Added extensive regression tests for FORCE VIEW scenarios and updated view-related cases.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Sep 12, 2025

Important

Review skipped

Review was skipped due to path filters

⛔ Files ignored due to path filters (2)
  • contrib/tsm_system_rows/expected/tsm_system_rows_1.out is excluded by !**/*.out
  • contrib/tsm_system_time/expected/tsm_system_time_1.out is excluded by !**/*.out

CodeRabbit blocks several paths by default. You can override this behavior by explicitly including those paths in the path filters. For example, including **/dist/** will override the default block on the dist directory, by removing the pattern from both the lists.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

Adds Oracle “FORCE VIEW” support: new pg_force_view catalog, parser/AST and ALTER VIEW COMPILE grammar, view creation/placeholder/compile/invalidate/validate APIs, parser-time compilation hooks, ruleutils integration, dependency/deletion handling, and ALTER TABLE integration. Introduces SYS.DBA_VIEWS/ALL_VIEWS/USER_VIEWS. Updates build scripts and adds extensive regression tests; minor comment tweaks elsewhere.

Changes

Cohort / File(s) Change summary
Oracle SYS views
contrib/ivorysql_ora/src/sysview/sysview--1.0.sql
Adds SYS.DBA_VIEWS, SYS.ALL_VIEWS, SYS.USER_VIEWS querying pg_class views, Oracle-style columns, ORA_CASE_TRANS, grants.
FORCE VIEW catalog and build wiring
src/include/catalog/pg_force_view.h, src/include/catalog/Makefile, src/include/catalog/meson.build
Introduces pg_force_view catalog, TOAST, unique index, syscache, identifier-case constants; wires header into build systems.
View subsystem (APIs and behavior)
src/backend/commands/view.c, src/include/commands/view.h
Implements force-view lifecycle: store/delete, detect, get def, compile, invalidate/validate, placeholder handling; exposes new externs.
Parser/AST and grammar
src/backend/oracle_parser/ora_gram.y, src/include/nodes/parsenodes.h
Adds CREATE [FORCE] VIEW (force flag, stmt_literal), ALTER VIEW ... COMPILE via AT_ForceViewCompile; extends AlterTableType.
Planner/parser hooks for FORCE VIEW
src/backend/parser/parse_relation.c, src/backend/parser/parse_clause.c
On referencing/targeting a force view in ORA mode, attempt compile; reopen relation, error if compilation fails.
Ruleutils and rewrite guards
src/backend/utils/adt/ruleutils.c, src/backend/rewrite/rewriteHandler.c
pg_get_viewdef returns stored force view SQL; guard get_view_query against force views in ORA mode.
Dependency and DDL integration
src/backend/catalog/dependency.c, src/backend/commands/tablecmds.c
DROP/DELETE flows: detect all-view dependency sets, invalidate views then refind deps; DeleteForceView on view deletion; ALTER VIEW COMPILE via new AlterTableType; ORA-specific rewrite handling during type alters.
Tests and scheduling
src/oracle_test/regress/sql/ora_force_view.sql, src/oracle_test/regress/parallel_schedule, src/oracle_test/regress/sql/create_view.sql, src/oracle_test/modules/test_ddl_deparse/test_ddl_deparse.c
Adds comprehensive FORCE VIEW regression tests and schedule entry; simplifies a create_view test; deparse adds AT_ForceViewCompile case (typo in string).
Minor comment updates
contrib/tsm_system_rows/sql/tsm_system_rows.sql, contrib/tsm_system_time/sql/tsm_system_time.sql
Clarifies DROP EXTENSION comments re Oracle FORCE VIEW; no functional changes.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor U as User
  participant P as Parser
  participant V as ViewMgr
  participant C as Catalog (pg_force_view)
  participant R as Rewriter/Ruleutils

  Note over U,P: CREATE FORCE VIEW v AS SELECT ...
  U->>P: Parse CREATE FORCE VIEW
  P->>V: DefineView(force=true, stmt_literal)
  alt compilation succeeds
    V->>C: StoreForceViewQuery(replace=false, ...)
    V-->>U: View created
  else compilation fails
    V->>V: CreateForceVirtualPlaceholder
    V->>C: StoreForceViewQuery(replace=false, source=stmt_literal)
    V-->>U: Placeholder created (warning)
  end

  Note over U,P: SELECT * FROM v
  U->>P: Parse query
  P->>V: rel_is_force_view(v)
  alt Is force view
    P->>V: compile_force_view(v)
    alt compile ok
      P->>R: get_view_query(v)
      R-->>P: Query tree
      P-->>U: Proceed
    else compile fails
      P-->>U: ERROR: view has errors
    end
  else Not force view
    P->>R: get_view_query(v)
    R-->>P: Query tree
  end
Loading
sequenceDiagram
  autonumber
  actor A as Admin
  participant D as DependencyMgr
  participant V as ViewMgr
  participant C as Catalog

  Note over A,D: DROP TABLE t RESTRICT (ORA mode)
  A->>D: performDeletion(t)
  D->>D: findDependentObjects(t)
  D->>D: analyze dependencies
  alt All dependents are views
    D->>V: make_view_invalid(each view)
    V->>C: Update pg_force_view placeholders
    D-->>D: signal refind
    D->>D: refindDependentObjects(t)
    D-->>A: Proceed with drop (views invalidated)
  else Mixed dependents
    D-->>A: Report dependency error
  end
Loading
sequenceDiagram
  autonumber
  actor U as User
  participant P as Parser
  participant T as Tablecmds
  participant V as ViewMgr

  Note over U,T: ALTER VIEW v COMPILE
  U->>P: Parse ALTER VIEW ... COMPILE
  P-->>T: AlterTableStmt(AT_ForceViewCompile)
  T->>V: compile_force_view(v)
  alt ok
    V-->>T: success
    T-->>U: COMPILE done
  else fail
    T-->>U: WARNING/ERROR per path
  end
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

  • Support package #714 — Also adds Oracle-compatibility SYS views in sysview--1.0.sql (procedures/sources/sequences), closely related to adding SYS.* views.

Suggested reviewers

  • jiaoshuntian
  • OreoYang

Pre-merge checks (3 passed)

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "Oracle Force View Support." is concise and accurately summarizes the primary change in the PR — introducing Oracle-compatible FORCE VIEW behavior, parser/catalog additions, compile-time support, and related tests. It is specific, directly related to the main changeset, and clear enough for a reviewer scanning history to understand the PR's intent.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.

Poem

A bunny compiled a view with force,
Hopping past errors, it stayed the course.
When tables fell, it didn’t cry—
Became a ghost, then learned to fly.
With COMPILE spells and catalog clues,
It turned placeholders into news. 🐇✨

Pre-merge checks (3 passed)

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "Oracle Force View Support." is concise and accurately summarizes the primary change in the PR — introducing Oracle-compatible FORCE VIEW behavior, parser/catalog additions, compile-time support, and related tests. It is specific, directly related to the main changeset, and clear enough for a reviewer scanning history to understand the PR's intent.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.

Poem

A bunny compiled a view with force,
Hopping past errors, it stayed the course.
When tables fell, it didn’t cry—
Became a ghost, then learned to fly.
With COMPILE spells and catalog clues,
It turned placeholders into news. 🐇✨

Pre-merge checks (3 passed)

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "Oracle Force View Support." is concise and accurately summarizes the primary change in the PR — introducing Oracle-compatible FORCE VIEW behavior, parser/catalog additions, compile-time support, and related tests. It is specific, directly related to the main changeset, and clear enough for a reviewer scanning history to understand the PR's intent.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.

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

Comment @coderabbitai help to get the list of available commands and usage tips.

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: 14

Caution

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

⚠️ Outside diff range comments (1)
src/backend/commands/view.c (1)

474-550: Consider more robust error handling for force view compilation

The current implementation catches all errors indiscriminately when force view compilation fails (lines 490-500). This could mask critical errors that should propagate.

Consider catching only specific error codes related to parsing/compilation failures:

 PG_CATCH();
 {
-    if (ORA_PARSER == compatible_db && stmt->force)
+    ErrorData *edata = CopyErrorData();
+    
+    if (ORA_PARSER == compatible_db && stmt->force &&
+        (edata->sqlerrcode == ERRCODE_UNDEFINED_TABLE ||
+         edata->sqlerrcode == ERRCODE_UNDEFINED_COLUMN ||
+         edata->sqlerrcode == ERRCODE_SYNTAX_ERROR))
     {
         compile_failed = true;
+        FreeErrorData(edata);
         FlushErrorState();
     }
     else
+    {
+        FreeErrorData(edata);
         PG_RE_THROW();
+    }
 }
🧹 Nitpick comments (25)
contrib/tsm_system_rows/sql/tsm_system_rows.sql (1)

39-39: Fix punctuation/grammar in trailing comment.

Use “extension, but succeeds …” (comma placement and verb form).

-DROP EXTENSION tsm_system_rows;  -- fail, view depends on extension ,but success in Oracle because of FORCE VIEW
+DROP EXTENSION tsm_system_rows;  -- fail, view depends on extension, but succeeds in Oracle because of FORCE VIEW
contrib/tsm_system_time/sql/tsm_system_time.sql (1)

51-51: Fix punctuation/grammar in trailing comment.

-DROP EXTENSION tsm_system_time;  -- fail, view depends on extension ,but success in Oracle because of FORCE VIEW
+DROP EXTENSION tsm_system_time;  -- fail, view depends on extension, but succeeds in Oracle because of FORCE VIEW
src/oracle_test/regress/sql/create_view.sql (1)

595-595: Replace curly apostrophe with ASCII to avoid encoding surprises.

--- a
+++ b
--- this perhaps should be rejected, but it isn’t:
+-- this perhaps should be rejected, but it isn't:
contrib/ivorysql_ora/src/sysview/sysview--1.0.sql (1)

1091-1194: Avoid triple calling pg_get_viewdef per row (perf).

Compute once via LATERAL and reuse; apply similarly to ALL_VIEWS and USER_VIEWS.

 SELECT
@@
-    LENGTH(pg_get_viewdef(C.OID))::NUMBER(38) AS TEXT_LENGTH,
-    SYS.ORA_CASE_TRANS(pg_get_viewdef(C.OID)::VARCHAR2)::TEXT AS TEXT,
-    SYS.ORA_CASE_TRANS(pg_get_viewdef(C.OID)::VARCHAR2)::VARCHAR2(4000) AS TEXT_VC,
+    LENGTH(v.vdef)::NUMBER(38) AS TEXT_LENGTH,
+    SYS.ORA_CASE_TRANS(v.vdef::VARCHAR2)::TEXT AS TEXT,
+    SYS.ORA_CASE_TRANS(v.vdef::VARCHAR2)::VARCHAR2(4000) AS TEXT_VC,
@@
-FROM
-    PG_CLASS AS C
+FROM
+    PG_CLASS AS C
+    CROSS JOIN LATERAL (SELECT pg_get_viewdef(C.OID) AS vdef) v
 WHERE
     C.RELKIND = 'v';
src/backend/parser/parse_clause.c (1)

22-22: Includes look OK; minor style nit.

access/relation.h and commands/view.h are justified. Nit: keep consistent spacing in conditionals (add a space after if).

Also applies to: 29-29

src/backend/rewrite/rewriteHandler.c (1)

2571-2582: Improve error reporting for force views in get_view_query().

Good guard to avoid NULL rd_rules deref. Prefer ereport with SQLSTATE and translatable message, and mention the relation name for diagnosability.

-    if (ORA_PARSER == compatible_db && rel_is_force_view(RelationGetRelid(view)))
-        elog(ERROR, "force view does not have a Query tree");
+    if (ORA_PARSER == compatible_db &&
+        rel_is_force_view(RelationGetRelid(view)))
+        ereport(ERROR,
+                (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+                 errmsg("force view \"%s\" does not have a rewrite rule",
+                        RelationGetRelationName(view)),
+                 errdetail("Compile the force view before accessing its query tree.")));
src/oracle_test/regress/sql/ora_force_view.sql (3)

5-7: Typo in comments.

“defination” → “definition”.

-select pg_get_viewdef('v_bug193');        -- force view defination not change
+select pg_get_viewdef('v_bug193');        -- force view definition not change
...
-select pg_get_viewdef('v_bug193');        -- force view defination be changed
+select pg_get_viewdef('v_bug193');        -- force view definition be changed

74-83: Optional: assert expected compile behavior.

Consider adding explicit checks (e.g., selecting from dba_views/user_views or a helper to show invalid/valid state) after ALTER VIEW ... COMPILE to make the test more robust.


114-124: Clarity nit on “error” cases.

Mark the intended error-producing statements with a comment like “-- should error” consistently (you already do elsewhere) to aid expected output maintenance.

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

1531-1563: Parse-time compile path: switch to ereport and reopen by OID to avoid name/path races.

  • Use ereport with an SQLSTATE instead of elog(ERROR).
  • After compilation, reopen the same object by OID to avoid search_path/rename races during the unlock window.

Apply:

-            /* reopen the view */
-            lockmode = isLockedRefname(pstate, refname) ? RowShareLock : AccessShareLock;
-            rel = parserOpenTable(pstate, relation, lockmode);
+            /* reopen the same view by OID to avoid name/path races */
+            lockmode = isLockedRefname(pstate, refname) ? RowShareLock : AccessShareLock;
+            rel = relation_open(viewoid, lockmode);
...
-            /* ORA-04063: view "xxx.xxx" has errors */
-            elog(ERROR, "view \"%s.%s\" has errors", nspname, viewname);
+            /* ORA-04063: view "xxx.xxx" has errors */
+            ereport(ERROR,
+                    (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+                     errmsg("view \"%s.%s\" has errors", nspname, viewname)));
src/include/commands/view.h (1)

25-32: Public force-view prototypes look consistent with view.c.

Consider documenting ownership/lifetime of returned char* from get_force_view_def (pfree by caller) in a follow-up comment.

src/include/catalog/pg_force_view.h (1)

72-75: Typo/consistency in identifier-case constant.

“UNDEFINE” → “UNDEFINED” for clarity (grep for usages before changing).

-#define          IDENT_CASE_UNDEFINE        'u'     /* UNDEFINE */
+#define          IDENT_CASE_UNDEFINED       'u'     /* UNDEFINED */
src/backend/catalog/dependency.c (2)

420-442: Avoid double AcquireDeletionLock on rewalk.

Locks were already taken before the first walk; re-acquiring is redundant. Not harmful but can be dropped.

-           AcquireDeletionLock(thisobj, flags);
+           /* Lock already held from the first pass; no need to reacquire here. */

1114-1116: Typo in comment.

“funciton” → “function”.

-    * Find out the dependent funciton which uses %TYPE or %ROWTYPE
+    * Find out the dependent function which uses %TYPE or %ROWTYPE
src/backend/commands/view.c (8)

295-368: Improve function documentation and clarify behavior

The function documentation mentions updating metadata when replace is true for existing force views, but the actual implementation at lines 320-323 only sets *need_update = replace without performing any updates. This could be misleading.

Update the comment to clarify that this function only creates placeholders and flags whether updates are needed:

 /*
  * CreateForceVirtualPlaceholder
  *
- * This function creates a placeholder for a force view. If a view with the same
- * name already exists and is a force view, it simply returns its address. If
- * "replace" is specified, it marks the metadata for update. If the existing view
- * is a regular view, it can be invalidated if "replace" is true, otherwise it
- * does nothing. If no view exists, a new placeholder view is created.
+ * This function creates a placeholder for a force view. If a view with the same
+ * name already exists and is a force view, it simply returns its address and
+ * sets need_update flag to indicate whether the caller should update metadata.
+ * If the existing view is a regular view, it can be invalidated if "replace" 
+ * is true. If no view exists, a new placeholder view is created.
+ *
+ * The need_update output parameter indicates whether the caller should update
+ * the pg_force_view catalog entry.
  */

699-700: Fix spacing issue in function parameter

There's a missing space after the comma in the function parameter list.

-StoreForceViewQuery(Oid viewOid, bool replace, char ident_case ,const char *queryString)
+StoreForceViewQuery(Oid viewOid, bool replace, char ident_case, const char *queryString)

768-778: Remove commented-out debug code

The commented-out error handling code (lines 768-771) should be removed if it's not needed, or uncommented if it represents a valid error condition.

Either remove the commented code or restore it if the error check is valid:

-    #if 0
-    if (!HeapTupleIsValid(tup))
-        elog(ERROR, "cache lookup failed for force view %u", forceviewid);
-    #endif
-
     if (!HeapTupleIsValid(tup))
     {
         table_close(force_view_rel, RowExclusiveLock);
         return;	
     }

792-808: Optimize syscache lookups in rel_is_force_view

The function performs two syscache lookups when it could potentially be optimized. Also, the logic assumes that a view without a _RETURN rule is a force view, which might not always be accurate.

Consider adding a more explicit check or documenting the assumption:

 bool
 rel_is_force_view(Oid relid)
 {
+    /* 
+     * A force view is identified by being a view relation without 
+     * a _RETURN rule in pg_rewrite catalog.
+     */
     if (IsUnderPostmaster &&
         get_rel_relkind(relid) == RELKIND_VIEW)
     {
         HeapTuple	tuple;
 
         tuple = SearchSysCache2(RULERELNAME, ObjectIdGetDatum(relid), CStringGetDatum("_RETURN"));
         if (HeapTupleIsValid(tuple))
         {
             ReleaseSysCache(tuple);
             return false;
         }
         else
             return true;
     }
     else
         return false;
 }

1036-1042: Fix typo in comment

There's a minor typo in the comment.

-/* 
+/*
  * make_view_invalid
  *
- *		Make a view to force view, which is an invalid state.
+ *		Convert a view to a force view, which is an invalid state.
  *		Currently, the invalidation of a view cannot be issued explicitly. 
  *		In most cases, it is caused by the deletion of dependent objects.
  */

1055-1057: Early return can bypass necessary resource cleanup

The early return at line 1056 when the view is already a force view bypasses any necessary cleanup or state validation.

Consider whether any validation or logging should occur even when the view is already invalid:

 if (rel_is_force_view(viewid))
+{
+    elog(DEBUG1, "View %u is already a force view", viewid);
     return true;
+}

1163-1165: Redundant check can be removed

The check at line 1163 is redundant since the caller already verified this is a force view.

Consider removing the redundant check or converting it to an assertion:

-if (!rel_is_force_view(viewOid))
-    return true;
+Assert(rel_is_force_view(viewOid));

1122-1137: Document and justify IDENT_CASE_UNDEFINE usage

Expand the existing comment to state explicitly that IDENT_CASE_UNDEFINE intentionally defers quoting/case interpretation to pg_get_viewdef (so the stored view SQL may differ from the user-entered quoting) and list compatibility trade-offs; optionally add a code path or GUC to preserve original identifier-case handling when Oracle-compatibility is required.

Location: src/backend/commands/view.c (lines 1122–1137)

  • Optional refactor: preserve original identifier case or derive behavior from a DB compatibility setting instead of always using IDENT_CASE_UNDEFINE.
src/backend/oracle_parser/ora_gram.y (3)

2377-2385: ALTER VIEW … COMPILE accepted but consider IF EXISTS parity

The new branch handles ALTER VIEW COMPILE cleanly. For UX parity with other ALTER VIEW branches in this file, consider adding an IF EXISTS form (even if it maps to the same execution), or explicitly documenting why it’s intentionally unsupported.

-       |   ALTER VIEW qualified_name ora_alter_view_cmds
+       |   ALTER VIEW qualified_name ora_alter_view_cmds
         {
           ...
         }
+       |   ALTER VIEW IF_P EXISTS qualified_name ora_alter_view_cmds
+           {
+               AlterTableStmt *n = makeNode(AlterTableStmt);
+               n->relation = $5;
+               n->cmds = $6;
+               n->objtype = OBJECT_VIEW;
+               n->missing_ok = true;
+               $$ = (Node *)n;
+           }

2478-2492: ora_alter_view_cmds allows repeated COMPILE without separator

As written, “ALTER VIEW v COMPILE COMPILE” parses into two cmds. If multiple COMPILEs are meaningless, simplify to a single-command nonterminal (or require commas) to avoid surprising accepts.

-ora_alter_view_cmds:
-           ora_alter_view_cmd                              { $$ = list_make1($1); }
-           | ora_alter_view_cmds ora_alter_view_cmd        { $$ = lappend($1, $2); }
-       ;
+ora_alter_view_cmds:
+           ora_alter_view_cmd                              { $$ = list_make1($1); }
+       ;

12550-12554: OptViewForce keyword shape is OK; consider Oracle-only gating

If FORCE/NO FORCE should be accepted only in Oracle-compat mode, guard in semantic phase or add a compatibility check to reduce dialect leakage when running in pure PG mode.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a14b4aa and c64be62.

⛔ Files ignored due to path filters (9)
  • contrib/tsm_system_rows/expected/tsm_system_rows.out is excluded by !**/*.out
  • contrib/tsm_system_rows/expected/tsm_system_rows_1.out is excluded by !**/*.out
  • contrib/tsm_system_time/expected/tsm_system_time.out is excluded by !**/*.out
  • contrib/tsm_system_time/expected/tsm_system_time_1.out is excluded by !**/*.out
  • src/oracle_test/regress/expected/create_view.out is excluded by !**/*.out
  • src/oracle_test/regress/expected/create_view_1.out is excluded by !**/*.out
  • src/oracle_test/regress/expected/functional_deps.out is excluded by !**/*.out
  • src/oracle_test/regress/expected/ora_force_view.out is excluded by !**/*.out
  • src/oracle_test/regress/expected/rangefuncs.out is excluded by !**/*.out
📒 Files selected for processing (20)
  • contrib/ivorysql_ora/src/sysview/sysview--1.0.sql (1 hunks)
  • contrib/tsm_system_rows/sql/tsm_system_rows.sql (1 hunks)
  • contrib/tsm_system_time/sql/tsm_system_time.sql (1 hunks)
  • src/backend/catalog/dependency.c (11 hunks)
  • src/backend/commands/tablecmds.c (14 hunks)
  • src/backend/commands/view.c (6 hunks)
  • src/backend/oracle_parser/ora_gram.y (7 hunks)
  • src/backend/parser/parse_clause.c (2 hunks)
  • src/backend/parser/parse_relation.c (2 hunks)
  • src/backend/rewrite/rewriteHandler.c (3 hunks)
  • src/backend/utils/adt/ruleutils.c (2 hunks)
  • src/include/catalog/Makefile (1 hunks)
  • src/include/catalog/meson.build (1 hunks)
  • src/include/catalog/pg_force_view.h (1 hunks)
  • src/include/commands/view.h (1 hunks)
  • src/include/nodes/parsenodes.h (2 hunks)
  • src/oracle_test/modules/test_ddl_deparse/test_ddl_deparse.c (1 hunks)
  • src/oracle_test/regress/parallel_schedule (1 hunks)
  • src/oracle_test/regress/sql/create_view.sql (1 hunks)
  • src/oracle_test/regress/sql/ora_force_view.sql (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (8)
src/include/commands/view.h (1)
src/backend/commands/view.c (7)
  • StoreForceViewQuery (698-751)
  • DeleteForceView (758-782)
  • rel_is_force_view (789-808)
  • get_force_view_def (815-836)
  • compile_force_view (987-1034)
  • make_view_invalid (1043-1142)
  • make_view_valid (1150-1278)
src/backend/utils/adt/ruleutils.c (1)
src/backend/commands/view.c (2)
  • rel_is_force_view (789-808)
  • get_force_view_def (815-836)
src/backend/parser/parse_clause.c (2)
src/backend/commands/view.c (2)
  • rel_is_force_view (789-808)
  • compile_force_view (987-1034)
src/backend/parser/parse_relation.c (1)
  • parserOpenTable (1447-1487)
src/backend/rewrite/rewriteHandler.c (1)
src/backend/commands/view.c (1)
  • rel_is_force_view (789-808)
src/backend/parser/parse_relation.c (2)
src/backend/commands/view.c (2)
  • rel_is_force_view (789-808)
  • compile_force_view (987-1034)
src/backend/utils/cache/lsyscache.c (1)
  • get_namespace_name (3791-3808)
src/backend/catalog/dependency.c (4)
src/backend/catalog/objectaddress.c (1)
  • getObjectDescription (2974-4164)
src/backend/utils/error/elog.c (1)
  • errmsg_internal (1156-1172)
src/backend/utils/cache/lsyscache.c (1)
  • get_rel_relkind (2363-2380)
src/backend/commands/view.c (2)
  • make_view_invalid (1043-1142)
  • DeleteForceView (758-782)
src/backend/commands/view.c (12)
src/backend/catalog/namespace.c (1)
  • RangeVarGetAndCheckCreationNamespace (747-848)
src/backend/commands/tablecmds.c (4)
  • DefineRelation (779-1440)
  • CheckTableNotInUse (4522-4543)
  • BuildDescForRelation (1451-1529)
  • AlterTableInternal (4669-4680)
src/backend/parser/analyze.c (1)
  • parse_analyze_fixedparams (110-141)
src/backend/utils/error/elog.c (4)
  • FlushErrorState (1887-1900)
  • errcode (852-863)
  • errmsg (1069-1085)
  • errhint (1316-1331)
src/backend/utils/cache/lsyscache.c (4)
  • get_rel_name (2288-2305)
  • get_rel_relkind (2363-2380)
  • get_namespace_name (3791-3808)
  • get_rel_namespace (2312-2329)
src/backend/catalog/indexing.c (3)
  • CatalogTupleUpdate (312-326)
  • CatalogTupleInsert (232-245)
  • CatalogTupleDelete (364-368)
src/backend/nodes/makefuncs.c (2)
  • makeDefElem (643-655)
  • makeColumnDef (565-587)
src/backend/parser/parser.c (1)
  • raw_parser (40-50)
src/backend/utils/adt/ruleutils.c (2)
  • pg_get_viewdef (696-712)
  • quote_identifier (13507-13519)
src/backend/catalog/heap.c (1)
  • DeleteAttributeTuples (1623-1650)
src/backend/catalog/pg_depend.c (1)
  • deleteDependencyRecordsFor (300-339)
src/common/stringinfo.c (3)
  • makeStringInfo (71-75)
  • appendStringInfoString (229-233)
  • appendStringInfoChar (241-252)
src/backend/commands/tablecmds.c (4)
src/include/access/tupdesc.h (1)
  • TupleDescAttr (154-160)
src/backend/commands/view.c (2)
  • compile_force_view (987-1034)
  • make_view_invalid (1043-1142)
src/backend/access/index/genam.c (3)
  • systable_beginscan (404-497)
  • systable_getnext (530-574)
  • systable_endscan (619-647)
src/include/access/htup_details.h (1)
  • GETSTRUCT (751-755)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: pg_regression (ubuntu-latest)
  • GitHub Check: oracle_pg_regression (ubuntu-latest)
  • GitHub Check: build (ubuntu-latest)
  • GitHub Check: oracle_regression (ubuntu-latest)
  • GitHub Check: contrib_regression
  • GitHub Check: meson_build (ubuntu-latest)
🔇 Additional comments (25)
src/oracle_test/regress/parallel_schedule (1)

158-158: Approve: ora_force_view scheduled; assets present.
Found src/oracle_test/regress/sql/ora_force_view.sql and src/oracle_test/regress/expected/ora_force_view.out; parallel_schedule references the test once (line 158).

src/include/catalog/meson.build (1)

75-75: Catalog header added; Makefile and meson.build updated — verified.
src/include/catalog/pg_force_view.h exists and is listed in src/include/catalog/Makefile (CATALOG_HEADERS) and src/include/catalog/meson.build; backend usage present in src/backend/commands/view.c.

src/oracle_test/modules/test_ddl_deparse/test_ddl_deparse.c (1)

296-298: Fix typo in subcommand string ("FOIRCE" → "FORCE")

Replace in src/oracle_test/modules/test_ddl_deparse/test_ddl_deparse.c (lines 296–298):

-            case AT_ForceViewCompile:
-                strtype = "COMPILE FOIRCE VIEW";
-                break;
+            case AT_ForceViewCompile:
+                strtype = "COMPILE FORCE VIEW";
+                break;

Also confirm this label matches test expectations (Oracle normally emits "ALTER VIEW … COMPILE").

src/include/nodes/parsenodes.h (1)

3879-3881: ViewStmt extensions (force, stmt_literal) — LGTM; confirm serialization needs.

If these fields affect deparse/jumbling, consider marking with pg_node_attr(query_jumble_ignore) as appropriate. Otherwise OK.

src/backend/rewrite/rewriteHandler.c (1)

28-28: Includes are fine.

Needed for rel_is_force_view() and GUC access.

Also applies to: 49-50

src/include/catalog/Makefile (2)

87-88: LGTM: catalog header added in correct list.

Placement and continuation formatting are correct.


87-88: Resolved — Meson/Makefile and genbki include pg_force_view.h.
meson.build (line 75) and src/include/catalog/Makefile (line 88) reference 'pg_force_view.h'; src/include/catalog/pg_force_view.h contains CATALOG(pg_force_view,9120,ForceViewRelationId) at line 28, so genbki will generate the corresponding *_d.h.

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

44-44: Header include looks correct and necessary.

Pulling in commands/view.h is appropriate for rel_is_force_view/get_force_view_def declarations and keeps compilation units tidy.


817-819: Early-return bypasses catalog-access checks and drops trailing semicolon — action required

  • Permission/visibility: returning get_force_view_def(viewoid) here bypasses the pg_rewrite SPI-based read-access check; move the ACL/read-check into get_force_view_def or perform a lightweight SPI lookup here to match existing semantics.
  • Output consistency: make_viewdef appends a trailing ';' but get_force_view_def returns the stored text verbatim — ensure FORCE VIEW results are semicolon-terminated.
  • Pretty/wrap: prettyFlags/wrapColumn are ignored for the ORA path; either accept/document this or normalize to match make_viewdef.

Suggested fix (preserves allocation semantics and adds missing semicolon):

-   if (ORA_PARSER == compatible_db && rel_is_force_view(viewoid))
-       return get_force_view_def(viewoid);
+   if (ORA_PARSER == compatible_db && rel_is_force_view(viewoid))
+   {
+       /* Fetch raw FORCE VIEW source and normalize terminator to match make_viewdef */
+       char *src = get_force_view_def(viewoid);
+       StringInfoData obuf;
+       initStringInfo(&obuf);
+       appendStringInfoString(&obuf, src);
+       if (obuf.len == 0 || obuf.data[obuf.len - 1] != ';')
+           appendStringInfoChar(&obuf, ';');
+       pfree(src);
+       return obuf.data;
+   }

Consider centralizing the catalog-read/ACL policy by having get_force_view_def perform the SPI-based fetch (or an ACL check) so access semantics match the pg_rewrite path.

Confirm:

  • Should FORCE VIEW results include the trailing semicolon and be pretty-printed?
  • Should pg_force_view access be subject to the same read-access semantics as pg_rewrite here?
src/backend/parser/parse_relation.c (1)

26-26: LGTM: correct header inclusion for force-view APIs.

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

82-84: Includes for table/view and ORA compatibility: OK.

Also applies to: 95-97


165-169: Signature extension is reasonable.

Adding origObject and refinddepobjects keeps call sites explicit.


1711-1713: LGTM: clean up pg_force_view entry when dropping a view.

This makes drops idempotent for both normal and force views.

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

48-48: Good include addition for rewrite objects.

pg_rewrite symbols are referenced later; this header is required.


70-70: Correct header for force-view APIs.

Needed for compile_force_view/make_view_invalid prototypes.


1023-1026: No-op/formatting change.

Nothing to do here.


4958-4960: Lock level looks appropriate.

AT_ForceViewCompile ultimately updates pg_class/pg_rewrite for the view; AccessExclusiveLock is justified.


5450-5453: Permission/type check is correct.

ATSimplePermissions with ATT_VIEW guards misuse on non-views.


15735-15736: No-op/formatting.

OK.


22597-22600: Helpful doc comment.

Clarifies intent of invalidate-only behavior.


657-661: Signature change verified — callers updated

Declaration (src/backend/commands/tablecmds.c:657–659) and definition (src/backend/commands/tablecmds.c:15381–15384) match the new signature; callers updated at src/backend/commands/tablecmds.c:9084 (passes NULL,NULL,NULL) and 15097–15098 (passes &numDependentFuncPkgOids, &maxDependentFuncPkgOids, &dependentFuncPkgOids).


15677-15679: Packages: confirm pending-oid accumulation and de-dup.

Automated search returned no matches; I could not confirm that PackageRelationId OIDs are appended to the pending/rebuild list or deduplicated. Verify that when package dependencies are discovered the code pushes PackageRelationId OIDs into the rebuild/pending list and prevents duplicates (check-before-push or use a set/hash). Provide the function(s)/file(s) that manage the pending list for an automated re-check.


15707-15709: Package bodies — verify pending-list addition & deduplication

I ran the search for PackageBodyRelationId + pending and got no matches; confirm that package bodies are added to the pending-dependency list and deduplicated using the same logic as functions. File/context: src/backend/commands/tablecmds.c (around lines 15707–15709).

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

858-862: New nonterminals wired correctly

Types for OptViewForce and ALTER VIEW COMPILE look consistent with downstream usage. No action needed.


12509-12513: Defaulting recursive-views’ force/stmt_literal

Explicitly setting force = false and stmt_literal = NULL for recursive cases is fine. Once you relocate stmt_literal capture (previous comment), ensure recursive paths keep it NULL to avoid accidental population.

Also applies to: 12530-12532

Comment on lines +1269 to +1350
if (ORA_PARSER == compatible_db && behavior == DROP_RESTRICT)
{
/* Check if all dependencies that require CASCADE are views */
bool all_cascade_dep_is_view = true;
ObjectAddresses *viewObjects;

viewObjects = new_object_addresses();

for (i = targetObjects->numrefs - 1; i >= 0; i--)
{
const ObjectAddress *obj = &targetObjects->refs[i];
const ObjectAddressExtra *extra = &targetObjects->extras[i];
char *objDesc;

/* Skip the initial objects requested for deletion */
if (extra->flags & DEPFLAG_ORIGINAL)
continue;

/* Skip reporting for subcomponents, handled elsewhere */
if (extra->flags & DEPFLAG_SUBOBJECT)
continue;

objDesc = getObjectDescription(obj, false);

/* Ignore objects that are being dropped concurrently */
if (objDesc == NULL)
continue;

/*
* If the object was found via an automatic, internal, partition, or extension dependency,
* it is permitted to be removed even with RESTRICT.
*/
if (extra->flags & (DEPFLAG_AUTO |
DEPFLAG_INTERNAL |
DEPFLAG_PARTITION |
DEPFLAG_EXTENSION))
{
/*
* Log automatic cascades at DEBUG2 level for clarity.
*/
ereport(DEBUG2,
(errmsg_internal("drop auto-cascades to %s",
objDesc)));
}
else if (extra->flags & DEPFLAG_TYPE)
{
pfree(objDesc);
continue;
}
else
{
/* If any dependent object is not a view, set flag and exit loop */
if (get_rel_relkind(obj->objectId) != RELKIND_VIEW)
{
all_cascade_dep_is_view = false;
pfree(objDesc);
break;
}
else
add_exact_object_address(obj, viewObjects);
}
pfree(objDesc);
}

/* If all dependencies are views, mark them as invalid and trigger a recheck */
if (all_cascade_dep_is_view && viewObjects->numrefs > 0)
{
for (i = viewObjects->numrefs - 1; i >= 0; i--)
{
make_view_invalid(viewObjects->refs[i].objectId);
}

/*
* After invalidating views, re-evaluate dependencies to ensure consistency.
*/
if (refinddepobj)
*refinddepobj = true;
return;
}

free_object_addresses(viewObjects);
}
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

RESTRICT path invalidation: free viewObjects on early return and guard relkind checks.

  • Leak: viewObjects is not freed on the early return path.
  • Minor: call get_rel_relkind() only for RelationRelationId objects.
-       if (all_cascade_dep_is_view && viewObjects->numrefs > 0)
+       if (all_cascade_dep_is_view && viewObjects->numrefs > 0)
        {
            for (i = viewObjects->numrefs - 1; i >= 0; i--)
            {
                make_view_invalid(viewObjects->refs[i].objectId);
            }
            /*
             * After invalidating views, re-evaluate dependencies to ensure consistency.
             */
            if (refinddepobj)
                *refinddepobj = true;
-           return;
+           free_object_addresses(viewObjects);
+           return;
        }
...
-           /*
-            * If any dependent object is not a view, set flag and exit loop
-            */
-           if (get_rel_relkind(obj->objectId) != RELKIND_VIEW)
+           /* Only relations can be views; otherwise, fail the "all views" test */
+           if (obj->classId != RelationRelationId ||
+               get_rel_relkind(obj->objectId) != RELKIND_VIEW)
            {
                all_cascade_dep_is_view = false;
                pfree(objDesc);
                break;
            }
📝 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 (ORA_PARSER == compatible_db && behavior == DROP_RESTRICT)
{
/* Check if all dependencies that require CASCADE are views */
bool all_cascade_dep_is_view = true;
ObjectAddresses *viewObjects;
viewObjects = new_object_addresses();
for (i = targetObjects->numrefs - 1; i >= 0; i--)
{
const ObjectAddress *obj = &targetObjects->refs[i];
const ObjectAddressExtra *extra = &targetObjects->extras[i];
char *objDesc;
/* Skip the initial objects requested for deletion */
if (extra->flags & DEPFLAG_ORIGINAL)
continue;
/* Skip reporting for subcomponents, handled elsewhere */
if (extra->flags & DEPFLAG_SUBOBJECT)
continue;
objDesc = getObjectDescription(obj, false);
/* Ignore objects that are being dropped concurrently */
if (objDesc == NULL)
continue;
/*
* If the object was found via an automatic, internal, partition, or extension dependency,
* it is permitted to be removed even with RESTRICT.
*/
if (extra->flags & (DEPFLAG_AUTO |
DEPFLAG_INTERNAL |
DEPFLAG_PARTITION |
DEPFLAG_EXTENSION))
{
/*
* Log automatic cascades at DEBUG2 level for clarity.
*/
ereport(DEBUG2,
(errmsg_internal("drop auto-cascades to %s",
objDesc)));
}
else if (extra->flags & DEPFLAG_TYPE)
{
pfree(objDesc);
continue;
}
else
{
/* If any dependent object is not a view, set flag and exit loop */
if (get_rel_relkind(obj->objectId) != RELKIND_VIEW)
{
all_cascade_dep_is_view = false;
pfree(objDesc);
break;
}
else
add_exact_object_address(obj, viewObjects);
}
pfree(objDesc);
}
/* If all dependencies are views, mark them as invalid and trigger a recheck */
if (all_cascade_dep_is_view && viewObjects->numrefs > 0)
{
for (i = viewObjects->numrefs - 1; i >= 0; i--)
{
make_view_invalid(viewObjects->refs[i].objectId);
}
/*
* After invalidating views, re-evaluate dependencies to ensure consistency.
*/
if (refinddepobj)
*refinddepobj = true;
return;
}
free_object_addresses(viewObjects);
}
if (ORA_PARSER == compatible_db && behavior == DROP_RESTRICT)
{
/* Check if all dependencies that require CASCADE are views */
bool all_cascade_dep_is_view = true;
ObjectAddresses *viewObjects;
viewObjects = new_object_addresses();
for (i = targetObjects->numrefs - 1; i >= 0; i--)
{
const ObjectAddress *obj = &targetObjects->refs[i];
const ObjectAddressExtra *extra = &targetObjects->extras[i];
char *objDesc;
/* Skip the initial objects requested for deletion */
if (extra->flags & DEPFLAG_ORIGINAL)
continue;
/* Skip reporting for subcomponents, handled elsewhere */
if (extra->flags & DEPFLAG_SUBOBJECT)
continue;
objDesc = getObjectDescription(obj, false);
/* Ignore objects that are being dropped concurrently */
if (objDesc == NULL)
continue;
/*
* If the object was found via an automatic, internal, partition, or extension dependency,
* it is permitted to be removed even with RESTRICT.
*/
if (extra->flags & (DEPFLAG_AUTO |
DEPFLAG_INTERNAL |
DEPFLAG_PARTITION |
DEPFLAG_EXTENSION))
{
/*
* Log automatic cascades at DEBUG2 level for clarity.
*/
ereport(DEBUG2,
(errmsg_internal("drop auto-cascades to %s",
objDesc)));
}
else if (extra->flags & DEPFLAG_TYPE)
{
pfree(objDesc);
continue;
}
else
{
/* Only relations can be views; otherwise, fail the "all views" test */
if (obj->classId != RelationRelationId ||
get_rel_relkind(obj->objectId) != RELKIND_VIEW)
{
all_cascade_dep_is_view = false;
pfree(objDesc);
break;
}
else
add_exact_object_address(obj, viewObjects);
}
pfree(objDesc);
}
/* If all dependencies are views, mark them as invalid and trigger a recheck */
if (all_cascade_dep_is_view && viewObjects->numrefs > 0)
{
for (i = viewObjects->numrefs - 1; i >= 0; i--)
{
make_view_invalid(viewObjects->refs[i].objectId);
}
/*
* After invalidating views, re-evaluate dependencies to ensure consistency.
*/
if (refinddepobj)
*refinddepobj = true;
free_object_addresses(viewObjects);
return;
}
free_object_addresses(viewObjects);
}
🤖 Prompt for AI Agents
In src/backend/catalog/dependency.c around lines 1269-1350, free viewObjects
before the early return and avoid calling get_rel_relkind() for non-relation
objects: free_object_addresses(viewObjects) right before returning when
all_cascade_dep_is_view is true; and change the relkind check to first verify
obj->classId == RelationRelationId and only call get_rel_relkind(obj->objectId)
in that case (treat non-relation objects as "not a view" so you set
all_cascade_dep_is_view = false and break).

Comment on lines +4076 to 4080
* if there is any functions(procedures, packages) that depend on
* the relation and attnum, invalidate functions(procedures, packages),
* but don't delete the dependency.
*/
check_function_depend_on_relation_column(myrelid, attnum);
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

Validate forward decls and error semantics for dependency invalidation.

check_function_depend_on_relation_column() must be declared before this use (modern C disallows implicit decls) and should be safe under the current lock. Also confirm it only invalidates, not deletes, as comment states.

Run:


🏁 Script executed:

#!/bin/bash
rg -n --type=c $'^\\s*(static\\s+)?void\\s+check_function_depend_on_relation_column\\s*\\('
rg -n --type=c 'check_function_depend_on_relation_column\s*\(' src/backend/commands/tablecmds.c -C2

Length of output: 276


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== occurrences of symbol ==="
rg -n --hidden -S 'check_function_depend_on_relation_column' || true

echo
echo "=== prototypes (declaration with semicolon) ==="
rg -n --hidden -S 'check_function_depend_on_relation_column\s*\([^\)]*\)\s*;' || true

echo
echo "=== call-site context (lines ~4000-4100) ==="
nl -ba -w6 -s': ' src/backend/commands/tablecmds.c | sed -n '4000,4100p' || true

echo
echo "=== function definition (lines ~22580-22680) ==="
nl -ba -w6 -s': ' src/backend/commands/tablecmds.c | sed -n '22580,22680p' || true

echo
echo "=== search for delete/remove operations inside that function's region ==="
sed -n '22590,22680p' src/backend/commands/tablecmds.c | nl -ba -w6 -s': ' | rg -n --line-number 'delete|remove|DROP|Dependency' || true

Length of output: 1006


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== call-site context (lines ~4070-4090) ==="
sed -n '4070,4090p' src/backend/commands/tablecmds.c

echo
echo "=== function definition and implementation ==="
sed -n '22590,22650p' src/backend/commands/tablecmds.c

echo
echo "=== search for delete/remove operations inside function ==="
sed -n '22602,22650p' src/backend/commands/tablecmds.c | grep -i -E 'delete|remove|drop|dependency' || echo "No delete/remove operations found"

echo
echo "=== check what the function actually does (look for invalidate) ==="
sed -n '22602,22650p' src/backend/commands/tablecmds.c | grep -i 'invalidate' || echo "No invalidate operations found"

Length of output: 2558


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== top-of-file includes (first 240 lines) ==="
sed -n '1,240p' src/backend/commands/tablecmds.c || true

echo
echo "=== caller context (lines 4000-4090) ==="
sed -n '4000,4090p' src/backend/commands/tablecmds.c || true

echo
echo "=== locate check_function_dependency_internal start line ==="
rg -n --hidden -S '^\s*(static\s+)?void\s+check_function_dependency_internal\s*\(' src/backend/commands/tablecmds.c || true

start=$(rg -n --hidden -S '^\s*(static\s+)?void\s+check_function_dependency_internal\s*\(' src/backend/commands/tablecmds.c | cut -d: -f1 || true)
if [ -n "$start" ]; then
  echo
  echo "=== check_function_dependency_internal body (start..start+400) ==="
  sed -n "${start},$((start+400))p" src/backend/commands/tablecmds.c || true

  echo
  echo "=== search for actions inside the function (invalidate/delete/remove/drop) ==="
  sed -n "${start},$((start+400))p" src/backend/commands/tablecmds.c | rg -n -i --line-number 'invalidate|invalidate|delete|remove|drop|delete_dependency|Dependency' || true
else
  echo "couldn't locate function start"
fi

Length of output: 25403


🏁 Script executed:

#!/bin/bash
set -euo pipefail
file=src/backend/commands/tablecmds.c

echo "=== header declaration in include ==="
sed -n '100,130p' src/include/commands/tablecmds.h || true

echo
echo "=== verify commands/tablecmds.h is included in tablecmds.c ==="
rg -n --hidden -S '^#include\s+"commands/tablecmds.h"' "$file" || true
sed -n '1,60p' "$file" || true

echo
echo "=== all occurrences of check_function_dependency_internal ==="
rg -n --hidden -S 'check_function_dependency_internal' || true

echo
echo "=== look for a definition (with opening brace) ==="
rg -n --hidden -S 'check_function_dependency_internal\s*\(.*\)\s*\{' "$file" || true

echo
echo "=== show contexts for each occurrence (±10 lines) ==="
rg -n --hidden -S 'check_function_dependency_internal\s*\(' "$file" | cut -d: -f1 | sort -u | while read -r l; do
  start=$((l>10?l-10:1))
  echo "----- context around line $l (lines $start..$((l+40))) -----"
  sed -n "${start},$((l+40))p" "$file"
done

echo
echo "=== if definition found, print full body (up to 500 lines) ==="
defline=$(rg -n --hidden -S 'check_function_dependency_internal\s*\(.*\)\s*\{' "$file" | cut -d: -f1 || true)
if [ -n "$defline" ]; then
  sed -n "${defline},$((defline+500))p" "$file"
else
  echo "No definition with '{' found in $file"
fi

echo
echo "=== search for delete/remove/drop/invalidate inside the function body region (if found) ==="
if [ -n "$defline" ]; then
  sed -n "${defline},$((defline+500))p" "$file" | rg -n -i --line-number 'delete|remove|drop|invalidate|invalidate|Dependency' || echo "No hits for those keywords in function body region"
else
  echo "Skipping keyword search because no function definition found in this file."
fi

Length of output: 10334


🏁 Script executed:

#!/bin/bash
set -euo pipefail
file=src/backend/commands/tablecmds.c

echo "=== occurrences of check_function_depend_on_relation_column ==="
rg -n --hidden -S 'check_function_depend_on_relation_column' || true

echo
echo "=== declaration in header (src/include/commands/tablecmds.h) ==="
sed -n '96,120p' src/include/commands/tablecmds.h || true

echo
echo "=== confirm header is included in tablecmds.c ==="
rg -n --hidden -S '^#include\s+"commands/tablecmds.h"' "$file" || true
sed -n '1,80p' "$file" || true

echo
echo "=== locations of check_function_dependency_internal occurrences ==="
rg -n --hidden -S 'check_function_dependency_internal\s*\(' "$file" || true

defline=$(rg -n --hidden -S 'check_function_dependency_internal\s*\(' "$file" | tail -n1 | cut -d: -f1 || true)
echo "assumed_def_line=$defline"

echo
echo "=== dump ~500 lines from assumed function start ==="
if [ -n "$defline" ]; then
  sed -n "${defline},$((defline+500))p" "$file"
else
  echo "function start not found; printing broader vicinity"
  sed -n '22580,22780p' "$file" || true
fi

echo
echo "=== look for deletion/removal of dependency/catalog tuples in that region ==="
if [ -n "$defline" ]; then
  sed -n "${defline},$((defline+500))p" "$file" | rg -n -i --line-number 'CatalogTupleDelete|delete_dependency|deleteDependency|delete_tuple|heap_delete|CatalogIndexDelete|delete|remove|drop' || echo "No deletion-like keywords found in region"
fi

echo
echo "=== look for invalidation/cache-invalidate calls in that region ==="
if [ -n "$defline" ]; then
  sed -n "${defline},$((defline+500))p" "$file" | rg -n -i --line-number 'invalidate|inval|PackageCache|packagecache|Invalidate|CacheInvalidate|InvalidatePackage|InvokeObjectPostAlterHook' || echo "No invalidate-like keywords found in region"
fi

echo
echo "=== usages of dependentFuncPkgOids in the file ==="
rg -n --hidden -S 'dependentFuncPkgOids' || true

echo
echo "=== global search for operations that modify pg_depend in repo ==="
rg -n -i --hidden 'delete_dependency|dependenc.*delete|CatalogTupleDelete|delete from pg_depend' || true

Length of output: 25682


Fix mismatch: function actually deletes pg_depend rows — change behavior or update comment

  • Prototype is present (src/include/commands/tablecmds.h:110) and that header is included in tablecmds.c, so no implicit-decl issue at the call site (src/backend/commands/tablecmds.c:4080).
  • Implementation (check_function_depend_on_relation_column, src/backend/commands/tablecmds.c, defined ~line 22644) calls CatalogTupleDelete(depRel, &depTup->t_self) (e.g. lines 22688, 22709, 22730), so it deletes dependency tuples — contrary to the comment "but don't delete the dependency."
  • Action required: either remove the CatalogTupleDelete calls so the function only invalidates, or update the comment/call-site expectation and add verification that deleting pg_depend here is intentional and safe under current lock/transaction semantics.
🤖 Prompt for AI Agents
In src/backend/commands/tablecmds.c around lines 4076-4080, the comment says
check_function_depend_on_relation_column should "invalidate functions... but
don't delete the dependency", however the implementation actually calls
CatalogTupleDelete on pg_depend rows; either remove the CatalogTupleDelete calls
so the function only marks/invalidates dependencies, or update the comment and
call-site to reflect that dependencies are deleted and add verification that
deleting pg_depend here is intentional and safe (ensure proper locks/transaction
semantics, add assertions or comments documenting safety, and add a regression
test to cover the behavior change).

Comment on lines +4364 to 4368
* if there is any functions(procedures, packages) that depend on
* the relation and attnum, invalidate functions(procedures, packages),
* but don't delete the dependency.
*/
check_function_depend_on_relation(relid);
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

Same as above for relation-wide dependency invalidation.

Ensure prototype order and that behavior is invalidate-only.


🏁 Script executed:

#!/bin/bash
rg -n --type=c $'^\\s*(static\\s+)?void\\s+check_function_depend_on_relation\\s*\\('
rg -n --type=c 'check_function_depend_on_relation\s*\(' src/backend/commands/tablecmds.c -C2

Length of output: 234


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Occurrences of symbol:"
rg -n --hidden -S 'check_function_depend_on_relation' || true

echo -e "\n--- Call site (around line ~4360) ---"
sed -n '4356,4376p' src/backend/commands/tablecmds.c || true

echo -e "\n--- Function definition (around line ~22561) ---"
sed -n '22556,22680p' src/backend/commands/tablecmds.c || true

echo -e "\n--- Search for prototype/declaration in headers ---"
rg -n --hidden -S 'check_function_depend_on_relation' src/include || true

Length of output: 5036


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n --hidden -S 'check_function_dependency_internal' || true

echo -e "\n--- Function context ---"
sed -n '22540,22880p' src/backend/commands/tablecmds.c || true

Length of output: 8347


Do not delete pg_depend rows — make this invalidate-only.

  • Prototypes/order are fine: src/include/commands/tablecmds.h:109–110.
  • Call site: src/backend/commands/tablecmds.c:4368 calls check_function_depend_on_relation(relid).
  • Implementation bug: check_function_dependency_internal (src/backend/commands/tablecmds.c, starts ~22644) currently calls CatalogTupleDelete(depRel, &depTup->t_self) for ProcedureRelationId / PackageRelationId / PackageBodyRelationId — remove those deletes. Instead only perform invalidation (invalidate function/package caches and set prostatus/package flags); do not remove pg_depend tuples.
🤖 Prompt for AI Agents
In src/backend/commands/tablecmds.c around lines starting ~22644 (called from
src/backend/commands/tablecmds.c:4368), the implementation of
check_function_dependency_internal incorrectly deletes pg_depend rows for
ProcedureRelationId, PackageRelationId and PackageBodyRelationId by calling
CatalogTupleDelete(depRel, &depTup->t_self); remove those CatalogTupleDelete
calls for those relation IDs and instead perform invalidate-only behavior: call
the existing function/package cache invalidation routines and set the
appropriate prostatus/package flags to mark objects invalid, but do not delete
any pg_depend tuples so the dependency entries remain intact.

Comment on lines +5880 to +5888
case AT_ForceViewCompile:
if (tab->rel)
{
relation_close(tab->rel, NoLock);
tab->rel = NULL;
}
if (!compile_force_view(tab->relid))
elog(WARNING, "View altered with compilation errors");
break;
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

Convert compile errors to WARNING to match Oracle semantics.

compile_force_view currently raises ERRORs in some paths (per its comment), so the WARNING branch here is often unreachable. Wrap with PG_TRY/PG_CATCH and also handle a false return to emit a warning without aborting ALTER VIEW COMPILE.

Apply:

 case AT_ForceViewCompile:
   if (tab->rel)
   {
     relation_close(tab->rel, NoLock);
     tab->rel = NULL;
   }
-  if (!compile_force_view(tab->relid))
-    elog(WARNING, "View altered with compilation errors");
+  /*
+   * Oracle-compatible behavior: do not abort on compilation errors;
+   * emit a WARNING instead.
+   */
+  do {
+    bool ok = false;
+    PG_TRY();
+    {
+      ok = compile_force_view(tab->relid);
+    }
+    PG_CATCH();
+    {
+      FlushErrorState();
+      ok = false;
+    }
+    PG_END_TRY();
+    if (!ok)
+      ereport(WARNING, (errmsg("View altered with compilation errors")));
+  } while (0);
   break;

To double-check compile_force_view’s behavior:


🏁 Script executed:

#!/bin/bash
rg -n -C3 --type=c 'bool\s+compile_force_view\s*\(' src/backend/commands/view.c
rg -n --type=c 'compile_force_view_internal' src/backend/commands/view.c -C3
rg -n --type=c 'elog\(\s*ERROR' src/backend/commands/view.c -n

Length of output: 1717


Wrap compile_force_view() in PG_TRY/PG_CATCH and convert failures to WARNING

compile_force_view/compile_force_view_internal in src/backend/commands/view.c contains elog(ERROR) paths (so the current if(!compile_force_view(...)) is often unreachable); wrap the call with PG_TRY/PG_CATCH, call FlushErrorState() in PG_CATCH, and emit a WARNING when the call returns false or an exception is caught.

Apply:

 case AT_ForceViewCompile:
   if (tab->rel)
   {
     relation_close(tab->rel, NoLock);
     tab->rel = NULL;
   }
-  if (!compile_force_view(tab->relid))
-    elog(WARNING, "View altered with compilation errors");
+  /*
+   * Oracle-compatible behavior: do not abort on compilation errors;
+   * emit a WARNING instead.
+   */
+  do {
+    bool ok = false;
+    PG_TRY();
+    {
+      ok = compile_force_view(tab->relid);
+    }
+    PG_CATCH();
+    {
+      FlushErrorState();
+      ok = false;
+    }
+    PG_END_TRY();
+    if (!ok)
+      ereport(WARNING, (errmsg("View altered with compilation errors")));
+  } while (0);
   break;
📝 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
case AT_ForceViewCompile:
if (tab->rel)
{
relation_close(tab->rel, NoLock);
tab->rel = NULL;
}
if (!compile_force_view(tab->relid))
elog(WARNING, "View altered with compilation errors");
break;
case AT_ForceViewCompile:
if (tab->rel)
{
relation_close(tab->rel, NoLock);
tab->rel = NULL;
}
/*
* Oracle-compatible behavior: do not abort on compilation errors;
* emit a WARNING instead.
*/
do {
bool ok = false;
PG_TRY();
{
ok = compile_force_view(tab->relid);
}
PG_CATCH();
{
FlushErrorState();
ok = false;
}
PG_END_TRY();
if (!ok)
ereport(WARNING, (errmsg("View altered with compilation errors")));
} while (0);
break;
🤖 Prompt for AI Agents
In src/backend/commands/tablecmds.c around lines 5880-5888, the call to
compile_force_view(tab->relid) can raise an ERROR and the current
if(!compile_force_view(...)) path is therefore often unreachable; wrap the
compile_force_view call in a PG_TRY / PG_CATCH block, keep the existing
relation_close(tab->rel, NoLock) and setting tab->rel = NULL before the try,
call compile_force_view inside PG_TRY and on normal false return emit
elog(WARNING, "...View altered with compilation errors"); in PG_CATCH call
FlushErrorState() and then emit the same elog(WARNING, "...View altered with
compilation errors") so exceptions are converted into warnings and do not abort
the transaction.

Comment on lines +15513 to 15576
if (ORA_PARSER == compatible_db)
{
Relation ruleDesc;
ScanKeyData skey[1];
SysScanDesc rcscan;
HeapTuple tup;
Form_pg_rewrite rule;

/*
* View/rule bodies have pretty much the same issues as
* function bodies. FIXME someday.
*/
if (subtype == AT_AlterColumnType)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot alter type of a column used by a view or rule"),
errdetail("%s depends on column \"%s\"",
getObjectDescription(&foundObject, false),
colName)));
ruleDesc = table_open(RewriteRelationId, AccessShareLock);

ScanKeyInit(&skey[0],
Anum_pg_rewrite_oid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(foundObject.objectId));

rcscan = systable_beginscan(ruleDesc, RewriteOidIndexId, true,
NULL, 1, skey);

tup = systable_getnext(rcscan);

if (!HeapTupleIsValid(tup))
{
systable_endscan(rcscan);
table_close(ruleDesc, AccessShareLock);
elog(ERROR, "could not find tuple for rule %u",
foundObject.objectId);
}

rule = (Form_pg_rewrite) GETSTRUCT(tup);

/* view ? */
if (strcmp(NameStr(rule->rulename), "_RETURN") == 0)
{
make_view_invalid(rule->ev_class);
systable_endscan(rcscan);
table_close(ruleDesc, AccessShareLock);
continue;
}
else
{
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot alter type of a column used by a view or rule"),
errdetail("%s depends on column \"%s\"",
getObjectDescription(&foundObject, false),
colName)));
break;
}
}
else
{
/*
* View/rule bodies have pretty much the same issues as
* function bodies. FIXME someday.
*/
if (subtype == AT_AlterColumnType)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot alter type of a column used by a view or rule"),
errdetail("%s depends on column \"%s\"",
getObjectDescription(&foundObject, false),
colName)));
}
break;
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

Avoid modifying pg_rewrite while scanning it; release scan before invalidation.

make_view_invalid deletes the _RETURN rule from pg_rewrite (RowExclusiveLock) and updates catalogs. Calling it while holding an active systable scan on pg_rewrite risks catalog self-conflicts and makes lock ordering brittle. Read the tuple, end the scan, close the relation, then invalidate. Also, release the scan before raising ERROR in the non-view branch.

Apply:

-                    rule = (Form_pg_rewrite) GETSTRUCT(tup);
-
-                    /* view ? */
-                    if (strcmp(NameStr(rule->rulename), "_RETURN") == 0)
-                    {
-                        make_view_invalid(rule->ev_class);
-                        systable_endscan(rcscan);
-                        table_close(ruleDesc, AccessShareLock);
-                        continue;
-                    }
+                    rule = (Form_pg_rewrite) GETSTRUCT(tup);
+                    /* view ? */
+                    if (strcmp(NameStr(rule->rulename), "_RETURN") == 0)
+                    {
+                        Oid viewid = rule->ev_class;
+                        /* Finish the scan before modifying pg_rewrite. */
+                        systable_endscan(rcscan);
+                        table_close(ruleDesc, AccessShareLock);
+                        make_view_invalid(viewid);
+                        continue;
+                    }
                     else
                     {
-                        ereport(ERROR,
+                        /* Clean up scan/lock before erroring out. */
+                        systable_endscan(rcscan);
+                        table_close(ruleDesc, AccessShareLock);
+                        ereport(ERROR,
                                 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                                  errmsg("cannot alter type of a column used by a view or rule"),
                                  errdetail("%s depends on column \"%s\"",
                                            getObjectDescription(&foundObject, false),
                                            colName)));
-                        break;
+                        break;
                     }

Optional: Replace the OID index scan with syscache (e.g., SearchSysCache1(REWRITEOID,...)) for simpler, safer lookup.

Consider batching dependent view invalidations and acquiring relation locks in OID order to minimize deadlock risk when many views depend on the target table.

📝 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 (ORA_PARSER == compatible_db)
{
Relation ruleDesc;
ScanKeyData skey[1];
SysScanDesc rcscan;
HeapTuple tup;
Form_pg_rewrite rule;
/*
* View/rule bodies have pretty much the same issues as
* function bodies. FIXME someday.
*/
if (subtype == AT_AlterColumnType)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot alter type of a column used by a view or rule"),
errdetail("%s depends on column \"%s\"",
getObjectDescription(&foundObject, false),
colName)));
ruleDesc = table_open(RewriteRelationId, AccessShareLock);
ScanKeyInit(&skey[0],
Anum_pg_rewrite_oid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(foundObject.objectId));
rcscan = systable_beginscan(ruleDesc, RewriteOidIndexId, true,
NULL, 1, skey);
tup = systable_getnext(rcscan);
if (!HeapTupleIsValid(tup))
{
systable_endscan(rcscan);
table_close(ruleDesc, AccessShareLock);
elog(ERROR, "could not find tuple for rule %u",
foundObject.objectId);
}
rule = (Form_pg_rewrite) GETSTRUCT(tup);
/* view ? */
if (strcmp(NameStr(rule->rulename), "_RETURN") == 0)
{
make_view_invalid(rule->ev_class);
systable_endscan(rcscan);
table_close(ruleDesc, AccessShareLock);
continue;
}
else
{
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot alter type of a column used by a view or rule"),
errdetail("%s depends on column \"%s\"",
getObjectDescription(&foundObject, false),
colName)));
break;
}
}
else
{
/*
* View/rule bodies have pretty much the same issues as
* function bodies. FIXME someday.
*/
if (subtype == AT_AlterColumnType)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot alter type of a column used by a view or rule"),
errdetail("%s depends on column \"%s\"",
getObjectDescription(&foundObject, false),
colName)));
}
break;
if (ORA_PARSER == compatible_db)
{
Relation ruleDesc;
ScanKeyData skey[1];
SysScanDesc rcscan;
HeapTuple tup;
Form_pg_rewrite rule;
ruleDesc = table_open(RewriteRelationId, AccessShareLock);
ScanKeyInit(&skey[0],
Anum_pg_rewrite_oid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(foundObject.objectId));
rcscan = systable_beginscan(ruleDesc, RewriteOidIndexId, true,
NULL, 1, skey);
tup = systable_getnext(rcscan);
if (!HeapTupleIsValid(tup))
{
systable_endscan(rcscan);
table_close(ruleDesc, AccessShareLock);
elog(ERROR, "could not find tuple for rule %u",
foundObject.objectId);
}
rule = (Form_pg_rewrite) GETSTRUCT(tup);
/* view ? */
if (strcmp(NameStr(rule->rulename), "_RETURN") == 0)
{
Oid viewid = rule->ev_class;
/* Finish the scan before modifying pg_rewrite. */
systable_endscan(rcscan);
table_close(ruleDesc, AccessShareLock);
make_view_invalid(viewid);
continue;
}
else
{
/* Clean up scan/lock before erroring out. */
systable_endscan(rcscan);
table_close(ruleDesc, AccessShareLock);
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot alter type of a column used by a view or rule"),
errdetail("%s depends on column \"%s\"",
getObjectDescription(&foundObject, false),
colName)));
break;
}
}
else
{
/*
* View/rule bodies have pretty much the same issues as
* function bodies. FIXME someday.
*/
if (subtype == AT_AlterColumnType)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot alter type of a column used by a view or rule"),
errdetail("%s depends on column \"%s\"",
getObjectDescription(&foundObject, false),
colName)));
}
break;

Comment on lines +1272 to +1273
DeleteForceView(viewOid);

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

Missing error handling for DeleteForceView

The DeleteForceView call at line 1272 could fail, but there's no error handling or verification that the deletion succeeded.

Consider adding error handling or at least logging:

-DeleteForceView(viewOid);
+PG_TRY();
+{
+    DeleteForceView(viewOid);
+}
+PG_CATCH();
+{
+    elog(WARNING, "Failed to delete force view entry for view %u", viewOid);
+    PG_RE_THROW();
+}
+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.

Suggested change
DeleteForceView(viewOid);
PG_TRY();
{
DeleteForceView(viewOid);
}
PG_CATCH();
{
elog(WARNING, "Failed to delete force view entry for view %u", viewOid);
PG_RE_THROW();
}
PG_END_TRY();
🤖 Prompt for AI Agents
In src/backend/commands/view.c around lines 1272-1273, the call to
DeleteForceView(viewOid); lacks any error handling or verification of success;
update the code to capture the function's result (or detect failures via its
return/status mechanism or by surrounding it with the appropriate error-checking
macros), and handle failures by logging a clear message with the view OID and
error details (using ereport/elog) and returning or propagating an error
code/exception as appropriate for this call site so the caller knows deletion
failed.

Comment on lines +12451 to 12474
ViewStmt: CREATE OptTemp OptViewForce VIEW qualified_name opt_column_list opt_reloptions
AS SelectStmt opt_check_option
{
ViewStmt *n = makeNode(ViewStmt);

n->view = $4;
char *stmt_iteral = NULL;
n->view = $5;
n->view->relpersistence = $2;
n->aliases = $5;
n->query = $8;
n->aliases = $6;
n->query = $9;
n->replace = false;
n->options = $6;
n->withCheckOption = $9;
n->force = $3;
n->options = $7;
n->withCheckOption = $10;
/*
* Save the source text of the force view definition in ViewStmt to avoid
* incorrectly obtaining the view definition when using a multi-statement
* parser tree.
*/
stmt_iteral = pnstrdup(pg_yyget_extra(yyscanner)->core_yy_extra.scanbuf + @1, yylloc - @1);
n->stmt_literal = psprintf("%s;", stmt_iteral);
pfree(stmt_iteral);
$$ = (Node *) n;
}
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

Incorrect view text capture: yylloc doesn’t hold end location here

The grammar’s YYLLOC_DEFAULT tracks only start positions (see lines 70–89). Using “yylloc - @1” yields zero-length slices, so n->stmt_literal becomes just “;”. This breaks later get-definition use.

Minimal safe fix: stop capturing here and let later phases (e.g., after RawStmt length is known) populate stmt_literal, or add an explicit end locator. Until then, set stmt_literal = NULL to avoid storing bogus text.

-                   char    *stmt_iteral = NULL; 
+                   /* stmt_literal is populated later once the full statement
+                    * boundaries are known (RawStmt end). */
...
-                   /*
-                    * Save the source text of the force view definition in ViewStmt to avoid
-                    * incorrectly obtaining the view definition when using a multi-statement
-                    * parser tree.
-                    */
-                   stmt_iteral = pnstrdup(pg_yyget_extra(yyscanner)->core_yy_extra.scanbuf + @1, yylloc - @1);
-                   n->stmt_literal = psprintf("%s;", stmt_iteral);
-                   pfree(stmt_iteral);
+                   n->stmt_literal = NULL;

And mirror the same change in the OR REPLACE branch.

Also applies to: 12475-12498

🤖 Prompt for AI Agents
In src/backend/oracle_parser/ora_gram.y around lines 12451–12474, the code
attempts to capture the view source using "yylloc - @1" but yylloc only holds
start positions so the slice is zero-length and produces just ";" — set
n->stmt_literal = NULL here instead of attempting to capture the text, and
remove the pnstrdup/psprintf/pfree steps; apply the same change in the OR
REPLACE branch covering lines 12475–12498 so both branches assign stmt_literal =
NULL and defer populating it to a later phase (or to code that has a correct end
locator).

Comment on lines +217 to +223
if(rel_is_force_view(RelationGetRelid(pstate->p_target_relation)))
{
relation_close(pstate->p_target_relation, RowExclusiveLock);
compile_force_view(RelationGetRelid(pstate->p_target_relation));
pstate->p_target_relation = parserOpenTable(pstate, relation,
RowExclusiveLock);
}
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: use Oid before close, and close with table_close (not relation_close).

You close the Relation and then read its Oid again via RelationGetRelid(...) on a closed handle. Also, parserOpenTable() opened the rel via table_open, so you should symmetrically close with table_close. This is a correctness bug and can lead to use-after-close.

Apply:

-   if(rel_is_force_view(RelationGetRelid(pstate->p_target_relation)))
+   if (rel_is_force_view(RelationGetRelid(pstate->p_target_relation)))
    {
-       relation_close(pstate->p_target_relation, RowExclusiveLock);
-       compile_force_view(RelationGetRelid(pstate->p_target_relation));
-       pstate->p_target_relation = parserOpenTable(pstate, relation,
-                                                   RowExclusiveLock);
+       Oid viewoid = RelationGetRelid(pstate->p_target_relation);
+       table_close(pstate->p_target_relation, RowExclusiveLock);
+       compile_force_view(viewoid);
+       pstate->p_target_relation =
+           parserOpenTable(pstate, relation, RowExclusiveLock);
    }
📝 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(rel_is_force_view(RelationGetRelid(pstate->p_target_relation)))
{
relation_close(pstate->p_target_relation, RowExclusiveLock);
compile_force_view(RelationGetRelid(pstate->p_target_relation));
pstate->p_target_relation = parserOpenTable(pstate, relation,
RowExclusiveLock);
}
if (rel_is_force_view(RelationGetRelid(pstate->p_target_relation)))
{
Oid viewoid = RelationGetRelid(pstate->p_target_relation);
table_close(pstate->p_target_relation, RowExclusiveLock);
compile_force_view(viewoid);
pstate->p_target_relation =
parserOpenTable(pstate, relation, RowExclusiveLock);
}
🤖 Prompt for AI Agents
In src/backend/parser/parse_clause.c around lines 217 to 223, you must save the
relation Oid before closing the Relation and use table_close instead of
relation_close; obtain the Oid via RelationGetRelid(pstate->p_target_relation)
into a local Oid variable, call table_close(pstate->p_target_relation,
RowExclusiveLock), call compile_force_view(savedOid), and then set
pstate->p_target_relation = parserOpenTable(pstate, relation, RowExclusiveLock);
this avoids reading the Oid from a closed Relation and ensures symmetric
table_open/table_close usage.

* Create a syscache for pg_force_view lookup by OID.
* This will automatically create the FORCEVIEWOID enum member.
*/
MAKE_SYSCACHE(FORCEVIEWOID, pg_force_view_fvoid_index, 16);
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 syscache key count: MAKE_SYSCACHE must use nkeys = 1 for fvoid index.

Passing 16 here will break SearchSysCache1 at runtime. The index pg_force_view_fvoid_index has a single key (fvoid).

Apply:

-MAKE_SYSCACHE(FORCEVIEWOID, pg_force_view_fvoid_index, 16);
+MAKE_SYSCACHE(FORCEVIEWOID, pg_force_view_fvoid_index, 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
MAKE_SYSCACHE(FORCEVIEWOID, pg_force_view_fvoid_index, 16);
MAKE_SYSCACHE(FORCEVIEWOID, pg_force_view_fvoid_index, 1);
🤖 Prompt for AI Agents
In src/include/catalog/pg_force_view.h around line 63, the MAKE_SYSCACHE
invocation uses nkeys = 16 but the pg_force_view_fvoid_index has a single key
(fvoid); update the MAKE_SYSCACHE macro call to use 1 as the nkeys argument
(replace 16 with 1) so the syscache key count matches the index definition and
SearchSysCache1 will operate correctly.

Comment on lines +2504 to 2505
AT_ForceViewCompile /* compile force view */
} AlterTableType;
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

New AlterTableType added — ensure all switch statements handle it.

Add AT_ForceViewCompile handling anywhere AlterTableType is switched over (tablecmds.c, deparse paths, utility deparsing).


🏁 Script executed:

#!/bin/bash
set -euo pipefail
# Find all switches mentioning AlterTableType and ensure new enum is covered
rg -nPU '(?s)switch\s*\(\s*[^)]*subtype[^)]*\)\s*\{.*?\}' src | rg -n 'AT_ForceViewCompile' -n || true
rg -n 'AT_ForceViewCompile' -g '!build/**'

Length of output: 245


🏁 Script executed:

#!/bin/bash
set -euo pipefail
python3 - <<'PY'
import os, re, sys
root='.'
exclude_dirs = {'build', '.git', 'node_modules', 'target', 'dist', 'vendor'}
switch_pattern = re.compile(r'switch\s*\(([^)]*)\)', re.S)
case_pattern = re.compile(r'\bcase\s+AT_[A-Za-z0-9_]+\b')
switches = []
for dirpath, dirnames, filenames in os.walk(root):
    dirnames[:] = [d for d in dirnames if d not in exclude_dirs and not d.startswith('.')]
    for fname in filenames:
        # skip typical binary files
        if fname.endswith(('.png','.jpg','.jpeg','.gif','.pdf','.dll','.so','.exe','.class')):
            continue
        fpath = os.path.join(dirpath, fname)
        # limit to src/ to match original context
        if not (fpath.startswith('./src') or fpath.startswith('src')):
            continue
        try:
            with open(fpath, 'r', encoding='utf-8', errors='ignore') as fh:
                text = fh.read()
        except Exception:
            continue
        for m in switch_pattern.finditer(text):
            args = m.group(1)
            if 'subtype' in args:
                # find opening brace after the switch(...)
                brace_idx = text.find('{', m.end())
                if brace_idx == -1:
                    continue
                i = brace_idx
                depth = 0
                end = None
                L = len(text)
                while i < L:
                    c = text[i]
                    if c == '{':
                        depth += 1
                    elif c == '}':
                        depth -= 1
                        if depth == 0:
                            end = i
                            break
                    i += 1
                block = text[brace_idx:end+1] if end else text[brace_idx: min(len(text), brace_idx+2000)]
                start_line = text[:m.start()].count('\n') + 1
                has_case = 'AT_ForceViewCompile' in block
                case_lines = case_pattern.findall(block)
                switches.append((fpath, start_line, has_case, case_lines))
# Output summary
print("Total 'switch(...subtype...)' statements found:", len(switches))
missing = [s for s in switches if not s[2]]
print("Switches missing AT_ForceViewCompile:", len(missing))
for fpath, line, has_case, case_lines in missing:
    print(f"{fpath}:{line} -- cases in block: {case_lines}")
# Files that contain AT_ForceViewCompile anywhere
files_with = []
for dirpath, dirnames, filenames in os.walk(root):
    dirnames[:] = [d for d in dirnames if d not in exclude_dirs and not d.startswith('.')]
    for fname in filenames:
        fpath = os.path.join(dirpath, fname)
        if not (fpath.startswith('./src') or fpath.startswith('src')):
            continue
        try:
            with open(fpath, 'r', encoding='utf-8', errors='ignore') as fh:
                txt = fh.read()
        except Exception:
            continue
        if 'AT_ForceViewCompile' in txt:
            files_with.append(fpath)
print("Files containing 'AT_ForceViewCompile':", len(files_with))
for f in sorted(files_with):
    print(f)
PY

Length of output: 2610


Add AT_ForceViewCompile handling in all AlterTableType switches

Five switch blocks do not include AT_ForceViewCompile — add a case AT_ForceViewCompile: (or explicit handling) at these locations:

  • src/backend/commands/tablecmds.c:5974
  • src/backend/parser/parse_utilcmd.c:3719
  • src/backend/tcop/utility.c:1350
  • src/backend/utils/adt/geo_spgist.c:533
  • src/test/modules/test_ddl_deparse/test_ddl_deparse.c:111
🤖 Prompt for AI Agents
In src/include/nodes/parsenodes.h around lines 2504-2505, the AlterTableType
enum includes AT_ForceViewCompile but five switch statements elsewhere don't
handle it; add a `case AT_ForceViewCompile:` (or explicit handling) to the
switch blocks at the following locations so the enum is exhaustively handled:
src/backend/commands/tablecmds.c:5974, src/backend/parser/parse_utilcmd.c:3719,
src/backend/tcop/utility.c:1350, src/backend/utils/adt/geo_spgist.c:533, and
src/test/modules/test_ddl_deparse/test_ddl_deparse.c:111 — implement the case to
perform the appropriate no-op or view-compile-specific behavior consistent with
surrounding cases and ensure compilation and any fallthrough/return logic
mirrors the intended handling for other AlterTableType cases.

# Conflicts:
#	contrib/tsm_system_rows/expected/tsm_system_rows_1.out
#	contrib/tsm_system_time/expected/tsm_system_time_1.out
@gaoxueyu gaoxueyu merged commit b2a0e48 into IvorySQL:master Sep 17, 2025
6 checks passed
@coderabbitai coderabbitai bot mentioned this pull request Nov 4, 2025
@coderabbitai coderabbitai bot mentioned this pull request Apr 7, 2026
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.

2 participants