Mercurial > hg > orthanc-databases
comparison Framework/MySQL/MySQLDatabase.cpp @ 0:7cea966b6829
initial commit
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Wed, 04 Jul 2018 08:16:29 +0200 |
parents | |
children | 5a97c68a7a51 |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:7cea966b6829 |
---|---|
1 /** | |
2 * Orthanc - A Lightweight, RESTful DICOM Store | |
3 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics | |
4 * Department, University Hospital of Liege, Belgium | |
5 * Copyright (C) 2017-2018 Osimis S.A., Belgium | |
6 * | |
7 * This program is free software: you can redistribute it and/or | |
8 * modify it under the terms of the GNU Affero General Public License | |
9 * as published by the Free Software Foundation, either version 3 of | |
10 * the License, or (at your option) any later version. | |
11 * | |
12 * This program is distributed in the hope that it will be useful, but | |
13 * WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
15 * Affero General Public License for more details. | |
16 * | |
17 * You should have received a copy of the GNU Affero General Public License | |
18 * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
19 **/ | |
20 | |
21 | |
22 #include "MySQLDatabase.h" | |
23 | |
24 #include "MySQLResult.h" | |
25 #include "MySQLStatement.h" | |
26 #include "MySQLTransaction.h" | |
27 #include "../Common/Integer64Value.h" | |
28 | |
29 #include <Core/Logging.h> | |
30 #include <Core/OrthancException.h> | |
31 #include <Core/Toolbox.h> | |
32 | |
33 #include <mysql/errmsg.h> | |
34 #include <mysqld_error.h> | |
35 | |
36 #include <memory> | |
37 | |
38 namespace OrthancDatabases | |
39 { | |
40 void MySQLDatabase::Close() | |
41 { | |
42 if (mysql_ != NULL) | |
43 { | |
44 LOG(INFO) << "Closing connection to MySQL database"; | |
45 mysql_close(mysql_); | |
46 mysql_ = NULL; | |
47 } | |
48 } | |
49 | |
50 | |
51 void MySQLDatabase::CheckErrorCode(int code) | |
52 { | |
53 if (code == 0) | |
54 { | |
55 return; | |
56 } | |
57 else | |
58 { | |
59 LogError(); | |
60 | |
61 unsigned int error = mysql_errno(mysql_); | |
62 if (error == CR_SERVER_GONE_ERROR || | |
63 error == CR_SERVER_LOST || | |
64 error == ER_QUERY_INTERRUPTED) | |
65 { | |
66 throw Orthanc::OrthancException(Orthanc::ErrorCode_DatabaseUnavailable); | |
67 } | |
68 else | |
69 { | |
70 throw Orthanc::OrthancException(Orthanc::ErrorCode_Database); | |
71 } | |
72 } | |
73 } | |
74 | |
75 | |
76 MySQLDatabase::MySQLDatabase(const MySQLParameters& parameters) : | |
77 parameters_(parameters), | |
78 mysql_(NULL) | |
79 { | |
80 } | |
81 | |
82 | |
83 void MySQLDatabase::LogError() | |
84 { | |
85 if (mysql_ != NULL) | |
86 { | |
87 LOG(ERROR) << "MySQL error (" << mysql_errno(mysql_) | |
88 << "," << mysql_sqlstate(mysql_) | |
89 << "): " << mysql_error(mysql_); | |
90 } | |
91 } | |
92 | |
93 | |
94 MYSQL* MySQLDatabase::GetObject() | |
95 { | |
96 if (mysql_ == NULL) | |
97 { | |
98 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
99 } | |
100 else | |
101 { | |
102 return mysql_; | |
103 } | |
104 } | |
105 | |
106 | |
107 void MySQLDatabase::Open() | |
108 { | |
109 if (mysql_ != NULL) | |
110 { | |
111 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
112 } | |
113 | |
114 mysql_ = mysql_init(NULL); | |
115 if (mysql_ == NULL) | |
116 { | |
117 LOG(ERROR) << "Cannot initialize the MySQL connector"; | |
118 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); | |
119 } | |
120 | |
121 const char* db = (parameters_.GetDatabase().empty() ? NULL : | |
122 parameters_.GetDatabase().c_str()); | |
123 | |
124 const char* socket = (parameters_.GetUnixSocket().empty() ? NULL : | |
125 parameters_.GetUnixSocket().c_str()); | |
126 | |
127 if (mysql_real_connect(mysql_, | |
128 parameters_.GetHost().c_str(), | |
129 parameters_.GetUsername().c_str(), | |
130 parameters_.GetPassword().c_str(), db, | |
131 parameters_.GetPort(), socket, 0) == 0) | |
132 { | |
133 LogError(); | |
134 Close(); | |
135 throw Orthanc::OrthancException(Orthanc::ErrorCode_DatabaseUnavailable); | |
136 } | |
137 else | |
138 { | |
139 LOG(INFO) << "Successful connection to MySQL database"; | |
140 } | |
141 | |
142 if (mysql_set_character_set(mysql_, "utf8mb4") != 0) | |
143 { | |
144 LOG(ERROR) << "Cannot set the character set to UTF8"; | |
145 Close(); | |
146 throw Orthanc::OrthancException(Orthanc::ErrorCode_Database); | |
147 } | |
148 | |
149 if (parameters_.HasLock()) | |
150 { | |
151 try | |
152 { | |
153 Query query("SELECT GET_LOCK('Lock', 0);", false); | |
154 MySQLStatement statement(*this, query); | |
155 | |
156 MySQLTransaction t(*this); | |
157 Dictionary args; | |
158 | |
159 std::auto_ptr<IResult> result(t.Execute(statement, args)); | |
160 | |
161 if (result->IsDone() || | |
162 result->GetField(0).GetType() != ValueType_Integer64 || | |
163 dynamic_cast<const Integer64Value&>(result->GetField(0)).GetValue() != 1) | |
164 { | |
165 throw Orthanc::OrthancException(Orthanc::ErrorCode_Database); | |
166 } | |
167 | |
168 t.Commit(); | |
169 } | |
170 catch (Orthanc::OrthancException&) | |
171 { | |
172 LOG(ERROR) << "The MySQL database is locked by another instance of Orthanc"; | |
173 Close(); | |
174 throw Orthanc::OrthancException(Orthanc::ErrorCode_Database); | |
175 } | |
176 } | |
177 } | |
178 | |
179 | |
180 bool MySQLDatabase::DoesTableExist(MySQLTransaction& transaction, | |
181 const std::string& name) | |
182 { | |
183 if (mysql_ == NULL) | |
184 { | |
185 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
186 } | |
187 | |
188 for (size_t i = 0; i < name.length(); i++) | |
189 { | |
190 if (!isalnum(name[i])) | |
191 { | |
192 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | |
193 } | |
194 } | |
195 | |
196 Query query("SELECT COUNT(*) FROM information_schema.TABLES WHERE " | |
197 "(TABLE_SCHEMA = ${database}) AND (TABLE_NAME = ${table})", true); | |
198 query.SetType("database", ValueType_Utf8String); | |
199 query.SetType("table", ValueType_Utf8String); | |
200 | |
201 MySQLStatement statement(*this, query); | |
202 | |
203 Dictionary args; | |
204 args.SetUtf8Value("database", parameters_.GetDatabase()); | |
205 args.SetUtf8Value("table", name); | |
206 | |
207 std::auto_ptr<IResult> result(statement.Execute(transaction, args)); | |
208 return (!result->IsDone() && | |
209 result->GetFieldsCount() == 1 && | |
210 result->GetField(0).GetType() == ValueType_Integer64 && | |
211 dynamic_cast<const Integer64Value&>(result->GetField(0)).GetValue() == 1); | |
212 } | |
213 | |
214 | |
215 void MySQLDatabase::Execute(const std::string& sql) | |
216 { | |
217 if (mysql_ == NULL) | |
218 { | |
219 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
220 } | |
221 | |
222 // This emulates the behavior of "CLIENT_MULTI_STATEMENTS" in | |
223 // "mysql_real_connect()", avoiding to implement a loop over | |
224 // "mysql_query()" | |
225 std::vector<std::string> commands; | |
226 Orthanc::Toolbox::TokenizeString(commands, sql, ';'); | |
227 | |
228 for (size_t i = 0; i < commands.size(); i++) | |
229 { | |
230 std::string s = Orthanc::Toolbox::StripSpaces(commands[i]); | |
231 | |
232 if (!s.empty()) | |
233 { | |
234 // Replace the escape character "@" by a semicolon | |
235 std::replace(s.begin(), s.end(), '@', ';'); | |
236 | |
237 LOG(TRACE) << "MySQL: " << s; | |
238 CheckErrorCode(mysql_query(mysql_, s.c_str())); | |
239 } | |
240 } | |
241 } | |
242 | |
243 | |
244 IPrecompiledStatement* MySQLDatabase::Compile(const Query& query) | |
245 { | |
246 if (mysql_ == NULL) | |
247 { | |
248 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
249 } | |
250 | |
251 return new MySQLStatement(*this, query); | |
252 } | |
253 | |
254 | |
255 ITransaction* MySQLDatabase::CreateTransaction() | |
256 { | |
257 if (mysql_ == NULL) | |
258 { | |
259 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
260 } | |
261 | |
262 return new MySQLTransaction(*this); | |
263 } | |
264 | |
265 | |
266 void MySQLDatabase::GlobalFinalization() | |
267 { | |
268 mysql_library_end(); | |
269 } | |
270 } |