Mercurial > hg > orthanc-databases
annotate Framework/PostgreSQL/PostgreSQLStatement.cpp @ 46:6a574d810b98
Compatibility with MySQL 8.0
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Wed, 18 Jul 2018 12:27:40 +0200 |
parents | b2ff1cd2907a |
children | 318c1ccb787c |
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 | |
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 "PostgreSQLStatement.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/ResultBase.h" | |
29 #include "../Common/Utf8StringValue.h" | |
30 #include "PostgreSQLResult.h" | |
31 | |
32 #include <Core/Logging.h> | |
33 #include <Core/OrthancException.h> | |
34 #include <Core/Toolbox.h> | |
35 | |
36 #include <cassert> | |
37 | |
38 // PostgreSQL includes | |
39 #include <libpq-fe.h> | |
40 #include <c.h> | |
41 #include <catalog/pg_type.h> | |
42 | |
43 #include <Core/Endianness.h> | |
44 | |
45 | |
46 namespace OrthancDatabases | |
47 { | |
48 class PostgreSQLStatement::Inputs : public boost::noncopyable | |
49 { | |
50 private: | |
51 std::vector<char*> values_; | |
52 std::vector<int> sizes_; | |
53 | |
54 static char* Allocate(const void* source, int size) | |
55 { | |
56 if (size == 0) | |
57 { | |
58 return NULL; | |
59 } | |
60 else | |
61 { | |
62 char* ptr = reinterpret_cast<char*>(malloc(size)); | |
63 | |
64 if (source != NULL) | |
65 { | |
66 memcpy(ptr, source, size); | |
67 } | |
68 | |
69 return ptr; | |
70 } | |
71 } | |
72 | |
73 void Resize(size_t size) | |
74 { | |
75 // Shrinking of the vector | |
76 for (size_t i = size; i < values_.size(); i++) | |
77 { | |
78 if (values_[i] != NULL) | |
79 free(values_[i]); | |
80 } | |
81 | |
82 values_.resize(size, NULL); | |
83 sizes_.resize(size, 0); | |
84 } | |
85 | |
86 void EnlargeForIndex(size_t index) | |
87 { | |
88 if (index >= values_.size()) | |
89 { | |
90 // The vector is too small | |
91 Resize(index + 1); | |
92 } | |
93 } | |
94 | |
95 public: | |
96 Inputs() | |
97 { | |
98 } | |
99 | |
100 ~Inputs() | |
101 { | |
102 Resize(0); | |
103 } | |
104 | |
105 void SetItem(size_t pos, const void* source, int size) | |
106 { | |
107 EnlargeForIndex(pos); | |
108 | |
109 if (sizes_[pos] == size) | |
110 { | |
111 if (source && size != 0) | |
112 { | |
113 memcpy(values_[pos], source, size); | |
114 } | |
115 } | |
116 else | |
117 { | |
118 if (values_[pos] != NULL) | |
119 { | |
120 free(values_[pos]); | |
121 } | |
122 | |
123 values_[pos] = Allocate(source, size); | |
124 sizes_[pos] = size; | |
125 } | |
126 } | |
127 | |
128 void SetItem(size_t pos, int size) | |
129 { | |
130 SetItem(pos, NULL, size); | |
131 } | |
132 | |
133 void* GetItem(size_t pos) const | |
134 { | |
135 if (pos >= values_.size()) | |
136 { | |
137 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | |
138 } | |
139 | |
140 return values_[pos]; | |
141 } | |
142 | |
143 const std::vector<char*>& GetValues() const | |
144 { | |
145 return values_; | |
146 } | |
147 | |
148 const std::vector<int>& GetSizes() const | |
149 { | |
150 return sizes_; | |
151 } | |
152 }; | |
153 | |
154 | |
155 void PostgreSQLStatement::Prepare() | |
156 { | |
157 if (id_.size() > 0) | |
158 { | |
159 // Already prepared | |
160 return; | |
161 } | |
162 | |
163 for (size_t i = 0; i < oids_.size(); i++) | |
164 { | |
165 if (oids_[i] == 0) | |
166 { | |
167 // The type of an input parameter was not set | |
168 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
169 } | |
170 } | |
171 | |
172 id_ = Orthanc::Toolbox::GenerateUuid(); | |
173 | |
174 const unsigned int* tmp = oids_.size() ? &oids_[0] : NULL; | |
175 | |
176 PGresult* result = PQprepare(reinterpret_cast<PGconn*>(database_.pg_), | |
177 id_.c_str(), sql_.c_str(), oids_.size(), tmp); | |
178 | |
179 if (result == NULL) | |
180 { | |
181 id_.clear(); | |
182 database_.ThrowException(true); | |
183 } | |
184 | |
185 bool ok = (PQresultStatus(result) == PGRES_COMMAND_OK); | |
186 if (ok) | |
187 { | |
188 PQclear(result); | |
189 } | |
190 else | |
191 { | |
192 std::string message = PQresultErrorMessage(result); | |
193 PQclear(result); | |
194 id_.clear(); | |
195 LOG(ERROR) << "PostgreSQL error: " << message; | |
196 database_.ThrowException(false); | |
197 } | |
198 } | |
199 | |
200 | |
201 void PostgreSQLStatement::Unprepare() | |
202 { | |
203 if (id_.size() > 0) | |
204 { | |
205 // "Although there is no libpq function for deleting a | |
206 // prepared statement, the SQL DEALLOCATE statement can be | |
207 // used for that purpose." | |
208 //database_.Execute("DEALLOCATE " + id_); | |
209 } | |
210 | |
211 id_.clear(); | |
212 } | |
213 | |
214 | |
215 void PostgreSQLStatement::DeclareInputInternal(unsigned int param, | |
216 unsigned int /*Oid*/ type) | |
217 { | |
218 Unprepare(); | |
219 | |
220 if (oids_.size() <= param) | |
221 { | |
222 oids_.resize(param + 1, 0); | |
223 binary_.resize(param + 1); | |
224 } | |
225 | |
226 oids_[param] = type; | |
227 binary_[param] = (type == TEXTOID || type == BYTEAOID || type == OIDOID) ? 0 : 1; | |
228 } | |
229 | |
230 | |
231 void PostgreSQLStatement::DeclareInputInteger(unsigned int param) | |
232 { | |
233 DeclareInputInternal(param, INT4OID); | |
234 } | |
235 | |
236 | |
237 void PostgreSQLStatement::DeclareInputInteger64(unsigned int param) | |
238 { | |
239 DeclareInputInternal(param, INT8OID); | |
240 } | |
241 | |
242 | |
243 void PostgreSQLStatement::DeclareInputString(unsigned int param) | |
244 { | |
245 DeclareInputInternal(param, TEXTOID); | |
246 } | |
247 | |
248 | |
249 void PostgreSQLStatement::DeclareInputBinary(unsigned int param) | |
250 { | |
251 DeclareInputInternal(param, BYTEAOID); | |
252 } | |
253 | |
254 | |
255 void PostgreSQLStatement::DeclareInputLargeObject(unsigned int param) | |
256 { | |
257 DeclareInputInternal(param, OIDOID); | |
258 } | |
259 | |
260 | |
261 void* /* PGresult* */ PostgreSQLStatement::Execute() | |
262 { | |
263 Prepare(); | |
264 | |
265 PGresult* result; | |
266 | |
267 if (oids_.size() == 0) | |
268 { | |
269 // No parameter | |
270 result = PQexecPrepared(reinterpret_cast<PGconn*>(database_.pg_), | |
271 id_.c_str(), 0, NULL, NULL, NULL, 1); | |
272 } | |
273 else | |
274 { | |
275 // At least 1 parameter | |
276 result = PQexecPrepared(reinterpret_cast<PGconn*>(database_.pg_), | |
277 id_.c_str(), | |
278 oids_.size(), | |
279 &inputs_->GetValues()[0], | |
280 &inputs_->GetSizes()[0], | |
281 &binary_[0], | |
282 1); | |
283 } | |
284 | |
285 if (result == NULL) | |
286 { | |
287 database_.ThrowException(true); | |
288 } | |
289 | |
290 return result; | |
291 } | |
292 | |
293 | |
294 PostgreSQLStatement::PostgreSQLStatement(PostgreSQLDatabase& database, | |
295 const std::string& sql, | |
296 bool readOnly) : | |
297 database_(database), | |
298 readOnly_(readOnly), | |
299 sql_(sql), | |
300 inputs_(new Inputs), | |
301 formatter_(Dialect_PostgreSQL) | |
302 { | |
303 LOG(TRACE) << "PostgreSQL: " << sql; | |
304 } | |
305 | |
306 | |
307 PostgreSQLStatement::PostgreSQLStatement(PostgreSQLDatabase& database, | |
308 const Query& query) : | |
309 database_(database), | |
310 readOnly_(query.IsReadOnly()), | |
311 inputs_(new Inputs), | |
312 formatter_(Dialect_PostgreSQL) | |
313 { | |
314 query.Format(sql_, formatter_); | |
315 LOG(TRACE) << "PostgreSQL: " << sql_; | |
316 | |
317 for (size_t i = 0; i < formatter_.GetParametersCount(); i++) | |
318 { | |
319 switch (formatter_.GetParameterType(i)) | |
320 { | |
321 case ValueType_Integer64: | |
322 DeclareInputInteger64(i); | |
323 break; | |
324 | |
325 case ValueType_Utf8String: | |
326 DeclareInputString(i); | |
327 break; | |
328 | |
329 case ValueType_BinaryString: | |
330 DeclareInputBinary(i); | |
331 break; | |
332 | |
333 case ValueType_File: | |
334 DeclareInputLargeObject(i); | |
335 break; | |
336 | |
337 case ValueType_Null: | |
338 default: | |
339 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); | |
340 } | |
341 } | |
342 } | |
343 | |
344 | |
46
6a574d810b98
Compatibility with MySQL 8.0
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
23
diff
changeset
|
345 PostgreSQLStatement::~PostgreSQLStatement() |
6a574d810b98
Compatibility with MySQL 8.0
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
23
diff
changeset
|
346 { |
6a574d810b98
Compatibility with MySQL 8.0
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
23
diff
changeset
|
347 try |
6a574d810b98
Compatibility with MySQL 8.0
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
23
diff
changeset
|
348 { |
6a574d810b98
Compatibility with MySQL 8.0
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
23
diff
changeset
|
349 Unprepare(); |
6a574d810b98
Compatibility with MySQL 8.0
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
23
diff
changeset
|
350 } |
6a574d810b98
Compatibility with MySQL 8.0
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
23
diff
changeset
|
351 catch (Orthanc::OrthancException&) |
6a574d810b98
Compatibility with MySQL 8.0
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
23
diff
changeset
|
352 { |
6a574d810b98
Compatibility with MySQL 8.0
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
23
diff
changeset
|
353 // Ignore possible exceptions due to connection loss |
6a574d810b98
Compatibility with MySQL 8.0
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
23
diff
changeset
|
354 } |
6a574d810b98
Compatibility with MySQL 8.0
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
23
diff
changeset
|
355 } |
6a574d810b98
Compatibility with MySQL 8.0
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
23
diff
changeset
|
356 |
6a574d810b98
Compatibility with MySQL 8.0
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
23
diff
changeset
|
357 |
0 | 358 void PostgreSQLStatement::Run() |
359 { | |
360 PGresult* result = reinterpret_cast<PGresult*>(Execute()); | |
361 assert(result != NULL); // An exception would have been thrown otherwise | |
362 | |
363 bool ok = (PQresultStatus(result) == PGRES_COMMAND_OK || | |
364 PQresultStatus(result) == PGRES_TUPLES_OK); | |
365 if (ok) | |
366 { | |
367 PQclear(result); | |
368 } | |
369 else | |
370 { | |
371 std::string error = PQresultErrorMessage(result); | |
372 PQclear(result); | |
373 LOG(ERROR) << "PostgreSQL error: " << error; | |
374 database_.ThrowException(false); | |
375 } | |
376 } | |
377 | |
378 | |
379 void PostgreSQLStatement::BindNull(unsigned int param) | |
380 { | |
381 if (param >= oids_.size()) | |
382 { | |
383 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | |
384 } | |
385 | |
386 inputs_->SetItem(param, 0); | |
387 } | |
388 | |
389 | |
390 void PostgreSQLStatement::BindInteger(unsigned int param, | |
391 int value) | |
392 { | |
393 if (param >= oids_.size()) | |
394 { | |
395 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | |
396 } | |
397 | |
398 if (oids_[param] != INT4OID) | |
399 { | |
400 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadParameterType); | |
401 } | |
402 | |
403 assert(sizeof(int32_t) == 4); | |
404 int32_t v = htobe32(static_cast<int32_t>(value)); | |
405 inputs_->SetItem(param, &v, sizeof(int32_t)); | |
406 } | |
407 | |
408 | |
409 void PostgreSQLStatement::BindInteger64(unsigned int param, | |
410 int64_t value) | |
411 { | |
412 if (param >= oids_.size()) | |
413 { | |
414 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | |
415 } | |
416 | |
417 if (oids_[param] != INT8OID) | |
418 { | |
419 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadParameterType); | |
420 } | |
421 | |
422 assert(sizeof(int64_t) == 8); | |
423 int64_t v = htobe64(value); | |
424 inputs_->SetItem(param, &v, sizeof(int64_t)); | |
425 } | |
426 | |
427 | |
428 void PostgreSQLStatement::BindString(unsigned int param, | |
429 const std::string& value) | |
430 { | |
431 if (param >= oids_.size()) | |
432 { | |
433 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | |
434 } | |
435 | |
436 if (oids_[param] != TEXTOID && oids_[param] != BYTEAOID) | |
437 { | |
438 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadParameterType); | |
439 } | |
440 | |
441 if (value.size() == 0) | |
442 { | |
443 inputs_->SetItem(param, "", 1 /* end-of-string character */); | |
444 } | |
445 else | |
446 { | |
447 inputs_->SetItem(param, value.c_str(), | |
448 value.size() + 1); // "+1" for end-of-string character | |
449 } | |
450 } | |
451 | |
452 | |
453 void PostgreSQLStatement::BindLargeObject(unsigned int param, | |
454 const PostgreSQLLargeObject& value) | |
455 { | |
456 if (param >= oids_.size()) | |
457 { | |
458 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | |
459 } | |
460 | |
461 if (oids_[param] != OIDOID) | |
462 { | |
463 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadParameterType); | |
464 } | |
465 | |
466 inputs_->SetItem(param, value.GetOid().c_str(), | |
467 value.GetOid().size() + 1); // "+1" for end-of-string character | |
468 } | |
469 | |
470 | |
471 class PostgreSQLStatement::ResultWrapper : public ResultBase | |
472 { | |
473 private: | |
474 std::auto_ptr<PostgreSQLResult> result_; | |
475 | |
476 protected: | |
477 virtual IValue* FetchField(size_t index) | |
478 { | |
479 return result_->GetValue(index); | |
480 } | |
481 | |
482 public: | |
483 ResultWrapper(PostgreSQLStatement& statement) : | |
484 result_(new PostgreSQLResult(statement)) | |
485 { | |
486 SetFieldsCount(result_->GetColumnsCount()); | |
487 FetchFields(); | |
488 } | |
489 | |
490 virtual void Next() | |
491 { | |
492 result_->Next(); | |
493 FetchFields(); | |
494 } | |
495 | |
496 virtual bool IsDone() const | |
497 { | |
498 return result_->IsDone(); | |
499 } | |
500 }; | |
501 | |
502 | |
23
b2ff1cd2907a
handling of implicit transactions in DatabaseManager
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
0
diff
changeset
|
503 IResult* PostgreSQLStatement::Execute(ITransaction& transaction, |
0 | 504 const Dictionary& parameters) |
505 { | |
506 for (size_t i = 0; i < formatter_.GetParametersCount(); i++) | |
507 { | |
508 const std::string& name = formatter_.GetParameterName(i); | |
509 | |
510 switch (formatter_.GetParameterType(i)) | |
511 { | |
512 case ValueType_Integer64: | |
513 BindInteger64(i, dynamic_cast<const Integer64Value&>(parameters.GetValue(name)).GetValue()); | |
514 break; | |
515 | |
516 case ValueType_Null: | |
517 BindNull(i); | |
518 break; | |
519 | |
520 case ValueType_Utf8String: | |
521 BindString(i, dynamic_cast<const Utf8StringValue&> | |
522 (parameters.GetValue(name)).GetContent()); | |
523 break; | |
524 | |
525 case ValueType_BinaryString: | |
526 BindString(i, dynamic_cast<const BinaryStringValue&> | |
527 (parameters.GetValue(name)).GetContent()); | |
528 break; | |
529 | |
530 case ValueType_File: | |
531 { | |
532 const FileValue& blob = | |
533 dynamic_cast<const FileValue&>(parameters.GetValue(name)); | |
534 | |
535 PostgreSQLLargeObject largeObject(database_, blob.GetContent()); | |
536 BindLargeObject(i, largeObject); | |
537 break; | |
538 } | |
539 | |
540 default: | |
541 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); | |
542 } | |
543 } | |
544 | |
545 return new ResultWrapper(*this); | |
546 } | |
547 | |
548 | |
23
b2ff1cd2907a
handling of implicit transactions in DatabaseManager
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
0
diff
changeset
|
549 void PostgreSQLStatement::ExecuteWithoutResult(ITransaction& transaction, |
0 | 550 const Dictionary& parameters) |
551 { | |
552 std::auto_ptr<IResult> dummy(Execute(transaction, parameters)); | |
553 } | |
554 } |