1010use Doctrine \DBAL \Exception ;
1111use OCP \DB \IPreparedStatement ;
1212use OCP \DB \QueryBuilder \IQueryBuilder ;
13+ use OCP \IAppConfig ;
14+ use OCP \ICache ;
15+ use OCP \ICacheFactory ;
1316use OCP \IDBConnection ;
1417use OCP \Server ;
1518use Psr \Log \LoggerInterface ;
@@ -27,16 +30,81 @@ abstract class AbstractMapping {
2730 */
2831 abstract protected function getTableName (bool $ includePrefix = true );
2932
33+ /**
34+ * A month worth of cache time for as good as never changing mapping data.
35+ * Implemented when it was found that name-to-DN lookups are quite frequent.
36+ */
37+ protected const LOCAL_CACHE_TTL = 2592000 ;
38+
39+ /**
40+ * A week worth of cache time for rarely changing user count data.
41+ */
42+ protected const LOCAL_USER_COUNT_TTL = 604800 ;
43+
44+ /**
45+ * By default, the local cache is only used up to a certain amount of objects.
46+ * This constant holds this number. The amount of entries would amount up to
47+ * 1 MiB (worst case) per mappings table.
48+ * Setting `use_local_mapping_cache` for `user_ldap` to `yes` or `no`
49+ * deliberately enables or disables this mechanism.
50+ */
51+ protected const LOCAL_CACHE_OBJECT_THRESHOLD = 2000 ;
52+
53+ protected ?ICache $ localNameToDnCache = null ;
54+
55+ /** @var array caches Names (value) by DN (key) */
56+ protected array $ cache = [];
57+
3058 /**
3159 * @param IDBConnection $dbc
3260 */
3361 public function __construct (
3462 protected IDBConnection $ dbc ,
63+ protected ICacheFactory $ cacheFactory ,
64+ protected IAppConfig $ config ,
65+ protected bool $ isCLI ,
3566 ) {
67+ $ this ->initLocalCache ();
3668 }
3769
38- /** @var array caches Names (value) by DN (key) */
39- protected $ cache = [];
70+ protected function initLocalCache (): void {
71+ if ($ this ->isCLI || !$ this ->cacheFactory ->isLocalCacheAvailable ()) {
72+ return ;
73+ }
74+
75+ $ useLocalCache = $ this ->config ->getValueString ('user_ldap ' , 'use_local_mapping_cache ' , 'auto ' , false );
76+ if ($ useLocalCache !== 'yes ' && $ useLocalCache !== 'auto ' ) {
77+ return ;
78+ }
79+
80+ $ section = \str_contains ($ this ->getTableName (), 'user ' ) ? 'u/ ' : 'g/ ' ;
81+ $ this ->localNameToDnCache = $ this ->cacheFactory ->createLocal ('ldap/map/ ' . $ section );
82+
83+ // We use the cache as well to store whether it shall be used. If the
84+ // answer was no, we unset it again.
85+ if ($ useLocalCache === 'auto ' && !$ this ->useCacheByUserCount ()) {
86+ $ this ->localNameToDnCache = null ;
87+ }
88+ }
89+
90+ protected function useCacheByUserCount (): bool {
91+ $ use = $ this ->localNameToDnCache ->get ('use ' );
92+ if ($ use !== null ) {
93+ return $ use ;
94+ }
95+
96+ $ qb = $ this ->dbc ->getQueryBuilder ();
97+ $ q = $ qb ->selectAlias ($ qb ->createFunction ('COUNT(owncloud_name) ' ), 'count ' )
98+ ->from ($ this ->getTableName ());
99+ $ q ->setMaxResults (self ::LOCAL_CACHE_OBJECT_THRESHOLD + 1 );
100+ $ result = $ q ->executeQuery ();
101+ $ row = $ result ->fetch ();
102+ $ result ->closeCursor ();
103+
104+ $ use = (int )$ row ['count ' ] <= self ::LOCAL_CACHE_OBJECT_THRESHOLD ;
105+ $ this ->localNameToDnCache ->set ('use ' , $ use , self ::LOCAL_USER_COUNT_TTL );
106+ return $ use ;
107+ }
40108
41109 /**
42110 * checks whether a provided string represents an existing table col
@@ -107,17 +175,22 @@ protected function modify(IPreparedStatement $statement, $parameters) {
107175
108176 /**
109177 * Gets the LDAP DN based on the provided name.
110- * Replaces Access::ocname2dn
111- *
112- * @param string $name
113- * @return string|false
114178 */
115- public function getDNByName ($ name ) {
116- $ dn = array_search ($ name , $ this ->cache );
117- if ($ dn === false && ($ dn = $ this ->getXbyY ('ldap_dn ' , 'owncloud_name ' , $ name )) !== false ) {
118- $ this ->cache [$ dn ] = $ name ;
179+ public function getDNByName (string $ name ): string |false {
180+ $ dn = array_search ($ name , $ this ->cache , true );
181+ if ($ dn === false ) {
182+ $ dn = $ this ->localNameToDnCache ?->get($ name );
183+ if ($ dn === null || $ dn === false ) {
184+ $ dn = $ this ->getXbyY ('ldap_dn ' , 'owncloud_name ' , $ name );
185+ if ($ dn === false ) {
186+ return false ;
187+ }
188+
189+ $ this ->cache [$ dn ] = $ name ;
190+ $ this ->localNameToDnCache ?->set($ name , $ dn , self ::LOCAL_CACHE_TTL );
191+ }
119192 }
120- return $ dn ;
193+ return $ dn ?? false ;
121194 }
122195
123196 /**
@@ -128,6 +201,7 @@ public function getDNByName($name) {
128201 * @return bool
129202 */
130203 public function setDNbyUUID ($ fdn , $ uuid ) {
204+ // FIXME: need to update local cache by name and broadcast this information
131205 $ oldDn = $ this ->getDnByUUID ($ uuid );
132206 $ statement = $ this ->dbc ->prepare ('
133207 UPDATE ` ' . $ this ->getTableName () . '`
@@ -351,6 +425,7 @@ public function map($fdn, $name, $uuid) {
351425 $ result = $ this ->dbc ->insertIfNotExist ($ this ->getTableName (), $ row );
352426 if ((bool )$ result === true ) {
353427 $ this ->cache [$ fdn ] = $ name ;
428+ $ this ->localNameToDnCache ?->set($ name , $ fdn , self ::LOCAL_CACHE_TTL );
354429 }
355430 // insertIfNotExist returns values as int
356431 return (bool )$ result ;
@@ -374,6 +449,7 @@ public function unmap($name) {
374449 if ($ dn !== false ) {
375450 unset($ this ->cache [$ dn ]);
376451 }
452+ $ this ->localNameToDnCache ?->remove($ name );
377453
378454 return $ this ->modify ($ statement , [$ name ]);
379455 }
@@ -389,6 +465,7 @@ public function clear() {
389465 ->getTruncateTableSQL ('` ' . $ this ->getTableName () . '` ' );
390466 try {
391467 $ this ->dbc ->executeQuery ($ sql );
468+ $ this ->localNameToDnCache ?->clear();
392469
393470 return true ;
394471 } catch (Exception $ e ) {
0 commit comments