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