From ce5964cd589d226de6772c7aee569d0bb351c878 Mon Sep 17 00:00:00 2001 From: Maxence Lange Date: Tue, 16 Sep 2025 12:52:27 -0100 Subject: [PATCH] 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 @@ +