Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion lib/private/AppConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -1027,7 +1027,7 @@ public function updateLazy(string $app, string $key, bool $lazy): bool {
if ($lazy === $this->isLazy($app, $key)) {
return false;
}
} catch (AppConfigUnknownKeyException $e) {
} catch (AppConfigUnknownKeyException) {
return false;
}

Expand Down Expand Up @@ -1724,6 +1724,12 @@ private function matchAndApplyLexiconDefinition(
$this->logger->notice('App config key ' . $app . '/' . $key . ' is set as deprecated.');
}

if ($lazy && isset($this->fastCache[$app][$key])) {
// while the Lexicon indicate that the config value is expected Lazy, we could
// have a previous entry still in fast cache. Updating Laziness.
$this->updateLazy($app, $key, true);
}

return true;
}

Expand Down
13 changes: 13 additions & 0 deletions lib/private/Config/UserConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -1929,6 +1929,19 @@ private function matchAndApplyLexiconDefinition(
$this->logger->notice('User config key ' . $app . '/' . $key . ' is set as deprecated.');
}

// There should be no downside to load all config values if search for
// a lazy config value while fast value are still not loaded.
if ($lazy && !($this->fastLoaded[$userId] ?? false)) {
$this->loadConfigAll($userId);
}

// while the Lexicon indicate that the config value is expected Lazy, we could
// have a previous entry still in fast cache. Updating Laziness for all users.
if ($lazy && isset($this->fastCache[$userId][$app][$key])) {
$this->updateGlobalLazy($app, $key, true);
}

// TODO: remove this feature before 32 if https://github.com/nextcloud/server/issues/51804 is implemented
$enforcedValue = $this->config->getSystemValue('lexicon.default.userconfig.enforced', [])[$app][$key] ?? false;
if (!$enforcedValue && $this->hasKey($userId, $app, $key, $lazy)) {
// if key exists there should be no need to extract default
Expand Down
80 changes: 80 additions & 0 deletions tests/lib/Config/LexiconTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,19 @@
use OC\AppConfig;
use OC\AppFramework\Bootstrap\Coordinator;
use OC\Config\ConfigManager;
use OC\Config\UserConfig;
use OCP\Config\Exceptions\TypeConflictException;
use OCP\Config\Exceptions\UnknownKeyException;
use OCP\Config\IUserConfig;
use OCP\Config\Lexicon\Preset;
use OCP\Exceptions\AppConfigTypeConflictException;
use OCP\Exceptions\AppConfigUnknownKeyException;
use OCP\IAppConfig;
use OCP\IConfig;
use OCP\IDBConnection;
use OCP\Security\ICrypto;
use OCP\Server;
use Psr\Log\LoggerInterface;
use Test\TestCase;

/**
Expand Down Expand Up @@ -68,6 +73,81 @@ public function testAppLexiconSetCorrect() {
$this->appConfig->deleteKey(TestLexicon_E::APPID, 'key1');
}

public function testAppConfigMigrationToLazy() {
$app = TestConfigLexicon_Migration::APPID . '_app';
// to avoid filling cache with an empty Lexicon, we use a new IAppConfig
$appConfig = new AppConfig(
Server::get(IDBConnection::class),
Server::get(IConfig::class),
Server::get(LoggerInterface::class),
Server::get(ICrypto::class),
);
$this->assertSame(true, $appConfig->setValueString($app, 'key1', 'value1'));

// confirm that key1 is not lazy, even after a refresh of the cache
$appConfig->clearCache();
$this->assertSame('value1', $appConfig->getValueString($app, 'key1'));
$this->assertSame(true, array_key_exists('key1', $appConfig->statusCache()['fastCache'][$app]));

// loading new Lexicon that set key1 as lazy
$bootstrapCoordinator = \OCP\Server::get(Coordinator::class);
$bootstrapCoordinator->getRegistrationContext()?->registerConfigLexicon($app, TestConfigLexicon_Migration::class);

// caching
$this->assertSame('default0', $this->appConfig->getValueString($app, 'key0'));
// still not lazy
$this->assertSame(true, array_key_exists('key1', $this->appConfig->statusCache()['fastCache'][$app]));
// trigger update of status
$this->assertSame('value1', $this->appConfig->getValueString($app, 'key1'));
// should be lazy now
$this->assertSame(true, array_key_exists('key1', $this->appConfig->statusCache()['lazyCache'][$app]));

$this->appConfig->clearCache();
$this->assertSame('default0', $this->appConfig->getValueString($app, 'key0'));
// definitively lazy
$this->assertSame(false, array_key_exists($app, $this->appConfig->statusCache()['fastCache']));

$this->appConfig->deleteKey($app, 'key1');
}

public function testUserConfigMigrationToLazy() {
$app = TestConfigLexicon_Migration::APPID . '_user';
// to avoid filling cache with an empty Lexicon, we use a new IAppConfig
$userConfig = new UserConfig(
Server::get(IDBConnection::class),
Server::get(IConfig::class),
Server::get(LoggerInterface::class),
Server::get(ICrypto::class),
);
$this->assertSame(true, $userConfig->setValueString('user1', $app, 'key1', 'value1'));

// confirm that key1 is not lazy, even after a refresh of Lexicon
$userConfig->clearCache('user1');
$this->assertSame('value1', $userConfig->getValueString('user1', $app, 'key1'));
$this->assertSame(true, array_key_exists('key1', $userConfig->statusCache()['fastCache']['user1'][$app]));

// loading new Lexicon that set key1 as lazy
$bootstrapCoordinator = \OCP\Server::get(Coordinator::class);
$bootstrapCoordinator->getRegistrationContext()?->registerConfigLexicon($app, TestConfigLexicon_Migration::class);

// caching
$this->assertSame('default0', $this->userConfig->getValueString('user1', $app, 'key0'));
// still not lazy
$this->assertSame(true, array_key_exists('key1', $this->userConfig->statusCache()['fastCache']['user1'][$app]));
// trigger update of status
$this->assertSame('value1', $this->userConfig->getValueString('user1', $app, 'key1'));
// should be lazy now
$this->assertSame(true, array_key_exists('key1', $this->userConfig->statusCache()['lazyCache']['user1'][$app]));

$this->userConfig->clearCache('user1');
$this->assertSame('default0', $this->userConfig->getValueString('user1', $app, 'key0'));
// definitively lazy
$this->assertSame(false, array_key_exists($app, $this->userConfig->statusCache()['fastCache']['user1']));

$this->userConfig->deleteKey($app, 'key1');
}


public function testAppLexiconGetCorrect() {
$this->assertSame('abcde', $this->appConfig->getValueString(TestLexicon_E::APPID, 'key1', 'default'));
}
Expand Down
36 changes: 36 additions & 0 deletions tests/lib/Config/TestConfigLexicon_Migration.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace Tests\lib\Config;

use NCU\Config\Lexicon\ConfigLexiconEntry;
use NCU\Config\Lexicon\ConfigLexiconStrictness;
use NCU\Config\Lexicon\IConfigLexicon;
use NCU\Config\ValueType;

class TestConfigLexicon_Migration implements IConfigLexicon {
public const APPID = 'lexicon_test_migration';

public function getStrictness(): ConfigLexiconStrictness {
return ConfigLexiconStrictness::EXCEPTION;
}

public function getAppConfigs(): array {
return [
new ConfigLexiconEntry('key0', ValueType::STRING, 'default0'),
new ConfigLexiconEntry('key1', ValueType::STRING, lazy: true)
];
}

public function getUserConfigs(): array {
return [
new ConfigLexiconEntry('key0', ValueType::STRING, 'default0'),
new ConfigLexiconEntry('key1', ValueType::STRING, lazy: true)
];
}
}
Loading