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