1 //
2 // Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2021
3 //
4 // Distributed under the Boost Software License, Version 1.0. (See accompanying
5 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6 //
7 #include "td/telegram/TdDb.h"
8 
9 #include "td/telegram/DialogDb.h"
10 #include "td/telegram/files/FileDb.h"
11 #include "td/telegram/Global.h"
12 #include "td/telegram/logevent/LogEvent.h"
13 #include "td/telegram/MessagesDb.h"
14 #include "td/telegram/Td.h"
15 #include "td/telegram/TdParameters.h"
16 #include "td/telegram/Version.h"
17 
18 #include "td/db/binlog/Binlog.h"
19 #include "td/db/binlog/ConcurrentBinlog.h"
20 #include "td/db/BinlogKeyValue.h"
21 #include "td/db/SqliteConnectionSafe.h"
22 #include "td/db/SqliteDb.h"
23 #include "td/db/SqliteKeyValue.h"
24 #include "td/db/SqliteKeyValueAsync.h"
25 #include "td/db/SqliteKeyValueSafe.h"
26 
27 #include "td/actor/MultiPromise.h"
28 
29 #include "td/utils/common.h"
30 #include "td/utils/format.h"
31 #include "td/utils/logging.h"
32 #include "td/utils/misc.h"
33 #include "td/utils/port/path.h"
34 #include "td/utils/Random.h"
35 #include "td/utils/SliceBuilder.h"
36 #include "td/utils/StringBuilder.h"
37 
38 #include <algorithm>
39 
40 namespace td {
41 
42 namespace {
43 
get_binlog_path(const TdParameters & parameters)44 std::string get_binlog_path(const TdParameters &parameters) {
45   return PSTRING() << parameters.database_directory << "td" << (parameters.use_test_dc ? "_test" : "") << ".binlog";
46 }
47 
get_sqlite_path(const TdParameters & parameters)48 std::string get_sqlite_path(const TdParameters &parameters) {
49   const string db_name = "db" + (parameters.use_test_dc ? string("_test") : string());
50   return parameters.database_directory + db_name + ".sqlite";
51 }
52 
check_encryption(string path)53 Result<TdDb::EncryptionInfo> check_encryption(string path) {
54   Binlog binlog;
55   auto status = binlog.init(std::move(path), Binlog::Callback());
56   if (status.is_error() && status.code() != Binlog::Error::WrongPassword) {
57     LOG(WARNING) << "Failed to check binlog: " << status;
58     return Status::Error(400, status.message());
59   }
60   TdDb::EncryptionInfo info;
61   info.is_encrypted = binlog.get_info().wrong_password;
62   binlog.close(false /*need_sync*/).ensure();
63   return info;
64 }
65 
init_binlog(Binlog & binlog,string path,BinlogKeyValue<Binlog> & binlog_pmc,BinlogKeyValue<Binlog> & config_pmc,TdDb::Events & events,DbKey key)66 Status init_binlog(Binlog &binlog, string path, BinlogKeyValue<Binlog> &binlog_pmc, BinlogKeyValue<Binlog> &config_pmc,
67                    TdDb::Events &events, DbKey key) {
68   auto callback = [&](const BinlogEvent &event) {
69     switch (event.type_) {
70       case LogEvent::HandlerType::SecretChats:
71         events.to_secret_chats_manager.push_back(event.clone());
72         break;
73       case LogEvent::HandlerType::Users:
74         events.user_events.push_back(event.clone());
75         break;
76       case LogEvent::HandlerType::Chats:
77         events.chat_events.push_back(event.clone());
78         break;
79       case LogEvent::HandlerType::Channels:
80         events.channel_events.push_back(event.clone());
81         break;
82       case LogEvent::HandlerType::SecretChatInfos:
83         events.secret_chat_events.push_back(event.clone());
84         break;
85       case LogEvent::HandlerType::WebPages:
86         events.web_page_events.push_back(event.clone());
87         break;
88       case LogEvent::HandlerType::SetPollAnswer:
89       case LogEvent::HandlerType::StopPoll:
90         events.to_poll_manager.push_back(event.clone());
91         break;
92       case LogEvent::HandlerType::SendMessage:
93       case LogEvent::HandlerType::DeleteMessage:
94       case LogEvent::HandlerType::DeleteMessagesOnServer:
95       case LogEvent::HandlerType::ReadHistoryOnServer:
96       case LogEvent::HandlerType::ReadMessageContentsOnServer:
97       case LogEvent::HandlerType::ForwardMessages:
98       case LogEvent::HandlerType::SendBotStartMessage:
99       case LogEvent::HandlerType::SendScreenshotTakenNotificationMessage:
100       case LogEvent::HandlerType::SendInlineQueryResultMessage:
101       case LogEvent::HandlerType::DeleteDialogHistoryOnServer:
102       case LogEvent::HandlerType::ReadAllDialogMentionsOnServer:
103       case LogEvent::HandlerType::DeleteAllChannelMessagesFromSenderOnServer:
104       case LogEvent::HandlerType::ToggleDialogIsPinnedOnServer:
105       case LogEvent::HandlerType::ReorderPinnedDialogsOnServer:
106       case LogEvent::HandlerType::SaveDialogDraftMessageOnServer:
107       case LogEvent::HandlerType::UpdateDialogNotificationSettingsOnServer:
108       case LogEvent::HandlerType::UpdateScopeNotificationSettingsOnServer:
109       case LogEvent::HandlerType::ResetAllNotificationSettingsOnServer:
110       case LogEvent::HandlerType::ToggleDialogReportSpamStateOnServer:
111       case LogEvent::HandlerType::RegetDialog:
112       case LogEvent::HandlerType::GetChannelDifference:
113       case LogEvent::HandlerType::ReadHistoryInSecretChat:
114       case LogEvent::HandlerType::ToggleDialogIsMarkedAsUnreadOnServer:
115       case LogEvent::HandlerType::SetDialogFolderIdOnServer:
116       case LogEvent::HandlerType::DeleteScheduledMessagesOnServer:
117       case LogEvent::HandlerType::ToggleDialogIsBlockedOnServer:
118       case LogEvent::HandlerType::ReadMessageThreadHistoryOnServer:
119       case LogEvent::HandlerType::BlockMessageSenderFromRepliesOnServer:
120       case LogEvent::HandlerType::UnpinAllDialogMessagesOnServer:
121       case LogEvent::HandlerType::DeleteAllCallMessagesOnServer:
122       case LogEvent::HandlerType::DeleteDialogMessagesByDateOnServer:
123         events.to_messages_manager.push_back(event.clone());
124         break;
125       case LogEvent::HandlerType::AddMessagePushNotification:
126       case LogEvent::HandlerType::EditMessagePushNotification:
127         events.to_notification_manager.push_back(event.clone());
128         break;
129       case LogEvent::HandlerType::BinlogPmcMagic:
130         binlog_pmc.external_init_handle(event);
131         break;
132       case LogEvent::HandlerType::ConfigPmcMagic:
133         config_pmc.external_init_handle(event);
134         break;
135       default:
136         LOG(FATAL) << "Unsupported log event type " << event.type_;
137     }
138   };
139 
140   auto binlog_info = binlog.init(std::move(path), callback, std::move(key));
141   if (binlog_info.is_error()) {
142     return binlog_info.move_as_error();
143   }
144   return Status::OK();
145 }
146 
147 }  // namespace
148 
get_file_db_shared()149 std::shared_ptr<FileDbInterface> TdDb::get_file_db_shared() {
150   return file_db_;
151 }
152 
get_sqlite_connection_safe()153 std::shared_ptr<SqliteConnectionSafe> &TdDb::get_sqlite_connection_safe() {
154   return sql_connection_;
155 }
156 
get_binlog_impl(const char * file,int line)157 BinlogInterface *TdDb::get_binlog_impl(const char *file, int line) {
158   LOG_CHECK(binlog_) << G()->close_flag() << " " << file << " " << line;
159   return binlog_.get();
160 }
161 
get_binlog_pmc_shared()162 std::shared_ptr<KeyValueSyncInterface> TdDb::get_binlog_pmc_shared() {
163   CHECK(binlog_pmc_);
164   return binlog_pmc_;
165 }
166 
get_config_pmc_shared()167 std::shared_ptr<KeyValueSyncInterface> TdDb::get_config_pmc_shared() {
168   CHECK(config_pmc_);
169   return config_pmc_;
170 }
171 
get_binlog_pmc()172 KeyValueSyncInterface *TdDb::get_binlog_pmc() {
173   CHECK(binlog_pmc_);
174   return binlog_pmc_.get();
175 }
176 
get_config_pmc()177 KeyValueSyncInterface *TdDb::get_config_pmc() {
178   CHECK(config_pmc_);
179   return config_pmc_.get();
180 }
181 
get_sqlite_sync_pmc()182 SqliteKeyValue *TdDb::get_sqlite_sync_pmc() {
183   CHECK(common_kv_safe_);
184   return &common_kv_safe_->get();
185 }
186 
get_sqlite_pmc()187 SqliteKeyValueAsyncInterface *TdDb::get_sqlite_pmc() {
188   CHECK(common_kv_async_);
189   return common_kv_async_.get();
190 }
191 
get_messages_db_sync()192 MessagesDbSyncInterface *TdDb::get_messages_db_sync() {
193   return &messages_db_sync_safe_->get();
194 }
get_messages_db_async()195 MessagesDbAsyncInterface *TdDb::get_messages_db_async() {
196   return messages_db_async_.get();
197 }
get_dialog_db_sync()198 DialogDbSyncInterface *TdDb::get_dialog_db_sync() {
199   return &dialog_db_sync_safe_->get();
200 }
get_dialog_db_async()201 DialogDbAsyncInterface *TdDb::get_dialog_db_async() {
202   return dialog_db_async_.get();
203 }
204 
binlog_path() const205 CSlice TdDb::binlog_path() const {
206   return binlog_->get_path();
207 }
sqlite_path() const208 CSlice TdDb::sqlite_path() const {
209   return sqlite_path_;
210 }
211 
flush_all()212 void TdDb::flush_all() {
213   LOG(INFO) << "Flush all databases";
214   if (messages_db_async_) {
215     messages_db_async_->force_flush();
216   }
217   binlog_->force_flush();
218 }
219 
close_all(Promise<> on_finished)220 void TdDb::close_all(Promise<> on_finished) {
221   LOG(INFO) << "Close all databases";
222   do_close(std::move(on_finished), false /*destroy_flag*/);
223 }
224 
close_and_destroy_all(Promise<> on_finished)225 void TdDb::close_and_destroy_all(Promise<> on_finished) {
226   LOG(INFO) << "Destroy all databases";
227   do_close(std::move(on_finished), true /*destroy_flag*/);
228 }
229 
do_close(Promise<> on_finished,bool destroy_flag)230 void TdDb::do_close(Promise<> on_finished, bool destroy_flag) {
231   MultiPromiseActorSafe mpas{"TdDbCloseMultiPromiseActor"};
232   mpas.add_promise(PromiseCreator::lambda(
233       [promise = std::move(on_finished), sql_connection = std::move(sql_connection_), destroy_flag](Unit) mutable {
234         if (sql_connection) {
235           LOG_CHECK(sql_connection.unique()) << sql_connection.use_count();
236           if (destroy_flag) {
237             sql_connection->close_and_destroy();
238           } else {
239             sql_connection->close();
240           }
241           sql_connection.reset();
242         }
243         promise.set_value(Unit());
244       }));
245   auto lock = mpas.get_promise();
246 
247   if (file_db_) {
248     file_db_->close(mpas.get_promise());
249     file_db_.reset();
250   }
251 
252   common_kv_safe_.reset();
253   if (common_kv_async_) {
254     common_kv_async_->close(mpas.get_promise());
255   }
256 
257   messages_db_sync_safe_.reset();
258   if (messages_db_async_) {
259     messages_db_async_->close(mpas.get_promise());
260   }
261 
262   dialog_db_sync_safe_.reset();
263   if (dialog_db_async_) {
264     dialog_db_async_->close(mpas.get_promise());
265   }
266 
267   // binlog_pmc is dependent on binlog_ and anyway it doesn't support close_and_destroy
268   CHECK(binlog_pmc_.unique());
269   binlog_pmc_.reset();
270   CHECK(config_pmc_.unique());
271   config_pmc_.reset();
272 
273   if (binlog_) {
274     if (destroy_flag) {
275       binlog_->close_and_destroy(mpas.get_promise());
276     } else {
277       binlog_->close(mpas.get_promise());
278     }
279     binlog_.reset();
280   }
281 
282   lock.set_value(Unit());
283 }
284 
init_sqlite(int32 scheduler_id,const TdParameters & parameters,const DbKey & key,const DbKey & old_key,BinlogKeyValue<Binlog> & binlog_pmc)285 Status TdDb::init_sqlite(int32 scheduler_id, const TdParameters &parameters, const DbKey &key, const DbKey &old_key,
286                          BinlogKeyValue<Binlog> &binlog_pmc) {
287   CHECK(!parameters.use_message_db || parameters.use_chat_info_db);
288   CHECK(!parameters.use_chat_info_db || parameters.use_file_db);
289 
290   const string sql_database_path = get_sqlite_path(parameters);
291 
292   bool use_sqlite = parameters.use_file_db;
293   bool use_file_db = parameters.use_file_db;
294   bool use_dialog_db = parameters.use_message_db;
295   bool use_message_db = parameters.use_message_db;
296   if (!use_sqlite) {
297     unlink(sql_database_path).ignore();
298     return Status::OK();
299   }
300 
301   sqlite_path_ = sql_database_path;
302   TRY_RESULT(db_instance, SqliteDb::change_key(sqlite_path_, true, key, old_key));
303   sql_connection_ = std::make_shared<SqliteConnectionSafe>(sql_database_path, key, db_instance.get_cipher_version());
304   sql_connection_->set(std::move(db_instance));
305   auto &db = sql_connection_->get();
306   TRY_STATUS(db.exec("PRAGMA journal_mode=WAL"));
307   TRY_STATUS(db.exec("PRAGMA secure_delete=1"));
308 
309   // Init databases
310   // Do initialization once and before everything else to avoid "database is locked" error.
311   // Must be in a transaction
312 
313   // NB: when database is dropped we should also drop corresponding binlog events
314   TRY_STATUS(db.exec("BEGIN TRANSACTION"));
315 
316   // Get 'PRAGMA user_version'
317   TRY_RESULT(user_version, db.user_version());
318   LOG(INFO) << "Got PRAGMA user_version = " << user_version;
319 
320   // init DialogDb
321   bool dialog_db_was_created = false;
322   if (use_dialog_db) {
323     TRY_STATUS(init_dialog_db(db, user_version, binlog_pmc, dialog_db_was_created));
324   } else {
325     TRY_STATUS(drop_dialog_db(db, user_version));
326   }
327 
328   // init MessagesDb
329   if (use_message_db) {
330     TRY_STATUS(init_messages_db(db, user_version));
331   } else {
332     TRY_STATUS(drop_messages_db(db, user_version));
333   }
334 
335   // init filesDb
336   if (use_file_db) {
337     TRY_STATUS(init_file_db(db, user_version));
338   } else {
339     TRY_STATUS(drop_file_db(db, user_version));
340   }
341 
342   // Update 'PRAGMA user_version'
343   auto db_version = current_db_version();
344   if (db_version != user_version) {
345     LOG(WARNING) << "Set PRAGMA user_version = " << db_version;
346     TRY_STATUS(db.set_user_version(db_version));
347   }
348 
349   if (dialog_db_was_created) {
350     binlog_pmc.erase_by_prefix("pinned_dialog_ids");
351     binlog_pmc.erase_by_prefix("last_server_dialog_date");
352     binlog_pmc.erase_by_prefix("unread_message_count");
353     binlog_pmc.erase_by_prefix("unread_dialog_count");
354     binlog_pmc.erase("sponsored_dialog_id");
355     binlog_pmc.erase_by_prefix("top_dialogs");
356   }
357   if (user_version == 0) {
358     binlog_pmc.erase("next_contacts_sync_date");
359     binlog_pmc.erase("saved_contact_count");
360     binlog_pmc.erase("old_featured_sticker_set_count");
361     binlog_pmc.erase("invalidate_old_featured_sticker_sets");
362   }
363   binlog_pmc.force_sync({});
364 
365   TRY_STATUS(db.exec("COMMIT TRANSACTION"));
366 
367   file_db_ = create_file_db(sql_connection_, scheduler_id);
368 
369   common_kv_safe_ = std::make_shared<SqliteKeyValueSafe>("common", sql_connection_);
370   common_kv_async_ = create_sqlite_key_value_async(common_kv_safe_, scheduler_id);
371 
372   if (use_dialog_db) {
373     dialog_db_sync_safe_ = create_dialog_db_sync(sql_connection_);
374     dialog_db_async_ = create_dialog_db_async(dialog_db_sync_safe_, scheduler_id);
375   }
376 
377   if (use_message_db) {
378     messages_db_sync_safe_ = create_messages_db_sync(sql_connection_);
379     messages_db_async_ = create_messages_db_async(messages_db_sync_safe_, scheduler_id);
380   }
381 
382   return Status::OK();
383 }
384 
init(int32 scheduler_id,const TdParameters & parameters,DbKey key,Events & events)385 Status TdDb::init(int32 scheduler_id, const TdParameters &parameters, DbKey key, Events &events) {
386   // Init pmc
387   Binlog *binlog_ptr = nullptr;
388   auto binlog = std::shared_ptr<Binlog>(new Binlog, [&](Binlog *ptr) { binlog_ptr = ptr; });
389 
390   auto binlog_pmc = make_unique<BinlogKeyValue<Binlog>>();
391   auto config_pmc = make_unique<BinlogKeyValue<Binlog>>();
392   binlog_pmc->external_init_begin(static_cast<int32>(LogEvent::HandlerType::BinlogPmcMagic));
393   config_pmc->external_init_begin(static_cast<int32>(LogEvent::HandlerType::ConfigPmcMagic));
394 
395   bool encrypt_binlog = !key.is_empty();
396   VLOG(td_init) << "Start binlog loading";
397   TRY_STATUS(init_binlog(*binlog, get_binlog_path(parameters), *binlog_pmc, *config_pmc, events, std::move(key)));
398   VLOG(td_init) << "Finish binlog loading";
399 
400   binlog_pmc->external_init_finish(binlog);
401   VLOG(td_init) << "Finish initialization of binlog PMC";
402   config_pmc->external_init_finish(binlog);
403   VLOG(td_init) << "Finish initialization of config PMC";
404 
405   DbKey new_sqlite_key;
406   DbKey old_sqlite_key;
407   bool encrypt_sqlite = encrypt_binlog;
408   bool drop_sqlite_key = false;
409   auto sqlite_key = binlog_pmc->get("sqlite_key");
410   if (encrypt_sqlite) {
411     if (sqlite_key.empty()) {
412       sqlite_key = string(32, ' ');
413       Random::secure_bytes(sqlite_key);
414       binlog_pmc->set("sqlite_key", sqlite_key);
415       binlog_pmc->force_sync(Auto());
416     }
417     new_sqlite_key = DbKey::raw_key(std::move(sqlite_key));
418   } else {
419     if (!sqlite_key.empty()) {
420       old_sqlite_key = DbKey::raw_key(std::move(sqlite_key));
421       drop_sqlite_key = true;
422     }
423   }
424   VLOG(td_init) << "Start to init database";
425   auto init_sqlite_status = init_sqlite(scheduler_id, parameters, new_sqlite_key, old_sqlite_key, *binlog_pmc);
426   VLOG(td_init) << "Finish to init database";
427   if (init_sqlite_status.is_error()) {
428     LOG(ERROR) << "Destroy bad SQLite database because of " << init_sqlite_status;
429     if (sql_connection_ != nullptr) {
430       sql_connection_->get().close();
431     }
432     SqliteDb::destroy(get_sqlite_path(parameters)).ignore();
433     TRY_STATUS(init_sqlite(scheduler_id, parameters, new_sqlite_key, old_sqlite_key, *binlog_pmc));
434   }
435   if (drop_sqlite_key) {
436     binlog_pmc->erase("sqlite_key");
437     binlog_pmc->force_sync(Auto());
438   }
439 
440   VLOG(td_init) << "Create concurrent_binlog_pmc";
441   auto concurrent_binlog_pmc = std::make_shared<BinlogKeyValue<ConcurrentBinlog>>();
442   concurrent_binlog_pmc->external_init_begin(binlog_pmc->get_magic());
443   concurrent_binlog_pmc->external_init_handle(std::move(*binlog_pmc));
444 
445   VLOG(td_init) << "Create concurrent_config_pmc";
446   auto concurrent_config_pmc = std::make_shared<BinlogKeyValue<ConcurrentBinlog>>();
447   concurrent_config_pmc->external_init_begin(config_pmc->get_magic());
448   concurrent_config_pmc->external_init_handle(std::move(*config_pmc));
449 
450   binlog.reset();
451   binlog_pmc.reset();
452   config_pmc.reset();
453 
454   CHECK(binlog_ptr != nullptr);
455   VLOG(td_init) << "Create concurrent_binlog";
456   auto concurrent_binlog = std::make_shared<ConcurrentBinlog>(unique_ptr<Binlog>(binlog_ptr), scheduler_id);
457 
458   VLOG(td_init) << "Init concurrent_binlog_pmc";
459   concurrent_binlog_pmc->external_init_finish(concurrent_binlog);
460   VLOG(td_init) << "Init concurrent_config_pmc";
461   concurrent_config_pmc->external_init_finish(concurrent_binlog);
462 
463   binlog_pmc_ = std::move(concurrent_binlog_pmc);
464   config_pmc_ = std::move(concurrent_config_pmc);
465   binlog_ = std::move(concurrent_binlog);
466 
467   return Status::OK();
468 }
469 
470 TdDb::TdDb() = default;
471 TdDb::~TdDb() = default;
472 
open(int32 scheduler_id,const TdParameters & parameters,DbKey key,Events & events)473 Result<unique_ptr<TdDb>> TdDb::open(int32 scheduler_id, const TdParameters &parameters, DbKey key, Events &events) {
474   auto db = make_unique<TdDb>();
475   TRY_STATUS(db->init(scheduler_id, parameters, std::move(key), events));
476   return std::move(db);
477 }
478 
check_encryption(const TdParameters & parameters)479 Result<TdDb::EncryptionInfo> TdDb::check_encryption(const TdParameters &parameters) {
480   return ::td::check_encryption(get_binlog_path(parameters));
481 }
482 
change_key(DbKey key,Promise<> promise)483 void TdDb::change_key(DbKey key, Promise<> promise) {
484   get_binlog()->change_key(std::move(key), std::move(promise));
485 }
486 
destroy(const TdParameters & parameters)487 Status TdDb::destroy(const TdParameters &parameters) {
488   SqliteDb::destroy(get_sqlite_path(parameters)).ignore();
489   Binlog::destroy(get_binlog_path(parameters)).ignore();
490   return Status::OK();
491 }
492 
with_db_path(const std::function<void (CSlice)> & callback)493 void TdDb::with_db_path(const std::function<void(CSlice)> &callback) {
494   SqliteDb::with_db_path(sqlite_path(), callback);
495   callback(binlog_path());
496 }
497 
get_stats()498 Result<string> TdDb::get_stats() {
499   auto sb = StringBuilder({}, true);
500   auto &sql = sql_connection_->get();
501   auto run_query = [&](CSlice query, Slice desc) -> Status {
502     TRY_RESULT(stmt, sql.get_statement(query));
503     TRY_STATUS(stmt.step());
504     CHECK(stmt.has_row());
505     auto key_size = stmt.view_int64(0);
506     auto value_size = stmt.view_int64(1);
507     auto count = stmt.view_int64(2);
508     sb << query << "\n";
509     sb << desc << ":\n";
510     sb << format::as_size(key_size + value_size) << "\t";
511     sb << format::as_size(key_size) << "\t";
512     sb << format::as_size(value_size) << "\t";
513     sb << format::as_size((key_size + value_size) / (count ? count : 1)) << "\t";
514     sb << "\n";
515     return Status::OK();
516   };
517   auto run_kv_query = [&](Slice mask, Slice table = Slice("common")) {
518     return run_query(PSLICE() << "SELECT SUM(length(k)), SUM(length(v)), COUNT(*) FROM " << table << " WHERE k like '"
519                               << mask << "'",
520                      PSLICE() << table << ":" << mask);
521   };
522   TRY_STATUS(run_query("SELECT 0, SUM(length(data)), COUNT(*) FROM messages WHERE 1", "messages"));
523   TRY_STATUS(run_query("SELECT 0, SUM(length(data)), COUNT(*) FROM dialogs WHERE 1", "dialogs"));
524   TRY_STATUS(run_kv_query("%", "common"));
525   TRY_STATUS(run_kv_query("%", "files"));
526   TRY_STATUS(run_kv_query("wp%"));
527   TRY_STATUS(run_kv_query("wpurl%"));
528   TRY_STATUS(run_kv_query("wpiv%"));
529   TRY_STATUS(run_kv_query("us%"));
530   TRY_STATUS(run_kv_query("ch%"));
531   TRY_STATUS(run_kv_query("ss%"));
532   TRY_STATUS(run_kv_query("gr%"));
533 
534   vector<int32> prev(1);
535   size_t count = 0;
536   int32 max_bad_to = 0;
537   size_t bad_count = 0;
538   file_db_->pmc().get_by_range("file0", "file:", [&](Slice key, Slice value) {
539     if (value.substr(0, 2) != "@@") {
540       return true;
541     }
542     count++;
543     auto from = to_integer<int32>(key.substr(4));
544     auto to = to_integer<int32>(value.substr(2));
545     if (from <= to) {
546       LOG(DEBUG) << "Have forward reference from " << from << " to " << to;
547       if (to > max_bad_to) {
548         max_bad_to = to;
549       }
550       bad_count++;
551       return true;
552     }
553     if (static_cast<size_t>(from) >= prev.size()) {
554       prev.resize(from + 1);
555     }
556     if (static_cast<size_t>(to) >= prev.size()) {
557       prev.resize(to + 1);
558     }
559     prev[from] = to;
560     return true;
561   });
562   for (size_t i = 1; i < prev.size(); i++) {
563     if (!prev[i]) {
564       continue;
565     }
566     prev[i] = prev[prev[i]] + 1;
567   }
568   sb << "Max file database depth out of " << prev.size() << '/' << count
569      << " elements: " << *std::max_element(prev.begin(), prev.end()) << "\n";
570   sb << "Have " << bad_count << " forward references with maximum reference to " << max_bad_to;
571 
572   return sb.as_cslice().str();
573 }
574 
575 }  // namespace td
576