-
Notifications
You must be signed in to change notification settings - Fork 14k
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
fix: prevent multiple pvm errors on migration #31332
base: master
Are you sure you want to change the base?
Conversation
@@ -45,10 +49,21 @@ def upgrade(): | |||
"slices", | |||
sa.Column("catalog_perm", sa.String(length=1000), nullable=True), | |||
) | |||
|
|||
if not table_has_index(perm_table, perm_index_view_menu): |
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.
might be good to add these into a helper function call create_index that will do the check and add it like here #31303
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.
Nice, I was thinking of a helper function. I don't want to create a conflict with that PR. Perhaps we can use his helper function after it's merged.
069b0fa
to
fc45193
Compare
"New permission %s already exists. Removing old one", | ||
new_perm, | ||
) | ||
session.delete(existing_pvm) |
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.
@betodealmeida not sure if you would prefer to return pvms to be deleted instead of deleting them here
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.
Do we really need to delete anything here? Should we just rename the existing ones and create new ones for new catalogs? The old ones are attached to roles already and we probably want to maintain those associations.
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.
@mistercrunch the current code does that, we get the current perm (where the catalog name is null
, see how we compute current_perm
in line 492) and change it to point to the new perm name new_perm
, which includes the default catalog name (line 497). Then we simply rename the existing PVM to have the new name, so that existing associations like roles are preserved (line 508 on the left).
In regards to this PR, I don't think we should delete existing PVMs. First, because they should not exist... the only reason I can imagine why it would exist is bad upgrade-downgrade that didn't rename the PVMs correctly. If that happens, I think it's better to just keep the existing PVM, since it already has the correct name, and might be associated with roles.
if table_has_index(perm_table, perm_index_permission_id): | ||
op.drop_index(op.f(perm_index_permission_id), table_name=perm_table) | ||
if table_has_index(perm_table, perm_index_view_menu): | ||
op.drop_index(op.f(perm_index_view_menu), table_name=perm_table) |
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.
@betodealmeida any thoughts whether you think this change should stay with the new migration or in a different PR? My thought is to keep them together so that tables don't get out of sync, but I also see the benefit of splitting them up.
__table_args__ = (UniqueConstraint("permission_id", "view_menu_id"),) | ||
__table_args__ = ( | ||
sqla.Index("idx_permission_view_menu_id", "view_menu_id"), | ||
sqla.Index("idx_permission_permission_id", "permission_id"), |
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.
haven't looked at the queries against that table, but could be that a compound index work well here. I saw some queries that predicate on view_menu_id
and some on both view_menu_id
and permission_id
. If permission is never predicated by itself, the compound on view_menu_id
and permission_id
(in that order) would cover those
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.
Adding more info here, FAB already has a compound index since it defines a unique constraint on these FK: https://github.com/preset-io/Flask-AppBuilder/blob/master/flask_appbuilder/security/sqla/models.py#L73
Checked the security converge code, and we have the following queries:
session.query(ViewMenu).filter(ViewMenu.name == view_name).one_or_none()
session.query(Permission).filter(Permission.name == permission_name).one_or_none()
session.query(PermissionView)
.filter(
PermissionView.view_menu_id == view_menu.id,
PermissionView.permission_id == permission.id,
)
session.query(PermissionView)
.join(Permission)
.join(ViewMenu)
.filter(ViewMenu.name == view_name, Permission.name == permission_name)
session.query(PermissionView)
.join(Permission)
.filter(Permission.name == old_permission_name)
session.query(PermissionView)
.join(ViewMenu)
.filter(ViewMenu.name == old_view_name)
We have indexes to efficiently query all of these:
- index on
ViewMenu.name
- index on
Permission.name
- Indexes on
Permission.id
andViewMenu.id
Yet we can have more eficient joins by adding indexes on the FK for ab_permission_view_role
and ab_permission_view
dpgaspar/Flask-AppBuilder#2293
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.
Thanks @dpgaspar! I'll remove the changes here and we'll bring them in from FAB then.
"New permission %s already exists. Removing old one", | ||
new_perm, | ||
) | ||
session.delete(existing_pvm) |
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.
@mistercrunch the current code does that, we get the current perm (where the catalog name is null
, see how we compute current_perm
in line 492) and change it to point to the new perm name new_perm
, which includes the default catalog name (line 497). Then we simply rename the existing PVM to have the new name, so that existing associations like roles are preserved (line 508 on the left).
In regards to this PR, I don't think we should delete existing PVMs. First, because they should not exist... the only reason I can imagine why it would exist is bad upgrade-downgrade that didn't rename the PVMs correctly. If that happens, I think it's better to just keep the existing PVM, since it already has the correct name, and might be associated with roles.
"New permission %s already exists. Deleting old one", | ||
new_name, | ||
) | ||
pvms_to_delete.append(pvm) |
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.
Same here, I would just leave the existing one, something like:
# check to see if the new name already exists
if not session.query(ViewMenu).filter_by(name=new_name).one_or_none():
# new perm doesn't exist, rename existing one to the new name
pvms_to_rename.append((pvm, new_name))
Yes, deleting is yet more potential locks. I also think there are methods/CLI commands that will clean up perms that should not exist. I forgot whether it's a |
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.
LGTM
SUMMARY
We're seeing some errors when running the catalog migration because of some PVMs that already exist. In that case, I'm not going to update the PVM.
BEFORE/AFTER SCREENSHOTS OR ANIMATED GIF
TESTING INSTRUCTIONS
ADDITIONAL INFORMATION