Skip to content

implement pg_proc table#2902

Open
jennifersp wants to merge 11 commits into
mainfrom
jennifer/proc
Open

implement pg_proc table#2902
jennifersp wants to merge 11 commits into
mainfrom
jennifer/proc

Conversation

@jennifersp

Copy link
Copy Markdown
Contributor

No description provided.

@jennifersp jennifersp requested a review from Hydrocharged July 7, 2026 21:01
@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor
Main PR
covering_index_scan_postgres 1898.91/s 1879.16/s -1.1%
groupby_scan_postgres 128.16/s 130.80/s +2.0%
index_join_postgres 666.81/s 663.76/s -0.5%
index_join_scan_postgres 846.42/s 851.63/s +0.6%
index_scan_postgres 27.01/s 27.53/s +1.9%
oltp_delete_insert_postgres 867.12/s 858.18/s -1.1%
oltp_insert 711.06/s 718.18/s +1.0%
oltp_point_select 3463.98/s 3535.23/s +2.0%
oltp_read_only 3301.34/s 3367.16/s +1.9%
oltp_read_write 2470.10/s 2492.71/s +0.9%
oltp_update_index 796.96/s 776.32/s -2.6%
oltp_update_non_index 849.65/s 845.06/s -0.6%
oltp_write_only 1857.33/s 1829.46/s -1.6%
select_random_points 1900.02/s 1960.14/s +3.1%
select_random_ranges 1581.79/s 1584.58/s +0.1%
table_scan_postgres 25.82/s 26.20/s +1.4%
types_delete_insert_postgres 833.17/s 830.19/s -0.4%
types_table_scan_postgres 11.04/s 11.32/s +2.5%

@itoqa

itoqa Bot commented Jul 7, 2026

Copy link
Copy Markdown

Ito QA test results
Commit: cc4f52e: 10 test cases ran, 2 failed ❌, 8 passed ✅.

Summary

This run exercises database routine metadata and definition lookup behavior across normal catalog introspection, argument-shape edge cases, and function-body merge handling for different quoting styles. Core routine discovery and definition round-trips look healthy, but security-boundary and metadata-consistency checks exposed important gaps.

Not safe to merge yet — this PR has attributable failures that include a high-severity authorization exposure plus an additional medium-severity catalog correctness defect, indicating real user-facing risk beyond minor edge noise. Because these are tied to this branch and affect both access control and metadata reliability, they are merge blockers rather than follow-up-only caveats.

Tests run by Ito

View full run

Result Severity Type Description
High severity Lookup The expected visibility boundary is inconsistent: pg_proc enforces permission checks, but pg_get_functiondef bypasses that boundary and returns full routine definitions to low-privilege users.
Medium severity Catalog proargnames includes duplicated names for named function signatures, so the array cardinality no longer matches the routine argument count.
Catalog The catalog query returned both created routines with the expected kind values: alt_func1 as a function and ptest5 as a procedure.
Catalog The pg_catalog.pg_proc row for procedure ptest5 correctly reports pronargs=3, pronargdefaults=1, proargtypes=23 25 23, and proargnames={a,b,c}, matching the declared signature with one defaulted input parameter.
Iteration Procedure-only verification passed: pg_proc returned ptest5 with prokind=p, OID lookup succeeded, and pg_get_functiondef returned the full procedure definition in the current build.
Lookup pg_get_functiondef resolved procedure OID 1886569565 to the expected CREATE OR REPLACE PROCEDURE definition for ptest5, including the expected SQL body with both INSERT statements.
Lookup The sampled function and procedure OIDs from pg_proc each resolved to their own expected definitions in pg_get_functiondef, with no cross-entity body mismatch.
Parsing Go test TestParsingFunctionDefinitionBody/PARSING-1 passed, confirming single-quoted AS function definitions are merged by inner-body content while preserving the surrounding DDL wrapper.
Parsing Go test TestParsingFunctionDefinitionBody/PARSING-2 passed, confirming $$-quoted function definitions are resolved by inner-body content while preserving the surrounding function wrapper and signature.
Parsing Legacy single-quoted function bodies with embedded doubled-quote patterns were merged at inner-body granularity and preserved the surrounding DDL wrapper.

Tip

Reply with @itoqa to send us feedback on this test run.

Comment thread server/tables/pgcatalog/pg_proc.go Outdated
Comment thread server/functions/pg_get_functiondef.go
@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor
Main PR
Total 42090 42090
Successful 18275 18279
Failures 23815 23811
Partial Successes1 5335 5335
Main PR
Successful 43.4189% 43.4284%
Failures 56.5811% 56.5716%

${\color{red}Regressions (1)}$

join

QUERY:          select * from int8_tbl i1 left join (int8_tbl i2 join
  (select 123 as x) ss on i2.q1 = x) on i1.q2 = i2.q2
order by 1, 2;
RECEIVED ERROR: integer: unhandled type: int64 (errno 1105) (sqlstate HY000)

${\color{lightgreen}Progressions (5)}$

opr_sanity

QUERY: SELECT a1.oid, a1.amname
FROM pg_am AS a1
WHERE a1.amhandler = 0;

subselect

QUERY: select count(*) from tenk1 t
where (exists(select 1 from tenk1 k where k.unique1 = t.unique2) or ten < 0);

type_sanity

QUERY: SELECT t1.oid, t1.typname
FROM pg_type as t1
WHERE (t1.typinput = 0 OR t1.typoutput = 0);
QUERY: SELECT t1.oid, t1.typname, t1.typelem
FROM pg_type AS t1
WHERE t1.typelem != 0 AND t1.typsubscript = 0;
QUERY: SELECT t1.oid, t1.typname,
       t1.typelem, t1.typlen, t1.typbyval
FROM pg_type AS t1
WHERE t1.typsubscript = 'array_subscript_handler'::regproc AND NOT
    (t1.typelem != 0 AND t1.typlen = -1 AND NOT t1.typbyval);

Footnotes

  1. These are tests that we're marking as Successful, however they do not match the expected output in some way. This is due to small differences, such as different wording on the error messages, or the column names being incorrect while the data itself is correct.

@Hydrocharged Hydrocharged left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM! Strange that we have such a large regression, I'm not seeing anything that would point to that being the case

@itoqa

itoqa Bot commented Jul 8, 2026

Copy link
Copy Markdown

Ito QA test results
Ito Diff Reportcc4f52e85194d8: 7 test cases ran, 1 fixed ✅, 5 passing ✅, 1 additional finding ⚠️.

Diff Summary

This run focused on database routine metadata behavior, covering normal and boundary signatures to confirm argument-name alignment, null handling, and compatibility expectations for metadata consumers. It also exercised a permission-boundary abuse path around routine-definition lookup, while overall behavior tied to this change remained stable.

Safe to merge — the exercised areas for this PR’s metadata changes passed, with no regressions, no new PR-attributable failures, and no previously flagged PR-owned failures still failing. One high-severity failure appeared as an unrelated additional finding in an existing authorization path, so it is a flag-for-later issue rather than a merge blocker for this PR.

Tests run by Ito

View full run

Result State Severity Type Description
❌->✅ Fixed Catalog Boundary routine signatures preserve expected pg_proc null-vs-array conventions: no-arg and all-unnamed inputs keep proargnames null, while mixed/named signatures keep positional blanks with aligned array lengths.
Passing Catalog Mixed named and unnamed function parameters keep positional alignment in pg_proc by preserving blank placeholders in proargnames.
Passing Catalog Functions with fully unnamed parameters returned proargnames as NULL (not an empty-string array), and mixed named/unnamed signatures still returned positional arrays with blank placeholders.
Passing Catalog Catalog evidence confirms pg_proc.prosupport remains NULL for alt_func1 while the column type is OID (atttypid = 26), matching the expected behavior.
Passing Catalog Verified with pgx/v5 that OID-typed NULL prosupport is handled correctly across nullable scan targets, and non-nullable int scans fail with a clear NULL conversion error instead of silent coercion.
Passing Catalog Named-parameter routines kept positional proargnames behavior, and routines without parameter names were flagged with explicit mismatch signals instead of silently mis-mapping legacy introspection output.
⏸️ Skipped Catalog The catalog query returned both created routines with the expected kind values: alt_func1 as a function and ptest5 as a procedure.
⏸️ Skipped Catalog The pg_catalog.pg_proc row for procedure ptest5 correctly reports pronargs=3, pronargdefaults=1, proargtypes=23 25 23, and proargnames={a,b,c}, matching the declared signature with one defaulted input parameter.
⏸️ Skipped Iteration Procedure-only verification passed: pg_proc returned ptest5 with prokind=p, OID lookup succeeded, and pg_get_functiondef returned the full procedure definition in the current build.
⏸️ Skipped Lookup pg_get_functiondef resolved procedure OID 1886569565 to the expected CREATE OR REPLACE PROCEDURE definition for ptest5, including the expected SQL body with both INSERT statements.
⏸️ Skipped Lookup The sampled function and procedure OIDs from pg_proc each resolved to their own expected definitions in pg_get_functiondef, with no cross-entity body mismatch.
⏸️ Skipped Parsing Go test TestParsingFunctionDefinitionBody/PARSING-1 passed, confirming single-quoted AS function definitions are merged by inner-body content while preserving the surrounding DDL wrapper.
⏸️ Skipped Parsing Go test TestParsingFunctionDefinitionBody/PARSING-2 passed, confirming $$-quoted function definitions are resolved by inner-body content while preserving the surrounding function wrapper and signature.
⏸️ Skipped Parsing Legacy single-quoted function bodies with embedded doubled-quote patterns were merged at inner-body granularity and preserved the surrounding DDL wrapper.
⚠️ Additional Finding High severity Lookup Visibility checks are applied to direct pg_proc access but not enforced in pg_get_functiondef, so the function leaks routine definitions to unauthorized roles.
Additional Findings Details

These findings are unrelated to the current changes but were observed during testing.

🟠 Low-privilege users can read routine source via pg_get_functiondef
  • Severity: High High severity
  • Description: Visibility checks are applied to direct pg_proc access but not enforced in pg_get_functiondef, so the function leaks routine definitions to unauthorized roles.
  • Impact: Low-privilege database users can read protected routine definitions, exposing sensitive business logic and potentially embedded secrets. This breaks intended authorization boundaries and can enable further targeted abuse.
  • Steps to Reproduce:
    1. Create a low-privilege role and grant minimal catalog access in the local test environment.
    2. Verify that the same role gets permission denied when selecting from pg_catalog.pg_proc.
    3. Call pg_get_functiondef(<routine_oid>) as the low-privilege role.
    4. Observe that full CREATE FUNCTION text is returned for both public and sensitive routines.
  • Stub / mock content: Authentication was disabled in the local harness for test execution, but role-based visibility behavior was still exercised and the low-privilege role could read routine source through pg_get_functiondef despite pg_proc access being denied.
  • Code Analysis: In server/functions/pg_get_functiondef.go, the callable resolves the OID and unconditionally copies function/procedure definitions into the return value via RunCallback callbacks, with no role/ACL check before returning the SQL text. The PR diff for this run changes only server/tables/pgcatalog/pg_proc.go (argument-name/prosupport handling) and does not modify this authorization path, so no direct changed-code causal path ties this bug to the PR.
Evidence Package

Tip

Reply with @itoqa to send us feedback on this test run.

@itoqa

itoqa Bot commented Jul 8, 2026

Copy link
Copy Markdown

Ito QA test results
Ito Diff Report85194d8f4a5d7a: 11 test cases ran, 6 new failures ❌, 4 passing ✅, 1 additional finding ⚠️.

Diff Summary

This run covered database catalog and type-system behavior, including normal subscript and transaction-stability paths plus tougher metadata queries with joins and casts that many tools rely on for introspection. Core subscript behavior looked healthy, but catalog OID/regproc handling showed instability under realistic query patterns.

Not safe to merge yet — this PR introduces multiple new attributable failures, including high-severity crashes in common metadata-introspection paths and several related medium-severity failures from the same type-handling area. There is also an unrelated pre-existing high-severity authorization issue noted as a caveat, but the merge-blocking risk comes from the new PR-attributable failures.

Tests run by Ito

View full run

Result State Severity Type Description
❌ New Failure High severity Type Both metadata queries panic with "interface conversion: interface {} is string, not id.Id" while serializing OID-typed columns, so PostgreSQL-compatible introspection does not complete.
❌ New Failure High severity Type Expected stable coercion behavior (or at least one consistent explicit error mode) for OID-declared catalog columns. Instead, direct projection and ::text routes panic in oid output, ::oid fails in cast planning, and ::regproc panics in regproc output for the same underlying fields.
❌ New Failure Medium severity Handler The join succeeds when amhandler is omitted, but adding amhandler causes DoltgresHandler to panic with "interface conversion: interface {} is string, not id.Id" from oidout.
❌ New Failure Medium severity Handler Projecting amhandler causes a server panic during OID serialization instead of returning rows.
❌ New Failure Medium severity Handler Both the join projection path and the explicit cast path panic when serializing amhandler, so no result rows are returned for handler-linkage checks.
❌ New Failure Medium severity Type A valid catalog query crashes the regproc output path with interface conversion: interface {} is string, not id.Id, so rows are not returned.
Passing Subscript Subscripted array and oidvector values resolved to element semantics, and downstream integer casts/comparisons executed successfully.
Passing Subscript Invalid string and date array indexes are rejected with stable conversion errors, NULL index returns NULL, and valid subscripting still succeeds afterward.
Passing Subscript Historical subscript patterns remained compatible after BaseType inference changes, including oidvector/int2vector element access, casts, WHERE predicates, aggregates, subqueries, and CASE expressions; multi-dimensional subscripts still fail with an explicit unsupported error.
Passing Type The test executed 50 full scans of pg_catalog.pg_type followed by an ordered LIMIT query and COMMIT in the same session, and every statement completed successfully without panics or degraded behavior.
⏸️ Skipped Catalog Boundary routine signatures preserve expected pg_proc null-vs-array conventions: no-arg and all-unnamed inputs keep proargnames null, while mixed/named signatures keep positional blanks with aligned array lengths.
⏸️ Skipped Catalog Mixed named and unnamed function parameters keep positional alignment in pg_proc by preserving blank placeholders in proargnames.
⏸️ Skipped Catalog Functions with fully unnamed parameters returned proargnames as NULL (not an empty-string array), and mixed named/unnamed signatures still returned positional arrays with blank placeholders.
⏸️ Skipped Catalog Catalog evidence confirms pg_proc.prosupport remains NULL for alt_func1 while the column type is OID (atttypid = 26), matching the expected behavior.
⏸️ Skipped Catalog Verified with pgx/v5 that OID-typed NULL prosupport is handled correctly across nullable scan targets, and non-nullable int scans fail with a clear NULL conversion error instead of silent coercion.
⏸️ Skipped Catalog Named-parameter routines kept positional proargnames behavior, and routines without parameter names were flagged with explicit mismatch signals instead of silently mis-mapping legacy introspection output.
⚠️ Additional Finding High severity Lookup Authorization is enforced for direct routine execution but not for routine-definition retrieval. The system exposes function body text to a caller that lacks EXECUTE permission on that routine.
Additional Findings Details

These findings are unrelated to the current changes but were observed during testing.

🟠 Low-privilege role can read routine source
  • Severity: High High severity
  • Description: Authorization is enforced for direct routine execution but not for routine-definition retrieval. The system exposes function body text to a caller that lacks EXECUTE permission on that routine.
  • Impact: Low-privilege database users can read routine source definitions they are not authorized to execute, exposing sensitive business logic and any secrets embedded in function bodies.
  • Steps to Reproduce:
    1. Create a low-privilege role with CONNECT/USAGE and no EXECUTE on public.alt_func1.
    2. As that role, query pg_catalog.pg_proc to obtain the routine OID and call SELECT pg_get_functiondef(<oid>);.
    3. Observe that direct SELECT public.alt_func1(5); is denied, but pg_get_functiondef still returns the full routine definition text.
  • Stub / mock content: Authentication was bypassed only to simplify local login during the run; no mock altered routine privilege checks or the pg_get_functiondef code path under test.
  • Code Analysis: server/functions/pg_get_functiondef.go returns function.Item.Definition and procedure.Item.Definition directly inside the callback without any privilege check. By contrast, direct invocation uses AuthType_EXECUTE in server/auth/auth_handler.go, which routes to checkPrivilegeOnRoutine and returns permission denied for routine when EXECUTE is missing. A practical fix is to add a routine privilege gate (owner/superuser/EXECUTE-compatible visibility check) before returning definition text in pg_get_functiondef.
Evidence Package

Tip

Reply with @itoqa to send us feedback on this test run.

Comment thread server/tables/pgcatalog/pg_am.go
Comment thread server/tables/pgcatalog/pg_am.go
Comment thread server/tables/pgcatalog/pg_am.go
Comment thread server/tables/pgcatalog/pg_type.go Outdated
Comment thread server/tables/pgcatalog/pg_type.go Outdated
Comment thread server/tables/pgcatalog/pg_type.go Outdated
@itoqa

itoqa Bot commented Jul 8, 2026

Copy link
Copy Markdown

Ito QA test results
Ito Diff Reportf4a5d7af9c1bab: 18 test cases ran, 7 new failures ❌, 6 still failing ❌, 4 passing ✅, 1 additional finding ⚠️.

Diff Summary

This run focused on database catalog compatibility behaviors, covering normal metadata lookups and tougher edge cases like casting, filtering, and joining handler-related fields across system tables. Basic routine and support-column reads were stable, but many catalog paths that expose handler metadata now fail with runtime crashes or inconsistent cast behavior instead of returning reliable results.

Not safe to merge yet — this PR is tied to a high-severity crash plus multiple medium-severity new and still-unfixed failures in core catalog introspection paths, indicating broad instability in behavior the change touched. There is also one unrelated additional finding to track separately, but the attributable failures alone are merge-blocking.

Tests run by Ito

View full run

Result State Severity Type Description
❌ New Failure Medium severity Handler SELECT amhandler FROM pg_catalog.pg_am and SELECT amhandler::text FROM pg_catalog.pg_am panic with interface conversion: interface {} is string, not id.Id in server/functions/regproc.go:81. SELECT amhandler::regproc::text returns cast from type regproc to type regproc is mislabeled as binary-coercible.
❌ New Failure Medium severity Handler Each query path touching regproc-backed handler columns fails: legacy oid-style projections panic with interface {} is string, not id.Id, and explicit regproc casts fail with cast from type regproc to type regproc is mislabeled as binary-coercible.
❌ New Failure Medium severity Handler amhandler comparisons panic with interface {} is string, not id.Id instead of evaluating predicates, and regproc text identity is inconsistent between known and unknown numeric OIDs.
❌ New Failure Medium severity Type The test expected type metadata discovery to return rows, but regproc-typed pg_type handler columns abort with cast errors and panic during projection.
❌ New Failure Medium severity Type The query engine aborts during regproc cast handling for pg_type handler columns, so resolver boundary cases (valid, missing, ambiguous names) are never evaluated.
❌ New Failure Medium severity Type Expected pg_type handler columns to project predictably after regproc typing, but queries panic because regproc output assumes id.Id while pg_type rows still contain string handler names.
❌ New Failure Medium severity Type Expected equivalent handler/support metadata columns to resolve consistently across catalog entities after regproc normalization. Instead, pg_type and pg_am projections panic with interface conversion errors while pg_proc returns null/empty rows without evaluating a comparable handler value.
❌->❌ Still Failing High severity Handler Expected the join to return index metadata rows with amhandler projected. Actual behavior is ERROR 1105 after a panic at regproc.go:81 caused by interface conversion from string to id.Id.
❌->❌ Still Failing Medium severity Handler The amhandler projection path panics with interface conversion: interface {} is string, not id.Id when converting the regproc-typed value for SQL output.
❌->❌ Still Failing Medium severity Handler The amhandler column is declared as Regproc but pg_am rows still provide plain string handler names, so projecting amhandler crashes type output and cross-table handler linkage is incoherent.
❌->❌ Still Failing Medium severity Type Any introspection query that projects regproc-declared handler columns from pg_type or pg_am triggers a panic in regproc output conversion instead of returning rows.
❌->❌ Still Failing Medium severity Type Expected catalog handler columns to be returned or to fail with stable explicit coercion errors. Instead, projecting regproc-typed columns triggers runtime panic paths and inconsistent cast failures.
❌->❌ Still Failing Minor severity Type Expected regproc handler names to be returned for typinput, but the query errors and regproc output handling panics on projected values.
Passing Routine Querying pg_catalog.pg_proc for alt_func1 and ptest5 returned both rows, with NULL prosupport values and no coercion or materialization errors.
Passing Routine A single pg_proc query returned both alt_func1 and ptest5 with consistent prokind, proargtypes, proallargtypes, and proargnames values and no decoding errors.
Passing Routine The test successfully looked up alt_func1 in pg_catalog.pg_proc, retrieved OID 2891346960, and resolved that OID through pg_get_functiondef without type-coercion or materialization errors.
Passing Type The test executed 50 consecutive full scans of pg_catalog.pg_type and each iteration returned count=75 without errors. A post-loop typname query still returned expected rows and the server process remained alive, so no persistent failure surfaced for this scenario.
⏸️ Skipped Subscript Subscripted array and oidvector values resolved to element semantics, and downstream integer casts/comparisons executed successfully.
⏸️ Skipped Subscript Invalid string and date array indexes are rejected with stable conversion errors, NULL index returns NULL, and valid subscripting still succeeds afterward.
⏸️ Skipped Subscript Historical subscript patterns remained compatible after BaseType inference changes, including oidvector/int2vector element access, casts, WHERE predicates, aggregates, subqueries, and CASE expressions; multi-dimensional subscripts still fail with an explicit unsupported error.
⚠️ Additional Finding Medium severity Lookup Permission boundaries are enforced for catalog table reads but not enforced in pg_get_functiondef(oid), allowing definition text disclosure to unauthorized callers.
Additional Findings Details

These findings are unrelated to the current changes but were observed during testing.

🟡 Low-privilege role can read routine source via pg_get_functiondef
  • Severity: Medium Medium severity
  • Description: Permission boundaries are enforced for catalog table reads but not enforced in pg_get_functiondef(oid), allowing definition text disclosure to unauthorized callers.
  • Impact: Some users may be unable to complete sign-in when the account lookup/authentication step does not return, requiring a retry.
  • Steps to Reproduce:
    1. Create a low-privilege role with CONNECT/USAGE but without SELECT on pg_proc.
    2. Confirm the role is denied by querying pg_proc or information_schema.routines.
    3. As a privileged role, get the OID of an existing function or procedure.
    4. As the low-privilege role, run SELECT pg_get_functiondef() and observe the full definition is returned.
  • Stub / mock content: Authentication was intentionally disabled in the QA run so the harness could execute database checks, but no mock routes or synthetic function-definition responses were used for this test path.
  • Code Analysis: In server/functions/pg_get_functiondef.go:38-55, pg_get_functiondef directly calls RunCallback and returns function/procedure definitions without any role or privilege check. The resolver in server/functions/iterate.go:581-695 performs object traversal by OID and callback dispatch, but does not enforce caller authorization before returning definition data.
Evidence Package

Tip

Reply with @itoqa to send us feedback on this test run.

Comment thread server/tables/pgcatalog/pg_am.go
Comment thread server/tables/pgcatalog/pg_am.go
Comment thread server/tables/pgcatalog/pg_am.go
Comment thread server/tables/pgcatalog/pg_type.go
Comment thread server/tables/pgcatalog/pg_type.go
Comment thread server/tables/pgcatalog/pg_type.go
@itoqa

itoqa Bot commented Jul 8, 2026

Copy link
Copy Markdown

Ito QA test results
Ito Diff Reportf9c1bab59e5914: 25 test cases ran, 1 new failure ❌, 13 fixed ✅, 10 passing ✅, 1 additional finding ⚠️.

Diff Summary

This run heavily exercised database system-catalog behavior around function-handler identifiers, including normal metadata reads, legacy compatibility patterns, type-casting edge cases, and repeated-read stability checks. It also covered adversarial access-boundary behavior for routine-definition lookup, while overall core catalog introspection behavior remained largely stable.

Merge with caution — there is one PR-attributable medium-severity new failure indicating a backward-compatibility break in a legacy metadata lookup path introduced by this change. A separate high-severity failure was observed but is marked unrelated to this PR, so it is a follow-up caveat rather than a merge blocker for this branch.

Tests run by Ito

View full run

Result State Severity Type Description
❌ New Failure Medium severity Type A legacy equality filter (typinput = 'int4in') fails at runtime with OID parsing error even though typinput renders as int4in in result output.
❌->✅ Fixed Handler The pg_class to pg_am join returned index rows with stable amname/amhandler values and no conversion errors while projecting amhandler.
❌->✅ Fixed Handler The pg_am scan returned all seven expected access methods, and amhandler projected successfully with regproc semantics (matching the updated pg_am schema and output behavior).
❌->✅ Fixed Handler Handler linkage stayed coherent across all exercised query forms: pg_class.relam joins projected btree->bthandler consistently, direct pg_am grouping kept one handler per access method, and nested projections preserved the same handler OID mappings without conflicts.
❌->✅ Fixed Handler Valid handler names resolve as expected, invalid handler probes fail with an explicit error, and pg_am queries remain stable after probing.
❌->✅ Fixed Handler Legacy oid-based metadata query patterns remain backward-compatible with regproc columns: casts to oid/int4/int8 and oid comparisons return consistent rows, and modern regproc filters return the same handler identity without silent metadata drift.
❌->✅ Fixed Handler Known OIDs resolve to function names, unknown OIDs remain numeric text, and regproc/oid/int4/int8 comparisons stay consistent without false identity matches.
❌->✅ Fixed Type SELECT typinput::regproc FROM pg_catalog.pg_type for float8 and _int4 returned 2 rows (float8in, array_in) consistently across two repeated executions.
❌->✅ Fixed Type Broad pg_catalog introspection queries over pg_type and pg_am completed successfully with regproc-typed handler columns returning expected symbolic values and no coercion failures.
❌->✅ Fixed Type OID/regproc/text coercion probes on pg_type and pg_am stayed consistent: symbolic handlers round-tripped to numeric OIDs and back, '-' placeholders mapped to 0 for ::oid and '-' for ::regproc/::text, and invalid names failed with a stable explicit error.
❌->✅ Fixed Type The pg_type discovery query returned all targeted rows with readable regproc handler names for typinput, typreceive, and typsend after the regproc transition.
❌->✅ Fixed Type Boundary regproc cases behaved in a controlled way: valid names resolved, invalid and ambiguous names produced explicit errors, and subsequent pg_type scans continued to return rows without failure.
❌->✅ Fixed Type Placeholder handler payloads render predictably after the oid-to-regproc transition: '-' values surface as regproc::text='-' and regproc::oid=0, pg_type rowcount remains 79, and both placeholder and named handlers stay queryable without coercion failures.
❌->✅ Fixed Type Catalog handler identifiers stayed consistent across pg_type and pg_am projections, with stable regproc name rendering and no conflicting identities detected.
Passing Handler All 7 access methods expose meaningful amhandler identities with valid regproc names and corresponding OIDs, with zero placeholder '-' or empty-text handler rows.
Passing Handler Catalog reads over pg_catalog.pg_am returned all seven access-method handlers with stable regproc, text, and oid round-trips across repeated reads, so no handler placeholder degradation or cast instability was reproduced.
Passing Handler ORM-style introspection across pg_catalog.pg_type and pg_catalog.pg_am returned resolvable function and handler identities, with ORM_CAPABILITY_DETECTION_OK and no placeholder degradation.
Passing Handler All seven default access methods map consistently between amhandler text and amhandler OID forms, and filtering by bthandler text matches filtering by OID 330.
Passing Type Fifty consecutive full scans of pg_catalog.pg_type completed without errors, and follow-up catalog queries still returned expected rows and regproc handler names.
Passing Type pg_catalog.pg_type regproc projections completed successfully and unresolved typ* function IDs degraded to '-' fallback output without aborting the query.
Passing Type Built-in pg_type handler metadata remains resolvable through regproc name casts and filters, including a negative filter check that returned no false positives.
Passing Type Numeric OID values cast to regproc remained numeric text (including unmapped IDs like 999999 and 0), and OID-style comparisons stayed consistent with expected behavior.
Passing Type pg_proc prosupport projection and cast queries completed without crashes, and NULL prosupport values stayed consistently represented across direct, text, and OID views.
Passing Type The runner marked this case blocked due to a harness hang, but the recorded query output and source inspection both show null-function-backed pg_type regproc fields rendering '-' as expected.
⏸️ Skipped Catalog Verification confirmed pg_catalog.pg_proc.prosupport remains NULL for alt_func1 while the column type is regproc, which matches the intentional PR behavior and the test plan's obsolete tag for OID typing.
⏸️ Skipped Routine Querying pg_catalog.pg_proc for alt_func1 and ptest5 returned both rows, with NULL prosupport values and no coercion or materialization errors.
⏸️ Skipped Routine A single pg_proc query returned both alt_func1 and ptest5 with consistent prokind, proargtypes, proallargtypes, and proargnames values and no decoding errors.
⏸️ Skipped Routine The test successfully looked up alt_func1 in pg_catalog.pg_proc, retrieved OID 2891346960, and resolved that OID through pg_get_functiondef without type-coercion or materialization errors.
⚠️ Additional Finding High severity Lookup Authorization boundaries for routine definitions are not enforced: pg_get_functiondef returns Definition content for matched routines without checking caller permissions.
Additional Findings Details

These findings are unrelated to the current changes but were observed during testing.

🟠 Low-privilege role can read private routine source via pg_get_functiondef
  • Severity: High High severity
  • Description: Authorization boundaries for routine definitions are not enforced: pg_get_functiondef returns Definition content for matched routines without checking caller permissions.
  • Impact: Low-privilege database users can retrieve source text for private routines, exposing internal logic and any embedded secrets. This violates authorization boundaries and can enable follow-on abuse of protected data paths.
  • Steps to Reproduce:
    1. Create a non-superuser role with only CONNECT on the database plus USAGE/SELECT access to pg_catalog metadata.
    2. Create a routine in a private schema that the low-privilege role does not have EXECUTE or schema USAGE privileges for.
    3. As the low-privilege role, query pg_catalog.pg_proc to get the routine OID and call pg_get_functiondef(oid).
    4. Observe that pg_get_functiondef returns the full CREATE FUNCTION text instead of denying access or redacting the definition.
  • Stub / mock content: Test setup created a qa_lowpriv role with minimal catalog access grants and created public/private routines to verify visibility boundaries; no mocks, stubs, or bypasses were used.
  • Code Analysis: In server/functions/pg_get_functiondef.go, Callable assigns result = function.Item.Definition / procedure.Item.Definition after RunCallback resolves the OID, and returns it directly with no privilege gate. The iteration path in server/functions/iterate.go resolves matching routines and invokes callbacks without ACL checks. The authorization layer has a routine privilege primitive (checkPrivilegeOnRoutine uses HasRoutinePrivilege in server/auth/auth_handler.go), but pg_get_functiondef does not call it, making unauthorized definition disclosure a plausible and direct code-level defect.
Evidence Package

Tip

Reply with @itoqa to send us feedback on this test run.

Comment thread server/tables/pgcatalog/pg_type.go
@itoqa

itoqa Bot commented Jul 9, 2026

Copy link
Copy Markdown

Ito QA test results
Ito Diff Report59e59146c2437a: 15 test cases ran, 1 fixed ✅, 12 passing ✅, 2 additional findings ⚠️.

Diff Summary

This run exercises core database catalog behavior around type and function metadata, casting, cross-catalog joins, and compatibility-oriented introspection patterns, including both normal read paths and edge-case type/coercion boundaries. It also covers an adversarial permission boundary scenario for routine-definition visibility, giving a broad signal on metadata correctness, stability, and access-control behavior.

Safe to merge — the observed failures are non-attributable additional findings rather than regressions introduced by this PR, and the change shows stable behavior across the exercised catalog and type-compatibility paths. There are pre-existing medium/high-impact product issues worth tracking separately, but they are not merge blockers for this PR.

Tests run by Ito

View full run

Result State Severity Type Description
❌->✅ Fixed Type The compatibility scenarios completed and produced explicit, actionable OID syntax errors for legacy string-literal comparisons while text and explicit cast query paths continued to work, matching the expected behavior.
Passing Catalog pg_catalog.pg_sequences returned PostgreSQL-compatible data_type names (bigint, integer) for sequences created with AS bigint and AS integer, matching the updated regression expectation and confirming the regtype-ID materialization path is working.
Passing Catalog Equivalent catalog queries using implicit equality and explicit casts returned consistent results, and repeated executions stayed stable without planner or runtime divergence.
Passing Catalog The pg_type.typinput to pg_proc.oid join executes successfully and returns an empty result set, which matches the fixture expectation for this catalog behavior.
Passing Handler Confirmed pg_catalog.pg_am exposes amhandler as regproc and returns all default access methods with regproc-compatible handler values, including successful ::text and ::oid projections.
Passing Handler Verified pg_am.amhandler comparisons against oid and regproc operands execute successfully with coherent results and no planner errors.
Passing Handler Comparisons between regproc and id-backed numeric operand types remain coherent, while regproc comparisons to text/name correctly fail with explicit operator errors unless an explicit cast is applied first.
Passing Handler Verified pg_catalog.pg_am amhandler values are coherent across regproc text and OID forms for all 7 default access methods, with text and oid predicates selecting the same rows and no mismatches.
Passing Type Catalog introspection over pg_catalog.pg_type completed successfully with 30 rows, resolved handlers rendered as expected regproc names, and unmapped function IDs degraded to '-' fallback output without aborting the query.
Passing Type The pg_type typ* regproc fields resolve to expected function names, ::text predicates return matching rows, and the pg_type-to-pg_proc join behavior matches the documented empty-fixture expectation.
Passing Type The blocked runner result was caused by harness completion hang; remediation reran the pg_type to pg_proc join and confirmed it plans and executes successfully without regproc versus oid type-equality errors.
Passing Type Typ metadata queries for typinput, typoutput, typreceive, and typsend remained stable across repeated materialization and returned regproc-compatible function names without panic or row-decoding errors.
Passing Type Broad ORM-style metadata queries over pg_type and pg_am completed without coercion errors, and regproc handler values rendered as expected.
⏸️ Skipped Handler The pg_class to pg_am join returned index rows with stable amname/amhandler values and no conversion errors while projecting amhandler.
⏸️ Skipped Handler All 7 access methods expose meaningful amhandler identities with valid regproc names and corresponding OIDs, with zero placeholder '-' or empty-text handler rows.
⏸️ Skipped Handler Catalog reads over pg_catalog.pg_am returned all seven access-method handlers with stable regproc, text, and oid round-trips across repeated reads, so no handler placeholder degradation or cast instability was reproduced.
⏸️ Skipped Handler ORM-style introspection across pg_catalog.pg_type and pg_catalog.pg_am returned resolvable function and handler identities, with ORM_CAPABILITY_DETECTION_OK and no placeholder degradation.
⏸️ Skipped Handler The pg_am scan returned all seven expected access methods, and amhandler projected successfully with regproc semantics (matching the updated pg_am schema and output behavior).
⏸️ Skipped Handler Handler linkage stayed coherent across all exercised query forms: pg_class.relam joins projected btree->bthandler consistently, direct pg_am grouping kept one handler per access method, and nested projections preserved the same handler OID mappings without conflicts.
⏸️ Skipped Handler Valid handler names resolve as expected, invalid handler probes fail with an explicit error, and pg_am queries remain stable after probing.
⏸️ Skipped Handler Legacy oid-based metadata query patterns remain backward-compatible with regproc columns: casts to oid/int4/int8 and oid comparisons return consistent rows, and modern regproc filters return the same handler identity without silent metadata drift.
⏸️ Skipped Handler Known OIDs resolve to function names, unknown OIDs remain numeric text, and regproc/oid/int4/int8 comparisons stay consistent without false identity matches.
⏸️ Skipped Type Fifty consecutive full scans of pg_catalog.pg_type completed without errors, and follow-up catalog queries still returned expected rows and regproc handler names.
⏸️ Skipped Type Numeric OID values cast to regproc remained numeric text (including unmapped IDs like 999999 and 0), and OID-style comparisons stayed consistent with expected behavior.
⏸️ Skipped Type pg_proc prosupport projection and cast queries completed without crashes, and NULL prosupport values stayed consistently represented across direct, text, and OID views.
⏸️ Skipped Type SELECT typinput::regproc FROM pg_catalog.pg_type for float8 and _int4 returned 2 rows (float8in, array_in) consistently across two repeated executions.
⏸️ Skipped Type OID/regproc/text coercion probes on pg_type and pg_am stayed consistent: symbolic handlers round-tripped to numeric OIDs and back, '-' placeholders mapped to 0 for ::oid and '-' for ::regproc/::text, and invalid names failed with a stable explicit error.
⏸️ Skipped Type The pg_type discovery query returned all targeted rows with readable regproc handler names for typinput, typreceive, and typsend after the regproc transition.
⏸️ Skipped Type Boundary regproc cases behaved in a controlled way: valid names resolved, invalid and ambiguous names produced explicit errors, and subsequent pg_type scans continued to return rows without failure.
⏸️ Skipped Type Placeholder handler payloads render predictably after the oid-to-regproc transition: '-' values surface as regproc::text='-' and regproc::oid=0, pg_type rowcount remains 79, and both placeholder and named handlers stay queryable without coercion failures.
⏸️ Skipped Type Catalog handler identifiers stayed consistent across pg_type and pg_am projections, with stable regproc name rendering and no conflicting identities detected.
⏸️ Skipped Type The runner marked this case blocked due to a harness hang, but the recorded query output and source inspection both show null-function-backed pg_type regproc fields rendering '-' as expected.
⚠️ Additional Finding High severity Lookup Authorization boundaries are inconsistent: catalog table access is blocked, but pg_get_functiondef still returns the routine body to the same low-privilege caller.
⚠️ Additional Finding Medium severity Type The system exposes built-in function identities through pg_type regproc columns but does not materialize matching built-in routine rows in pg_proc, so cross-catalog joins are structurally valid yet semantically empty.
Additional Findings Details

These findings are unrelated to the current changes but were observed during testing.

🟠 Low-privilege role can read routine source via pg_get_functiondef
  • Severity: High High severity
  • Description: Authorization boundaries are inconsistent: catalog table access is blocked, but pg_get_functiondef still returns the routine body to the same low-privilege caller.
  • Impact: Low-privilege users can retrieve full routine source definitions, which can expose sensitive business logic or embedded secrets. This breaks expected privilege boundaries for database metadata access.
  • Steps to Reproduce:
    1. Create a function as a privileged user and note its OID from pg_catalog.pg_proc.
    2. Connect as a low-privilege role that is denied SELECT on pg_catalog.pg_proc and revoke EXECUTE on the target function.
    3. Run SELECT pg_get_functiondef(<known_oid>); as the low-privilege role.
    4. Observe that the full CREATE FUNCTION text is still returned.
  • Stub / mock content: No stubs, mocks, or bypasses were applied for this test in the recorded run.
  • Code Analysis: pg_get_functiondef directly resolves the object by OID and copies Function/Procedure Definition into the result string without any privilege gate (server/functions/pg_get_functiondef.go). The callback traversal used by this function also performs object lookup only, with no role-based authorization check in the function path (server/functions/iterate.go). A targeted fix is to enforce owner/EXECUTE/visibility checks before returning definition text and return an authorization error (or empty result) when the caller lacks permission.
Evidence Package
🟡 Cross-catalog pg_type to pg_proc identity join is incoherent
  • Severity: Medium Medium severity
  • Description: The system exposes built-in function identities through pg_type regproc columns but does not materialize matching built-in routine rows in pg_proc, so cross-catalog joins are structurally valid yet semantically empty.
  • Impact: Queries and tools that depend on PostgreSQL system-catalog identifier joins can return empty or misleading metadata even though the SQL executes successfully. This can break schema introspection and compatibility-dependent workflows for users relying on catalog coherence.
  • Steps to Reproduce:
    1. Connect to the server as superuser.
    2. Run SELECT count(*) FROM pg_type; and SELECT count(*) FROM pg_proc;.
    3. Run SELECT t.typname, t.typinput, p.oid, p.proname FROM pg_type t LEFT JOIN pg_proc p ON t.typinput = p.oid WHERE t.typname IN ('int4','text','bool','float8','varchar','int2','int8','numeric','date','timestamp','bytea','json') ORDER BY t.typname;.
    4. Observe that typinput renders built-in names while p.oid/p.proname are null, and an equivalent inner join returns zero rows.
  • Stub / mock content: No stubs, mocks, or bypasses were applied for this test in the recorded run.
  • Code Analysis: server/tables/pgcatalog/pg_type.go writes typinput and related fields from FromFuncID(...).AsId(), which produces built-in routine identities (rendered as boolin, int4in, etc.). In server/tables/pgcatalog/pg_proc.go, cachePgProcs only iterates current-database user-defined functions/procedures and explicitly notes // TODO: add built-in functions, leaving built-in rows absent. The smallest practical fix is to populate pg_proc with built-in function entries (or otherwise ensure typinput identity references can resolve in pg_proc) so the existing join path returns coherent routine identity rows.
Evidence Package

Tip

Reply with @itoqa to send us feedback on this test run.

@itoqa

itoqa Bot commented Jul 11, 2026

Copy link
Copy Markdown

Ito QA test results
Ito Diff Report6c2437ae281334: 22 test cases ran, 1 regression ❌, 18 passing ✅, 3 additional findings ⚠️.

Diff Summary

This run exercised database catalog behavior and type-handling compatibility across normal metadata queries, casting and conversion edge cases, and planner consistency checks, with broad coverage of upgrade and access-control scenarios. Most compatibility and stability paths held, but one planner-path consistency gap appeared under equivalent query forms.

Merge with caution — this PR introduces a medium-severity regression in query planning consistency for equivalent predicates, which creates real performance risk even though core functionality remains largely intact. There are also unrelated additional findings, including high-severity issues, but those are not attributable to this PR and should be tracked separately.

Tests run by Ito

View full run

Result State Severity Type Description
🆕 Regression Medium severity Catalog The optimizer applies different join strategies to semantically equivalent predicates, indicating cast handling occurs before a stable equality-join canonicalization step.
Passing Catalog Server startup and core catalog operations remained stable with unresolved typinput references; deferred type resolution behaved as expected without startup failure.
Passing Catalog pg_proc COALESCE behavior matches centralized cast resolution: compatible numeric combinations return the promoted value, and incompatible name versus numeric combinations fail with a deterministic type-matching error.
Passing Catalog Rapid datestyle mutation and timestamp formatting checks stayed consistent across 50+ iterations: each timestamp output matched the active DateStyle setting, with no stale-cache mismatches observed.
Passing Catalog The implicit pg_type.typinput = pg_proc.oid join path executed deterministically with no silent coercion, and both implicit and explicit-cast variants consistently returned 0 rows in this fixture state.
Passing Handler Uncast regproc vs integer/oid literal is accepted (integer coerces to oid). Uncast regproc vs text literal is rejected with invalid input syntax for type oid, while explicit amhandler::oid and amhandler::text predicates return the expected row.
Passing Handler pg_catalog.pg_am handler metadata remained queryable: the main listing returned all seven default access methods, explicit ::text/::oid filters returned expected rows, and EXPLAIN produced a valid plan.
Passing Handler SELECT amname, amhandler::regproc::text FROM pg_catalog.pg_am ORDER BY amname returned stable handler-name text for all seven default access methods across repeated runs with no cast or row errors.
Passing Handler Comparing pg_am.amhandler to a text literal fails deterministically with a type-mismatch operator error, and the explicit text-cast sanity query returns zero rows without incorrect matches.
Passing Handler The legacy join predicate pg_am.amhandler = pg_proc.oid executes successfully without explicit casts, and the empty result reflects an unpopulated pg_proc catalog in this test environment rather than a compatibility regression.
Passing Handler pg_catalog.pg_am returned coherent handler text and OID representations, and both amhandler::text='bthandler' and amhandler::oid=330::oid resolved to the same btree row.
Passing Type pg_type regproc handler columns render PostgreSQL-style function names and cast-based name filters return the expected built-in type rows.
Passing Type Legacy metadata queries continue to work because pg_type regproc values still render as function names by default.
Passing Type The TYPE-20 verification steps completed successfully in recorded evidence, and the regproc-to-oid join behavior stayed constrained without surfacing a production-code defect.
Passing Type COALESCE with text and integer arguments returned the expected type-mismatch error, confirming strict assignment-cast behavior.
Passing Type The recorded SQL trace shows COALESCE returns converted values for compatible mixed types and explicit type-mismatch errors for incompatible pairs, matching expected ConvertToType behavior.
Passing Type The runner failed on an unavailable pg_typeof helper, but direct COALESCE and boundary checks in the same run confirm smallint+bigint promotion behavior and value output are correct for this test objective.
Passing Type The COALESCE consistency suite executed 30 mixed-type queries across three runs and produced stable, expected promotion and mismatch outcomes with no intermittent cast behavior.
Passing Type ORM-style catalog introspection queries over pg_type and pg_am completed successfully, and the pg_type.typinput to pg_proc.oid join executed without type-coercion failures.
⏸️ Skipped Catalog pg_catalog.pg_sequences returned PostgreSQL-compatible data_type names (bigint, integer) for sequences created with AS bigint and AS integer, matching the updated regression expectation and confirming the regtype-ID materialization path is working.
⏸️ Skipped Handler Confirmed pg_catalog.pg_am exposes amhandler as regproc and returns all default access methods with regproc-compatible handler values, including successful ::text and ::oid projections.
⏸️ Skipped Handler Comparisons between regproc and id-backed numeric operand types remain coherent, while regproc comparisons to text/name correctly fail with explicit operator errors unless an explicit cast is applied first.
⏸️ Skipped Type Catalog introspection over pg_catalog.pg_type completed successfully with 30 rows, resolved handlers rendered as expected regproc names, and unmapped function IDs degraded to '-' fallback output without aborting the query.
⏸️ Skipped Type Typ metadata queries for typinput, typoutput, typreceive, and typsend remained stable across repeated materialization and returned regproc-compatible function names without panic or row-decoding errors.
⚠️ Additional Finding High severity Lookup Visibility boundaries for routine source text are not enforced: pg_get_functiondef discloses full definitions to a role that cannot execute the routine.
⚠️ Additional Finding High severity Type Expected pg_proc queries to return rows or a deterministic SQL-level error after upgrade. Instead, the server panics with runtime error: index out of range [0] with length 0 from cachePgProcs.
⚠️ Additional Finding Medium severity Type The catalog exposes typinput identifiers in pg_type but does not materialize corresponding built-in rows in pg_proc, so an equality join that should resolve function identity yields no matches.
Tests that are no longer relevant

Below are tests that previously ran and are no longer relevant:

Type Test Description
Handler amhandler comparisons to oid/regproc remain coherent in planner Dropped because Both probe amhandler comparisons across oid/regproc operands; server/types/type.go deleted the idType shortcut that previously made id-backed cross-type comparisons auto-equal, changing the behavior HANDLER-14 asserted.
Type Cross-catalog id equality join succeeds Dropped because Both target the same pg_type.typinput = pg_proc.oid equality path, but server/types/type.go removed the idType()-based cross-type shortcut in DoltgresType.Equals, so TYPE-16's implicit-equality expectation is no longer valid.
Additional Findings Details

These findings are unrelated to the current changes but were observed during testing.

🟠 Low-privilege role can read routine source without EXECUTE privilege
  • Severity: High High severity
  • Description: Visibility boundaries for routine source text are not enforced: pg_get_functiondef discloses full definitions to a role that cannot execute the routine.
  • Impact: Low-privilege users can read protected routine source code and embedded secrets even when EXECUTE is denied, breaking access-control boundaries and exposing sensitive logic.
  • Steps to Reproduce:
    1. Create a routine owned by a privileged role with a non-public body (for example SELECT 'secret').
    2. Create a low-privilege login role with catalog read access needed to discover routines in pg_proc, but do not grant EXECUTE on that routine.
    3. As the low-privilege role, call pg_get_functiondef on the routine OID from pg_proc and observe the full CREATE FUNCTION text is returned.
    4. As the same role, attempt direct EXECUTE and observe permission denied for routine, confirming source disclosure bypasses privilege expectations.
  • Stub / mock content: No stubs, mocks, or bypasses were applied for this test in the recorded run.
  • Code Analysis: In server/functions/pg_get_functiondef.go, the Callable callback assigns result = function.Item.Definition (line 43) and result = procedure.Item.Definition (line 47) without any privilege check tied to the caller. That unconditional return path plausibly causes the observed leak. A minimal fix is to gate those returns on routine-visibility/privilege checks for the current role and return NULL or an authorization error when access is not allowed.
Evidence Package
🟠 Upgrade path pg_proc queries panic
  • Severity: High High severity
  • Description: Expected pg_proc queries to return rows or a deterministic SQL-level error after upgrade. Instead, the server panics with runtime error: index out of range [0] with length 0 from cachePgProcs.
  • Impact: Repositories on the legacy-to-current upgrade path can trigger server panics on pg_proc queries, breaking metadata-dependent workflows and catalog introspection until the regression is fixed.
  • Steps to Reproduce:
    1. Start Doltgres v0.56.0, seed a repo, and confirm the legacy repository state is readable.
    2. Stop v0.56.0 and start HEAD v0.57.0 against the same data directory.
    3. Run pg_proc queries (for example COUNT(*) FROM pg_proc or joins from pg_type.typinput to pg_proc.oid) and observe the runtime panic.
  • Stub / mock content: No stubs, mocks, or bypasses were applied for this test in the recorded run.
  • Code Analysis: Runtime evidence points to server/tables/pgcatalog/pg_proc.go:116 inside cachePgProcs. The function loop indexes f.Item.ParameterDefaults[i] and f.Item.ParameterNames[i] while iterating f.Item.ParameterTypes, but it does not guard against defaults/names slices being shorter than parameter types for some loaded functions in upgraded repos. That unchecked indexing is a direct cause of the panic and should be fixed with bounds checks (or by normalizing lengths before indexed reads) so pg_proc construction stays safe.
Evidence Package
🟡 pg_type typinput OIDs do not resolve in pg_proc
  • Severity: Medium Medium severity
  • Description: The catalog exposes typinput identifiers in pg_type but does not materialize corresponding built-in rows in pg_proc, so an equality join that should resolve function identity yields no matches.
  • Impact: Metadata consumers that resolve type input functions through pg_type to pg_proc joins cannot reliably discover built-in routines. This can break introspection and compatibility tooling that depends on PostgreSQL-style catalog coherence.
  • Steps to Reproduce:
    1. Run SELECT t.typname, t.typinput, p.oid, p.proname FROM pg_type t INNER JOIN pg_proc p ON t.typinput = p.oid WHERE t.typname IN ('float8','int4','int8','text','bool') ORDER BY t.typname;
    2. Observe that the join returns zero rows while pg_type.typinput still renders built-in names like boolin and float8in.
    3. Confirm that pg_proc only contains user-defined entries and does not contain matching built-in OIDs for those typinput values.
  • Stub / mock content: No stubs, mocks, or bypasses were applied for this test in the recorded run.
  • Code Analysis: In server/tables/pgcatalog/pg_type.go, cachePgTypes seeds all built-in types via pgtypes.GetAllBuitInTypes(), which includes typinput metadata for built-ins. In server/tables/pgcatalog/pg_proc.go, cachePgProcs only iterates current-database functions/procedures and explicitly notes TODO: add built-in functions, leaving pg_proc without built-in rows needed for typinput OID resolution. The PR diff is inconclusive for introduction and does not modify these code paths, so this is a pre-existing limitation rather than a direct PR regression.
Evidence Package

Tip

Reply with @itoqa to send us feedback on this test run.

Comment thread server/types/type.go

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔁 Regression: previously passing at 6c2437a

Medium severity Equivalent catalog join predicates take inconsistent planner paths

What failed: The optimizer applies different join strategies to semantically equivalent predicates, indicating cast handling occurs before a stable equality-join canonicalization step.

Impact · Steps · Stub / mock · Analysis · Why this is likely a bug
  • Severity: Medium Medium severity
  • Impact: Queries using this explicit cast path can be planned with a less efficient join strategy, which may cause noticeably slower catalog lookups under realistic data volumes.
  • Steps to Reproduce:
    1. Run EXPLAIN ANALYZE SELECT t1.typname, p1.proname FROM pg_type AS t1, pg_proc AS p1 WHERE t1.typinput = p1.oid ORDER BY t1.typname, p1.proname LIMIT 5;.
    2. Run EXPLAIN ANALYZE SELECT t1.typname, p1.proname FROM pg_type AS t1, pg_proc AS p1 WHERE t1.typinput::oid = p1.oid ORDER BY t1.typname, p1.proname LIMIT 5;.
    3. Compare plans: implicit form uses HashJoin with HashLookup, explicit-cast form uses CrossJoin with Filter despite equivalent predicate semantics.
  • Stub / mock content: No stubs, mocks, or bypasses were applied for this test in the recorded run.
  • Code Analysis: In server/types/type.go, the PR changed (*DoltgresType).Equals to strict serialized-byte equality only and removed the previous idType() shortcut path. That changed-code path can alter when the analyzer treats id-backed types as compatible, which aligns with the observed split: the implicit variant remains on a hash-join shortcut path while the explicit cast variant is left as a filter over cross join. Smallest practical fix: normalize explicit ::oid equality predicates into the same canonical join-equality form used by implicit id-backed comparisons (or restore a narrowly scoped id-type compatibility check for join-key recognition) so both query spellings take the same planner path.
  • Why this is likely a bug: The two predicates are semantically equivalent comparisons on the same join keys, so planner strategy should not diverge purely because one side is explicitly cast to the target type. This inconsistency points to a planning/cast-ordering defect rather than expected query semantics.
Relevant code

server/types/type.go:583-588

// Equals implements the types.ExtendedType interface.
func (t *DoltgresType) Equals(otherType sql.Type) bool {
	if otherExtendedType, ok := otherType.(*DoltgresType); ok {
		return bytes.Equal(t.Serialize(), otherExtendedType.Serialize())
	}
	return false
}
Evidence Package
Copy prompt for an agent
Ito QA identified the following failure during automated PR testing. Please investigate and propose a fix.

**Medium severity — Equivalent catalog join predicates take inconsistent planner paths**

**What failed:** The optimizer applies different join strategies to semantically equivalent predicates, indicating cast handling occurs before a stable equality-join canonicalization step.

- **Impact:** Queries using this explicit cast path can be planned with a less efficient join strategy, which may cause noticeably slower catalog lookups under realistic data volumes.
- **Steps to reproduce:**
  1. Run `EXPLAIN ANALYZE SELECT t1.typname, p1.proname FROM pg_type AS t1, pg_proc AS p1 WHERE t1.typinput = p1.oid ORDER BY t1.typname, p1.proname LIMIT 5;`.
  2. Run `EXPLAIN ANALYZE SELECT t1.typname, p1.proname FROM pg_type AS t1, pg_proc AS p1 WHERE t1.typinput::oid = p1.oid ORDER BY t1.typname, p1.proname LIMIT 5;`.
  3. Compare plans: implicit form uses `HashJoin` with `HashLookup`, explicit-cast form uses `CrossJoin` with `Filter` despite equivalent predicate semantics.
- **Stub / mock content:** No stubs, mocks, or bypasses were applied for this test in the recorded run.
- **Code analysis:** In `server/types/type.go`, the PR changed `(*DoltgresType).Equals` to strict serialized-byte equality only and removed the previous `idType()` shortcut path. That changed-code path can alter when the analyzer treats id-backed types as compatible, which aligns with the observed split: the implicit variant remains on a hash-join shortcut path while the explicit cast variant is left as a filter over cross join. Smallest practical fix: normalize explicit `::oid` equality predicates into the same canonical join-equality form used by implicit id-backed comparisons (or restore a narrowly scoped id-type compatibility check for join-key recognition) so both query spellings take the same planner path.
- **Why this is likely a bug:** The two predicates are semantically equivalent comparisons on the same join keys, so planner strategy should not diverge purely because one side is explicitly cast to the target type. This inconsistency points to a planning/cast-ordering defect rather than expected query semantics.

**Relevant code:**

`server/types/type.go:583-588`

~~~go
// Equals implements the types.ExtendedType interface.
func (t *DoltgresType) Equals(otherType sql.Type) bool {
	if otherExtendedType, ok := otherType.(*DoltgresType); ok {
		return bytes.Equal(t.Serialize(), otherExtendedType.Serialize())
	}
	return false
}
~~~

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