fix(userconfig): using api bit functions

Signed-off-by: Maxence Lange <maxence@artificial-owl.com>
pull/55139/head
Maxence Lange 2025-09-16 12:52:27 +07:00
parent 0249e3a2f5
commit 70dd0de0ff
6 changed files with 136 additions and 9 deletions

@ -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),
];
}
}

@ -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));
}
}

@ -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

@ -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'));
}
}

@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
namespace Tests\lib\Config;
use OCP\Config\IUserConfig;
use OCP\Config\Lexicon\Entry;
use OCP\Config\Lexicon\ILexicon;
use OCP\Config\Lexicon\Strictness;
use OCP\Config\ValueType;
class TestLexicon_UserIndexed implements ILexicon {
public const APPID = 'lexicon_user_indexed';
public function getStrictness(): Strictness {
return Strictness::EXCEPTION;
}
public function getAppConfigs(): array {
return [
];
}
public function getUserConfigs(): array {
return [
new Entry(key: 'key1', type: ValueType::STRING, defaultRaw: '', definition: 'test key', flags: IUserConfig::FLAG_INDEXED),
];
}
}

@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
namespace Tests\lib\Config;
use OCP\Config\Lexicon\Entry;
use OCP\Config\Lexicon\ILexicon;
use OCP\Config\Lexicon\Strictness;
use OCP\Config\ValueType;
class TestLexicon_UserIndexedRemove implements ILexicon {
public const APPID = 'lexicon_user_not_indexed';
public function getStrictness(): Strictness {
return Strictness::EXCEPTION;
}
public function getAppConfigs(): array {
return [
];
}
public function getUserConfigs(): array {
return [
new Entry(key: 'key1', type: ValueType::STRING, defaultRaw: '', definition: 'test key'),
];
}
}