From 0249e3a2f5a97769d4fd9b3ecced5c8b2feadb77 Mon Sep 17 00:00:00 2001 From: Maxence Lange Date: Tue, 16 Sep 2025 10:46:45 -0100 Subject: [PATCH 1/3] fix(userconfig): set 'mail' as indexed Signed-off-by: Maxence Lange --- .../composer/composer/autoload_classmap.php | 1 + .../composer/composer/autoload_static.php | 1 + apps/settings/lib/AppInfo/Application.php | 3 + apps/settings/lib/ConfigLexicon.php | 38 +++++++++ lib/private/App/AppManager.php | 1 + lib/private/Config/ConfigManager.php | 15 ++++ lib/private/Config/UserConfig.php | 81 ++++++++++++------- lib/private/Repair/ConfigKeyMigration.php | 1 + 8 files changed, 111 insertions(+), 30 deletions(-) create mode 100644 apps/settings/lib/ConfigLexicon.php diff --git a/apps/settings/composer/composer/autoload_classmap.php b/apps/settings/composer/composer/autoload_classmap.php index bb63026da77..b12f345e05b 100644 --- a/apps/settings/composer/composer/autoload_classmap.php +++ b/apps/settings/composer/composer/autoload_classmap.php @@ -19,6 +19,7 @@ return array( 'OCA\\Settings\\Command\\AdminDelegation\\Add' => $baseDir . '/../lib/Command/AdminDelegation/Add.php', 'OCA\\Settings\\Command\\AdminDelegation\\Remove' => $baseDir . '/../lib/Command/AdminDelegation/Remove.php', 'OCA\\Settings\\Command\\AdminDelegation\\Show' => $baseDir . '/../lib/Command/AdminDelegation/Show.php', + 'OCA\\Settings\\ConfigLexicon' => $baseDir . '/../lib/ConfigLexicon.php', 'OCA\\Settings\\Controller\\AISettingsController' => $baseDir . '/../lib/Controller/AISettingsController.php', 'OCA\\Settings\\Controller\\AdminSettingsController' => $baseDir . '/../lib/Controller/AdminSettingsController.php', 'OCA\\Settings\\Controller\\AppSettingsController' => $baseDir . '/../lib/Controller/AppSettingsController.php', diff --git a/apps/settings/composer/composer/autoload_static.php b/apps/settings/composer/composer/autoload_static.php index cca48b409ad..e6e2ed9f45c 100644 --- a/apps/settings/composer/composer/autoload_static.php +++ b/apps/settings/composer/composer/autoload_static.php @@ -34,6 +34,7 @@ class ComposerStaticInitSettings 'OCA\\Settings\\Command\\AdminDelegation\\Add' => __DIR__ . '/..' . '/../lib/Command/AdminDelegation/Add.php', 'OCA\\Settings\\Command\\AdminDelegation\\Remove' => __DIR__ . '/..' . '/../lib/Command/AdminDelegation/Remove.php', 'OCA\\Settings\\Command\\AdminDelegation\\Show' => __DIR__ . '/..' . '/../lib/Command/AdminDelegation/Show.php', + 'OCA\\Settings\\ConfigLexicon' => __DIR__ . '/..' . '/../lib/ConfigLexicon.php', 'OCA\\Settings\\Controller\\AISettingsController' => __DIR__ . '/..' . '/../lib/Controller/AISettingsController.php', 'OCA\\Settings\\Controller\\AdminSettingsController' => __DIR__ . '/..' . '/../lib/Controller/AdminSettingsController.php', 'OCA\\Settings\\Controller\\AppSettingsController' => __DIR__ . '/..' . '/../lib/Controller/AppSettingsController.php', diff --git a/apps/settings/lib/AppInfo/Application.php b/apps/settings/lib/AppInfo/Application.php index 6e59e56fe82..de007a6978f 100644 --- a/apps/settings/lib/AppInfo/Application.php +++ b/apps/settings/lib/AppInfo/Application.php @@ -12,6 +12,7 @@ use OC\AppFramework\Utility\TimeFactory; use OC\Authentication\Events\AppPasswordCreatedEvent; use OC\Authentication\Token\IProvider; use OC\Server; +use OCA\Settings\ConfigLexicon; use OCA\Settings\Hooks; use OCA\Settings\Listener\AppPasswordCreatedActivityListener; use OCA\Settings\Listener\GroupRemovedListener; @@ -112,6 +113,8 @@ class Application extends App implements IBootstrap { $context->registerSearchProvider(AppSearch::class); $context->registerSearchProvider(UserSearch::class); + $context->registerConfigLexicon(ConfigLexicon::class); + // Register listeners $context->registerEventListener(AppPasswordCreatedEvent::class, AppPasswordCreatedActivityListener::class); $context->registerEventListener(UserAddedEvent::class, UserAddedToGroupActivityListener::class); diff --git a/apps/settings/lib/ConfigLexicon.php b/apps/settings/lib/ConfigLexicon.php new file mode 100644 index 00000000000..8d2035dbc73 --- /dev/null +++ b/apps/settings/lib/ConfigLexicon.php @@ -0,0 +1,38 @@ +configManager->migrateConfigLexiconKeys($appId); + $this->configManager->updateLexiconEntries($appId); $this->dispatcher->dispatchTyped(new AppUpdateEvent($appId)); $this->dispatcher->dispatch(ManagerEvent::EVENT_APP_UPDATE, new ManagerEvent( diff --git a/lib/private/Config/ConfigManager.php b/lib/private/Config/ConfigManager.php index 28397402249..7c763e2ae37 100644 --- a/lib/private/Config/ConfigManager.php +++ b/lib/private/Config/ConfigManager.php @@ -82,6 +82,21 @@ class ConfigManager { $this->userConfig->ignoreLexiconAliases(false); } + /** + * Upgrade stored data in case of changes in the lexicon. + * Heavy process to be executed on core and app upgrade. + * + * - upgrade UserConfig entries if set as indexed + */ + public function updateLexiconEntries(string $appId): void { + $this->loadConfigServices(); + $lexicon = $this->userConfig->getConfigDetailsFromLexicon($appId); + foreach ($lexicon['entries'] as $entry) { + // upgrade based on index flag + $this->userConfig->updateGlobalIndexed($appId, $entry->getKey(), $entry->isFlagged(UserConfig::FLAG_INDEXED)); + } + } + /** * config services cannot be load at __construct() or install will fail */ diff --git a/lib/private/Config/UserConfig.php b/lib/private/Config/UserConfig.php index d0d19561e2d..bf86cfa493d 100644 --- a/lib/private/Config/UserConfig.php +++ b/lib/private/Config/UserConfig.php @@ -477,40 +477,55 @@ class UserConfig implements IUserConfig { $this->assertParams('', $app, $key, allowEmptyUser: true); $this->matchAndApplyLexiconDefinition('', $app, $key); + $lexiconEntry = $this->getLexiconEntry($app, $key); + if ($lexiconEntry?->isFlagged(self::FLAG_INDEXED) === false) { + $this->logger->notice('UserConfig+Lexicon: using searchUsersByTypedValue on a config key not set as indexed'); + } + $qb = $this->connection->getQueryBuilder(); $qb->from('preferences'); $qb->select('userid'); $qb->where($qb->expr()->eq('appid', $qb->createNamedParameter($app))); $qb->andWhere($qb->expr()->eq('configkey', $qb->createNamedParameter($key))); - // search within 'indexed' OR 'configvalue' only if 'flags' is set as not indexed - // TODO: when implementing config lexicon remove the searches on 'configvalue' if value is set as indexed $configValueColumn = ($this->connection->getDatabaseProvider() === IDBConnection::PLATFORM_ORACLE) ? $qb->expr()->castColumn('configvalue', IQueryBuilder::PARAM_STR) : 'configvalue'; if (is_array($value)) { - $where = $qb->expr()->orX( - $qb->expr()->in('indexed', $qb->createNamedParameter($value, IQueryBuilder::PARAM_STR_ARRAY)), - $qb->expr()->andX( - $qb->expr()->neq($qb->expr()->bitwiseAnd('flags', self::FLAG_INDEXED), $qb->createNamedParameter(self::FLAG_INDEXED, IQueryBuilder::PARAM_INT)), - $qb->expr()->in($configValueColumn, $qb->createNamedParameter($value, IQueryBuilder::PARAM_STR_ARRAY)) - ) - ); + $where = $qb->expr()->in('indexed', $qb->createNamedParameter($value, IQueryBuilder::PARAM_STR_ARRAY)); + // in case lexicon does not exist for this key - or is not set as indexed - we keep searching for non-index entries if 'flags' is set as not indexed + if ($lexiconEntry?->isFlagged(self::FLAG_INDEXED) !== true) { + $where = $qb->expr()->orX( + $where, + $qb->expr()->andX( + $qb->expr()->neq($qb->expr()->bitwiseAnd('flags', self::FLAG_INDEXED), $qb->createNamedParameter(self::FLAG_INDEXED, IQueryBuilder::PARAM_INT)), + $qb->expr()->in($configValueColumn, $qb->createNamedParameter($value, IQueryBuilder::PARAM_STR_ARRAY)) + ) + ); + } } else { if ($caseInsensitive) { - $where = $qb->expr()->orX( - $qb->expr()->eq($qb->func()->lower('indexed'), $qb->createNamedParameter(strtolower($value))), - $qb->expr()->andX( - $qb->expr()->neq($qb->expr()->bitwiseAnd('flags', self::FLAG_INDEXED), $qb->createNamedParameter(self::FLAG_INDEXED, IQueryBuilder::PARAM_INT)), - $qb->expr()->eq($qb->func()->lower($configValueColumn), $qb->createNamedParameter(strtolower($value))) - ) - ); + $where = $qb->expr()->eq($qb->func()->lower('indexed'), $qb->createNamedParameter(strtolower($value))); + // in case lexicon does not exist for this key - or is not set as indexed - we keep searching for non-index entries if 'flags' is set as not indexed + if ($lexiconEntry?->isFlagged(self::FLAG_INDEXED) !== true) { + $where = $qb->expr()->orX( + $where, + $qb->expr()->andX( + $qb->expr()->neq($qb->expr()->bitwiseAnd('flags', self::FLAG_INDEXED), $qb->createNamedParameter(self::FLAG_INDEXED, IQueryBuilder::PARAM_INT)), + $qb->expr()->eq($qb->func()->lower($configValueColumn), $qb->createNamedParameter(strtolower($value))) + ) + ); + } } else { - $where = $qb->expr()->orX( - $qb->expr()->eq('indexed', $qb->createNamedParameter($value)), - $qb->expr()->andX( - $qb->expr()->neq($qb->expr()->bitwiseAnd('flags', self::FLAG_INDEXED), $qb->createNamedParameter(self::FLAG_INDEXED, IQueryBuilder::PARAM_INT)), - $qb->expr()->eq($configValueColumn, $qb->createNamedParameter($value)) - ) - ); + $where = $qb->expr()->eq('indexed', $qb->createNamedParameter($value)); + // in case lexicon does not exist for this key - or is not set as indexed - we keep searching for non-index entries if 'flags' is set as not indexed + if ($lexiconEntry?->isFlagged(self::FLAG_INDEXED) !== true) { + $where = $qb->expr()->orX( + $where, + $qb->expr()->andX( + $qb->expr()->neq($qb->expr()->bitwiseAnd('flags', self::FLAG_INDEXED), $qb->createNamedParameter(self::FLAG_INDEXED, IQueryBuilder::PARAM_INT)), + $qb->expr()->eq($configValueColumn, $qb->createNamedParameter($value)) + ) + ); + } } } @@ -1408,13 +1423,19 @@ class UserConfig implements IUserConfig { $this->assertParams('', $app, $key, allowEmptyUser: true); $this->matchAndApplyLexiconDefinition('', $app, $key); - foreach (array_keys($this->getValuesByUsers($app, $key)) as $userId) { - try { - $this->updateIndexed($userId, $app, $key, $indexed); - } catch (UnknownKeyException) { - // should not happen and can be ignored - } - } + $bitPosition = log(self::FLAG_INDEXED) / log(2); // emulate base-2 logarithm (log2) + $bitOperation = ($indexed) ? '`flags` | (1 << ' . $bitPosition . ')' : '`flags` & ~(1 << ' . $bitPosition . ')'; + + $update = $this->connection->getQueryBuilder(); + $update->update('preferences') + ->set('flags', $update->createFunction($bitOperation)) + ->set('indexed', ($indexed) ? 'configvalue' : $update->createNamedParameter('')) + ->where( + $update->expr()->eq('appid', $update->createNamedParameter($app)), + $update->expr()->eq('configkey', $update->createNamedParameter($key)) + ); + + $update->executeStatement(); // we clear all cache $this->clearCacheAll(); diff --git a/lib/private/Repair/ConfigKeyMigration.php b/lib/private/Repair/ConfigKeyMigration.php index da4aa153dc5..dcca43d65df 100644 --- a/lib/private/Repair/ConfigKeyMigration.php +++ b/lib/private/Repair/ConfigKeyMigration.php @@ -25,5 +25,6 @@ class ConfigKeyMigration implements IRepairStep { public function run(IOutput $output) { $this->configManager->migrateConfigLexiconKeys(); + $this->configManager->updateLexiconEntries('core'); } } From 70dd0de0ff414d04cb25a0abd4630c713ee14883 Mon Sep 17 00:00:00 2001 From: Maxence Lange Date: Tue, 16 Sep 2025 12:52:27 -0100 Subject: [PATCH 2/3] fix(userconfig): using api bit functions Signed-off-by: Maxence Lange --- apps/settings/lib/ConfigLexicon.php | 6 +-- lib/private/Config/ConfigManager.php | 2 +- lib/private/Config/UserConfig.php | 22 ++++++-- tests/lib/Config/LexiconTest.php | 50 +++++++++++++++++++ tests/lib/Config/TestLexicon_UserIndexed.php | 33 ++++++++++++ .../Config/TestLexicon_UserIndexedRemove.php | 32 ++++++++++++ 6 files changed, 136 insertions(+), 9 deletions(-) create mode 100644 tests/lib/Config/TestLexicon_UserIndexed.php create mode 100644 tests/lib/Config/TestLexicon_UserIndexedRemove.php diff --git a/apps/settings/lib/ConfigLexicon.php b/apps/settings/lib/ConfigLexicon.php index 8d2035dbc73..dc898c78b4a 100644 --- a/apps/settings/lib/ConfigLexicon.php +++ b/apps/settings/lib/ConfigLexicon.php @@ -8,7 +8,7 @@ declare(strict_types=1); */ namespace OCA\Settings; -use OC\Config\UserConfig; +use OCP\Config\IUserConfig; use OCP\Config\Lexicon\Entry; use OCP\Config\Lexicon\ILexicon; use OCP\Config\Lexicon\Strictness; @@ -20,7 +20,7 @@ use OCP\Config\ValueType; * Please Add & Manage your Config Keys in that file and keep the Lexicon up to date! */ class ConfigLexicon implements ILexicon { - public const USER_SETTINGS_MAIL = 'mail'; + public const USER_SETTINGS_EMAIL = 'email'; public function getStrictness(): Strictness { return Strictness::IGNORE; @@ -32,7 +32,7 @@ class ConfigLexicon implements ILexicon { public function getUserConfigs(): array { return [ - new Entry(key: self::USER_SETTINGS_MAIL, type: ValueType::STRING, defaultRaw: '', definition: 'account mail address', flags: UserConfig::FLAG_INDEXED), + new Entry(key: self::USER_SETTINGS_EMAIL, type: ValueType::STRING, defaultRaw: '', definition: 'account mail address', flags: IUserConfig::FLAG_INDEXED), ]; } } diff --git a/lib/private/Config/ConfigManager.php b/lib/private/Config/ConfigManager.php index 7c763e2ae37..f5ef024578c 100644 --- a/lib/private/Config/ConfigManager.php +++ b/lib/private/Config/ConfigManager.php @@ -93,7 +93,7 @@ class ConfigManager { $lexicon = $this->userConfig->getConfigDetailsFromLexicon($appId); foreach ($lexicon['entries'] as $entry) { // upgrade based on index flag - $this->userConfig->updateGlobalIndexed($appId, $entry->getKey(), $entry->isFlagged(UserConfig::FLAG_INDEXED)); + $this->userConfig->updateGlobalIndexed($appId, $entry->getKey(), $entry->isFlagged(IUserConfig::FLAG_INDEXED)); } } diff --git a/lib/private/Config/UserConfig.php b/lib/private/Config/UserConfig.php index bf86cfa493d..8db6258d52a 100644 --- a/lib/private/Config/UserConfig.php +++ b/lib/private/Config/UserConfig.php @@ -479,7 +479,7 @@ class UserConfig implements IUserConfig { $lexiconEntry = $this->getLexiconEntry($app, $key); if ($lexiconEntry?->isFlagged(self::FLAG_INDEXED) === false) { - $this->logger->notice('UserConfig+Lexicon: using searchUsersByTypedValue on a config key not set as indexed'); + $this->logger->notice('UserConfig+Lexicon: using searchUsersByTypedValue on config key ' . $app . '/' . $key . ' which is not set as indexed'); } $qb = $this->connection->getQueryBuilder(); @@ -1423,18 +1423,30 @@ class UserConfig implements IUserConfig { $this->assertParams('', $app, $key, allowEmptyUser: true); $this->matchAndApplyLexiconDefinition('', $app, $key); - $bitPosition = log(self::FLAG_INDEXED) / log(2); // emulate base-2 logarithm (log2) - $bitOperation = ($indexed) ? '`flags` | (1 << ' . $bitPosition . ')' : '`flags` & ~(1 << ' . $bitPosition . ')'; - $update = $this->connection->getQueryBuilder(); $update->update('preferences') - ->set('flags', $update->createFunction($bitOperation)) + // emptying field 'indexed' if key is not set as indexed anymore ->set('indexed', ($indexed) ? 'configvalue' : $update->createNamedParameter('')) ->where( $update->expr()->eq('appid', $update->createNamedParameter($app)), $update->expr()->eq('configkey', $update->createNamedParameter($key)) ); + // switching flags 'indexed' on and off is about adding/removing the bit value on the correct entries + if ($indexed) { + $update->set('flags', $update->func()->add('flags', $update->createNamedParameter(self::FLAG_INDEXED, IQueryBuilder::PARAM_INT))); + $update->andWhere( + $update->expr()->neq($update->expr()->castColumn( + $update->expr()->bitwiseAnd('flags', self::FLAG_INDEXED), IQueryBuilder::PARAM_INT), $update->createNamedParameter(self::FLAG_INDEXED, IQueryBuilder::PARAM_INT) + )); + } else { + $update->set('flags', $update->func()->subtract('flags', $update->createNamedParameter(self::FLAG_INDEXED, IQueryBuilder::PARAM_INT))); + $update->andWhere( + $update->expr()->eq($update->expr()->castColumn( + $update->expr()->bitwiseAnd('flags', self::FLAG_INDEXED), IQueryBuilder::PARAM_INT), $update->createNamedParameter(self::FLAG_INDEXED, IQueryBuilder::PARAM_INT) + )); + } + $update->executeStatement(); // we clear all cache diff --git a/tests/lib/Config/LexiconTest.php b/tests/lib/Config/LexiconTest.php index 3f14721dd6e..b422588f662 100644 --- a/tests/lib/Config/LexiconTest.php +++ b/tests/lib/Config/LexiconTest.php @@ -60,11 +60,15 @@ class LexiconTest extends TestCase { $this->appConfig->deleteApp(TestLexicon_N::APPID); $this->appConfig->deleteApp(TestLexicon_W::APPID); $this->appConfig->deleteApp(TestLexicon_E::APPID); + $this->appConfig->deleteApp(TestLexicon_UserIndexed::APPID); + $this->appConfig->deleteApp(TestLexicon_UserIndexedRemove::APPID); $this->userConfig->deleteApp(TestConfigLexicon_I::APPID); $this->userConfig->deleteApp(TestLexicon_N::APPID); $this->userConfig->deleteApp(TestLexicon_W::APPID); $this->userConfig->deleteApp(TestLexicon_E::APPID); + $this->userConfig->deleteApp(TestLexicon_UserIndexed::APPID); + $this->userConfig->deleteApp(TestLexicon_UserIndexedRemove::APPID); } public function testAppLexiconSetCorrect() { @@ -234,4 +238,50 @@ class LexiconTest extends TestCase { $this->presetManager->setLexiconPreset(Preset::FAMILY); $this->assertSame('family', $this->userConfig->getValueString('user1', TestLexicon_E::APPID, 'key3')); } + + public function testLexiconIndexedUpdate() { + $this->userConfig->setValueString('user1', TestLexicon_UserIndexed::APPID, 'key1', 'abcd'); + $this->userConfig->setValueString('user2', TestLexicon_UserIndexed::APPID, 'key1', '1234', flags: 64); + $this->userConfig->setValueString('user3', TestLexicon_UserIndexed::APPID, 'key1', 'qwer', flags: IUserConfig::FLAG_INDEXED); + $this->userConfig->setValueString('user4', TestLexicon_UserIndexed::APPID, 'key1', 'uiop', flags: 64 | IUserConfig::FLAG_INDEXED); + + $bootstrapCoordinator = Server::get(Coordinator::class); + $bootstrapCoordinator->getRegistrationContext()?->registerConfigLexicon(TestLexicon_UserIndexed::APPID, TestLexicon_UserIndexed::class); + $this->userConfig->clearCacheAll(); + + $this->configManager->updateLexiconEntries(TestLexicon_UserIndexed::APPID); + + $this->assertTrue($this->userConfig->isIndexed('user1', TestLexicon_UserIndexed::APPID, 'key1')); + $this->assertTrue($this->userConfig->isIndexed('user2', TestLexicon_UserIndexed::APPID, 'key1')); + $this->assertTrue($this->userConfig->isIndexed('user3', TestLexicon_UserIndexed::APPID, 'key1')); + $this->assertTrue($this->userConfig->isIndexed('user4', TestLexicon_UserIndexed::APPID, 'key1')); + + $this->assertSame(2, $this->userConfig->getValueFlags('user1', TestLexicon_UserIndexed::APPID, 'key1')); + $this->assertSame(66, $this->userConfig->getValueFlags('user2', TestLexicon_UserIndexed::APPID, 'key1')); + $this->assertSame(2, $this->userConfig->getValueFlags('user3', TestLexicon_UserIndexed::APPID, 'key1')); + $this->assertSame(66, $this->userConfig->getValueFlags('user4', TestLexicon_UserIndexed::APPID, 'key1')); + } + + public function testLexiconIndexedUpdateRemove() { + $this->userConfig->setValueString('user1', TestLexicon_UserIndexedRemove::APPID, 'key1', 'abcd'); + $this->userConfig->setValueString('user2', TestLexicon_UserIndexedRemove::APPID, 'key1', '1234', flags: 64); + $this->userConfig->setValueString('user3', TestLexicon_UserIndexedRemove::APPID, 'key1', 'qwer', flags: IUserConfig::FLAG_INDEXED); + $this->userConfig->setValueString('user4', TestLexicon_UserIndexedRemove::APPID, 'key1', 'uiop', flags: 64 | IUserConfig::FLAG_INDEXED); + + $bootstrapCoordinator = Server::get(Coordinator::class); + $bootstrapCoordinator->getRegistrationContext()?->registerConfigLexicon(TestLexicon_UserIndexedRemove::APPID, TestLexicon_UserIndexedRemove::class); + $this->userConfig->clearCacheAll(); + + $this->configManager->updateLexiconEntries(TestLexicon_UserIndexedRemove::APPID); + + $this->assertFalse($this->userConfig->isIndexed('user1', TestLexicon_UserIndexedRemove::APPID, 'key1')); + $this->assertFalse($this->userConfig->isIndexed('user2', TestLexicon_UserIndexedRemove::APPID, 'key1')); + $this->assertFalse($this->userConfig->isIndexed('user3', TestLexicon_UserIndexedRemove::APPID, 'key1')); + $this->assertFalse($this->userConfig->isIndexed('user4', TestLexicon_UserIndexedRemove::APPID, 'key1')); + + $this->assertSame(0, $this->userConfig->getValueFlags('user1', TestLexicon_UserIndexedRemove::APPID, 'key1')); + $this->assertSame(64, $this->userConfig->getValueFlags('user2', TestLexicon_UserIndexedRemove::APPID, 'key1')); + $this->assertSame(0, $this->userConfig->getValueFlags('user3', TestLexicon_UserIndexedRemove::APPID, 'key1')); + $this->assertSame(64, $this->userConfig->getValueFlags('user4', TestLexicon_UserIndexedRemove::APPID, 'key1')); + } } diff --git a/tests/lib/Config/TestLexicon_UserIndexed.php b/tests/lib/Config/TestLexicon_UserIndexed.php new file mode 100644 index 00000000000..07107db4ec4 --- /dev/null +++ b/tests/lib/Config/TestLexicon_UserIndexed.php @@ -0,0 +1,33 @@ + Date: Wed, 17 Sep 2025 12:33:04 -0100 Subject: [PATCH 3/3] fix(userconfig): crop configvalue at 64 before index Signed-off-by: Maxence Lange --- lib/private/Config/UserConfig.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/private/Config/UserConfig.php b/lib/private/Config/UserConfig.php index 8db6258d52a..05018adc3f8 100644 --- a/lib/private/Config/UserConfig.php +++ b/lib/private/Config/UserConfig.php @@ -1425,8 +1425,6 @@ class UserConfig implements IUserConfig { $update = $this->connection->getQueryBuilder(); $update->update('preferences') - // emptying field 'indexed' if key is not set as indexed anymore - ->set('indexed', ($indexed) ? 'configvalue' : $update->createNamedParameter('')) ->where( $update->expr()->eq('appid', $update->createNamedParameter($app)), $update->expr()->eq('configkey', $update->createNamedParameter($key)) @@ -1434,12 +1432,15 @@ class UserConfig implements IUserConfig { // switching flags 'indexed' on and off is about adding/removing the bit value on the correct entries if ($indexed) { + $update->set('indexed', $update->func()->substring('configvalue', $update->createNamedParameter(1, IQueryBuilder::PARAM_INT), $update->createNamedParameter(64, IQueryBuilder::PARAM_INT))); $update->set('flags', $update->func()->add('flags', $update->createNamedParameter(self::FLAG_INDEXED, IQueryBuilder::PARAM_INT))); $update->andWhere( $update->expr()->neq($update->expr()->castColumn( $update->expr()->bitwiseAnd('flags', self::FLAG_INDEXED), IQueryBuilder::PARAM_INT), $update->createNamedParameter(self::FLAG_INDEXED, IQueryBuilder::PARAM_INT) )); } else { + // emptying field 'indexed' if key is not set as indexed anymore + $update->set('indexed', $update->createNamedParameter('')); $update->set('flags', $update->func()->subtract('flags', $update->createNamedParameter(self::FLAG_INDEXED, IQueryBuilder::PARAM_INT))); $update->andWhere( $update->expr()->eq($update->expr()->castColumn(