1/**
2 * The Forgotten Server - a free and open-source MMORPG server emulator
3 * Copyright (C) 2019 Mark Samman <mark.samman@gmail.com>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20#ifndef FS_DATABASE_H_A484B0CDFDE542838F506DCE3D40C693
21#define FS_DATABASE_H_A484B0CDFDE542838F506DCE3D40C693
22
23#include <boost/lexical_cast.hpp>
24
25#include <mysql/mysql.h>
26
27class DBResult;
28using DBResult_ptr = std::shared_ptr<DBResult>;
29
30class Database
31{
32 public:
33 Database() = default;
34 ~Database();
35
36 // non-copyable
37 Database(const Database&) = delete;
38 Database& operator=(const Database&) = delete;
39
40 /**
41 * Singleton implementation.
42 *
43 * @return database connection handler singleton
44 */
45 static Database& getInstance()
46 {
47 static Database instance;
48 return instance;
49 }
50
51 /**
52 * Connects to the database
53 *
54 * @return true on successful connection, false on error
55 */
56 bool connect();
57
58 /**
59 * Executes command.
60 *
61 * Executes query which doesn't generates results (eg. INSERT, UPDATE, DELETE...).
62 *
63 * @param query command
64 * @return true on success, false on error
65 */
66 bool executeQuery(const std::string& query);
67
68 /**
69 * Queries database.
70 *
71 * Executes query which generates results (mostly SELECT).
72 *
73 * @return results object (nullptr on error)
74 */
75 DBResult_ptr storeQuery(const std::string& query);
76
77 /**
78 * Escapes string for query.
79 *
80 * Prepares string to fit SQL queries including quoting it.
81 *
82 * @param s string to be escaped
83 * @return quoted string
84 */
85 std::string escapeString(const std::string& s) const;
86
87 /**
88 * Escapes binary stream for query.
89 *
90 * Prepares binary stream to fit SQL queries.
91 *
92 * @param s binary stream
93 * @param length stream length
94 * @return quoted string
95 */
96 std::string escapeBlob(const char* s, uint32_t length) const;
97
98 /**
99 * Retrieve id of last inserted row
100 *
101 * @return id on success, 0 if last query did not result on any rows with auto_increment keys
102 */
103 uint64_t getLastInsertId() const {
104 return static_cast<uint64_t>(mysql_insert_id(handle));
105 }
106
107 /**
108 * Get database engine version
109 *
110 * @return the database engine version
111 */
112 static const char* getClientVersion() {
113 return mysql_get_client_info();
114 }
115
116 uint64_t getMaxPacketSize() const {
117 return maxPacketSize;
118 }
119
120 private:
121 /**
122 * Transaction related methods.
123 *
124 * Methods for starting, commiting and rolling back transaction. Each of the returns boolean value.
125 *
126 * @return true on success, false on error
127 */
128 bool beginTransaction();
129 bool rollback();
130 bool commit();
131
132 MYSQL* handle = nullptr;
133 std::recursive_mutex databaseLock;
134 uint64_t maxPacketSize = 1048576;
135
136 friend class DBTransaction;
137};
138
139class DBResult
140{
141 public:
142 explicit DBResult(MYSQL_RES* res);
143 ~DBResult();
144
145 // non-copyable
146 DBResult(const DBResult&) = delete;
147 DBResult& operator=(const DBResult&) = delete;
148
149 template<typename T>
150 T getNumber(const std::string& s) const
151 {
152 auto it = listNames.find(s);
153 if (it == listNames.end()) {
154 std::cout << "[Error - DBResult::getNumber] Column '" << s << "' doesn't exist in the result set" << std::endl;
155 return static_cast<T>(0);
156 }
157
158 if (row[it->second] == nullptr) {
159 return static_cast<T>(0);
160 }
161
162 T data;
163 try {
164 data = boost::lexical_cast<T>(row[it->second]);
165 } catch (boost::bad_lexical_cast&) {
166 data = 0;
167 }
168 return data;
169 }
170
171 std::string getString(const std::string& s) const;
172 const char* getStream(const std::string& s, unsigned long& size) const;
173
174 bool hasNext() const;
175 bool next();
176
177 private:
178 MYSQL_RES* handle;
179 MYSQL_ROW row;
180
181 std::map<std::string, size_t> listNames;
182
183 friend class Database;
184};
185
186/**
187 * INSERT statement.
188 */
189class DBInsert
190{
191 public:
192 explicit DBInsert(std::string query);
193 bool addRow(const std::string& row);
194 bool addRow(std::ostringstream& row);
195 bool execute();
196
197 private:
198 std::string query;
199 std::string values;
200 size_t length;
201};
202
203class DBTransaction
204{
205 public:
206 constexpr DBTransaction() = default;
207
208 ~DBTransaction() {
209 if (state == STATE_START) {
210 Database::getInstance().rollback();
211 }
212 }
213
214 // non-copyable
215 DBTransaction(const DBTransaction&) = delete;
216 DBTransaction& operator=(const DBTransaction&) = delete;
217
218 bool begin() {
219 state = STATE_START;
220 return Database::getInstance().beginTransaction();
221 }
222
223 bool commit() {
224 if (state != STATE_START) {
225 return false;
226 }
227
228 state = STATE_COMMIT;
229 return Database::getInstance().commit();
230 }
231
232 private:
233 enum TransactionStates_t {
234 STATE_NO_START,
235 STATE_START,
236 STATE_COMMIT,
237 };
238
239 TransactionStates_t state = STATE_NO_START;
240};
241
242#endif
243