Mercurial > hg > orthanc-databases
annotate Framework/MySQL/MySQLStatement.cpp @ 189:b968e7bfa7f9
fix build against mariadb client
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Wed, 16 Dec 2020 15:49:32 +0100 |
parents | 8a4cc1f715eb |
children | 3236894320d6 |
rev | line source |
---|---|
0 | 1 /** |
2 * Orthanc - A Lightweight, RESTful DICOM Store | |
3 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics | |
4 * Department, University Hospital of Liege, Belgium | |
140
4cd7e45b671e
upgrade to year 2020
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
67
diff
changeset
|
5 * Copyright (C) 2017-2020 Osimis S.A., Belgium |
0 | 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 "MySQLStatement.h" | |
23 | |
24 #include "../Common/BinaryStringValue.h" | |
25 #include "../Common/FileValue.h" | |
26 #include "../Common/Integer64Value.h" | |
27 #include "../Common/NullValue.h" | |
28 #include "../Common/Utf8StringValue.h" | |
29 #include "MySQLResult.h" | |
30 | |
157
275e14f57f1e
replacing deprecated std::auto_ptr by std::unique_ptr
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
152
diff
changeset
|
31 #include <Compatibility.h> // For std::unique_ptr<> |
152 | 32 #include <Logging.h> |
33 #include <OrthancException.h> | |
0 | 34 |
35 #include <list> | |
36 #include <memory> | |
37 | |
38 namespace OrthancDatabases | |
39 { | |
40 class MySQLStatement::ResultField : public boost::noncopyable | |
41 { | |
42 private: | |
186 | 43 IValue* CreateIntegerValue(const MYSQL_BIND& bind) const |
0 | 44 { |
45 if (length_ != buffer_.size()) | |
46 { | |
47 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); | |
48 } | |
49 | |
50 switch (mysqlType_) | |
51 { | |
52 case MYSQL_TYPE_TINY: | |
53 if (bind.is_unsigned) | |
54 { | |
55 return new Integer64Value(*reinterpret_cast<const uint8_t*>(&buffer_[0])); | |
56 } | |
57 else | |
58 { | |
59 return new Integer64Value(*reinterpret_cast<const int8_t*>(&buffer_[0])); | |
60 } | |
61 | |
62 case MYSQL_TYPE_SHORT: | |
63 if (bind.is_unsigned) | |
64 { | |
65 return new Integer64Value(*reinterpret_cast<const uint16_t*>(&buffer_[0])); | |
66 } | |
67 else | |
68 { | |
69 return new Integer64Value(*reinterpret_cast<const int16_t*>(&buffer_[0])); | |
70 } | |
71 | |
72 break; | |
73 | |
74 case MYSQL_TYPE_LONG: | |
75 if (bind.is_unsigned) | |
76 { | |
77 return new Integer64Value(*reinterpret_cast<const uint32_t*>(&buffer_[0])); | |
78 } | |
79 else | |
80 { | |
81 return new Integer64Value(*reinterpret_cast<const int32_t*>(&buffer_[0])); | |
82 } | |
83 | |
84 break; | |
85 | |
86 case MYSQL_TYPE_LONGLONG: | |
87 if (bind.is_unsigned) | |
88 { | |
89 uint64_t value = *reinterpret_cast<const uint64_t*>(&buffer_[0]); | |
90 if (static_cast<uint64_t>(static_cast<int64_t>(value)) != value) | |
91 { | |
92 LOG(WARNING) << "Overflow in a 64 bit integer"; | |
93 } | |
94 | |
95 return new Integer64Value(static_cast<int64_t>(value)); | |
96 } | |
97 else | |
98 { | |
99 return new Integer64Value(*reinterpret_cast<const int64_t*>(&buffer_[0])); | |
100 } | |
101 | |
102 break; | |
103 | |
104 default: | |
105 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); | |
106 } | |
107 } | |
108 | |
109 | |
110 enum enum_field_types mysqlType_; | |
111 ValueType orthancType_; | |
112 std::string buffer_; | |
188 | 113 |
189
b968e7bfa7f9
fix build against mariadb client
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
188
diff
changeset
|
114 #if (MYSQL_VERSION_ID < 80000) || defined(MARIADB_VERSION_ID) |
0 | 115 my_bool isNull_; |
116 my_bool isError_; | |
188 | 117 #else |
118 bool isNull_; | |
119 bool isError_; | |
120 #endif | |
121 | |
0 | 122 unsigned long length_; |
123 | |
124 public: | |
186 | 125 explicit ResultField(const MYSQL_FIELD& field) : |
126 mysqlType_(field.type), | |
127 length_(0) | |
0 | 128 { |
129 // https://dev.mysql.com/doc/refman/8.0/en/c-api-data-structures.html | |
130 // https://dev.mysql.com/doc/refman/8.0/en/mysql-stmt-fetch.html => size of "buffer_" | |
131 switch (field.type) | |
132 { | |
133 case MYSQL_TYPE_TINY: | |
134 orthancType_ = ValueType_Integer64; | |
135 buffer_.resize(1); | |
136 break; | |
137 | |
138 case MYSQL_TYPE_SHORT: | |
139 orthancType_ = ValueType_Integer64; | |
140 buffer_.resize(2); | |
141 break; | |
142 | |
143 case MYSQL_TYPE_LONG: | |
144 orthancType_ = ValueType_Integer64; | |
145 buffer_.resize(4); | |
146 break; | |
147 | |
148 case MYSQL_TYPE_LONGLONG: | |
149 orthancType_ = ValueType_Integer64; | |
150 buffer_.resize(8); | |
151 break; | |
152 | |
153 case MYSQL_TYPE_STRING: | |
154 case MYSQL_TYPE_VAR_STRING: | |
155 case MYSQL_TYPE_BLOB: | |
156 // https://medium.com/@adamhooper/in-mysql-never-use-utf8-use-utf8mb4-11761243e434 | |
157 switch (field.charsetnr) | |
158 { | |
159 case 45: // utf8mb4_general_ci | |
160 case 46: // utf8mb4_bin | |
161 case 224: // utf8mb4_unicode_ci => RECOMMENDED collation | |
46
6a574d810b98
Compatibility with MySQL 8.0
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
23
diff
changeset
|
162 case 255: // utf8mb4_0900_ai_ci => necessary for MySQL 8.0 |
0 | 163 // https://stackoverflow.com/questions/766809/whats-the-difference-between-utf8-general-ci-and-utf8-unicode-ci |
164 orthancType_ = ValueType_Utf8String; | |
165 break; | |
166 | |
167 case 63: | |
168 orthancType_ = ValueType_BinaryString; | |
169 break; | |
170 | |
171 default: | |
172 LOG(ERROR) << "Unsupported MySQL charset: " << field.charsetnr; | |
173 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); | |
174 } | |
175 | |
176 if (field.max_length > 0) | |
177 { | |
178 buffer_.resize(field.max_length); | |
179 } | |
180 | |
181 break; | |
182 | |
183 default: | |
184 LOG(ERROR) << "MYSQL_TYPE not implemented: " << field.type; | |
185 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); | |
186 } | |
187 } | |
188 | |
189 enum enum_field_types GetMysqlType() const | |
190 { | |
191 return mysqlType_; | |
192 } | |
193 | |
194 ValueType GetOrthancType() const | |
195 { | |
196 return orthancType_; | |
197 } | |
198 | |
199 void PrepareBind(MYSQL_BIND& bind) | |
200 { | |
201 memset(&bind, 0, sizeof(bind)); | |
202 | |
9
eff482803d30
fix uninitialized values
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
0
diff
changeset
|
203 isNull_ = false; |
eff482803d30
fix uninitialized values
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
0
diff
changeset
|
204 isError_ = false; |
0 | 205 length_ = 0; |
206 | |
207 bind.buffer_length = buffer_.size(); | |
208 bind.buffer_type = mysqlType_; | |
209 bind.is_null = &isNull_; | |
210 bind.length = &length_; | |
211 | |
212 if (buffer_.empty()) | |
213 { | |
214 // Only fetches the actual size of the field (*): | |
215 // mysql_stmt_fetch_column() must be invoked afterward | |
216 bind.buffer = 0; | |
217 } | |
218 else | |
219 { | |
220 bind.buffer = &buffer_[0]; | |
221 bind.error = &isError_; | |
222 } | |
223 } | |
224 | |
225 | |
226 IValue* FetchValue(MySQLDatabase& database, | |
227 MYSQL_STMT& statement, | |
228 MYSQL_BIND& bind, | |
229 unsigned int column) const | |
230 { | |
231 if (isError_) | |
232 { | |
233 throw Orthanc::OrthancException(Orthanc::ErrorCode_Database); | |
234 } | |
235 else if (isNull_) | |
236 { | |
237 return new NullValue; | |
238 } | |
239 else if (orthancType_ == ValueType_Integer64) | |
240 { | |
241 return CreateIntegerValue(bind); | |
242 } | |
243 else if (orthancType_ == ValueType_Utf8String || | |
244 orthancType_ == ValueType_BinaryString) | |
245 { | |
246 std::string tmp; | |
247 tmp.resize(length_); | |
248 | |
249 if (!tmp.empty()) | |
250 { | |
251 if (buffer_.empty()) | |
252 { | |
253 bind.buffer = &tmp[0]; | |
254 bind.buffer_length = tmp.size(); | |
255 | |
256 database.CheckErrorCode(mysql_stmt_fetch_column(&statement, &bind, column, 0)); | |
257 } | |
258 else if (tmp.size() <= buffer_.size()) | |
259 { | |
260 memcpy(&tmp[0], &buffer_[0], length_); | |
261 } | |
262 else | |
263 { | |
264 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); | |
265 } | |
266 } | |
267 | |
268 if (orthancType_ == ValueType_Utf8String) | |
269 { | |
270 return new Utf8StringValue(tmp); | |
271 } | |
272 else | |
273 { | |
274 return new BinaryStringValue(tmp); | |
275 } | |
276 } | |
277 else | |
278 { | |
279 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); | |
280 } | |
281 } | |
282 }; | |
283 | |
284 | |
285 class MySQLStatement::ResultMetadata : public boost::noncopyable | |
286 { | |
287 private: | |
288 MYSQL_RES* metadata_; | |
289 | |
290 public: | |
291 ResultMetadata(MySQLDatabase& db, | |
292 MySQLStatement& statement) : | |
293 metadata_(NULL) | |
294 { | |
295 metadata_ = mysql_stmt_result_metadata(statement.GetObject()); | |
296 } | |
297 | |
298 ~ResultMetadata() | |
299 { | |
300 if (metadata_ != NULL) | |
301 { | |
302 mysql_free_result(metadata_); | |
303 } | |
304 } | |
305 | |
306 bool HasFields() const | |
307 { | |
308 return metadata_ != NULL; | |
309 } | |
310 | |
311 size_t GetFieldsCount() | |
312 { | |
313 if (HasFields()) | |
314 { | |
315 return mysql_num_fields(metadata_); | |
316 } | |
317 else | |
318 { | |
319 return 0; | |
320 } | |
321 } | |
322 | |
323 MYSQL_RES* GetObject() | |
324 { | |
325 return metadata_; | |
326 } | |
327 }; | |
328 | |
329 | |
330 void MySQLStatement::Close() | |
331 { | |
332 for (size_t i = 0; i < result_.size(); i++) | |
333 { | |
334 if (result_[i] != NULL) | |
335 { | |
336 delete result_[i]; | |
337 } | |
338 } | |
339 | |
340 if (statement_ != NULL) | |
341 { | |
342 mysql_stmt_close(statement_); | |
343 statement_ = NULL; | |
344 } | |
345 } | |
346 | |
347 | |
348 MySQLStatement::MySQLStatement(MySQLDatabase& db, | |
349 const Query& query) : | |
350 db_(db), | |
351 readOnly_(query.IsReadOnly()), | |
352 statement_(NULL), | |
353 formatter_(Dialect_MySQL) | |
354 { | |
355 std::string sql; | |
356 query.Format(sql, formatter_); | |
357 | |
358 statement_ = mysql_stmt_init(db.GetObject()); | |
359 if (statement_ == NULL) | |
360 { | |
361 db.LogError(); | |
362 throw Orthanc::OrthancException(Orthanc::ErrorCode_Database); | |
363 } | |
364 | |
169
c17f219cec42
replacing VLOG(1) by LOG(TRACE)
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
162
diff
changeset
|
365 LOG(TRACE) << "Preparing MySQL statement: " << sql; |
0 | 366 |
367 db_.CheckErrorCode(mysql_stmt_prepare(statement_, sql.c_str(), sql.size())); | |
368 | |
369 if (mysql_stmt_param_count(statement_) != formatter_.GetParametersCount()) | |
370 { | |
371 Close(); | |
372 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); | |
373 } | |
374 | |
375 try | |
376 { | |
377 ResultMetadata result(db, *this); | |
378 | |
379 if (result.HasFields()) | |
380 { | |
381 MYSQL_FIELD *field; | |
382 while ((field = mysql_fetch_field(result.GetObject()))) | |
383 { | |
384 result_.push_back(new ResultField(*field)); | |
385 } | |
386 } | |
387 | |
388 if (result_.size() != result.GetFieldsCount()) | |
389 { | |
390 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); | |
391 } | |
392 } | |
393 catch (Orthanc::OrthancException&) | |
394 { | |
395 Close(); | |
396 throw; | |
397 } | |
398 | |
399 if (query.IsReadOnly()) | |
400 { | |
401 unsigned long type = (unsigned long) CURSOR_TYPE_READ_ONLY; | |
402 mysql_stmt_attr_set(statement_, STMT_ATTR_CURSOR_TYPE, (void*) &type); | |
403 } | |
404 } | |
405 | |
406 | |
46
6a574d810b98
Compatibility with MySQL 8.0
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
23
diff
changeset
|
407 MySQLStatement::~MySQLStatement() |
6a574d810b98
Compatibility with MySQL 8.0
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
23
diff
changeset
|
408 { |
6a574d810b98
Compatibility with MySQL 8.0
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
23
diff
changeset
|
409 try |
6a574d810b98
Compatibility with MySQL 8.0
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
23
diff
changeset
|
410 { |
6a574d810b98
Compatibility with MySQL 8.0
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
23
diff
changeset
|
411 Close(); |
6a574d810b98
Compatibility with MySQL 8.0
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
23
diff
changeset
|
412 } |
6a574d810b98
Compatibility with MySQL 8.0
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
23
diff
changeset
|
413 catch (Orthanc::OrthancException&) |
6a574d810b98
Compatibility with MySQL 8.0
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
23
diff
changeset
|
414 { |
6a574d810b98
Compatibility with MySQL 8.0
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
23
diff
changeset
|
415 // Ignore possible exceptions due to connection loss |
6a574d810b98
Compatibility with MySQL 8.0
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
23
diff
changeset
|
416 } |
6a574d810b98
Compatibility with MySQL 8.0
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
23
diff
changeset
|
417 } |
6a574d810b98
Compatibility with MySQL 8.0
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
23
diff
changeset
|
418 |
6a574d810b98
Compatibility with MySQL 8.0
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
23
diff
changeset
|
419 |
0 | 420 MYSQL_STMT* MySQLStatement::GetObject() |
421 { | |
422 if (statement_ == NULL) | |
423 { | |
424 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
425 } | |
426 else | |
427 { | |
428 return statement_; | |
429 } | |
430 } | |
431 | |
432 | |
433 IValue* MySQLStatement::FetchResultField(size_t i) | |
434 { | |
435 if (i >= result_.size()) | |
436 { | |
437 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | |
438 } | |
439 else | |
440 { | |
441 assert(result_[i] != NULL); | |
442 return result_[i]->FetchValue(db_, *statement_, outputs_[i], i); | |
443 } | |
444 } | |
445 | |
446 | |
23
b2ff1cd2907a
handling of implicit transactions in DatabaseManager
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
9
diff
changeset
|
447 IResult* MySQLStatement::Execute(ITransaction& transaction, |
0 | 448 const Dictionary& parameters) |
449 { | |
450 std::list<long long int> int64Parameters; | |
451 | |
452 std::vector<MYSQL_BIND> inputs(formatter_.GetParametersCount()); | |
453 | |
454 for (size_t i = 0; i < inputs.size(); i++) | |
455 { | |
456 memset(&inputs[i], 0, sizeof(MYSQL_BIND)); | |
457 | |
458 const std::string& name = formatter_.GetParameterName(i); | |
459 if (!parameters.HasKey(name)) | |
460 { | |
461 LOG(ERROR) << "Missing required parameter in a SQL query: " << name; | |
462 throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentItem); | |
463 } | |
464 | |
465 ValueType type = formatter_.GetParameterType(i); | |
466 | |
467 const IValue& value = parameters.GetValue(name); | |
468 if (value.GetType() != type) | |
469 { | |
470 LOG(ERROR) << "Bad type of argument provided to a SQL query: " << name; | |
471 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadParameterType); | |
472 } | |
473 | |
474 // https://dev.mysql.com/doc/refman/8.0/en/c-api-prepared-statement-type-codes.html | |
475 switch (type) | |
476 { | |
477 case ValueType_Integer64: | |
478 { | |
479 int64Parameters.push_back(dynamic_cast<const Integer64Value&>(value).GetValue()); | |
480 inputs[i].buffer = &int64Parameters.back(); | |
481 inputs[i].buffer_type = MYSQL_TYPE_LONGLONG; | |
482 break; | |
483 } | |
484 | |
485 case ValueType_Utf8String: | |
486 { | |
487 const std::string& utf8 = dynamic_cast<const Utf8StringValue&>(value).GetContent(); | |
488 inputs[i].buffer = const_cast<char*>(utf8.c_str()); | |
489 inputs[i].buffer_length = utf8.size(); | |
490 inputs[i].buffer_type = MYSQL_TYPE_STRING; | |
491 break; | |
492 } | |
493 | |
494 case ValueType_BinaryString: | |
495 { | |
496 const std::string& content = dynamic_cast<const BinaryStringValue&>(value).GetContent(); | |
497 inputs[i].buffer = const_cast<char*>(content.c_str()); | |
498 inputs[i].buffer_length = content.size(); | |
499 inputs[i].buffer_type = MYSQL_TYPE_BLOB; | |
500 break; | |
501 } | |
502 | |
503 case ValueType_File: | |
504 { | |
505 const std::string& content = dynamic_cast<const FileValue&>(value).GetContent(); | |
506 inputs[i].buffer = const_cast<char*>(content.c_str()); | |
507 inputs[i].buffer_length = content.size(); | |
508 inputs[i].buffer_type = MYSQL_TYPE_BLOB; | |
509 break; | |
510 } | |
511 | |
512 case ValueType_Null: | |
513 { | |
514 inputs[i].buffer = NULL; | |
515 inputs[i].buffer_type = MYSQL_TYPE_NULL; | |
516 break; | |
517 } | |
518 | |
519 default: | |
520 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); | |
521 } | |
522 } | |
523 | |
524 if (!inputs.empty()) | |
525 { | |
526 db_.CheckErrorCode(mysql_stmt_bind_param(statement_, &inputs[0])); | |
527 } | |
528 | |
529 db_.CheckErrorCode(mysql_stmt_execute(statement_)); | |
530 | |
531 outputs_.resize(result_.size()); | |
532 | |
533 for (size_t i = 0; i < result_.size(); i++) | |
534 { | |
535 assert(result_[i] != NULL); | |
536 result_[i]->PrepareBind(outputs_[i]); | |
537 } | |
538 | |
539 if (!outputs_.empty()) | |
540 { | |
541 db_.CheckErrorCode(mysql_stmt_bind_result(statement_, &outputs_[0])); | |
542 db_.CheckErrorCode(mysql_stmt_store_result(statement_)); | |
543 } | |
544 | |
545 return new MySQLResult(db_, *this); | |
546 } | |
547 | |
548 | |
23
b2ff1cd2907a
handling of implicit transactions in DatabaseManager
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
9
diff
changeset
|
549 void MySQLStatement::ExecuteWithoutResult(ITransaction& transaction, |
0 | 550 const Dictionary& parameters) |
551 { | |
157
275e14f57f1e
replacing deprecated std::auto_ptr by std::unique_ptr
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
152
diff
changeset
|
552 std::unique_ptr<IResult> dummy(Execute(transaction, parameters)); |
0 | 553 } |
554 } |