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