Conversation
|
Important Review skippedReview was skipped due to path filters ⛔ Files ignored due to path filters (2)
CodeRabbit blocks several paths by default. You can override this behavior by explicitly including those paths in the path filters. For example, including You can disable this status message by setting the WalkthroughAdds 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
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
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
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
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related PRs
Suggested reviewers
Pre-merge checks (3 passed)✅ Passed checks (3 passed)
Poem
Pre-merge checks (3 passed)✅ Passed checks (3 passed)
Poem
Pre-merge checks (3 passed)✅ Passed checks (3 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 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 compilationThe 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 VIEWcontrib/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 VIEWsrc/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 %ROWTYPEsrc/backend/commands/view.c (8)
295-368: Improve function documentation and clarify behaviorThe function documentation mentions updating metadata when
replaceis true for existing force views, but the actual implementation at lines 320-323 only sets*need_update = replacewithout 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 parameterThere'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 codeThe 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_viewThe 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 commentThere'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 cleanupThe 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 removedThe 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 usageExpand 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 parityThe 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 separatorAs 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 gatingIf 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
⛔ Files ignored due to path filters (9)
contrib/tsm_system_rows/expected/tsm_system_rows.outis excluded by!**/*.outcontrib/tsm_system_rows/expected/tsm_system_rows_1.outis excluded by!**/*.outcontrib/tsm_system_time/expected/tsm_system_time.outis excluded by!**/*.outcontrib/tsm_system_time/expected/tsm_system_time_1.outis excluded by!**/*.outsrc/oracle_test/regress/expected/create_view.outis excluded by!**/*.outsrc/oracle_test/regress/expected/create_view_1.outis excluded by!**/*.outsrc/oracle_test/regress/expected/functional_deps.outis excluded by!**/*.outsrc/oracle_test/regress/expected/ora_force_view.outis excluded by!**/*.outsrc/oracle_test/regress/expected/rangefuncs.outis 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 updatedDeclaration (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 & deduplicationI 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 correctlyTypes for OptViewForce and ALTER VIEW COMPILE look consistent with downstream usage. No action needed.
12509-12513: Defaulting recursive-views’ force/stmt_literalExplicitly 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
| 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); | ||
| } |
There was a problem hiding this comment.
🛠️ 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.
| 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).
| * 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); |
There was a problem hiding this comment.
💡 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 -C2Length 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' || trueLength 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"
fiLength 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."
fiLength 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' || trueLength 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).
| * 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); |
There was a problem hiding this comment.
💡 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 -C2Length 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 || trueLength 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 || trueLength 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.
| 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; |
There was a problem hiding this comment.
💡 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 -nLength 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.
| 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.
| 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; |
There was a problem hiding this comment.
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.
| 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; |
| DeleteForceView(viewOid); | ||
|
|
There was a problem hiding this comment.
🛠️ 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.
| 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.
| 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; | ||
| } |
There was a problem hiding this comment.
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).
| 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); | ||
| } |
There was a problem hiding this comment.
🛠️ 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.
| 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); |
There was a problem hiding this comment.
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.
| 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.
| AT_ForceViewCompile /* compile force view */ | ||
| } AlterTableType; |
There was a problem hiding this comment.
💡 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)
PYLength 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
Summary by CodeRabbit
New Features
Improvements
Documentation
Tests