1 #include "biboumi.h"
2 #ifdef USE_DATABASE
3
4 #include <database/select_query.hpp>
5 #include <database/save.hpp>
6 #include <database/database.hpp>
7 #include <utils/get_first_non_empty.hpp>
8 #include <utils/time.hpp>
9 #include <utils/uuid.hpp>
10
11 #include <config/config.hpp>
12 #include <database/sqlite3_engine.hpp>
13 #include <database/postgresql_engine.hpp>
14
15 #include <database/engine.hpp>
16 #include <database/index.hpp>
17
18 #include <memory>
19
20 std::unique_ptr<DatabaseEngine> Database::db;
21 Database::MucLogLineTable Database::muc_log_lines("muclogline_");
22 Database::GlobalOptionsTable Database::global_options("globaloptions_");
23 Database::IrcServerOptionsTable Database::irc_server_options("ircserveroptions_");
24 Database::IrcChannelOptionsTable Database::irc_channel_options("ircchanneloptions_");
25 Database::RosterTable Database::roster("roster");
26 Database::AfterConnectionCommandsTable Database::after_connection_commands("after_connection_commands_");
27 std::map<Database::CacheKey, Database::EncodingIn::real_type> Database::encoding_in_cache{};
28
GlobalPersistent()29 Database::GlobalPersistent::GlobalPersistent():
30 Column<bool>{Config::get_bool("persistent_by_default", false)}
31 {}
32
open(const std::string & filename)33 void Database::open(const std::string& filename)
34 {
35 // Try to open the specified database.
36 // Close and replace the previous database pointer if it succeeded. If it did
37 // not, just leave things untouched
38 std::unique_ptr<DatabaseEngine> new_db;
39 static const auto psql_prefix = "postgresql://"s;
40 static const auto psql_prefix2 = "postgres://"s;
41 if ((filename.substr(0, psql_prefix.size()) == psql_prefix) ||
42 (filename.substr(0, psql_prefix2.size()) == psql_prefix2))
43 new_db = PostgresqlEngine::open(filename);
44 else
45 new_db = Sqlite3Engine::open(filename);
46 if (!new_db)
47 return;
48 Database::db = std::move(new_db);
49 Database::muc_log_lines.create(*Database::db);
50 Database::muc_log_lines.upgrade(*Database::db);
51 Database::global_options.create(*Database::db);
52 Database::global_options.upgrade(*Database::db);
53 Database::irc_server_options.create(*Database::db);
54 Database::irc_server_options.upgrade(*Database::db);
55 Database::irc_channel_options.create(*Database::db);
56 Database::irc_channel_options.upgrade(*Database::db);
57 Database::roster.create(*Database::db);
58 Database::roster.upgrade(*Database::db);
59 Database::after_connection_commands.create(*Database::db);
60 Database::after_connection_commands.upgrade(*Database::db);
61 create_index<Database::Owner, Database::IrcChanName, Database::IrcServerName>(*Database::db, "archive_index", Database::muc_log_lines.get_name());
62 }
63
64
get_global_options(const std::string & owner)65 Database::GlobalOptions Database::get_global_options(const std::string& owner)
66 {
67 auto request = select(Database::global_options);
68 request.where() << Owner{} << "=" << owner;
69
70 auto result = request.execute(*Database::db);
71 if (result.size() == 1)
72 return result.front();
73 Database::GlobalOptions options{Database::global_options.get_name()};
74 options.col<Owner>() = owner;
75 return options;
76 }
77
get_irc_server_options(const std::string & owner,const std::string & server)78 Database::IrcServerOptions Database::get_irc_server_options(const std::string& owner, const std::string& server)
79 {
80 auto request = select(Database::irc_server_options);
81 request.where() << Owner{} << "=" << owner << " and " << Server{} << "=" << server;
82
83 auto result = request.execute(*Database::db);
84 if (result.size() == 1)
85 return result.front();
86 Database::IrcServerOptions options{Database::irc_server_options.get_name()};
87 options.col<Owner>() = owner;
88 options.col<Server>() = server;
89 return options;
90 }
91
get_after_connection_commands(const IrcServerOptions & server_options)92 Database::AfterConnectionCommands Database::get_after_connection_commands(const IrcServerOptions& server_options)
93 {
94 const auto id = server_options.col<Id>();
95 if (id == Id::unset_value)
96 return {};
97 auto request = select(Database::after_connection_commands);
98 request.where() << ForeignKey{} << "=" << id;
99 return request.execute(*Database::db);
100 }
101
set_after_connection_commands(const Database::IrcServerOptions & server_options,Database::AfterConnectionCommands & commands)102 void Database::set_after_connection_commands(const Database::IrcServerOptions& server_options, Database::AfterConnectionCommands& commands)
103 {
104 const auto id = server_options.col<Id>();
105 if (id == Id::unset_value)
106 return ;
107
108 Transaction transaction;
109 auto query = Database::after_connection_commands.del();
110 query.where() << ForeignKey{} << "=" << id;
111 query.execute(*Database::db);
112
113 for (auto& command: commands)
114 {
115 command.col<ForeignKey>() = server_options.col<Id>();
116 save(command, *Database::db);
117 }
118 }
119
get_irc_channel_options(const std::string & owner,const std::string & server,const std::string & channel)120 Database::IrcChannelOptions Database::get_irc_channel_options(const std::string& owner, const std::string& server, const std::string& channel)
121 {
122 auto request = select(Database::irc_channel_options);
123 request.where() << Owner{} << "=" << owner <<\
124 " and " << Server{} << "=" << server <<\
125 " and " << Channel{} << "=" << channel;
126 auto result = request.execute(*Database::db);
127 if (result.size() == 1)
128 return result.front();
129 Database::IrcChannelOptions options{Database::irc_channel_options.get_name()};
130 options.col<Owner>() = owner;
131 options.col<Server>() = server;
132 options.col<Channel>() = channel;
133 return options;
134 }
135
get_irc_channel_options_with_server_default(const std::string & owner,const std::string & server,const std::string & channel)136 Database::IrcChannelOptions Database::get_irc_channel_options_with_server_default(const std::string& owner, const std::string& server,
137 const std::string& channel)
138 {
139 auto coptions = Database::get_irc_channel_options(owner, server, channel);
140 auto soptions = Database::get_irc_server_options(owner, server);
141
142 coptions.col<EncodingIn>() = get_first_non_empty(coptions.col<EncodingIn>(),
143 soptions.col<EncodingIn>());
144 coptions.col<EncodingOut>() = get_first_non_empty(coptions.col<EncodingOut>(),
145 soptions.col<EncodingOut>());
146
147 coptions.col<MaxHistoryLength>() = get_first_non_empty(coptions.col<MaxHistoryLength>(),
148 soptions.col<MaxHistoryLength>());
149
150 return coptions;
151 }
152
get_irc_channel_options_with_server_and_global_default(const std::string & owner,const std::string & server,const std::string & channel)153 Database::IrcChannelOptions Database::get_irc_channel_options_with_server_and_global_default(const std::string& owner, const std::string& server, const std::string& channel)
154 {
155 auto coptions = Database::get_irc_channel_options(owner, server, channel);
156 auto soptions = Database::get_irc_server_options(owner, server);
157 auto goptions = Database::get_global_options(owner);
158
159 coptions.col<EncodingIn>() = get_first_non_empty(coptions.col<EncodingIn>(),
160 soptions.col<EncodingIn>());
161
162 coptions.col<EncodingOut>() = get_first_non_empty(coptions.col<EncodingOut>(),
163 soptions.col<EncodingOut>());
164
165 return coptions;
166 }
167
store_muc_message(const std::string & owner,const std::string & chan_name,const std::string & server_name,Database::time_point date,const std::string & body,const std::string & nick)168 std::string Database::store_muc_message(const std::string& owner, const std::string& chan_name,
169 const std::string& server_name, Database::time_point date,
170 const std::string& body, const std::string& nick)
171 {
172 auto line = Database::muc_log_lines.row();
173
174 auto uuid = Database::gen_uuid();
175
176 line.col<Uuid>() = uuid;
177 line.col<Owner>() = owner;
178 line.col<IrcChanName>() = chan_name;
179 line.col<IrcServerName>() = server_name;
180 line.col<Date>() = std::chrono::duration_cast<std::chrono::seconds>(date.time_since_epoch()).count();
181 line.col<Body>() = body;
182 line.col<Nick>() = nick;
183
184 save(line, *Database::db);
185
186 return uuid;
187 }
188
get_muc_logs(const std::string & owner,const std::string & chan_name,const std::string & server,std::size_t limit,const std::string & start,const std::string & end,const Id::real_type reference_record_id,Database::Paging paging)189 std::tuple<bool, std::vector<Database::MucLogLine>> Database::get_muc_logs(const std::string& owner, const std::string& chan_name, const std::string& server,
190 std::size_t limit, const std::string& start, const std::string& end, const Id::real_type reference_record_id, Database::Paging paging)
191 {
192 auto request = select(Database::muc_log_lines);
193 request.where() << Database::Owner{} << "=" << owner << \
194 " and " << Database::IrcChanName{} << "=" << chan_name << \
195 " and " << Database::IrcServerName{} << "=" << server;
196
197 if (!start.empty())
198 {
199 const auto start_time = utils::parse_datetime(start);
200 if (start_time != -1)
201 request << " and " << Database::Date{} << ">=" << start_time;
202 }
203 if (!end.empty())
204 {
205 const auto end_time = utils::parse_datetime(end);
206 if (end_time != -1)
207 request << " and " << Database::Date{} << "<=" << end_time;
208 }
209 if (reference_record_id != Id::unset_value)
210 {
211 request << " and " << Id{};
212 if (paging == Database::Paging::first)
213 request << ">";
214 else
215 request << "<";
216 request << reference_record_id;
217 }
218
219 if (paging == Database::Paging::first)
220 request.order_by() << Id{} << " ASC ";
221 else
222 request.order_by() << Id{} << " DESC ";
223
224 // Just a simple trick: to know whether we got the totality of the
225 // possible results matching this query (except for the limit), we just
226 // ask one more element. If we get that additional element, this means
227 // we don’t have everything. And then we just discard it. If we don’t
228 // have more, this means we have everything.
229 request.limit() << limit + 1;
230
231 auto result = request.execute(*Database::db);
232 bool complete = true;
233
234 if (result.size() == limit + 1)
235 {
236 complete = false;
237 result.erase(std::prev(result.end()));
238 }
239
240 if (paging == Database::Paging::first)
241 return {complete, result};
242 else
243 return {complete, {result.crbegin(), result.crend()}};
244 }
245
get_muc_log(const std::string & owner,const std::string & chan_name,const std::string & server,const std::string & uuid,const std::string & start,const std::string & end)246 Database::MucLogLine Database::get_muc_log(const std::string& owner, const std::string& chan_name, const std::string& server,
247 const std::string& uuid, const std::string& start, const std::string& end)
248 {
249 auto request = select(Database::muc_log_lines);
250 request.where() << Database::Owner{} << "=" << owner << \
251 " and " << Database::IrcChanName{} << "=" << chan_name << \
252 " and " << Database::IrcServerName{} << "=" << server << \
253 " and " << Database::Uuid{} << "=" << uuid;
254
255 if (!start.empty())
256 {
257 const auto start_time = utils::parse_datetime(start);
258 if (start_time != -1)
259 request << " and " << Database::Date{} << ">=" << start_time;
260 }
261 if (!end.empty())
262 {
263 const auto end_time = utils::parse_datetime(end);
264 if (end_time != -1)
265 request << " and " << Database::Date{} << "<=" << end_time;
266 }
267
268 auto result = request.execute(*Database::db);
269
270 if (result.empty())
271 throw Database::RecordNotFound{};
272 return result.front();
273 }
274
add_roster_item(const std::string & local,const std::string & remote)275 void Database::add_roster_item(const std::string& local, const std::string& remote)
276 {
277 auto roster_item = Database::roster.row();
278
279 roster_item.col<Database::LocalJid>() = local;
280 roster_item.col<Database::RemoteJid>() = remote;
281
282 save(roster_item, *Database::db);
283 }
284
delete_roster_item(const std::string & local,const std::string & remote)285 void Database::delete_roster_item(const std::string& local, const std::string& remote)
286 {
287 Query query("DELETE FROM "s + Database::roster.get_name());
288 query << " WHERE " << Database::RemoteJid{} << "=" << remote << \
289 " AND " << Database::LocalJid{} << "=" << local;
290
291 // query.execute(*Database::db);
292 }
293
has_roster_item(const std::string & local,const std::string & remote)294 bool Database::has_roster_item(const std::string& local, const std::string& remote)
295 {
296 auto query = select(Database::roster);
297 query.where() << Database::LocalJid{} << "=" << local << \
298 " and " << Database::RemoteJid{} << "=" << remote;
299
300 auto res = query.execute(*Database::db);
301
302 return !res.empty();
303 }
304
get_contact_list(const std::string & local)305 std::vector<Database::RosterItem> Database::get_contact_list(const std::string& local)
306 {
307 auto query = select(Database::roster);
308 query.where() << Database::LocalJid{} << "=" << local;
309
310 return query.execute(*Database::db);
311 }
312
get_full_roster()313 std::vector<Database::RosterItem> Database::get_full_roster()
314 {
315 auto query = select(Database::roster);
316
317 return query.execute(*Database::db);
318 }
319
close()320 void Database::close()
321 {
322 Database::db = nullptr;
323 }
324
gen_uuid()325 std::string Database::gen_uuid()
326 {
327 return utils::gen_uuid();
328 }
329
Transaction()330 Transaction::Transaction()
331 {
332 const auto result = Database::raw_exec("BEGIN");
333 if (std::get<bool>(result) == false)
334 log_error("Failed to create SQL transaction: ", std::get<std::string>(result));
335 else
336 this->success = true;
337 }
338
~Transaction()339 Transaction::~Transaction()
340 {
341 if (this->success)
342 {
343 const auto result = Database::raw_exec("END");
344 if (std::get<bool>(result) == false)
345 log_error("Failed to end SQL transaction: ", std::get<std::string>(result));
346 }
347 }
348 #endif
349