-
Notifications
You must be signed in to change notification settings - Fork 261
feat(experimental): add official support for model grants #5275
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR introduces experimental support for grants configuration, allowing users to specify database permissions for SQLMesh models. The feature includes grants parsing, validation, and application across different evaluation strategies.
- Adds grants configuration support to models with validation for compatible model types
- Implements grants application logic in snapshot evaluators for physical and virtual layers
- Provides PostgreSQL engine adapter implementation with grants management functionality
Reviewed Changes
Copilot reviewed 19 out of 20 changed files in this pull request and generated 3 comments.
Show a summary per file
File | Description |
---|---|
tests/dbt/test_model.py | Tests dbt model grants conversion to SQLMesh format and validation |
tests/core/test_snapshot_evaluator.py | Tests grants application in snapshot evaluation strategies |
tests/core/test_model.py | Tests model grants parsing, validation, and rendering functionality |
tests/core/engine_adapter/test_postgres.py | Unit tests for PostgreSQL grants operations (apply, revoke, sync) |
tests/core/engine_adapter/test_base_postgres.py | Tests PostgreSQL current schema retrieval functionality |
tests/core/engine_adapter/test_base.py | Tests base engine adapter grants configuration diffing logic |
tests/core/engine_adapter/integration/test_integration_postgres.py | Integration tests for PostgreSQL grants with real database operations |
sqlmesh/dbt/model.py | Adds grants field to SQLMesh model conversion |
sqlmesh/dbt/basemodel.py | Updates grants field validation to handle optional grants |
sqlmesh/core/snapshot/evaluator.py | Implements grants application in evaluation strategies |
sqlmesh/core/model/meta.py | Defines grants target layer enum and grants property parsing |
sqlmesh/core/model/definition.py | Adds grants to model metadata hash and table type determination |
sqlmesh/core/model/common.py | Registers grants field for property parsing |
sqlmesh/core/engine_adapter/postgres.py | Implements PostgreSQL-specific grants operations |
sqlmesh/core/engine_adapter/base_postgres.py | Adds current schema retrieval for grants queries |
sqlmesh/core/engine_adapter/base.py | Defines base grants interface and configuration diffing |
sqlmesh/core/engine_adapter/_typing.py | Adds GrantsConfig type definition |
sqlmesh/core/_typing.py | Minor formatting change |
pyproject.toml | Updates sqlglot dependency version |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
1019d57
to
ada2040
Compare
8a93de3
to
ef2ca9b
Compare
54ca124
to
a7e72f5
Compare
render_kwargs, | ||
skip_grants=True, # Skip grants; they're applied after data insertion | ||
**kwargs, | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if this create is even needed? We do the create a few lines down with the replace query. I wonder if we did this before to maintain the contract of creating empty table first but we don't do that anymore. I bring it up since I then think you could just have it grant and not have to do the skip grants here.
653e987
to
9da034d
Compare
- sqlmesh grants uses None it explicitly means unmanaged and {} to mean managed with no grants (revoke all existing grants) - empty {} dbt grants config is always considered as unmanaged dbt manifest always parses None grants as {}
…pter and make sync_grants_config public
and always apply grants on migrate(). This way, grants will always be applied even when grants are the only metadata that changed.
…_snapshot_deployable" for grants application.
2905b8e
to
391ede2
Compare
@@ -126,6 +161,14 @@ def _normalize(value: t.Any) -> t.Any: | |||
|
|||
return v | |||
|
|||
@classmethod | |||
def _validate_str_enum_value(cls, v: t.Any) -> t.Any: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
dead code?
@@ -465,6 +520,67 @@ def custom_materialization_properties(self) -> CustomMaterializationProperties: | |||
return self.kind.materialization_properties | |||
return {} | |||
|
|||
@cached_property | |||
def grants(self) -> t.Optional[GrantsConfig]: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems a little weird to go through the trouble of converting t.Dict[str, t.List[str]]
into exp.Tuple
just to convert it back again here.
def normalize_to_string_list(value_expr: exp.Expression) -> t.List[str]: | ||
result = [] | ||
|
||
def process_expression(expr: exp.Expression) -> None: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
3 level of nested function definitions is probably a bit too much. Any opportunity to move exp_to_string
and normalize_to_string_list
out?
|
||
model_grants_target_layer = model.grants_target_layer | ||
|
||
is_prod_and_dev_only = is_snapshot_deployable and model.virtual_environment_mode.is_dev_only |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the name is a bit misleading. Without reading the expression itself it reads as "Is Prod and Dev only?"
|
||
is_prod_and_dev_only = is_snapshot_deployable and model.virtual_environment_mode.is_dev_only | ||
|
||
if not ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To avoid a somewhat confusing inversion can we just put the grant application under the if
and put the skip debug log message in else
?
is_snapshot_deployable = deployability_index.is_deployable(snapshot) | ||
|
||
# Apply grants to the physical layer (referenced table / view) after promotion | ||
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL, is_snapshot_deployable) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are we applying the physical grants as part of promotion?
**kwargs, | ||
) | ||
|
||
if not skip_grants: | ||
# Apply grants after SCD Type 2 table creation |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't we recreate a table for SCD 2 every time? Shouldn't we add grants on insert then too?
if self.adapter.table_exists(table_name): | ||
# Make sure we don't recreate the view to prevent deletion of downstream views in engines with no late | ||
# binding support (because of DROP CASCADE). | ||
logger.info("View '%s' already exists", table_name) | ||
|
||
if not skip_grants: | ||
# Always apply grants when present, even if view exists, to handle grants updates |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why isn't migration taking care of that?
Native grants for SQLMesh models
SQLMesh now supports configuring model-level grants directly in model definitions. Use the new
grants
andgrants_target_layer
properties to assign privileges to specific roles.grants
: maps each privilege to the list of grantees.grants_target_layer
: selects the data layer object that receives the grant (virtual
default view,physical
table, orall
).This PR only has grants support for the Postgres engine adapter.
Model configuration example
See
tests/core/test_model.py
for additional scenarios.Fixes #569.