From c61bdfebb7672b48c16c7d108227b7068ec1b6ca Mon Sep 17 00:00:00 2001 From: Robert Chiniquy Date: Wed, 20 May 2026 11:51:31 -0700 Subject: [PATCH 1/2] fix(postgres): close per-schema typeRows inside the iteration RevokeAllGrantsFromRole iterates schemas and for each schema queries the database type list via typeRows. The Close was registered with defer, but defer fires only on FUNCTION return, not iteration return -- so each schema iteration accumulated a pending typeRows.Close, and the underlying sql connections were held until the outer function returned. On a database with many schemas this exhausts the connection pool. Replaced the deferred Close with an explicit Close after the inner for typeRows.Next() loop completes. Iteration semantics are unchanged: typeRows.Close on a fully-consumed Rows is safe; the Close on the error path is intentionally not reached because the err branch already records the error and falls through without opening typeRows. How found: occult-go-analysis baton pool scan (2026-05-20), detector defer_in_loop_close. --- pkg/postgres/roles.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pkg/postgres/roles.go b/pkg/postgres/roles.go index 25088073..9371f6e5 100644 --- a/pkg/postgres/roles.go +++ b/pkg/postgres/roles.go @@ -272,8 +272,6 @@ func (c *Client) RevokeAllGrantsFromRole(ctx context.Context, roleName string) e l.Warn("error querying types", zap.String("schema", schema), zap.Error(err)) revokeError = errors.Join(revokeError, err) } else { - defer typeRows.Close() - for typeRows.Next() { var typeName string if err := typeRows.Scan(&typeName); err != nil { @@ -290,6 +288,7 @@ func (c *Client) RevokeAllGrantsFromRole(ctx context.Context, roleName string) e revokeError = errors.Join(revokeError, err) } } + typeRows.Close() } revokeSchemaQuery := fmt.Sprintf("REVOKE ALL ON SCHEMA %s FROM %s", sanitizedSchema, sanitizedRoleName) From 0dbf60382b14bbde5e71b3cabf839ccf899f0e8a Mon Sep 17 00:00:00 2001 From: Robert Chiniquy Date: Wed, 20 May 2026 13:10:43 -0700 Subject: [PATCH 2/2] fix: check typeRows.Err() after the type-iteration loop Bot review suggestion on #45: the for typeRows.Next() loop didn't check typeRows.Err() afterward, so a driver/network error that ends iteration before exhaustion was silently lost. Surface it into revokeError alongside the existing Warn-and-join pattern used elsewhere in this function. --- pkg/postgres/roles.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkg/postgres/roles.go b/pkg/postgres/roles.go index 9371f6e5..9898110a 100644 --- a/pkg/postgres/roles.go +++ b/pkg/postgres/roles.go @@ -288,6 +288,10 @@ func (c *Client) RevokeAllGrantsFromRole(ctx context.Context, roleName string) e revokeError = errors.Join(revokeError, err) } } + if err := typeRows.Err(); err != nil { + l.Warn("error iterating types", zap.String("schema", schema), zap.Error(err)) + revokeError = errors.Join(revokeError, err) + } typeRows.Close() }