Mercurial > hg > orthanc-databases
annotate Framework/PostgreSQL/PostgreSQLStatement.cpp @ 28:c0cb5d2cd696
checks depending on Orthanc version
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Mon, 16 Jul 2018 14:48:43 +0200 |
parents | b2ff1cd2907a |
children | 6a574d810b98 |
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 | |
345 void PostgreSQLStatement::Run() | |
346 { | |
347 PGresult* result = reinterpret_cast<PGresult*>(Execute()); | |
348 assert(result != NULL); // An exception would have been thrown otherwise | |
349 | |
350 bool ok = (PQresultStatus(result) == PGRES_COMMAND_OK || | |
351 PQresultStatus(result) == PGRES_TUPLES_OK); | |
352 if (ok) | |
353 { | |
354 PQclear(result); | |
355 } | |
356 else | |
357 { | |
358 std::string error = PQresultErrorMessage(result); | |
359 PQclear(result); | |
360 LOG(ERROR) << "PostgreSQL error: " << error; | |
361 database_.ThrowException(false); | |
362 } | |
363 } | |
364 | |
365 | |
366 void PostgreSQLStatement::BindNull(unsigned int param) | |
367 { | |
368 if (param >= oids_.size()) | |
369 { | |
370 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | |
371 } | |
372 | |
373 inputs_->SetItem(param, 0); | |
374 } | |
375 | |
376 | |
377 void PostgreSQLStatement::BindInteger(unsigned int param, | |
378 int value) | |
379 { | |
380 if (param >= oids_.size()) | |
381 { | |
382 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | |
383 } | |
384 | |
385 if (oids_[param] != INT4OID) | |
386 { | |
387 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadParameterType); | |
388 } | |
389 | |
390 assert(sizeof(int32_t) == 4); | |
391 int32_t v = htobe32(static_cast<int32_t>(value)); | |
392 inputs_->SetItem(param, &v, sizeof(int32_t)); | |
393 } | |
394 | |
395 | |
396 void PostgreSQLStatement::BindInteger64(unsigned int param, | |
397 int64_t value) | |
398 { | |
399 if (param >= oids_.size()) | |
400 { | |
401 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | |
402 } | |
403 | |
404 if (oids_[param] != INT8OID) | |
405 { | |
406 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadParameterType); | |
407 } | |
408 | |
409 assert(sizeof(int64_t) == 8); | |
410 int64_t v = htobe64(value); | |
411 inputs_->SetItem(param, &v, sizeof(int64_t)); | |
412 } | |
413 | |
414 | |
415 void PostgreSQLStatement::BindString(unsigned int param, | |
416 const std::string& value) | |
417 { | |
418 if (param >= oids_.size()) | |
419 { | |
420 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | |
421 } | |
422 | |
423 if (oids_[param] != TEXTOID && oids_[param] != BYTEAOID) | |
424 { | |
425 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadParameterType); | |
426 } | |
427 | |
428 if (value.size() == 0) | |
429 { | |
430 inputs_->SetItem(param, "", 1 /* end-of-string character */); | |
431 } | |
432 else | |
433 { | |
434 inputs_->SetItem(param, value.c_str(), | |
435 value.size() + 1); // "+1" for end-of-string character | |
436 } | |
437 } | |
438 | |
439 | |
440 void PostgreSQLStatement::BindLargeObject(unsigned int param, | |
441 const PostgreSQLLargeObject& value) | |
442 { | |
443 if (param >= oids_.size()) | |
444 { | |
445 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | |
446 } | |
447 | |
448 if (oids_[param] != OIDOID) | |
449 { | |
450 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadParameterType); | |
451 } | |
452 | |
453 inputs_->SetItem(param, value.GetOid().c_str(), | |
454 value.GetOid().size() + 1); // "+1" for end-of-string character | |
455 } | |
456 | |
457 | |
458 class PostgreSQLStatement::ResultWrapper : public ResultBase | |
459 { | |
460 private: | |
461 std::auto_ptr<PostgreSQLResult> result_; | |
462 | |
463 protected: | |
464 virtual IValue* FetchField(size_t index) | |
465 { | |
466 return result_->GetValue(index); | |
467 } | |
468 | |
469 public: | |
470 ResultWrapper(PostgreSQLStatement& statement) : | |
471 result_(new PostgreSQLResult(statement)) | |
472 { | |
473 SetFieldsCount(result_->GetColumnsCount()); | |
474 FetchFields(); | |
475 } | |
476 | |
477 virtual void Next() | |
478 { | |
479 result_->Next(); | |
480 FetchFields(); | |
481 } | |
482 | |
483 virtual bool IsDone() const | |
484 { | |
485 return result_->IsDone(); | |
486 } | |
487 }; | |
488 | |
489 | |
23
b2ff1cd2907a
handling of implicit transactions in DatabaseManager
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
0
diff
changeset
|
490 IResult* PostgreSQLStatement::Execute(ITransaction& transaction, |
0 | 491 const Dictionary& parameters) |
492 { | |
493 for (size_t i = 0; i < formatter_.GetParametersCount(); i++) | |
494 { | |
495 const std::string& name = formatter_.GetParameterName(i); | |
496 | |
497 switch (formatter_.GetParameterType(i)) | |
498 { | |
499 case ValueType_Integer64: | |
500 BindInteger64(i, dynamic_cast<const Integer64Value&>(parameters.GetValue(name)).GetValue()); | |
501 break; | |
502 | |
503 case ValueType_Null: | |
504 BindNull(i); | |
505 break; | |
506 | |
507 case ValueType_Utf8String: | |
508 BindString(i, dynamic_cast<const Utf8StringValue&> | |
509 (parameters.GetValue(name)).GetContent()); | |
510 break; | |
511 | |
512 case ValueType_BinaryString: | |
513 BindString(i, dynamic_cast<const BinaryStringValue&> | |
514 (parameters.GetValue(name)).GetContent()); | |
515 break; | |
516 | |
517 case ValueType_File: | |
518 { | |
519 const FileValue& blob = | |
520 dynamic_cast<const FileValue&>(parameters.GetValue(name)); | |
521 | |
522 PostgreSQLLargeObject largeObject(database_, blob.GetContent()); | |
523 BindLargeObject(i, largeObject); | |
524 break; | |
525 } | |
526 | |
527 default: | |
528 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); | |
529 } | |
530 } | |
531 | |
532 return new ResultWrapper(*this); | |
533 } | |
534 | |
535 | |
23
b2ff1cd2907a
handling of implicit transactions in DatabaseManager
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
0
diff
changeset
|
536 void PostgreSQLStatement::ExecuteWithoutResult(ITransaction& transaction, |
0 | 537 const Dictionary& parameters) |
538 { | |
539 std::auto_ptr<IResult> dummy(Execute(transaction, parameters)); | |
540 } | |
541 } |