1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
|
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2023
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/telegram/MessageThreadDb.h"
#include "td/telegram/Version.h"
#include "td/db/SqliteConnectionSafe.h"
#include "td/db/SqliteDb.h"
#include "td/db/SqliteStatement.h"
#include "td/actor/actor.h"
#include "td/actor/SchedulerLocalStorage.h"
#include "td/utils/common.h"
#include "td/utils/format.h"
#include "td/utils/logging.h"
#include "td/utils/ScopeGuard.h"
#include "td/utils/Time.h"
namespace td {
// NB: must happen inside a transaction
Status init_message_thread_db(SqliteDb &db, int32 version) {
LOG(INFO) << "Init message thread database " << tag("version", version);
// Check if database exists
TRY_RESULT(has_table, db.has_table("threads"));
if (!has_table) {
version = 0;
}
if (version > current_db_version()) {
TRY_STATUS(drop_message_thread_db(db, version));
version = 0;
}
if (version == 0) {
LOG(INFO) << "Create new message thread database";
TRY_STATUS(
db.exec("CREATE TABLE IF NOT EXISTS threads (dialog_id INT8, thread_id INT8, thread_order INT8, data BLOB, "
"PRIMARY KEY (dialog_id, thread_id))"));
TRY_STATUS(
db.exec("CREATE INDEX IF NOT EXISTS dialog_threads_by_thread_order ON threads (dialog_id, thread_order)"));
version = current_db_version();
}
return Status::OK();
}
// NB: must happen inside a transaction
Status drop_message_thread_db(SqliteDb &db, int version) {
if (version > current_db_version()) {
LOG(WARNING) << "Drop message_thread_db " << tag("version", version)
<< tag("current_db_version", current_db_version());
}
return db.exec("DROP TABLE IF EXISTS threads");
}
class MessageThreadDbImpl final : public MessageThreadDbSyncInterface {
public:
explicit MessageThreadDbImpl(SqliteDb db) : db_(std::move(db)) {
init().ensure();
}
Status init() {
TRY_RESULT_ASSIGN(add_thread_stmt_, db_.get_statement("INSERT OR REPLACE INTO threads VALUES(?1, ?2, ?3, ?4)"));
TRY_RESULT_ASSIGN(delete_thread_stmt_,
db_.get_statement("DELETE FROM threads WHERE dialog_id = ?1 AND thread_id = ?2"));
TRY_RESULT_ASSIGN(delete_all_dialog_threads_stmt_, db_.get_statement("DELETE FROM threads WHERE dialog_id = ?1"));
TRY_RESULT_ASSIGN(get_thread_stmt_,
db_.get_statement("SELECT data FROM threads WHERE dialog_id = ?1 AND thread_id = ?2"));
TRY_RESULT_ASSIGN(get_threads_stmt_,
db_.get_statement("SELECT data, dialog_id, thread_id, thread_order FROM threads WHERE dialog_id "
"= ?1 AND thread_order < ?2 ORDER BY thread_order DESC LIMIT ?3"));
// LOG(ERROR) << delete_thread_stmt_.explain().ok();
// LOG(ERROR) << delete_all_dialog_threads_stmt_.explain().ok();
// LOG(ERROR) << get_thread_stmt_.explain().ok();
// LOG(ERROR) << get_threads_stmt_.explain().ok();
// LOG(FATAL) << "EXPLAINED";
return Status::OK();
}
void add_message_thread(DialogId dialog_id, MessageId top_thread_message_id, int64 order, BufferSlice data) final {
SCOPE_EXIT {
add_thread_stmt_.reset();
};
add_thread_stmt_.bind_int64(1, dialog_id.get()).ensure();
add_thread_stmt_.bind_int64(2, top_thread_message_id.get()).ensure();
add_thread_stmt_.bind_int64(3, order).ensure();
add_thread_stmt_.bind_blob(4, data.as_slice()).ensure();
add_thread_stmt_.step().ensure();
}
void delete_message_thread(DialogId dialog_id, MessageId top_thread_message_id) final {
SCOPE_EXIT {
delete_thread_stmt_.reset();
};
delete_thread_stmt_.bind_int64(1, dialog_id.get()).ensure();
delete_thread_stmt_.bind_int64(2, top_thread_message_id.get()).ensure();
delete_thread_stmt_.step().ensure();
}
void delete_all_dialog_message_threads(DialogId dialog_id) final {
SCOPE_EXIT {
delete_all_dialog_threads_stmt_.reset();
};
delete_all_dialog_threads_stmt_.bind_int64(1, dialog_id.get()).ensure();
delete_all_dialog_threads_stmt_.step().ensure();
}
BufferSlice get_message_thread(DialogId dialog_id, MessageId top_thread_message_id) final {
SCOPE_EXIT {
get_thread_stmt_.reset();
};
get_thread_stmt_.bind_int64(1, dialog_id.get()).ensure();
get_thread_stmt_.bind_int64(2, top_thread_message_id.get()).ensure();
get_thread_stmt_.step().ensure();
if (!get_thread_stmt_.has_row()) {
return BufferSlice();
}
return BufferSlice(get_thread_stmt_.view_blob(0));
}
MessageThreadDbMessageThreads get_message_threads(DialogId dialog_id, int64 offset_order, int32 limit) final {
SCOPE_EXIT {
get_threads_stmt_.reset();
};
get_threads_stmt_.bind_int64(1, dialog_id.get()).ensure();
get_threads_stmt_.bind_int64(2, offset_order).ensure();
get_threads_stmt_.bind_int32(3, limit).ensure();
MessageThreadDbMessageThreads result;
result.next_order = offset_order;
get_threads_stmt_.step().ensure();
while (get_threads_stmt_.has_row()) {
BufferSlice data(get_threads_stmt_.view_blob(0));
result.next_order = get_threads_stmt_.view_int64(3);
LOG(INFO) << "Load thread of " << MessageId(get_threads_stmt_.view_int64(2)) << " in "
<< DialogId(get_threads_stmt_.view_int64(1)) << " with order " << result.next_order;
result.message_threads.emplace_back(std::move(data));
get_threads_stmt_.step().ensure();
}
return result;
}
Status begin_write_transaction() final {
return db_.begin_write_transaction();
}
Status commit_transaction() final {
return db_.commit_transaction();
}
private:
SqliteDb db_;
SqliteStatement add_thread_stmt_;
SqliteStatement delete_thread_stmt_;
SqliteStatement delete_all_dialog_threads_stmt_;
SqliteStatement get_thread_stmt_;
SqliteStatement get_threads_stmt_;
};
std::shared_ptr<MessageThreadDbSyncSafeInterface> create_message_thread_db_sync(
std::shared_ptr<SqliteConnectionSafe> sqlite_connection) {
class MessageThreadDbSyncSafe final : public MessageThreadDbSyncSafeInterface {
public:
explicit MessageThreadDbSyncSafe(std::shared_ptr<SqliteConnectionSafe> sqlite_connection)
: lsls_db_([safe_connection = std::move(sqlite_connection)] {
return make_unique<MessageThreadDbImpl>(safe_connection->get().clone());
}) {
}
MessageThreadDbSyncInterface &get() final {
return *lsls_db_.get();
}
private:
LazySchedulerLocalStorage<unique_ptr<MessageThreadDbSyncInterface>> lsls_db_;
};
return std::make_shared<MessageThreadDbSyncSafe>(std::move(sqlite_connection));
}
class MessageThreadDbAsync final : public MessageThreadDbAsyncInterface {
public:
MessageThreadDbAsync(std::shared_ptr<MessageThreadDbSyncSafeInterface> sync_db, int32 scheduler_id) {
impl_ = create_actor_on_scheduler<Impl>("MessageThreadDbActor", scheduler_id, std::move(sync_db));
}
void add_message_thread(DialogId dialog_id, MessageId top_thread_message_id, int64 order, BufferSlice data,
Promise<Unit> promise) final {
send_closure(impl_, &Impl::add_message_thread, dialog_id, top_thread_message_id, order, std::move(data),
std::move(promise));
}
void delete_message_thread(DialogId dialog_id, MessageId top_thread_message_id, Promise<Unit> promise) final {
send_closure(impl_, &Impl::delete_message_thread, dialog_id, top_thread_message_id, std::move(promise));
}
void delete_all_dialog_message_threads(DialogId dialog_id, Promise<Unit> promise) final {
send_closure(impl_, &Impl::delete_all_dialog_message_threads, dialog_id, std::move(promise));
}
void get_message_thread(DialogId dialog_id, MessageId top_thread_message_id, Promise<BufferSlice> promise) final {
send_closure_later(impl_, &Impl::get_message_thread, dialog_id, top_thread_message_id, std::move(promise));
}
void get_message_threads(DialogId dialog_id, int64 offset_order, int32 limit,
Promise<MessageThreadDbMessageThreads> promise) final {
send_closure_later(impl_, &Impl::get_message_threads, dialog_id, offset_order, limit, std::move(promise));
}
void close(Promise<Unit> promise) final {
send_closure_later(impl_, &Impl::close, std::move(promise));
}
void force_flush() final {
send_closure_later(impl_, &Impl::force_flush);
}
private:
class Impl final : public Actor {
public:
explicit Impl(std::shared_ptr<MessageThreadDbSyncSafeInterface> sync_db_safe)
: sync_db_safe_(std::move(sync_db_safe)) {
}
void add_message_thread(DialogId dialog_id, MessageId top_thread_message_id, int64 order, BufferSlice data,
Promise<Unit> promise) {
add_write_query([this, dialog_id, top_thread_message_id, order, data = std::move(data),
promise = std::move(promise)](Unit) mutable {
sync_db_->add_message_thread(dialog_id, top_thread_message_id, order, std::move(data));
on_write_result(std::move(promise));
});
}
void delete_message_thread(DialogId dialog_id, MessageId top_thread_message_id, Promise<Unit> promise) {
add_write_query([this, dialog_id, top_thread_message_id, promise = std::move(promise)](Unit) mutable {
sync_db_->delete_message_thread(dialog_id, top_thread_message_id);
on_write_result(std::move(promise));
});
}
void delete_all_dialog_message_threads(DialogId dialog_id, Promise<Unit> promise) {
add_write_query([this, dialog_id, promise = std::move(promise)](Unit) mutable {
sync_db_->delete_all_dialog_message_threads(dialog_id);
on_write_result(std::move(promise));
});
}
void on_write_result(Promise<Unit> &&promise) {
// We are inside a transaction and don't know how to handle errors
finished_writes_.push_back(std::move(promise));
}
void get_message_thread(DialogId dialog_id, MessageId top_thread_message_id, Promise<BufferSlice> promise) {
add_read_query();
promise.set_result(sync_db_->get_message_thread(dialog_id, top_thread_message_id));
}
void get_message_threads(DialogId dialog_id, int64 offset_order, int32 limit,
Promise<MessageThreadDbMessageThreads> promise) {
add_read_query();
promise.set_result(sync_db_->get_message_threads(dialog_id, offset_order, limit));
}
void close(Promise<> promise) {
do_flush();
sync_db_safe_.reset();
sync_db_ = nullptr;
promise.set_value(Unit());
stop();
}
void force_flush() {
do_flush();
LOG(INFO) << "MessageThreadDb flushed";
}
private:
std::shared_ptr<MessageThreadDbSyncSafeInterface> sync_db_safe_;
MessageThreadDbSyncInterface *sync_db_ = nullptr;
static constexpr size_t MAX_PENDING_QUERIES_COUNT{50};
static constexpr double MAX_PENDING_QUERIES_DELAY{0.01};
//NB: order is important, destructor of pending_writes_ will change finished_writes_
vector<Promise<Unit>> finished_writes_;
vector<Promise<Unit>> pending_writes_; // TODO use Action
double wakeup_at_ = 0;
template <class F>
void add_write_query(F &&f) {
pending_writes_.push_back(PromiseCreator::lambda(std::forward<F>(f)));
if (pending_writes_.size() > MAX_PENDING_QUERIES_COUNT) {
do_flush();
wakeup_at_ = 0;
} else if (wakeup_at_ == 0) {
wakeup_at_ = Time::now_cached() + MAX_PENDING_QUERIES_DELAY;
}
if (wakeup_at_ != 0) {
set_timeout_at(wakeup_at_);
}
}
void add_read_query() {
do_flush();
}
void do_flush() {
if (pending_writes_.empty()) {
return;
}
sync_db_->begin_write_transaction().ensure();
set_promises(pending_writes_);
sync_db_->commit_transaction().ensure();
set_promises(finished_writes_);
cancel_timeout();
}
void timeout_expired() final {
do_flush();
}
void start_up() final {
sync_db_ = &sync_db_safe_->get();
}
};
ActorOwn<Impl> impl_;
};
std::shared_ptr<MessageThreadDbAsyncInterface> create_message_thread_db_async(
std::shared_ptr<MessageThreadDbSyncSafeInterface> sync_db, int32 scheduler_id) {
return std::make_shared<MessageThreadDbAsync>(std::move(sync_db), scheduler_id);
}
} // namespace td
|