-
Notifications
You must be signed in to change notification settings - Fork 2.1k
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
Central user account table arriving ..... #27165
Conversation
@DeepDiver1975, thanks for your PR! By analyzing the history of the files in this pull request, we identified @butonic, @VicDeo and @PVince81 to be potential reviewers. |
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.
Great start!
public function changeSchema(Schema $schema, array $options) { | ||
$prefix = $options['tablePrefix']; | ||
$table = $schema->createTable("{$prefix}accounts"); | ||
$table->addColumn('id', 'integer', [ |
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.
let's go big ? bigint ?
'notnull' => false, | ||
'length' => 255, | ||
]); | ||
$table->addColumn('quota', 'string', [ |
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.
Now seeing this, I remember that we already have a quota value in the oc_preferences table. But not as easy to retrieve as a single select on this table here.
Might need to think of migrating it.
'notnull' => false, | ||
'length' => 32, | ||
]); | ||
$table->addColumn('backend', 'string', [ |
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.
also add a backend_id
which is the user id as seen by the backend ? unless we think it is always the same as the "user_id" column. That is, assuming that multiple backends can't provide the same user 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.
Maybe not that clear but this is - as of now - the class name of the backend
@@ -94,7 +100,7 @@ public function getBackends() { | |||
* @param \OCP\UserInterface $backend | |||
*/ | |||
public function registerBackend($backend) { | |||
$this->backends[] = $backend; | |||
$this->backends[get_class($backend)] = $backend; |
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 think in the end this would only be called in the background job (or on-demand) as we won't need the backend any more for every operation since we can rely on the account 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.
we need the backend for login, inital setup of new accounts (auto provision mode) and import scenarios
2f66865
to
78e1a6b
Compare
lib/private/Files/View.php
Outdated
} else { | ||
return new User($ownerId, null); | ||
} | ||
return $this->userManager->get($ownerId); |
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.
This will break with federated share owners which are not resolveable through the user manager. That's why we create a "virtual" user...
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.
Hmmmm - ok - then we need a second implementation of IUser for this case.
User requires an instance of Account and AccountMapper to be passed in ...
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.
Indeed, that would align with this idea: #25815 (comment)
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.
Later on I wonder if we should also have a "federated users backend" which would deliver such users, they'd also appear in the account table under the "federation" backend for quick lookup. (but that sounds redundant from the system address book)
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.
something to think about - for sure ....
3e1e383
to
54a77ef
Compare
'notnull' => false, | ||
'length' => 32, | ||
]); | ||
$table->addColumn('backend', Type::STRING, [ |
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.
@DeepDiver1975 Why it is a String? Shouldnt it be INT representing foreign key? Why do we allow NULL 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.
Is it connected to 0: initial?
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.
Because it's the php class name
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.
How do you ensure, that if the backend is deleted, all related records are also deleted. Will we do single transaction doing that to ensure atomicity and consistency? Is backend primary key somewhere? This smells like possible inconsistency later, if some developer is not careful in the future.
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.
If a backend is not enabled or has been deleted the user can no longer login. Deleting a user is an explicit operation and nothing we should do 'just because the backend is no longer there'
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 wound ensure this not null, and add another state 4: backend-deleted or mark user as deleted, in a single transaction - also not sure about this. Actualy this sound quite dangerous for me, that someone can enable and disable backends and we still have a hanging relation pointing to nowhere. Not sure how to resolve it not to brake consistency
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.
yes - nut null makes sense here - thx
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.
Ok, because I assume we dont have a new table holding backends, we just use classnames to do that? Makes sense.
'length' => 64, | ||
]); | ||
$table->addColumn('home', Type::STRING, [ | ||
'notnull' => false, |
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 do we allow null 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.
Is it connected to 0: initial?
$table->addColumn('state', Type::SMALLINT, [ | ||
'notnull' => true, | ||
'default' => 0, | ||
'comment' => '0: initial, 1: enabled, 2: disabled, 3: deleted' |
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.
0: initial, I start loving it. Is it connected with last_login, backend and home?
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'm not yet fully sure if we need 0:initial ...
lib/private/User/AccountMapper.php
Outdated
$qb->select('*') | ||
->from($this->getTableName()) | ||
->where($qb->expr()->iLike($fieldName, $qb->createNamedParameter('%' . $this->db->escapeLikeParameter($pattern) . '%'))) | ||
->orderBy($fieldName, $orderBy ? 'ASC' : 'DESC'); |
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.
orderBy is quite expensive operation, do we need to do that by default? I think it makes sense only for displayname, isnt it?
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 dont we use LIMIT on DB level? What findEntities is exactly doing? Please be aware of that it will go over all never-logged-in accounts in LDAP also, doing sequential read on the disk for pages for all of them!
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.
Limit is on db level. And if we want pagination we need to sort on db level.
Orderby is not expensive is there is an index.
(Need to add it 😆)
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.
Internaly in DB, in order to do limit, you have to finish all others. Adding OrderBy basically tells the plan generator that you need a whole picture of the table, thus whole scan (it is enforced by sort requiring full view on the input - blocking operator). LIMIT just tells the sort to stop sorting. Of course, unless you have index, found good reference https://www.percona.com/blog/2006/09/01/mysql-order-by-limit-performance-optimization/. Just wanted to ensure that we wont do anything crazy 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.
The users are expected to be sorted - this has to be done in the db - no other option available
lib/private/User/AccountMapper.php
Outdated
->where($qb->expr()->iLike($fieldName, $qb->createNamedParameter('%' . $this->db->escapeLikeParameter($pattern) . '%'))) | ||
->orderBy($fieldName, $orderBy ? 'ASC' : 'DESC'); | ||
|
||
return $this->findEntities($qb->getSQL(), $qb->getParameters(), $limit, $offset); |
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.
We are passing here $limit. Why do we first scan whole the table and then limit that on PHP level? What findEntities is exactly doing, is it belonging to the query above or we extract logic from DB to PHP?
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.
It is on db level. See the code inside find entries
$prefix = $options['tablePrefix']; | ||
$table = $schema->createTable("{$prefix}accounts"); | ||
$table->addColumn('id', Type::BIGINT, [ | ||
'autoincrement' => true, |
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.
Is it a primary key or foreign key somewhere? I might have a question here about user uniqueness, where backend tries to add user which already exists, and how do we distinguish between them
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.
auto increment always forces to be the primary key
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.
Yes, this is true
'notnull' => false, | ||
'length' => 255, | ||
]); | ||
$table->addColumn('user_id', Type::STRING, [ |
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.
Is it a primary key or foreign key somewhere? I might have a question here about user uniqueness, where backend tries to add user which already exists, and how do we distinguish between them. I dont also understand why we have two IDs. DO we have redundant tables? Do you have any drawing showing relations between all involved tables?
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.
see the index definition - this is unique
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 you have some db relations drawing of Migration feature so we can easier understand?
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 think I see the whole picture of relations of this table with others, but it is easier to look at it
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 you have some db relations drawing of Migration feature so we can easier understand?
I don't get this question. The migration feature is unrelated any db relations.
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 think I need to fully understand the relationships and redundancy between tables
I guess so 😉 |
9f0621a
to
331d06c
Compare
@DeepDiver1975 I did not go in details in the code yet, but seems the priority in implementation of this table will be to put as much load on table scan of oc_account table, so most of queries will just ask for records of this table, and very very seldomely do the join with e.g. ldap table (this is expensive, if both tables are big, and it will be like that). (Does it replace ldap table, or is just normalisation ?) |
@mrow4a no, if we had that it would be nice. With this table partial searches can be done only in a single location: the oc_account table. |
Obviously, this will require use to kill a lot of code in user_ldap and make migration rock solid. |
see #27205 |
Something comes to mind: do we need a "lastsynctime" column ? |
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 tried understanding the changes. Impressive list of test changes ... the avatar stuff can be discussed idependently.
lib/private/User/AccountMapper.php
Outdated
$parameter = (string)$qb->createNamedParameter($uid); | ||
$qb->select('*') | ||
->from($this->getTableName()) | ||
->where($qb->expr()->eq('lower_user_id', $qb->createFunction("LOWER($parameter)"))); |
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.
strtolower $uid instead of using SQL?
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.
php strtolower and database tolower behave slightly different - we have to choose one way .... (hmmm .... do I?)
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.
grrr - actually I'm mixing it 🤦♂️
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.
fixed
* @return IImage|null | ||
* @since 9.0.0 | ||
*/ | ||
public function getAvatarImage($size) { |
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 don't think we really need the avatar image as part of the user. I'd prefer to implement it as an app. That app can store the avatar image inside its app folder, no need for our internal filesystem. It could try to fetch the avatar from gravatar based on the email address if an internal avatar is not available.
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.
separate discussion - from an api poc it is there - not much to change right away ...
lib/private/User/User.php
Outdated
@@ -183,18 +157,16 @@ public function setEMailAddress($mailAddress) { | |||
* @return int | |||
*/ | |||
public function getLastLogin() { | |||
return $this->lastLogin; | |||
return (int)$this->account->getLastLogin(); |
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.
shouldn't the entity return the right type?
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.
yes - it should ....
|
||
// FIXME: Feels like an hack - suggestions? | ||
// FIXME: Feels like an hack - suggestions? |
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.
foreign keys ftw ;-)
*/ | ||
public function __construct($uid, $backend, $emitter = null, IConfig $config = null, | ||
public function __construct(Account $account, AccountMapper $mapper, $emitter = null, IConfig $config = null, |
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.
so we don't really need the user database backend anymore, right?
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 backend is accessible via the account
e14c1f2
to
2bc42a7
Compare
…ction - behave accordingly
…avatar and displayname
3aaaf2a
to
3b088af
Compare
@@ -322,7 +322,7 @@ function testSearchByTag() { | |||
$userId = $this->getUniqueId('user'); | |||
\OC::$server->getUserManager()->createUser($userId, $userId); | |||
$this->loginAsUser($userId); | |||
$user = new \OC\User\User($userId, null); | |||
// $user = new \OC\User\User($userId, null); |
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.
commented out ?
This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs. |
Related Issue
fixes #23558
Missing
Types of changes
Checklist: