From 1de3b01269011a2ccffe86e3ebf7c5bbf36e4bc9 Mon Sep 17 00:00:00 2001 From: Benjamin Gaussorgues Date: Fri, 23 Oct 2026 11:19:38 +0200 Subject: [PATCH 1/3] feat(query): introduce from_unixtime function Signed-off-by: Benjamin Gaussorgues --- .../FunctionBuilder/FunctionBuilder.php | 37 +++++++++++-------- .../FunctionBuilder/OCIFunctionBuilder.php | 20 ++++++---- .../FunctionBuilder/PgSqlFunctionBuilder.php | 12 ++++-- .../FunctionBuilder/SqliteFunctionBuilder.php | 18 ++++++--- .../DB/QueryBuilder/IFunctionBuilder.php | 10 ++++- 5 files changed, 64 insertions(+), 33 deletions(-) diff --git a/lib/private/DB/QueryBuilder/FunctionBuilder/FunctionBuilder.php b/lib/private/DB/QueryBuilder/FunctionBuilder/FunctionBuilder.php index e5727df324060..e75fb7ac88744 100644 --- a/lib/private/DB/QueryBuilder/FunctionBuilder/FunctionBuilder.php +++ b/lib/private/DB/QueryBuilder/FunctionBuilder/FunctionBuilder.php @@ -25,12 +25,12 @@ public function __construct( ) { } - #[\Override] + #[Override] public function md5($input): IQueryFunction { return new QueryFunction('MD5(' . $this->helper->quoteColumnName($input) . ')'); } - #[\Override] + #[Override] public function concat($x, ...$expr): IQueryFunction { $args = func_get_args(); $list = []; @@ -40,13 +40,13 @@ public function concat($x, ...$expr): IQueryFunction { return new QueryFunction(sprintf('CONCAT(%s)', implode(', ', $list))); } - #[\Override] + #[Override] public function groupConcat($expr, ?string $separator = ','): IQueryFunction { $separator = $this->connection->quote($separator); return new QueryFunction('GROUP_CONCAT(' . $this->helper->quoteColumnName($expr) . ' SEPARATOR ' . $separator . ')'); } - #[\Override] + #[Override] public function substring($input, $start, $length = null): IQueryFunction { if ($length) { return new QueryFunction('SUBSTR(' . $this->helper->quoteColumnName($input) . ', ' . $this->helper->quoteColumnName($start) . ', ' . $this->helper->quoteColumnName($length) . ')'); @@ -55,68 +55,68 @@ public function substring($input, $start, $length = null): IQueryFunction { } } - #[\Override] + #[Override] public function regexSubstring($input, $pattern): IQueryFunction { return new QueryFunction('REGEXP_SUBSTR(' . $this->helper->quoteColumnName($input) . ', ' . $this->helper->quoteColumnName($pattern) . ')'); } - #[\Override] + #[Override] public function sum($field): IQueryFunction { return new QueryFunction('SUM(' . $this->helper->quoteColumnName($field) . ')'); } - #[\Override] + #[Override] public function lower($field): IQueryFunction { return new QueryFunction('LOWER(' . $this->helper->quoteColumnName($field) . ')'); } - #[\Override] + #[Override] public function add($x, $y): IQueryFunction { return new QueryFunction($this->helper->quoteColumnName($x) . ' + ' . $this->helper->quoteColumnName($y)); } - #[\Override] + #[Override] public function subtract($x, $y): IQueryFunction { return new QueryFunction($this->helper->quoteColumnName($x) . ' - ' . $this->helper->quoteColumnName($y)); } - #[\Override] + #[Override] public function count($count = '', $alias = ''): IQueryFunction { $alias = $alias ? (' AS ' . $this->helper->quoteColumnName($alias)) : ''; $quotedName = $count === '' ? '*' : $this->helper->quoteColumnName($count); return new QueryFunction('COUNT(' . $quotedName . ')' . $alias); } - #[\Override] + #[Override] public function octetLength($field, $alias = ''): IQueryFunction { $alias = $alias ? (' AS ' . $this->helper->quoteColumnName($alias)) : ''; $quotedName = $this->helper->quoteColumnName($field); return new QueryFunction('OCTET_LENGTH(' . $quotedName . ')' . $alias); } - #[\Override] + #[Override] public function charLength($field, $alias = ''): IQueryFunction { $alias = $alias ? (' AS ' . $this->helper->quoteColumnName($alias)) : ''; $quotedName = $this->helper->quoteColumnName($field); return new QueryFunction('CHAR_LENGTH(' . $quotedName . ')' . $alias); } - #[\Override] + #[Override] public function max($field): IQueryFunction { return new QueryFunction('MAX(' . $this->helper->quoteColumnName($field) . ')'); } - #[\Override] + #[Override] public function min($field): IQueryFunction { return new QueryFunction('MIN(' . $this->helper->quoteColumnName($field) . ')'); } - #[\Override] + #[Override] public function greatest($x, $y): IQueryFunction { return new QueryFunction('GREATEST(' . $this->helper->quoteColumnName($x) . ', ' . $this->helper->quoteColumnName($y) . ')'); } - #[\Override] + #[Override] public function least($x, $y): IQueryFunction { return new QueryFunction('LEAST(' . $this->helper->quoteColumnName($x) . ', ' . $this->helper->quoteColumnName($y) . ')'); } @@ -130,4 +130,9 @@ public function now(): IQueryFunction { public function coalesce(string|ILiteral|IParameter|IQueryFunction $value, string|ILiteral|IParameter|IQueryFunction $default): IQueryFunction { return new QueryFunction('COALESCE(' . $this->helper->quoteColumnName($value) . ', ' . $this->helper->quoteColumnName($default) . ')'); } + + #[Override] + public function fromUnixtime(int|string $timestamp): IQueryFunction { + return new QueryFunction('FROM_UNIXTIME(' . (int)$timestamp . ')'); + } } diff --git a/lib/private/DB/QueryBuilder/FunctionBuilder/OCIFunctionBuilder.php b/lib/private/DB/QueryBuilder/FunctionBuilder/OCIFunctionBuilder.php index d0370f97a2d43..b58e0a7d34474 100644 --- a/lib/private/DB/QueryBuilder/FunctionBuilder/OCIFunctionBuilder.php +++ b/lib/private/DB/QueryBuilder/FunctionBuilder/OCIFunctionBuilder.php @@ -12,9 +12,10 @@ use OCP\DB\QueryBuilder\ILiteral; use OCP\DB\QueryBuilder\IParameter; use OCP\DB\QueryBuilder\IQueryFunction; +use Override; class OCIFunctionBuilder extends FunctionBuilder { - #[\Override] + #[Override] public function md5($input): IQueryFunction { /** @var ConnectionAdapter $co */ $co = $this->connection; @@ -36,7 +37,7 @@ public function md5($input): IQueryFunction { * @param string|ILiteral|IParameter|IQueryFunction $y * @return IQueryFunction */ - #[\Override] + #[Override] public function greatest($x, $y): IQueryFunction { if (is_string($y) || $y instanceof IQueryFunction) { return parent::greatest($y, $x); @@ -57,7 +58,7 @@ public function greatest($x, $y): IQueryFunction { * @param string|ILiteral|IParameter|IQueryFunction $y * @return IQueryFunction */ - #[\Override] + #[Override] public function least($x, $y): IQueryFunction { if (is_string($y) || $y instanceof IQueryFunction) { return parent::least($y, $x); @@ -66,7 +67,7 @@ public function least($x, $y): IQueryFunction { return parent::least($x, $y); } - #[\Override] + #[Override] public function concat($x, ...$expr): IQueryFunction { $args = func_get_args(); $list = []; @@ -76,7 +77,7 @@ public function concat($x, ...$expr): IQueryFunction { return new QueryFunction(sprintf('(%s)', implode(' || ', $list))); } - #[\Override] + #[Override] public function groupConcat($expr, ?string $separator = ','): IQueryFunction { $orderByClause = ' WITHIN GROUP(ORDER BY NULL)'; if (is_null($separator)) { @@ -87,17 +88,22 @@ public function groupConcat($expr, ?string $separator = ','): IQueryFunction { return new QueryFunction('LISTAGG(' . $this->helper->quoteColumnName($expr) . ', ' . $separator . ')' . $orderByClause); } - #[\Override] + #[Override] public function octetLength($field, $alias = ''): IQueryFunction { $alias = $alias ? (' AS ' . $this->helper->quoteColumnName($alias)) : ''; $quotedName = $this->helper->quoteColumnName($field); return new QueryFunction('COALESCE(LENGTHB(' . $quotedName . '), 0)' . $alias); } - #[\Override] + #[Override] public function charLength($field, $alias = ''): IQueryFunction { $alias = $alias ? (' AS ' . $this->helper->quoteColumnName($alias)) : ''; $quotedName = $this->helper->quoteColumnName($field); return new QueryFunction('COALESCE(LENGTH(' . $quotedName . '), 0)' . $alias); } + + #[Override] + public function fromUnixtime(int|string $timestamp): IQueryFunction { + return new QueryFunction('TIMESTAMP "1970-01-01 00:00:00 UTC" + NUMTODSINTERVAL(', (int)$timestamp, ', "SECOND")'); + } } diff --git a/lib/private/DB/QueryBuilder/FunctionBuilder/PgSqlFunctionBuilder.php b/lib/private/DB/QueryBuilder/FunctionBuilder/PgSqlFunctionBuilder.php index 8fbbd09af0d99..cdd93223dbea4 100644 --- a/lib/private/DB/QueryBuilder/FunctionBuilder/PgSqlFunctionBuilder.php +++ b/lib/private/DB/QueryBuilder/FunctionBuilder/PgSqlFunctionBuilder.php @@ -10,9 +10,10 @@ use OC\DB\QueryBuilder\QueryFunction; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\DB\QueryBuilder\IQueryFunction; +use Override; class PgSqlFunctionBuilder extends FunctionBuilder { - #[\Override] + #[Override] public function concat($x, ...$expr): IQueryFunction { $args = func_get_args(); $list = []; @@ -22,7 +23,7 @@ public function concat($x, ...$expr): IQueryFunction { return new QueryFunction(sprintf('(%s)', implode(' || ', $list))); } - #[\Override] + #[Override] public function groupConcat($expr, ?string $separator = ','): IQueryFunction { $castedExpression = $this->queryBuilder->expr()->castColumn($expr, IQueryBuilder::PARAM_STR); @@ -34,8 +35,13 @@ public function groupConcat($expr, ?string $separator = ','): IQueryFunction { return new QueryFunction('string_agg(' . $castedExpression . ', ' . $separator . ')'); } - #[\Override] + #[Override] public function regexSubstring($input, $pattern): IQueryFunction { return new QueryFunction('substring(' . $this->helper->quoteColumnName($input) . ' from ' . $this->helper->quoteColumnName($pattern) . ')'); } + + #[Override] + public function fromUnixtime(int|string $timestamp): IQueryFunction { + return new QueryFunction('to_timestamp(' . (int)$timestamp . ')'); + } } diff --git a/lib/private/DB/QueryBuilder/FunctionBuilder/SqliteFunctionBuilder.php b/lib/private/DB/QueryBuilder/FunctionBuilder/SqliteFunctionBuilder.php index 1813e21a86e5c..9275755179fb4 100644 --- a/lib/private/DB/QueryBuilder/FunctionBuilder/SqliteFunctionBuilder.php +++ b/lib/private/DB/QueryBuilder/FunctionBuilder/SqliteFunctionBuilder.php @@ -9,9 +9,10 @@ use OC\DB\QueryBuilder\QueryFunction; use OCP\DB\QueryBuilder\IQueryFunction; +use Override; class SqliteFunctionBuilder extends FunctionBuilder { - #[\Override] + #[Override] public function concat($x, ...$expr): IQueryFunction { $args = func_get_args(); $list = []; @@ -21,33 +22,38 @@ public function concat($x, ...$expr): IQueryFunction { return new QueryFunction(sprintf('(%s)', implode(' || ', $list))); } - #[\Override] + #[Override] public function groupConcat($expr, ?string $separator = ','): IQueryFunction { $separator = $this->connection->quote($separator); return new QueryFunction('GROUP_CONCAT(' . $this->helper->quoteColumnName($expr) . ', ' . $separator . ')'); } - #[\Override] + #[Override] public function greatest($x, $y): IQueryFunction { return new QueryFunction('MAX(' . $this->helper->quoteColumnName($x) . ', ' . $this->helper->quoteColumnName($y) . ')'); } - #[\Override] + #[Override] public function least($x, $y): IQueryFunction { return new QueryFunction('MIN(' . $this->helper->quoteColumnName($x) . ', ' . $this->helper->quoteColumnName($y) . ')'); } - #[\Override] + #[Override] public function octetLength($field, $alias = ''): IQueryFunction { $alias = $alias ? (' AS ' . $this->helper->quoteColumnName($alias)) : ''; $quotedName = $this->helper->quoteColumnName($field); return new QueryFunction('LENGTH(CAST(' . $quotedName . ' as BLOB))' . $alias); } - #[\Override] + #[Override] public function charLength($field, $alias = ''): IQueryFunction { $alias = $alias ? (' AS ' . $this->helper->quoteColumnName($alias)) : ''; $quotedName = $this->helper->quoteColumnName($field); return new QueryFunction('LENGTH(' . $quotedName . ')' . $alias); } + + #[Override] + public function fromUnixtime(int|string $timestamp): IQueryFunction { + return new QueryFunction('datetime(' . (int)$timestamp . ', "unixepoch")'); + } } diff --git a/lib/public/DB/QueryBuilder/IFunctionBuilder.php b/lib/public/DB/QueryBuilder/IFunctionBuilder.php index 1cabd356defdc..d33f2a716bfff 100644 --- a/lib/public/DB/QueryBuilder/IFunctionBuilder.php +++ b/lib/public/DB/QueryBuilder/IFunctionBuilder.php @@ -196,5 +196,13 @@ public function now(): IQueryFunction; * @return IQueryFunction * @since 35.0.0 */ - public function coalesce(string|ILiteral|IParameter|IQueryFunction $value, string|ILiteral|IParameter|IQueryFunction $default): IQueryFunction; + public function coalesce( + string|ILiteral|IParameter|IQueryFunction $value, + string|ILiteral|IParameter|IQueryFunction $default, + ): IQueryFunction; + + /** + * @since 36.0.0 + */ + public function fromUnixtime(int|string $timestamp): IQueryFunction; } From 87adb8ed3b2a375d1c481a4b9b8d3e899093fb9c Mon Sep 17 00:00:00 2001 From: Benjamin Gaussorgues Date: Fri, 23 Oct 2026 11:31:05 +0200 Subject: [PATCH 2/3] feat(users): create unified user storage table Signed-off-by: Benjamin Gaussorgues --- .../Version36000Date20261005112424.php | 142 ++++++++++++++++++ 1 file changed, 142 insertions(+) create mode 100644 core/Migrations/Version36000Date20261005112424.php diff --git a/core/Migrations/Version36000Date20261005112424.php b/core/Migrations/Version36000Date20261005112424.php new file mode 100644 index 0000000000000..eaaedd3a5ac72 --- /dev/null +++ b/core/Migrations/Version36000Date20261005112424.php @@ -0,0 +1,142 @@ +getDatabasePlatform(); + $caseInsensitiveCollation = match (true) { + $platform instanceof MySQLPlatform => 'utf8mb4_unicode_ci', + $platform instanceof MariaDBPlatform => 'utf8mb4_unicode_ci', + $platform instanceof PostgreSQLPlatform => 'und-x-icu', + $platform instanceof SqlitePlatform => 'NOCASE', + default => null, + }; + + if (!$schema->hasTable('user_backends')) { + $userBackendsTable = $schema->createTable('user_backends'); + $userBackendsTable->addColumn('backend_id', Types::SMALLINT, ['autoincrement' => true, 'unsigned' => true, 'notnull' => true]); + $userBackendsTable->addColumn('backend_name', Types::STRING, [ + 'length' => 64, + 'customSchemaOptions' => [ + 'collation' => $caseInsensitiveCollation, + ], + ]); + $userBackendsTable->addColumn('backend_class', Types::STRING, [ + 'length' => 64, + 'customSchemaOptions' => [ + 'collation' => $caseInsensitiveCollation, + ], + ]); + $userBackendsTable->setPrimaryKey(['backend_id']); + $userBackendsTable->addUniqueIndex(['backend_name']); + $changed = true; + } + + if (!$schema->hasTable('user_accounts')) { + $userAccountsTable = $schema->createTable('user_accounts'); + $userAccountsTable->addColumn('account_id', Types::BIGINT, ['unsigned' => true, 'notnull' => true]); + $userAccountsTable->addColumn('backend_id', Types::SMALLINT, ['unsigned' => true, 'notnull' => true, 'default' => 0]); + $userAccountsTable->addColumn('account_identifier', Types::STRING, [ + 'notnull' => true, + 'length' => 64, + 'customSchemaOptions' => [ + 'collation' => $caseInsensitiveCollation, + ], + ]); + $userAccountsTable->addColumn('account_primary_email', Types::STRING, [ + 'notnull' => true, + 'length' => 64, + 'customSchemaOptions' => [ + 'collation' => $caseInsensitiveCollation, + ], + ]); + $userAccountsTable->addColumn('account_display_name', Types::STRING, [ + 'default' => '', + 'notnull' => true, + 'length' => 64, + 'customSchemaOptions' => [ + 'collation' => $caseInsensitiveCollation, + ], + ]); + $userAccountsTable->addColumn('account_password', Types::BINARY, [ + 'default' => '', + 'notnull' => true, + 'length' => 255, + ]); + $userAccountsTable->addColumn('legacy_uid', Types::STRING, [ + 'default' => '', + 'notnull' => true, + 'length' => 64, + 'customSchemaOptions' => [ + 'collation' => $caseInsensitiveCollation, + ], + ]); + $userAccountsTable->addColumn('account_status', ColumnType::TinyInt, ['unsigned' => true, 'notnull' => true, 'default' => 0]); + $userAccountsTable->addColumn('account_last_seen', ColumnType::TimestampImmutable, [ + 'notnull' => false, + ]); + + $userAccountsTable->setPrimaryKey(['account_id']); + $userAccountsTable->addUniqueIndex(['account_identifier']); + $changed = true; + } + + return $changed ? $schema : null; + } +} From 55fabdd75f18994ce0ab2ae83f3c609f6426e7e9 Mon Sep 17 00:00:00 2001 From: Benjamin Gaussorgues Date: Fri, 23 Oct 2026 11:32:44 +0200 Subject: [PATCH 3/3] feat(users): add migration job to populate new table Signed-off-by: Benjamin Gaussorgues --- core/BackgroundJobs/UserMigrationJob.php | 166 ++++++++++++++++++++ lib/composer/composer/autoload_classmap.php | 8 + lib/composer/composer/autoload_static.php | 8 + lib/private/Repair.php | 2 + lib/private/Repair/AddMoveUsersJob.php | 38 +++++ 5 files changed, 222 insertions(+) create mode 100644 core/BackgroundJobs/UserMigrationJob.php create mode 100644 lib/private/Repair/AddMoveUsersJob.php diff --git a/core/BackgroundJobs/UserMigrationJob.php b/core/BackgroundJobs/UserMigrationJob.php new file mode 100644 index 0000000000000..30606cec4817c --- /dev/null +++ b/core/BackgroundJobs/UserMigrationJob.php @@ -0,0 +1,166 @@ +setTimeSensitivity(self::TIME_INSENSITIVE); + $this->setInterval(3600); + } + + #[Override] + protected function run(mixed $argument): void { + $start = time(); + $workerId = (int)$argument['workerId']; + $nbWorkers = (int)$argument['nbWorkers']; + $this->logger->debug('Start to move users from bucket {bucketId} ({nbBuckets} in total).', [ + 'bucketId' => $workerId, + 'nbWorkers' => $nbWorkers, + ]); + + // Try to move users until none are moved or time elapsed + do { + $this->connection->beginTransaction(); + $nbMoved = $this->moveUsers(0, /*FIXME*/ $workerId, $nbWorkers); + $this->connection->commit(); + } while ($nbMoved > 0 && time() < ($start + 3300)); + } + + private function moveUsers(int $backendId, int $workerId, int $nbWorkers): int { + $qb = $this->connection->getQueryBuilder(); + $results = $qb + ->select( + 'uid', + 'displayname', + 'password', + 'disabled.configvalue AS enabled', + 'email.configvalue AS email', + 'lastseen.configvalue AS lastseen', + ) + ->from('users', 'U') + ->leftJoin( + 'U', + 'preferences', + 'email', + $qb->expr()->andX( + $qb->expr()->eq('email.userid', 'U.uid'), + $qb->expr()->eq('email.appid', $qb->expr()->literal('settings')), + $qb->expr()->eq('email.configkey', $qb->expr()->literal('email')), + ), + ) + ->leftJoin( + 'U', + 'preferences', + 'disabled', + $qb->expr()->andX( + $qb->expr()->eq('disabled.userid', 'U.uid'), + $qb->expr()->eq('disabled.appid', $qb->expr()->literal('core')), + $qb->expr()->eq('disabled.configkey', $qb->expr()->literal('enabled')), + ), + ) + ->leftJoin( + 'U', + 'preferences', + 'lastseen', + $qb->expr()->andX( + $qb->expr()->eq('lastseen.userid', 'U.uid'), + $qb->expr()->eq('lastseen.appid', $qb->expr()->literal('login')), + $qb->expr()->eq('lastseen.configkey', $qb->expr()->literal('lastLogin')), + ), + ) + ->setMaxResults(self::BATCH_SIZE) + ->executeQuery(); + + if ($results->rowCount() === 0) { + // User table is empty + // Remove the job + $this->jobList->removeById($this->getId()); + return 0; + } + + $movedIds = []; + foreach ($results->iterateAssociative() as $user) { + // Should we handle it? + $bucketId = hexdec(hash('xxh32', $user['uid'])) % $nbWorkers; + if ($bucketId !== $workerId) { + continue; + } + try { + $this->copyUser($backendId, $user); + $movedIds[] = $user['uid']; + } catch (Exception $e) { + $this->logger->warning('Fail to move user {user} to new user table: {error}', [ + 'error' => $e->getMessage(), + 'user' => $user['uid'], + 'exception' => $e, + ]); + } + } + + if (count($movedIds) === 0) { + // No matching user to move, but table isn't empty yet. + // Stop job for now + return 0; + } + + // Remove moved users + $qb + ->delete('users') + ->where($qb->expr()->in('uid', $qb->createNamedParameter($movedIds, IQueryBuilder::PARAM_STR_ARRAY))) + ->executeStatement(); + + return count($movedIds); + } + + private function copyUser(int $backendId, array $user): void { + $qb = $this->connection->getQueryBuilder(); + $lastSeen = $user['lastseen'] === null ? $qb->createNamedParameter(null) : $qb->func()->fromUnixtime($user['lastseen']); + + $qb + ->insert('user_accounts') + ->setValue('account_id', $qb->createNamedParameter($this->snowflakeGenerator->nextId())) + ->setValue('backend_id', $qb->createNamedParameter($backendId)) + ->setValue('account_identifier', $qb->createNamedParameter($user['uid'])) + ->setValue('account_primary_email', $qb->createNamedParameter($user['email'] ?? '')) + ->setValue('account_display_name', $qb->createNamedParameter($user['displayname'] ?? $user['uid'])) + ->setValue('account_password', $qb->createNamedParameter($user['password'])) + ->setValue('legacy_uid', $qb->createNamedParameter($user['uid'])) + ->setValue( + 'account_status', + $qb->createNamedParameter($user['enabled'] === 'false' ? UserStatus::DISABLED->value : UserStatus::ENABLED->value), + ) + ->setValue('account_last_seen', $lastSeen) + ->executeStatement(); + } +} diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index f59ac03d74ba0..c17364a878da8 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -81,6 +81,9 @@ 'NCU\\Sharing\\Source\\IShareSourceType' => $baseDir . '/lib/unstable/Sharing/Source/IShareSourceType.php', 'NCU\\Sharing\\Source\\ShareSource' => $baseDir . '/lib/unstable/Sharing/Source/ShareSource.php', 'NCU\\Sharing\\Source\\ShareSourceMetadata' => $baseDir . '/lib/unstable/Sharing/Source/ShareSourceMetadata.php', + 'NCU\\User\\IAccount' => $baseDir . '/lib/unstable/User/IAccount.php', + 'NCU\\User\\IUserBackend' => $baseDir . '/lib/unstable/User/IUserBackend.php', + 'NCU\\User\\UserStatus' => $baseDir . '/lib/unstable/User/UserStatus.php', 'NCU\\WorkflowEngine\\Events\\RegisterRuntimeOperationsEvent' => $baseDir . '/lib/unstable/WorkflowEngine/Events/RegisterRuntimeOperationsEvent.php', 'NCU\\WorkflowEngine\\RuntimeOperation' => $baseDir . '/lib/unstable/WorkflowEngine/RuntimeOperation.php', 'NCU\\WorkflowEngine\\RuntimeScope' => $baseDir . '/lib/unstable/WorkflowEngine/RuntimeScope.php', @@ -1451,6 +1454,7 @@ 'OC\\Core\\BackgroundJobs\\GenerateMetadataJob' => $baseDir . '/core/BackgroundJobs/GenerateMetadataJob.php', 'OC\\Core\\BackgroundJobs\\LookupServerSendCheckBackgroundJob' => $baseDir . '/core/BackgroundJobs/LookupServerSendCheckBackgroundJob.php', 'OC\\Core\\BackgroundJobs\\PreviewMigrationJob' => $baseDir . '/core/BackgroundJobs/PreviewMigrationJob.php', + 'OC\\Core\\BackgroundJobs\\UserMigrationJob' => $baseDir . '/core/BackgroundJobs/UserMigrationJob.php', 'OC\\Core\\Command\\App\\Disable' => $baseDir . '/core/Command/App/Disable.php', 'OC\\Core\\Command\\App\\Enable' => $baseDir . '/core/Command/App/Enable.php', 'OC\\Core\\Command\\App\\GetPath' => $baseDir . '/core/Command/App/GetPath.php', @@ -1758,6 +1762,7 @@ 'OC\\Core\\Migrations\\Version34000Date20260518163022' => $baseDir . '/core/Migrations/Version34000Date20260518163022.php', 'OC\\Core\\Migrations\\Version34000Date20260521110333' => $baseDir . '/core/Migrations/Version34000Date20260521110333.php', 'OC\\Core\\Migrations\\Version35000Date20260527162338' => $baseDir . '/core/Migrations/Version35000Date20260527162338.php', + 'OC\\Core\\Migrations\\Version36000Date20261005112424' => $baseDir . '/core/Migrations/Version36000Date20261005112424.php', 'OC\\Core\\Notification\\CoreNotifier' => $baseDir . '/core/Notification/CoreNotifier.php', 'OC\\Core\\ResponseDefinitions' => $baseDir . '/core/ResponseDefinitions.php', 'OC\\Core\\Service\\CronService' => $baseDir . '/core/Service/CronService.php', @@ -2234,6 +2239,7 @@ 'OC\\Repair\\AddCleanupUpdaterBackupsJob' => $baseDir . '/lib/private/Repair/AddCleanupUpdaterBackupsJob.php', 'OC\\Repair\\AddMetadataGenerationJob' => $baseDir . '/lib/private/Repair/AddMetadataGenerationJob.php', 'OC\\Repair\\AddMovePreviewJob' => $baseDir . '/lib/private/Repair/AddMovePreviewJob.php', + 'OC\\Repair\\AddMoveUsersJob' => $baseDir . '/lib/private/Repair/AddMoveUsersJob.php', 'OC\\Repair\\AddRemoveOldTasksBackgroundJob' => $baseDir . '/lib/private/Repair/AddRemoveOldTasksBackgroundJob.php', 'OC\\Repair\\CleanTags' => $baseDir . '/lib/private/Repair/CleanTags.php', 'OC\\Repair\\CleanUpAbandonedApps' => $baseDir . '/lib/private/Repair/CleanUpAbandonedApps.php', @@ -2457,11 +2463,13 @@ 'OC\\Updater\\VersionCheck' => $baseDir . '/lib/private/Updater/VersionCheck.php', 'OC\\UserStatus\\ISettableProvider' => $baseDir . '/lib/private/UserStatus/ISettableProvider.php', 'OC\\UserStatus\\Manager' => $baseDir . '/lib/private/UserStatus/Manager.php', + 'OC\\User\\Account' => $baseDir . '/lib/private/User/Account.php', 'OC\\User\\AvailabilityCoordinator' => $baseDir . '/lib/private/User/AvailabilityCoordinator.php', 'OC\\User\\Backend' => $baseDir . '/lib/private/User/Backend.php', 'OC\\User\\BackgroundJobs\\CleanupDeletedUsers' => $baseDir . '/lib/private/User/BackgroundJobs/CleanupDeletedUsers.php', 'OC\\User\\BackgroundJobs\\CleanupLoginTokens' => $baseDir . '/lib/private/User/BackgroundJobs/CleanupLoginTokens.php', 'OC\\User\\Database' => $baseDir . '/lib/private/User/Database.php', + 'OC\\User\\Db\\AccountRepository' => $baseDir . '/lib/private/User/Db/AccountRepository.php', 'OC\\User\\DisabledUserException' => $baseDir . '/lib/private/User/DisabledUserException.php', 'OC\\User\\DisplayNameCache' => $baseDir . '/lib/private/User/DisplayNameCache.php', 'OC\\User\\LastInteractiveLogin' => $baseDir . '/lib/private/User/LastInteractiveLogin.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index 05c5fc681930b..1ad6d9cb0c82b 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -122,6 +122,9 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'NCU\\Sharing\\Source\\IShareSourceType' => __DIR__ . '/../../..' . '/lib/unstable/Sharing/Source/IShareSourceType.php', 'NCU\\Sharing\\Source\\ShareSource' => __DIR__ . '/../../..' . '/lib/unstable/Sharing/Source/ShareSource.php', 'NCU\\Sharing\\Source\\ShareSourceMetadata' => __DIR__ . '/../../..' . '/lib/unstable/Sharing/Source/ShareSourceMetadata.php', + 'NCU\\User\\IAccount' => __DIR__ . '/../../..' . '/lib/unstable/User/IAccount.php', + 'NCU\\User\\IUserBackend' => __DIR__ . '/../../..' . '/lib/unstable/User/IUserBackend.php', + 'NCU\\User\\UserStatus' => __DIR__ . '/../../..' . '/lib/unstable/User/UserStatus.php', 'NCU\\WorkflowEngine\\Events\\RegisterRuntimeOperationsEvent' => __DIR__ . '/../../..' . '/lib/unstable/WorkflowEngine/Events/RegisterRuntimeOperationsEvent.php', 'NCU\\WorkflowEngine\\RuntimeOperation' => __DIR__ . '/../../..' . '/lib/unstable/WorkflowEngine/RuntimeOperation.php', 'NCU\\WorkflowEngine\\RuntimeScope' => __DIR__ . '/../../..' . '/lib/unstable/WorkflowEngine/RuntimeScope.php', @@ -1492,6 +1495,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'OC\\Core\\BackgroundJobs\\GenerateMetadataJob' => __DIR__ . '/../../..' . '/core/BackgroundJobs/GenerateMetadataJob.php', 'OC\\Core\\BackgroundJobs\\LookupServerSendCheckBackgroundJob' => __DIR__ . '/../../..' . '/core/BackgroundJobs/LookupServerSendCheckBackgroundJob.php', 'OC\\Core\\BackgroundJobs\\PreviewMigrationJob' => __DIR__ . '/../../..' . '/core/BackgroundJobs/PreviewMigrationJob.php', + 'OC\\Core\\BackgroundJobs\\UserMigrationJob' => __DIR__ . '/../../..' . '/core/BackgroundJobs/UserMigrationJob.php', 'OC\\Core\\Command\\App\\Disable' => __DIR__ . '/../../..' . '/core/Command/App/Disable.php', 'OC\\Core\\Command\\App\\Enable' => __DIR__ . '/../../..' . '/core/Command/App/Enable.php', 'OC\\Core\\Command\\App\\GetPath' => __DIR__ . '/../../..' . '/core/Command/App/GetPath.php', @@ -1799,6 +1803,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'OC\\Core\\Migrations\\Version34000Date20260518163022' => __DIR__ . '/../../..' . '/core/Migrations/Version34000Date20260518163022.php', 'OC\\Core\\Migrations\\Version34000Date20260521110333' => __DIR__ . '/../../..' . '/core/Migrations/Version34000Date20260521110333.php', 'OC\\Core\\Migrations\\Version35000Date20260527162338' => __DIR__ . '/../../..' . '/core/Migrations/Version35000Date20260527162338.php', + 'OC\\Core\\Migrations\\Version36000Date20261005112424' => __DIR__ . '/../../..' . '/core/Migrations/Version36000Date20261005112424.php', 'OC\\Core\\Notification\\CoreNotifier' => __DIR__ . '/../../..' . '/core/Notification/CoreNotifier.php', 'OC\\Core\\ResponseDefinitions' => __DIR__ . '/../../..' . '/core/ResponseDefinitions.php', 'OC\\Core\\Service\\CronService' => __DIR__ . '/../../..' . '/core/Service/CronService.php', @@ -2275,6 +2280,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'OC\\Repair\\AddCleanupUpdaterBackupsJob' => __DIR__ . '/../../..' . '/lib/private/Repair/AddCleanupUpdaterBackupsJob.php', 'OC\\Repair\\AddMetadataGenerationJob' => __DIR__ . '/../../..' . '/lib/private/Repair/AddMetadataGenerationJob.php', 'OC\\Repair\\AddMovePreviewJob' => __DIR__ . '/../../..' . '/lib/private/Repair/AddMovePreviewJob.php', + 'OC\\Repair\\AddMoveUsersJob' => __DIR__ . '/../../..' . '/lib/private/Repair/AddMoveUsersJob.php', 'OC\\Repair\\AddRemoveOldTasksBackgroundJob' => __DIR__ . '/../../..' . '/lib/private/Repair/AddRemoveOldTasksBackgroundJob.php', 'OC\\Repair\\CleanTags' => __DIR__ . '/../../..' . '/lib/private/Repair/CleanTags.php', 'OC\\Repair\\CleanUpAbandonedApps' => __DIR__ . '/../../..' . '/lib/private/Repair/CleanUpAbandonedApps.php', @@ -2498,11 +2504,13 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'OC\\Updater\\VersionCheck' => __DIR__ . '/../../..' . '/lib/private/Updater/VersionCheck.php', 'OC\\UserStatus\\ISettableProvider' => __DIR__ . '/../../..' . '/lib/private/UserStatus/ISettableProvider.php', 'OC\\UserStatus\\Manager' => __DIR__ . '/../../..' . '/lib/private/UserStatus/Manager.php', + 'OC\\User\\Account' => __DIR__ . '/../../..' . '/lib/private/User/Account.php', 'OC\\User\\AvailabilityCoordinator' => __DIR__ . '/../../..' . '/lib/private/User/AvailabilityCoordinator.php', 'OC\\User\\Backend' => __DIR__ . '/../../..' . '/lib/private/User/Backend.php', 'OC\\User\\BackgroundJobs\\CleanupDeletedUsers' => __DIR__ . '/../../..' . '/lib/private/User/BackgroundJobs/CleanupDeletedUsers.php', 'OC\\User\\BackgroundJobs\\CleanupLoginTokens' => __DIR__ . '/../../..' . '/lib/private/User/BackgroundJobs/CleanupLoginTokens.php', 'OC\\User\\Database' => __DIR__ . '/../../..' . '/lib/private/User/Database.php', + 'OC\\User\\Db\\AccountRepository' => __DIR__ . '/../../..' . '/lib/private/User/Db/AccountRepository.php', 'OC\\User\\DisabledUserException' => __DIR__ . '/../../..' . '/lib/private/User/DisabledUserException.php', 'OC\\User\\DisplayNameCache' => __DIR__ . '/../../..' . '/lib/private/User/DisplayNameCache.php', 'OC\\User\\LastInteractiveLogin' => __DIR__ . '/../../..' . '/lib/private/User/LastInteractiveLogin.php', diff --git a/lib/private/Repair.php b/lib/private/Repair.php index 20349267b005d..391fc668c7e7c 100644 --- a/lib/private/Repair.php +++ b/lib/private/Repair.php @@ -16,6 +16,7 @@ use OC\Repair\AddCleanupUpdaterBackupsJob; use OC\Repair\AddMetadataGenerationJob; use OC\Repair\AddMovePreviewJob; +use OC\Repair\AddMoveUsersJob; use OC\Repair\AddRemoveOldTasksBackgroundJob; use OC\Repair\CleanTags; use OC\Repair\CleanUpAbandonedApps; @@ -199,6 +200,7 @@ public static function getRepairSteps(bool $includeExpensive = false): array { Server::get(AddCleanupLoginTokens::class), Server::get(SanitizeAccountProperties::class), Server::get(AddMovePreviewJob::class), + Server::get(AddMoveUsersJob::class), Server::get(ConfigKeyMigration::class), Server::get(AddCleanupBackgroundJobsJob::class), Server::get(AddCheckCodeIntegrityJob::class), diff --git a/lib/private/Repair/AddMoveUsersJob.php b/lib/private/Repair/AddMoveUsersJob.php new file mode 100644 index 0000000000000..329224068707e --- /dev/null +++ b/lib/private/Repair/AddMoveUsersJob.php @@ -0,0 +1,38 @@ +jobList->add(UserMigrationJob::class, ['workerId' => $i, 'nbWorkers' => $nbWorkers]); + } + } +}