Skip to content
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

[#3348] feat(core,server): Add the list operation of the user #4055

Open
wants to merge 15 commits into
base: main
Choose a base branch
from

Conversation

jerqi
Copy link
Collaborator

@jerqi jerqi commented Jul 3, 2024

What changes were proposed in this pull request?

Add the list operation of the user

Why are the changes needed?

Fix: #3348

Does this PR introduce any user-facing change?

I will add the document later.

How was this patch tested?

Add the new ut.

Comment on lines 104 to 110
public String[] listUsers(String metalake) throws NoSuchMetalakeException {
return doWithNonAdminLock(() -> userGroupManager.listUsers(metalake));
}

public User[] listUsersInfo(String metalake) throws NoSuchMetalakeException {
return doWithNonAdminLock(() -> userGroupManager.listUsersInfo(metalake));
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I think listUsers -> listUserNames, listUsersInfo -> listUsers would be more appropriate

Copy link
Contributor

Choose a reason for hiding this comment

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

Done.

@jerqi jerqi force-pushed the ISSUE-3348 branch 4 times, most recently from a124b10 to 2f1476c Compare July 3, 2024 11:45
@jerqi jerqi closed this Jul 4, 2024
@jerqi jerqi reopened this Jul 4, 2024
@jerqi
Copy link
Collaborator Author

jerqi commented Jul 4, 2024

@lw-yang Could you take an another look?

Comment on lines 253 to 256
for (UserPO userPO : userPOs) {
List<RolePO> rolePOs = RoleMetaService.getInstance().listRolesByUserId(userPO.getUserId());
userEntities.add(POConverters.fromUserPO(userPO, rolePOs, namespace));
}
Copy link
Contributor

Choose a reason for hiding this comment

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

the num of users > 1w is common situation, this may execute for a long time, maybe we should consider using cache in listUsers

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

cc @yuqi1129 Maybe we can have a common cache layer for this.

Copy link
Member

Choose a reason for hiding this comment

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

I think it is better to use a paging query.

@jerryshao
Copy link
Contributor

@jerqi can you please update the PR.

@jerqi jerqi force-pushed the ISSUE-3348 branch 2 times, most recently from 9d07ea0 to a57e4e3 Compare July 16, 2024 06:56
@jerqi
Copy link
Collaborator Author

jerqi commented Jul 16, 2024

@lw-yang @jerryshao Do you have any other suggestion?

LOG.error("Listing user under metalake {} failed due to storage issues", metalake, ioe);
throw new RuntimeException(ioe);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

  1. I think you need to use tree lock here.
  2. listUserNames can be implemented based on listUsers, don't need to duplicate the codes.

Copy link
Collaborator Author

@jerqi jerqi Jul 17, 2024

Choose a reason for hiding this comment

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

  1. I think you need to use tree lock here.

I have used tree lock in the UserOperations.

  1. listUserNames can be implemented based on listUsers, don't need to duplicate the codes.

OK, I have refactored the code.

userPOs.addAll(
SessionUtils.doWithoutCommitAndFetchResult(
UserMetaMapper.class,
mapper -> mapper.listUserPOsByMetalakeId(metalakeId.get()))));
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we don't have to use transaction here, simply because we don't use select for update, so the transaction is not useful and will lead to dead lock.

Copy link
Contributor

Choose a reason for hiding this comment

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

I modified. But it shouldn't bring dead lock using the transaction.

@Getter
@ToString
@EqualsAndHashCode(callSuper = true)
public class NameListResponse extends BaseResponse {
Copy link
Contributor

Choose a reason for hiding this comment

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

You should implement validate() method.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

CatalogListResponse doesn't implement validate, too. I created an issue #4197 to track it.

super(0);
this.users = null;
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Also here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Added.

for (UserPO userPO : userPOs) {
List<RolePO> rolePOs = RoleMetaService.getInstance().listRolesByUserId(userPO.getUserId());
userEntities.add(POConverters.fromUserPO(userPO, rolePOs, namespace));
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Do you really want to return all the roles for all the users? This will be extremely cost, is that really the frontend needs?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Now, we adopts the lazily loading information about users/roles.

} catch (Throwable t) {
throw t;
} finally {
SqlSessions.closeSqlSession();
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do you need to add this?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Removed.

@jerqi jerqi force-pushed the ISSUE-3348 branch 3 times, most recently from 62c5484 to 8118c63 Compare September 9, 2024 03:05
@jerqi
Copy link
Collaborator Author

jerqi commented Sep 9, 2024

After discussing with @shaofengshi offline, we need the ability to list all the role information of the user. It will be convenient for users. I have supported the user lazily loading. So if we only want to load the user names, we don't need to load unnecessary information.
In the future, we have many users, we can support to page. It's an improvement. We can support it in an another pull request.

@jerqi jerqi force-pushed the ISSUE-3348 branch 3 times, most recently from 4b728a5 to b2d206d Compare September 9, 2024 08:25
@jerqi jerqi self-assigned this Sep 9, 2024
@jerqi jerqi added the branch-0.6 Automatically cherry-pick commit to branch-0.6 label Sep 9, 2024
@xunliu
Copy link
Member

xunliu commented Sep 13, 2024

Query all data at once or cache buffering in the service also has the problem of dirty data. I think paged queries are better.
@jerryshao @jerqi What do you think?

try {
Namespace namespace = AuthorizationUtils.ofUserNamespace(metalake);
return store.list(namespace, UserEntity.class, Entity.EntityType.USER).stream()
.map(entity -> (User) entity)
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do we need this line?

Copy link
Contributor

Choose a reason for hiding this comment

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

So you only get the user list here, right? Will you also fetch the roles for each user, I saw that User and UserDTO have roles fields, will this be empty?

Copy link
Collaborator Author

@jerqi jerqi Sep 14, 2024

Choose a reason for hiding this comment

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

Yes, I only get the user list here. I won't fetch roles unless they are needed. Now the roles is empty for every user.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

You can see the pull request. #4690
UserEntity roles field don't be fetched directly. We will put a supplier method field instead. So we don't fetch the values directly if we only need the names.
If we need all the information of entities, we can also have the ability to put them. You can see the list details. Because the front side need the all the users information, so I provide the interface.

Copy link
Contributor

Choose a reason for hiding this comment

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

I mean here why do we need to do a type conversion entity -> (User) entity?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Sorry, it's unnecessary. I ever designed other interface listEntitiesByRelation. It need me to use type cast. For this place, we don't need. I have removed it.

+ " mt ON ut.metalake_id = mt.metalake_id"
+ " WHERE mt.metalake_name = #{metalakeName}"
+ " AND ut.deleted_at = 0 AND mt.deleted_at = 0";
}
Copy link
Contributor

Choose a reason for hiding this comment

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

This SQL will not throw an exception when metalake is not existed, it will only return an empty list. In your definition, you have NoSuchMetalakeException defined, to throw an such exception, you have to add one more check to see if metalake is existed or not.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Added the checkMetalakeExists.

ErrorResponse errorResponse2 = resp3.readEntity(ErrorResponse.class);
Assertions.assertEquals(ErrorConstants.INTERNAL_ERROR_CODE, errorResponse2.getCode());
Assertions.assertEquals(RuntimeException.class.getSimpleName(), errorResponse2.getType());
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I think you also need to add integration tests for this.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I have added IT in the AccessControlId.java.

Namespace namespace,
Class<E> type,
Entity.EntityType entityType,
List<Field> allowMissingFields)
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this design used for skipping some unnecessary fields? @yuqi1129 please also take a look about this design.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yes, because different upper layers may ignore different fields. For example, list roles
Some upper layers may ignore objects and objects count.
Some upper layers may only ignore objects.
Some upper layers may not ignore any field.

*/
<E extends Entity & HasIdentifier> List<E> list(
Namespace namespace, Class<E> type, EntityType entityType) throws IOException;
Namespace namespace, Class<E> type, EntityType entityType, List<Field> allowMissingFields)
throws IOException;
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe we can add a another inteface with default to fetch all the fields, then you don't have to change the code above for different catalogs.

Copy link
Collaborator Author

@jerqi jerqi Sep 20, 2024

Choose a reason for hiding this comment

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

OK, I preferring adding another interface, too. cc @yuqi1129 @xunliu

Copy link
Contributor

@jerryshao jerryshao Sep 20, 2024

Choose a reason for hiding this comment

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

Also, I would suggest to rename the parameter to like: List<Field> skippingFields

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

ok for me.

* @return the list of entities
* @throws IOException if the list operation fails
*/
<E extends Entity & HasIdentifier> List<E> list(
Namespace namespace, Class<E> type, EntityType entityType) throws IOException;
Copy link
Contributor

Choose a reason for hiding this comment

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

What I mean is that these interface can be implemented like:

default XX list(xxxx) {
    return list(xx, xx, xx, Lists.emptyList)
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done.

Comment on lines +36 to +37
+ " '[' || GROUP_CONCAT('\"' || rot.role_name || '\"') || ']' as roleNames,"
+ " '[' || GROUP_CONCAT('\"' || rot.role_id || '\"') || ']' as roleIds"
Copy link
Contributor

Choose a reason for hiding this comment

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

What is the usage of these code, can you explain it?

Copy link
Collaborator Author

@jerqi jerqi Sep 20, 2024

Choose a reason for hiding this comment

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

H2 doesn't support json_agg function. This uses GROUP_CONCAT to implement similar feature.

&& Objects.equal(getLastVersion(), combinedUserPO.getLastVersion())
&& Objects.equal(getDeletedAt(), combinedUserPO.getDeletedAt())
&& Objects.equal(getRoleIds(), combinedUserPO.getRoleIds())
&& Objects.equal(getRoleNames(), combinedUserPO.getRoleNames());
Copy link
Contributor

Choose a reason for hiding this comment

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

I think you can use like super.equals(xxxx) and super.hashCode() to simplify the code here and below.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

OK, I will do.

String metalakeName = namespace.level(0);

if (skippingFields.contains(UserEntity.ROLE_IDS)
&& skippingFields.contains(UserEntity.ROLE_NAMES)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

It seems that skippingFields is not flexible enough, you still need to explicitly check these fields and execute by different branches.

Copy link
Collaborator Author

@jerqi jerqi Sep 20, 2024

Choose a reason for hiding this comment

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

After considering twice, I extract an interface SupportsSkippingFields to express the list user namespace handler. I also wrap the logic which we select the handler according to skipping fields into SupportsSKippingFieldsHandlers.
Now, we can match the skipping fields according to the handler which defined. It reduce the branches.
Moreover, we ask the developer must add high priority handler first.(You can see the code SupportsSkippingFieldsHandlers for details). I can add the priority method for the SupportsSkippingFields. But for now, this is not a blocker. So just add a comment to notify others the correct usage for this interface.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
branch-0.6 Automatically cherry-pick commit to branch-0.6
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[FEATURE] Need REST API to list all users' info in a metalake
5 participants