Skip to content

Optimize grant macros for performance, case-insensitive roles, and empty-list fix#34

Merged
jonhopper-dataengineers merged 4 commits into
mainfrom
feature/grant-performance-optimization
Jun 25, 2026
Merged

Optimize grant macros for performance, case-insensitive roles, and empty-list fix#34
jonhopper-dataengineers merged 4 commits into
mainfrom
feature/grant-performance-optimization

Conversation

@jonhopper-dataengineers

@jonhopper-dataengineers jonhopper-dataengineers commented Jun 25, 2026

Copy link
Copy Markdown
Member

Summary

  • Performance optimization: All grant macros now check existing state before issuing SQL statements. Schemas/objects where grants are already in place are skipped entirely, reducing runtime from 20-30 minutes to under 2-3 minutes on large databases.
  • Case-insensitive role matching: All role/application name comparisons are now case-insensitive via uppercase normalization.
  • Grant-only mode for grant_object: The grant_object macro no longer revokes privileges — it only ensures specified privileges are granted.
  • New helper macros: Added _grants_get_schema_object_privs (with object_type filtering), _grants_get_schema_grants, _grants_get_future_grants, and _grants_normalize_roles.
  • Bug fixes: Fixed empty NOT IN () SQL syntax error when grant_roles is an empty list. Fixed grant_schema_object_privileges to filter by object_type to avoid cross-object-type leakage.
  • Integration tests: Added est_grants_helpers (13 pure Jinja unit tests) and est_grants_idempotency (9 integration tests).
  • Version bump: 1.0.6 -> 1.0.7

Test plan

  • Run dbt run-operation test_grants_helpers — validates helper macro logic
  • Run dbt run-operation test_grants_idempotency — validates helpers and skip-logic against live Snowflake
  • Run dbt run-operation grants_smoke_test --vars '{"grants_dry_run": true}' — exercises all major grant macros in dry-run mode
  • Run dbt run-operation grant_privileges on a test environment and verify reduced statement count
  • Run grant_privileges with empty role lists (dev environment) and confirm no SQL syntax errors
  • Run grant_privileges twice in succession and confirm second run completes quickly

.... Generated with Cortex Code

Summary by Sourcery

Optimize Snowflake grant macros for faster, idempotent execution and case-insensitive role handling, while tightening grant-only behavior and improving share/database utilities.

New Features:

  • Introduce helper macros for bulk inspection of schema object privileges, schema grants, future grants, and role normalization.
  • Add integration and helper tests to validate grant helper behavior, idempotency, and case-insensitive role matching.

Bug Fixes:

  • Prevent SQL syntax errors when grant role lists are empty by guarding NOT IN clauses.
  • Ensure grant_schema_object_privileges filters privileges by object_type to avoid cross-object-type leakage.

Enhancements:

  • Make grant_object macro grant-only so it no longer revokes privileges, simplifying its behavior.
  • Normalize roles and applications to uppercase across grant macros for case-insensitive comparison and consistent matching.
  • Refactor schema read, monitor, operate, procedure usage, share-read, and database usage macros to reuse bulk helper queries, skip already-satisfied schemas/roles, and reduce redundant grant statements.
  • Improve dry-run and summary logging for grant macros, including per-schema skip counts and executed statement totals.
  • Update documentation and README to reflect new grant_object semantics and the package version bump to 1.0.7.

Documentation:

  • Update CHANGELOG and README to document grant performance optimizations, new helper macros, behavioral changes, and version bump to 1.0.7.

Tests:

  • Add test_grants_helpers macro with unit-style checks for role normalization and helper behavior.
  • Add test_grants_idempotency macro to verify grant macros are idempotent and correctly detect no-op scenarios against live Snowflake.

jonhopper-dataengineers and others added 3 commits June 25, 2026 20:48
…atching

Grant macros now check existing state before issuing SQL statements, skipping
schemas/objects where grants are already in place. This reduces runtime from
20-30 minutes to under 2-3 minutes on large databases where grants are already
applied. Also makes all role comparisons case-insensitive via normalization to
uppercase. Adds integration tests for grant helpers and idempotency.

.... Generated with [Cortex Code](https://docs.snowflake.com/en/user-guide/cortex-code/cortex-code)

Co-Authored-By: Cortex Code <noreply@snowflake.com>
…idempotency

- _grants_get_schema_object_privs: removed unused _role variable, added
  internal grantees uppercase normalization, added optional object_type
  parameter to filter by specific object type
- grant_schema_read_specific: removed incorrect per-privilege skip logic
  that aggregated across object types. GRANT ON ALL is idempotent in
  Snowflake so always issue it. Only skip schema USAGE (single check)
  and future grants (which error on duplicates)
- grant_schema_object_privileges: now passes object_type to both the
  helper query and the revoke query to avoid cross-object-type leakage

.... Generated with [Cortex Code](https://docs.snowflake.com/en/user-guide/cortex-code/cortex-code)

Co-Authored-By: Cortex Code <noreply@snowflake.com>
Guard NOT IN () clauses with length checks to prevent invalid SQL when
grant_roles/role_list is an empty list. Snowflake rejects NOT IN () as
a syntax error.

.... Generated with [Cortex Code](https://docs.snowflake.com/en/user-guide/cortex-code/cortex-code)

Co-Authored-By: Cortex Code <noreply@snowflake.com>
@sourcery-ai

sourcery-ai Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Refactors Snowflake grant macros to be faster and idempotent by bulk-querying privilege state, normalizing role/application names to uppercase for case-insensitive matching, and making grant_object grant-only, while adding helper/query macros, tests, and documentation/version updates.

Sequence diagram for optimized grant_schema_read_specific flow

sequenceDiagram
    actor DbtRunner
    participant grant_schema_read_specific
    participant _grants_normalize_roles
    participant _grants_get_schema_grants
    participant _grants_get_future_grants
    participant information_schema

    DbtRunner->>grant_schema_read_specific: call grant_schema_read_specific(schemas, grant_roles,...)
    grant_schema_read_specific->>_grants_normalize_roles: normalize grant_roles
    _grants_normalize_roles-->>grant_schema_read_specific: uppercased grant_roles

    loop per schema
        grant_schema_read_specific->>_grants_get_schema_grants: _grants_get_schema_grants(schema, 'USAGE', 'ROLE')
        _grants_get_schema_grants->>information_schema: show grants on schema
        information_schema-->>_grants_get_schema_grants: existing USAGE grants
        _grants_get_schema_grants-->>grant_schema_read_specific: roles_with_usage

        alt revoke_current_grants and grant_roles not empty
            grant_schema_read_specific->>information_schema: select table_privileges for roles not in grant_roles
            information_schema-->>grant_schema_read_specific: rows to revoke
            grant_schema_read_specific->>grant_schema_read_specific: append revoke statements
        end

        alt include_future_grants
            grant_schema_read_specific->>_grants_get_future_grants: _grants_get_future_grants(schema)
            _grants_get_future_grants->>information_schema: show future grants in schema
            information_schema-->>_grants_get_future_grants: existing future grants
            _grants_get_future_grants-->>grant_schema_read_specific: role_future map
        end

        grant_schema_read_specific->>grant_schema_read_specific: build grant statements (usage, read, future)
        opt schema has changes
            grant_schema_read_specific->>information_schema: run_query(grant/revoke statements)
        end
    end

    grant_schema_read_specific-->>DbtRunner: completed with many schemas skipped when no changes
Loading

File-Level Changes

Change Details Files
Optimize schema object grant macro to use bulk privilege queries and filtered revokes per schema/object type.
  • Normalize roles argument via _grants_normalize_roles and ensure string role inputs are uppercased.
  • Replace per-object SHOW GRANTS loop with single _grants_get_schema_object_privs call filtered by schema, privilege list, role list, and object_type.
  • Generate revoke statements using information_schema.object_privileges for roles not in the managed list, avoiding empty NOT IN when role list is empty.
  • Always issue bulk GRANT ON ALL statements for missing privilege/role combinations and simplify dry-run vs execute logging.
macros/grants/grant_schema_object_privileges.sql
Make schema-level read grants idempotent and role-case-insensitive using centralized helpers and future-grant awareness.
  • Normalize grant_roles and use _grants_collect_roles and _grants_get_schema_grants to avoid redundant USAGE grants.
  • When revoking, query information_schema.table_privileges once per schema with grantee NOT IN (roles) instead of client-side filtering.
  • Add optional _grants_get_future_grants calls and only issue future GRANT statements if not already present to avoid duplicate-future-grant errors.
  • Track per-schema statements and log skipped schemas when no changes are required.
macros/grants/grant_schema_read.sql
Optimize procedure usage grants to avoid per-procedure SHOW GRANTS and to skip already-satisfied schemas.
  • Normalize grant_roles to uppercase via _grants_normalize_roles.
  • Use information_schema.procedures to count procedures in a schema instead of SHOW + result_scan parsing.
  • Query information_schema.object_privileges once per schema to compute existing USAGE grantees and revoke from unmanaged roles in bulk.
  • Conditionally grant schema USAGE and USAGE ON ALL PROCEDURES only when roles don’t already have them, tracking per-schema skips and totals in logs.
macros/grants/grant_procedure_usage.sql
Introduce reusable helper macros for role normalization and bulk privilege state inspection.
  • Add _grants_normalize_roles to uppercase any list of roles/applications for case-insensitive comparisons.
  • Add _grants_get_schema_object_privs to return a role→privileges map from information_schema.object_privileges with optional privilege/grantee/object_type filters.
  • Add _grants_get_schema_grants to inspect schema-level privileges (e.g., USAGE) and return roles that already hold them.
  • Add _grants_get_future_grants to map existing future grants per role to avoid duplicate future-grant statements.
macros/grants/_helpers.sql
Make share read grant macros idempotent by introspecting existing share state via DESC SHARE.
  • In grant_external_share_read and grant_internal_share_read, call DESC SHARE once to build a list of existing TABLE/VIEW/SCHEMA grants and skip already-granted items.
  • Only issue GRANT USAGE ON SCHEMA and GRANT SELECT ON ALL TABLES when the share does not already have any table grants for that schema.
  • Grant SELECT on views individually only when a view is not already part of the share, and aggregate per-schema statements to log skipped schemas.
  • In grant_share_read_specific_schema, precompute existing schema and view grants per share via DESC SHARE and avoid redundant statements, with a no-op path when nothing to change.
macros/grants/grant_external_share_read.sql
macros/grants/grant_internal_share_read.sql
macros/grants/grant_share_read.sql
Optimize schema-level OPERATE and MONITOR grants to use bulk privilege inspection and avoid redundant grants.
  • Normalize grant_roles via _grants_normalize_roles in monitor/operate macros.
  • Use information_schema.object_privileges to compute existing MONITOR/OPERATE grantees per schema instead of per-object revokes.
  • Revoke privileges from unmanaged roles using bulk REVOKE ON ALL TASKS/PIPES statements when requested.
  • Grant schema USAGE only if missing for a role, and skip schemas where no statements are required while logging execution summaries.
macros/grants/grant_schema_operate.sql
macros/grants/grant_schema_monitor.sql
Change grant_object to be grant-only and case-insensitive for roles while preserving signature.
  • Normalize grant_roles using _grants_normalize_roles and remove all revoke logic from the macro.
  • When inspecting SHOW GRANTS output, only track privileges that are both in the desired grant_types and assigned to normalized roles, ignoring other grants.
  • Generate GRANT statements only for missing privilege/role combinations and execute/log them without any revokes.
  • Update log messages to reflect grant-only behavior and simplified summary (no revokes counted).
macros/grants/grant_object.sql
Make database and application-grant macros case-insensitive for roles/applications and reuse normalization helper.
  • In grant_database_usage, normalize grant_roles via _grants_normalize_roles and grant_shares to uppercase, and compare current grantees after uppercasing; store existing roles/shares normalized.
  • In grant_operate_to_application and grant_usage_to_application, normalize grant_applications and compare row.grantee_name in uppercase, storing existing_grants with uppercase role names.
  • In grant_object_application, normalize grant_applications and uppercase Snowflake grantee_name before comparison, preserving previous revoke/grant behavior.
macros/grants/grant_database_usage.sql
macros/grants/grant_operate_to_application.sql
macros/grants/grant_usage_to_application.sql
macros/grants/grant_object_application.sql
Extend smoke test and add dedicated helper/idempotency integration tests for the new behavior.
  • Update grants_smoke_test to exercise case-insensitive roles, grant_schema_procedure_usage_specific, grant_schema_object_privileges, grant_object with empty object list, grant_share_read, and grant_database_usage with mixed-case roles.
  • Add test_grants_helpers containing 13 pure-Jinja unit tests for _grants_normalize_roles, _grants_format_list, _grants_append_unique, and role membership semantics.
  • Add test_grants_idempotency containing 9 integration tests that validate helper return types, schema collection, live idempotency paths for monitor/read/procedure/operate macros, and live case-insensitive role normalization.
  • Wire these tests to be runnable via dbt run-operation commands as described in the PR test plan.
macros/grants/grants_smoke_test.sql
integration_tests/macros/test_grants_helpers.sql
integration_tests/macros/test_grants_idempotency.sql
Update documentation and metadata to reflect new version and behavior of grant_object.
  • Add v1.0.7 entry to CHANGELOG describing performance improvements, behavioral change of grant_object, and new helper macros.
  • Bump package version from 1.0.6 to 1.0.7 in dbt_project.yml and README.
  • Update README macro table to describe grant_object as grant-only (no revokes), aligning docs with implementation.
CHANGELOG.md
README.md
dbt_project.yml

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hey - I've found 4 issues, and left some high level feedback:

  • The new _grants_normalize_roles helper assumes it always receives a list; consider making it resilient to common alternative inputs (e.g., strings, none) so downstream macros don’t have to pre-normalize types themselves.
  • The share-grant logic that builds existing_share_objects from DESC SHARE is duplicated and slightly varied across grant_external_share_read, grant_internal_share_read, and grant_share_read_specific_schema; factoring this into a shared helper would reduce repetition and make future changes to the share-inspection logic easier to apply consistently.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The new `_grants_normalize_roles` helper assumes it always receives a list; consider making it resilient to common alternative inputs (e.g., strings, `none`) so downstream macros don’t have to pre-normalize types themselves.
- The share-grant logic that builds `existing_share_objects` from `DESC SHARE` is duplicated and slightly varied across `grant_external_share_read`, `grant_internal_share_read`, and `grant_share_read_specific_schema`; factoring this into a shared helper would reduce repetition and make future changes to the share-inspection logic easier to apply consistently.

## Individual Comments

### Comment 1
<location path="macros/grants/_helpers.sql" line_range="160" />
<code_context>
+{% endmacro %}
+
+{# Check schema-level grants (USAGE etc) for given roles. Returns list of roles that already have the privilege. #}
+{% macro _grants_get_schema_grants(schema, privilege, grantee_type) %}
+    {% set existing = [] %}
+    {% set query %}
</code_context>
<issue_to_address>
**issue (bug_risk):** Schema grant helper returns raw role names, which can conflict with uppercased `grant_roles` comparisons.

Most callers uppercase `grant_roles` via `_grants_normalize_roles`, but `_grants_get_schema_grants` returns `row.grantee_name` without normalization. In macros like `grant_schema_read`, `grant_schema_operate_specific`, and `grant_schema_monitor_specific`, you compare an uppercased `role` against `roles_with_usage` containing mixed-case values, so case-only differences can cause the membership check to fail and lead to redundant `grant usage on schema` statements. To align behavior, normalize `row.grantee_name` to uppercase inside `_grants_get_schema_grants` so its output matches the casing of `grant_roles`.
</issue_to_address>

### Comment 2
<location path="macros/grants/grant_procedure_usage.sql" line_range="36" />
<code_context>
-                {% set grant_results = run_query(grant_query) %}
+            {# Get existing USAGE grants on procedures in this schema in one query #}
+            {% set existing_usage_roles = [] %}
+            {% set usage_query %}
+                select distinct grantee
+                from information_schema.object_privileges
</code_context>
<issue_to_address>
**issue (bug_risk):** Procedure usage revokes are not filtered by grantee type, which may revoke from non-role grantees.

In `grant_schema_procedure_usage_specific`, `usage_query` selects `grantee` for `USAGE` on `PROCEDURE` but doesn’t filter by `granted_to = 'ROLE'`. As a result, grants to non-role grantees (e.g. APPLICATION, SHARE, ACCOUNT) will be treated as roles and generate invalid `revoke usage ... from role {{ role_with_usage }}` statements. Please add `and granted_to = 'ROLE'` to keep this macro consistent with its role-only assumption.
</issue_to_address>

### Comment 3
<location path="macros/grants/grant_schema_object_privileges.sql" line_range="83" />
<code_context>
+    {# Build revoke statements for roles NOT in the list that have these privs on this object type #}
+    {% set revoke_statements = [] %}
+    {% if role_list | length > 0 %}
+    {% set revoke_query %}
+        select distinct privilege_type, grantee
+        from information_schema.object_privileges
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Revoke query relies on caller-supplied privilege casing, which may not match `information_schema` values.

`permission_list` is used directly in:
```sql
and privilege_type in ({{ permission_list | map('tojson') | join(', ') }})
```
without normalizing case, while `information_schema.object_privileges.privilege_type` is typically uppercase. If the caller passes lower/mixed-case privileges, the filter may not match and revokes will be skipped. For consistency with `_grants_get_schema_object_privs`, normalize `permission_list` to uppercase (or upper-case in the query) before using it here.

```suggestion
          and privilege_type in ({{ permission_list | map('upper') | map('tojson') | join(', ') }})
```
</issue_to_address>

### Comment 4
<location path="macros/grants/grant_schema_operate.sql" line_range="21-25" />
<code_context>
+    {% set result_map = {} %}
+    {% set priv_filter = privilege_types | map('upper') | list %}
+    {% set grantees_upper = grantees | map('upper') | list %}
+    {% set query %}
+        select privilege_type, grantee
+        from information_schema.object_privileges
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Operate revokes are generated only as bulk revokes on tasks/pipes, regardless of actual object_type.

The query selects all `OPERATE` grants from `information_schema.object_privileges` without filtering by `object_type`, but revokes are only issued as bulk revokes on tasks and pipes. If `OPERATE` is granted on other object types (e.g. stages or future types), those grants won’t be revoked and the reconcile behavior becomes inconsistent. Please either restrict the query to `object_type in ('PIPE','TASK')` or expand the revoke statements to cover all object types you intend to manage so the query and revokes stay aligned.

```suggestion
        {% set query %}
            select privilege_type, grantee
            from information_schema.object_privileges
            where privilege_type = 'OPERATE'
              and object_schema = '{{ schema }}'
              and object_type in ('PIPE','TASK')
        {% endset %}
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread macros/grants/_helpers.sql
Comment thread macros/grants/grant_procedure_usage.sql
Comment thread macros/grants/grant_schema_object_privileges.sql Outdated
Comment thread macros/grants/grant_schema_operate.sql
… and granted_to

- _grants_get_schema_grants: now uppercases grantee_name so output
  matches normalized grant_roles for consistent comparisons
- grant_procedure_usage: added granted_to = 'ROLE' filter to prevent
  picking up APPLICATION/SHARE grantees and generating invalid revokes
- grant_schema_object_privileges: uppercase permission_list in revoke
  query to match information_schema which stores privileges uppercase
- grant_schema_operate_specific: filter by object_type in ('PIPE','TASK')
  so OPERATE grants on other object types don't affect reconciliation
- grant_schema_monitor_specific: same object_type filter for consistency

.... Generated with [Cortex Code](https://docs.snowflake.com/en/user-guide/cortex-code/cortex-code)

Co-Authored-By: Cortex Code <noreply@snowflake.com>
@jonhopper-dataengineers
jonhopper-dataengineers merged commit e8ff321 into main Jun 25, 2026
2 checks passed
@jonhopper-dataengineers
jonhopper-dataengineers deleted the feature/grant-performance-optimization branch June 25, 2026 09:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant