comparison OrthancServer/Sources/Database/SQLiteDatabaseWrapper.cpp @ 4591:ff8170d17d90 db-changes

moving all accesses to databases from IDatabaseWrapper to ITransaction
author Sebastien Jodogne <s.jodogne@gmail.com>
date Mon, 15 Mar 2021 15:30:42 +0100
parents bec74e29f86b
children 36bbf3169a27
comparison
equal deleted inserted replaced
4590:4a0bf1019335 4591:ff8170d17d90
37 #include "../../../OrthancFramework/Sources/DicomFormat/DicomArray.h" 37 #include "../../../OrthancFramework/Sources/DicomFormat/DicomArray.h"
38 #include "../../../OrthancFramework/Sources/Logging.h" 38 #include "../../../OrthancFramework/Sources/Logging.h"
39 #include "../../../OrthancFramework/Sources/SQLite/Transaction.h" 39 #include "../../../OrthancFramework/Sources/SQLite/Transaction.h"
40 #include "../Search/ISqlLookupFormatter.h" 40 #include "../Search/ISqlLookupFormatter.h"
41 #include "../ServerToolbox.h" 41 #include "../ServerToolbox.h"
42 #include "Compatibility/ICreateInstance.h"
43 #include "Compatibility/IGetChildrenMetadata.h"
44 #include "Compatibility/ILookupResourceAndParent.h"
45 #include "Compatibility/ISetResourcesContent.h"
46 #include "VoidDatabaseListener.h"
42 47
43 #include <OrthancServerResources.h> 48 #include <OrthancServerResources.h>
44 49
45 #include <stdio.h> 50 #include <stdio.h>
46 #include <boost/lexical_cast.hpp> 51 #include <boost/lexical_cast.hpp>
47 52
48 namespace Orthanc 53 namespace Orthanc
49 { 54 {
55 class SQLiteDatabaseWrapper::LookupFormatter : public ISqlLookupFormatter
56 {
57 private:
58 std::list<std::string> values_;
59
60 public:
61 virtual std::string GenerateParameter(const std::string& value) ORTHANC_OVERRIDE
62 {
63 values_.push_back(value);
64 return "?";
65 }
66
67 virtual std::string FormatResourceType(ResourceType level) ORTHANC_OVERRIDE
68 {
69 return boost::lexical_cast<std::string>(level);
70 }
71
72 virtual std::string FormatWildcardEscape() ORTHANC_OVERRIDE
73 {
74 return "ESCAPE '\\'";
75 }
76
77 void Bind(SQLite::Statement& statement) const
78 {
79 size_t pos = 0;
80
81 for (std::list<std::string>::const_iterator
82 it = values_.begin(); it != values_.end(); ++it, pos++)
83 {
84 statement.BindString(pos, *it);
85 }
86 }
87 };
88
89
90 class SQLiteDatabaseWrapper::SignalRemainingAncestor : public SQLite::IScalarFunction
91 {
92 private:
93 bool hasRemainingAncestor_;
94 std::string remainingPublicId_;
95 ResourceType remainingType_;
96
97 public:
98 SignalRemainingAncestor() :
99 hasRemainingAncestor_(false)
100 {
101 }
102
103 void Reset()
104 {
105 hasRemainingAncestor_ = false;
106 }
107
108 virtual const char* GetName() const ORTHANC_OVERRIDE
109 {
110 return "SignalRemainingAncestor";
111 }
112
113 virtual unsigned int GetCardinality() const ORTHANC_OVERRIDE
114 {
115 return 2;
116 }
117
118 virtual void Compute(SQLite::FunctionContext& context) ORTHANC_OVERRIDE
119 {
120 CLOG(TRACE, SQLITE) << "There exists a remaining ancestor with public ID \""
121 << context.GetStringValue(0) << "\" of type "
122 << context.GetIntValue(1);
123
124 if (!hasRemainingAncestor_ ||
125 remainingType_ >= context.GetIntValue(1))
126 {
127 hasRemainingAncestor_ = true;
128 remainingPublicId_ = context.GetStringValue(0);
129 remainingType_ = static_cast<ResourceType>(context.GetIntValue(1));
130 }
131 }
132
133 bool HasRemainingAncestor() const
134 {
135 return hasRemainingAncestor_;
136 }
137
138 const std::string& GetRemainingAncestorId() const
139 {
140 assert(hasRemainingAncestor_);
141 return remainingPublicId_;
142 }
143
144 ResourceType GetRemainingAncestorType() const
145 {
146 assert(hasRemainingAncestor_);
147 return remainingType_;
148 }
149 };
150
151
152 class SQLiteDatabaseWrapper::TransactionBase :
153 public SQLiteDatabaseWrapper::UnitTestsTransaction,
154 public Compatibility::ICreateInstance,
155 public Compatibility::IGetChildrenMetadata,
156 public Compatibility::ILookupResourceAndParent,
157 public Compatibility::ISetResourcesContent
158 {
159 private:
160 void AnswerLookup(std::list<std::string>& resourcesId,
161 std::list<std::string>& instancesId,
162 ResourceType level)
163 {
164 resourcesId.clear();
165 instancesId.clear();
166
167 std::unique_ptr<SQLite::Statement> statement;
168
169 switch (level)
170 {
171 case ResourceType_Patient:
172 {
173 statement.reset(
174 new SQLite::Statement(
175 db_, SQLITE_FROM_HERE,
176 "SELECT patients.publicId, instances.publicID FROM Lookup AS patients "
177 "INNER JOIN Resources studies ON patients.internalId=studies.parentId "
178 "INNER JOIN Resources series ON studies.internalId=series.parentId "
179 "INNER JOIN Resources instances ON series.internalId=instances.parentId "
180 "GROUP BY patients.publicId"));
181
182 break;
183 }
184
185 case ResourceType_Study:
186 {
187 statement.reset(
188 new SQLite::Statement(
189 db_, SQLITE_FROM_HERE,
190 "SELECT studies.publicId, instances.publicID FROM Lookup AS studies "
191 "INNER JOIN Resources series ON studies.internalId=series.parentId "
192 "INNER JOIN Resources instances ON series.internalId=instances.parentId "
193 "GROUP BY studies.publicId"));
194
195 break;
196 }
197
198 case ResourceType_Series:
199 {
200 statement.reset(
201 new SQLite::Statement(
202 db_, SQLITE_FROM_HERE,
203 "SELECT series.publicId, instances.publicID FROM Lookup AS series "
204 "INNER JOIN Resources instances ON series.internalId=instances.parentId "
205 "GROUP BY series.publicId"));
206
207 break;
208 }
209
210 case ResourceType_Instance:
211 {
212 statement.reset(
213 new SQLite::Statement(
214 db_, SQLITE_FROM_HERE, "SELECT publicId, publicId FROM Lookup"));
215
216 break;
217 }
218
219 default:
220 throw OrthancException(ErrorCode_InternalError);
221 }
222
223 assert(statement.get() != NULL);
224
225 while (statement->Step())
226 {
227 resourcesId.push_back(statement->ColumnString(0));
228 instancesId.push_back(statement->ColumnString(1));
229 }
230 }
231
232
233 void ClearTable(const std::string& tableName)
234 {
235 db_.Execute("DELETE FROM " + tableName);
236 }
237
238
239 void GetChangesInternal(std::list<ServerIndexChange>& target,
240 bool& done,
241 SQLite::Statement& s,
242 uint32_t maxResults)
243 {
244 target.clear();
245
246 while (target.size() < maxResults && s.Step())
247 {
248 int64_t seq = s.ColumnInt64(0);
249 ChangeType changeType = static_cast<ChangeType>(s.ColumnInt(1));
250 ResourceType resourceType = static_cast<ResourceType>(s.ColumnInt(3));
251 const std::string& date = s.ColumnString(4);
252
253 int64_t internalId = s.ColumnInt64(2);
254 std::string publicId = GetPublicId(internalId);
255
256 target.push_back(ServerIndexChange(seq, changeType, resourceType, publicId, date));
257 }
258
259 done = !(target.size() == maxResults && s.Step());
260 }
261
262
263 void GetExportedResourcesInternal(std::list<ExportedResource>& target,
264 bool& done,
265 SQLite::Statement& s,
266 uint32_t maxResults)
267 {
268 target.clear();
269
270 while (target.size() < maxResults && s.Step())
271 {
272 int64_t seq = s.ColumnInt64(0);
273 ResourceType resourceType = static_cast<ResourceType>(s.ColumnInt(1));
274 std::string publicId = s.ColumnString(2);
275
276 ExportedResource resource(seq,
277 resourceType,
278 publicId,
279 s.ColumnString(3), // modality
280 s.ColumnString(8), // date
281 s.ColumnString(4), // patient ID
282 s.ColumnString(5), // study instance UID
283 s.ColumnString(6), // series instance UID
284 s.ColumnString(7)); // sop instance UID
285
286 target.push_back(resource);
287 }
288
289 done = !(target.size() == maxResults && s.Step());
290 }
291
292
293 void GetChildren(std::list<std::string>& childrenPublicIds,
294 int64_t id)
295 {
296 SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT publicId FROM Resources WHERE parentId=?");
297 s.BindInt64(0, id);
298
299 childrenPublicIds.clear();
300 while (s.Step())
301 {
302 childrenPublicIds.push_back(s.ColumnString(0));
303 }
304 }
305
306 IDatabaseListener& listener_;
307 SignalRemainingAncestor& signalRemainingAncestor_;
308
309 public:
310 TransactionBase(SQLite::Connection& db,
311 IDatabaseListener& listener,
312 SignalRemainingAncestor& signalRemainingAncestor) :
313 UnitTestsTransaction(db),
314 listener_(listener),
315 signalRemainingAncestor_(signalRemainingAncestor)
316 {
317 }
318
319 IDatabaseListener& GetListener() const
320 {
321 return listener_;
322 }
323
324
325 virtual void AddAttachment(int64_t id,
326 const FileInfo& attachment) ORTHANC_OVERRIDE
327 {
328 SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO AttachedFiles VALUES(?, ?, ?, ?, ?, ?, ?, ?)");
329 s.BindInt64(0, id);
330 s.BindInt(1, attachment.GetContentType());
331 s.BindString(2, attachment.GetUuid());
332 s.BindInt64(3, attachment.GetCompressedSize());
333 s.BindInt64(4, attachment.GetUncompressedSize());
334 s.BindInt(5, attachment.GetCompressionType());
335 s.BindString(6, attachment.GetUncompressedMD5());
336 s.BindString(7, attachment.GetCompressedMD5());
337 s.Run();
338 }
339
340
341 virtual void ApplyLookupResources(std::list<std::string>& resourcesId,
342 std::list<std::string>* instancesId,
343 const std::vector<DatabaseConstraint>& lookup,
344 ResourceType queryLevel,
345 size_t limit) ORTHANC_OVERRIDE
346 {
347 LookupFormatter formatter;
348
349 std::string sql;
350 LookupFormatter::Apply(sql, formatter, lookup, queryLevel, limit);
351
352 sql = "CREATE TEMPORARY TABLE Lookup AS " + sql;
353
354 {
355 SQLite::Statement s(db_, SQLITE_FROM_HERE, "DROP TABLE IF EXISTS Lookup");
356 s.Run();
357 }
358
359 {
360 SQLite::Statement statement(db_, sql);
361 formatter.Bind(statement);
362 statement.Run();
363 }
364
365 if (instancesId != NULL)
366 {
367 AnswerLookup(resourcesId, *instancesId, queryLevel);
368 }
369 else
370 {
371 resourcesId.clear();
372
373 SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT publicId FROM Lookup");
374
375 while (s.Step())
376 {
377 resourcesId.push_back(s.ColumnString(0));
378 }
379 }
380 }
381
382
383 virtual void AttachChild(int64_t parent,
384 int64_t child) ORTHANC_OVERRIDE
385 {
386 SQLite::Statement s(db_, SQLITE_FROM_HERE, "UPDATE Resources SET parentId = ? WHERE internalId = ?");
387 s.BindInt64(0, parent);
388 s.BindInt64(1, child);
389 s.Run();
390 }
391
392
393 virtual void ClearChanges() ORTHANC_OVERRIDE
394 {
395 ClearTable("Changes");
396 }
397
398 virtual void ClearExportedResources() ORTHANC_OVERRIDE
399 {
400 ClearTable("ExportedResources");
401 }
402
403
404 virtual void ClearMainDicomTags(int64_t id) ORTHANC_OVERRIDE
405 {
406 {
407 SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM DicomIdentifiers WHERE id=?");
408 s.BindInt64(0, id);
409 s.Run();
410 }
411
412 {
413 SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM MainDicomTags WHERE id=?");
414 s.BindInt64(0, id);
415 s.Run();
416 }
417 }
418
419
420 virtual bool CreateInstance(CreateInstanceResult& result,
421 int64_t& instanceId,
422 const std::string& patient,
423 const std::string& study,
424 const std::string& series,
425 const std::string& instance) ORTHANC_OVERRIDE
426 {
427 return ICreateInstance::Apply
428 (*this, result, instanceId, patient, study, series, instance);
429 }
430
431
432 virtual int64_t CreateResource(const std::string& publicId,
433 ResourceType type) ORTHANC_OVERRIDE
434 {
435 SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO Resources VALUES(NULL, ?, ?, NULL)");
436 s.BindInt(0, type);
437 s.BindString(1, publicId);
438 s.Run();
439 return db_.GetLastInsertRowId();
440 }
441
442
443 virtual void DeleteAttachment(int64_t id,
444 FileContentType attachment) ORTHANC_OVERRIDE
445 {
446 SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM AttachedFiles WHERE id=? AND fileType=?");
447 s.BindInt64(0, id);
448 s.BindInt(1, attachment);
449 s.Run();
450 }
451
452
453 virtual void DeleteMetadata(int64_t id,
454 MetadataType type) ORTHANC_OVERRIDE
455 {
456 SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM Metadata WHERE id=? and type=?");
457 s.BindInt64(0, id);
458 s.BindInt(1, type);
459 s.Run();
460 }
461
462
463 virtual void DeleteResource(int64_t id) ORTHANC_OVERRIDE
464 {
465 signalRemainingAncestor_.Reset();
466
467 SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM Resources WHERE internalId=?");
468 s.BindInt64(0, id);
469 s.Run();
470
471 if (signalRemainingAncestor_.HasRemainingAncestor())
472 {
473 listener_.SignalRemainingAncestor(signalRemainingAncestor_.GetRemainingAncestorType(),
474 signalRemainingAncestor_.GetRemainingAncestorId());
475 }
476 }
477
478
479 virtual void GetAllMetadata(std::map<MetadataType, std::string>& target,
480 int64_t id) ORTHANC_OVERRIDE
481 {
482 target.clear();
483
484 SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT type, value FROM Metadata WHERE id=?");
485 s.BindInt64(0, id);
486
487 while (s.Step())
488 {
489 MetadataType key = static_cast<MetadataType>(s.ColumnInt(0));
490 target[key] = s.ColumnString(1);
491 }
492 }
493
494
495 virtual void GetAllPublicIds(std::list<std::string>& target,
496 ResourceType resourceType) ORTHANC_OVERRIDE
497 {
498 SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT publicId FROM Resources WHERE resourceType=?");
499 s.BindInt(0, resourceType);
500
501 target.clear();
502 while (s.Step())
503 {
504 target.push_back(s.ColumnString(0));
505 }
506 }
507
508
509 virtual void GetAllPublicIds(std::list<std::string>& target,
510 ResourceType resourceType,
511 size_t since,
512 size_t limit) ORTHANC_OVERRIDE
513 {
514 if (limit == 0)
515 {
516 target.clear();
517 return;
518 }
519
520 SQLite::Statement s(db_, SQLITE_FROM_HERE,
521 "SELECT publicId FROM Resources WHERE "
522 "resourceType=? LIMIT ? OFFSET ?");
523 s.BindInt(0, resourceType);
524 s.BindInt64(1, limit);
525 s.BindInt64(2, since);
526
527 target.clear();
528 while (s.Step())
529 {
530 target.push_back(s.ColumnString(0));
531 }
532 }
533
534
535 virtual void GetChanges(std::list<ServerIndexChange>& target /*out*/,
536 bool& done /*out*/,
537 int64_t since,
538 uint32_t maxResults) ORTHANC_OVERRIDE
539 {
540 SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT * FROM Changes WHERE seq>? ORDER BY seq LIMIT ?");
541 s.BindInt64(0, since);
542 s.BindInt(1, maxResults + 1);
543 GetChangesInternal(target, done, s, maxResults);
544 }
545
546
547 virtual void GetChildrenMetadata(std::list<std::string>& target,
548 int64_t resourceId,
549 MetadataType metadata) ORTHANC_OVERRIDE
550 {
551 IGetChildrenMetadata::Apply(*this, target, resourceId, metadata);
552 }
553
554
555 virtual void GetChildrenInternalId(std::list<int64_t>& target,
556 int64_t id) ORTHANC_OVERRIDE
557 {
558 SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT a.internalId FROM Resources AS a, Resources AS b "
559 "WHERE a.parentId = b.internalId AND b.internalId = ?");
560 s.BindInt64(0, id);
561
562 target.clear();
563
564 while (s.Step())
565 {
566 target.push_back(s.ColumnInt64(0));
567 }
568 }
569
570
571 virtual void GetChildrenPublicId(std::list<std::string>& target,
572 int64_t id) ORTHANC_OVERRIDE
573 {
574 SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT a.publicId FROM Resources AS a, Resources AS b "
575 "WHERE a.parentId = b.internalId AND b.internalId = ?");
576 s.BindInt64(0, id);
577
578 target.clear();
579
580 while (s.Step())
581 {
582 target.push_back(s.ColumnString(0));
583 }
584 }
585
586
587 virtual void GetExportedResources(std::list<ExportedResource>& target,
588 bool& done,
589 int64_t since,
590 uint32_t maxResults) ORTHANC_OVERRIDE
591 {
592 SQLite::Statement s(db_, SQLITE_FROM_HERE,
593 "SELECT * FROM ExportedResources WHERE seq>? ORDER BY seq LIMIT ?");
594 s.BindInt64(0, since);
595 s.BindInt(1, maxResults + 1);
596 GetExportedResourcesInternal(target, done, s, maxResults);
597 }
598
599
600 virtual void GetLastChange(std::list<ServerIndexChange>& target /*out*/) ORTHANC_OVERRIDE
601 {
602 bool done; // Ignored
603 SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT * FROM Changes ORDER BY seq DESC LIMIT 1");
604 GetChangesInternal(target, done, s, 1);
605 }
606
607
608 int64_t GetLastChangeIndex() ORTHANC_OVERRIDE
609 {
610 SQLite::Statement s(db_, SQLITE_FROM_HERE,
611 "SELECT seq FROM sqlite_sequence WHERE name='Changes'");
612
613 if (s.Step())
614 {
615 int64_t c = s.ColumnInt(0);
616 assert(!s.Step());
617 return c;
618 }
619 else
620 {
621 // No change has been recorded so far in the database
622 return 0;
623 }
624 }
625
626
627 virtual void GetLastExportedResource(std::list<ExportedResource>& target) ORTHANC_OVERRIDE
628 {
629 bool done; // Ignored
630 SQLite::Statement s(db_, SQLITE_FROM_HERE,
631 "SELECT * FROM ExportedResources ORDER BY seq DESC LIMIT 1");
632 GetExportedResourcesInternal(target, done, s, 1);
633 }
634
635
636 virtual void GetMainDicomTags(DicomMap& map,
637 int64_t id) ORTHANC_OVERRIDE
638 {
639 map.Clear();
640
641 SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT * FROM MainDicomTags WHERE id=?");
642 s.BindInt64(0, id);
643 while (s.Step())
644 {
645 map.SetValue(s.ColumnInt(1),
646 s.ColumnInt(2),
647 s.ColumnString(3), false);
648 }
649 }
650
651
652 virtual std::string GetPublicId(int64_t resourceId) ORTHANC_OVERRIDE
653 {
654 SQLite::Statement s(db_, SQLITE_FROM_HERE,
655 "SELECT publicId FROM Resources WHERE internalId=?");
656 s.BindInt64(0, resourceId);
657
658 if (s.Step())
659 {
660 return s.ColumnString(0);
661 }
662 else
663 {
664 throw OrthancException(ErrorCode_UnknownResource);
665 }
666 }
667
668
669 virtual uint64_t GetResourceCount(ResourceType resourceType) ORTHANC_OVERRIDE
670 {
671 SQLite::Statement s(db_, SQLITE_FROM_HERE,
672 "SELECT COUNT(*) FROM Resources WHERE resourceType=?");
673 s.BindInt(0, resourceType);
674
675 if (!s.Step())
676 {
677 return 0;
678 }
679 else
680 {
681 int64_t c = s.ColumnInt(0);
682 assert(!s.Step());
683 return c;
684 }
685 }
686
687
688 virtual ResourceType GetResourceType(int64_t resourceId) ORTHANC_OVERRIDE
689 {
690 SQLite::Statement s(db_, SQLITE_FROM_HERE,
691 "SELECT resourceType FROM Resources WHERE internalId=?");
692 s.BindInt64(0, resourceId);
693
694 if (s.Step())
695 {
696 return static_cast<ResourceType>(s.ColumnInt(0));
697 }
698 else
699 {
700 throw OrthancException(ErrorCode_UnknownResource);
701 }
702 }
703
704
705 virtual uint64_t GetTotalCompressedSize() ORTHANC_OVERRIDE
706 {
707 // Old SQL query that was used in Orthanc <= 1.5.0:
708 // SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT SUM(compressedSize) FROM AttachedFiles");
709
710 SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT value FROM GlobalIntegers WHERE key=0");
711 s.Run();
712 return static_cast<uint64_t>(s.ColumnInt64(0));
713 }
714
715
716 virtual uint64_t GetTotalUncompressedSize() ORTHANC_OVERRIDE
717 {
718 // Old SQL query that was used in Orthanc <= 1.5.0:
719 // SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT SUM(uncompressedSize) FROM AttachedFiles");
720
721 SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT value FROM GlobalIntegers WHERE key=1");
722 s.Run();
723 return static_cast<uint64_t>(s.ColumnInt64(0));
724 }
725
726
727 virtual bool IsDiskSizeAbove(uint64_t threshold) ORTHANC_OVERRIDE
728 {
729 return GetTotalCompressedSize() > threshold;
730 }
731
732
733 virtual bool IsExistingResource(int64_t internalId) ORTHANC_OVERRIDE
734 {
735 SQLite::Statement s(db_, SQLITE_FROM_HERE,
736 "SELECT * FROM Resources WHERE internalId=?");
737 s.BindInt64(0, internalId);
738 return s.Step();
739 }
740
741
742 virtual bool IsProtectedPatient(int64_t internalId) ORTHANC_OVERRIDE
743 {
744 SQLite::Statement s(db_, SQLITE_FROM_HERE,
745 "SELECT * FROM PatientRecyclingOrder WHERE patientId = ?");
746 s.BindInt64(0, internalId);
747 return !s.Step();
748 }
749
750
751 virtual void ListAvailableAttachments(std::set<FileContentType>& target,
752 int64_t id) ORTHANC_OVERRIDE
753 {
754 target.clear();
755
756 SQLite::Statement s(db_, SQLITE_FROM_HERE,
757 "SELECT fileType FROM AttachedFiles WHERE id=?");
758 s.BindInt64(0, id);
759
760 while (s.Step())
761 {
762 target.insert(static_cast<FileContentType>(s.ColumnInt(0)));
763 }
764 }
765
766
767 virtual void LogChange(int64_t internalId,
768 const ServerIndexChange& change) ORTHANC_OVERRIDE
769 {
770 SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO Changes VALUES(NULL, ?, ?, ?, ?)");
771 s.BindInt(0, change.GetChangeType());
772 s.BindInt64(1, internalId);
773 s.BindInt(2, change.GetResourceType());
774 s.BindString(3, change.GetDate());
775 s.Run();
776 }
777
778
779 virtual void LogExportedResource(const ExportedResource& resource) ORTHANC_OVERRIDE
780 {
781 SQLite::Statement s(db_, SQLITE_FROM_HERE,
782 "INSERT INTO ExportedResources VALUES(NULL, ?, ?, ?, ?, ?, ?, ?, ?)");
783
784 s.BindInt(0, resource.GetResourceType());
785 s.BindString(1, resource.GetPublicId());
786 s.BindString(2, resource.GetModality());
787 s.BindString(3, resource.GetPatientId());
788 s.BindString(4, resource.GetStudyInstanceUid());
789 s.BindString(5, resource.GetSeriesInstanceUid());
790 s.BindString(6, resource.GetSopInstanceUid());
791 s.BindString(7, resource.GetDate());
792 s.Run();
793 }
794
795
796 virtual bool LookupAttachment(FileInfo& attachment,
797 int64_t id,
798 FileContentType contentType) ORTHANC_OVERRIDE
799 {
800 SQLite::Statement s(db_, SQLITE_FROM_HERE,
801 "SELECT uuid, uncompressedSize, compressionType, compressedSize, "
802 "uncompressedMD5, compressedMD5 FROM AttachedFiles WHERE id=? AND fileType=?");
803 s.BindInt64(0, id);
804 s.BindInt(1, contentType);
805
806 if (!s.Step())
807 {
808 return false;
809 }
810 else
811 {
812 attachment = FileInfo(s.ColumnString(0),
813 contentType,
814 s.ColumnInt64(1),
815 s.ColumnString(4),
816 static_cast<CompressionType>(s.ColumnInt(2)),
817 s.ColumnInt64(3),
818 s.ColumnString(5));
819 return true;
820 }
821 }
822
823
824 virtual bool LookupGlobalProperty(std::string& target,
825 GlobalProperty property) ORTHANC_OVERRIDE
826 {
827 SQLite::Statement s(db_, SQLITE_FROM_HERE,
828 "SELECT value FROM GlobalProperties WHERE property=?");
829 s.BindInt(0, property);
830
831 if (!s.Step())
832 {
833 return false;
834 }
835 else
836 {
837 target = s.ColumnString(0);
838 return true;
839 }
840 }
841
842
843 virtual bool LookupMetadata(std::string& target,
844 int64_t id,
845 MetadataType type) ORTHANC_OVERRIDE
846 {
847 SQLite::Statement s(db_, SQLITE_FROM_HERE,
848 "SELECT value FROM Metadata WHERE id=? AND type=?");
849 s.BindInt64(0, id);
850 s.BindInt(1, type);
851
852 if (!s.Step())
853 {
854 return false;
855 }
856 else
857 {
858 target = s.ColumnString(0);
859 return true;
860 }
861 }
862
863
864 virtual bool LookupParent(int64_t& parentId,
865 int64_t resourceId) ORTHANC_OVERRIDE
866 {
867 SQLite::Statement s(db_, SQLITE_FROM_HERE,
868 "SELECT parentId FROM Resources WHERE internalId=?");
869 s.BindInt64(0, resourceId);
870
871 if (!s.Step())
872 {
873 throw OrthancException(ErrorCode_UnknownResource);
874 }
875
876 if (s.ColumnIsNull(0))
877 {
878 return false;
879 }
880 else
881 {
882 parentId = s.ColumnInt(0);
883 return true;
884 }
885 }
886
887
888 virtual bool LookupResourceAndParent(int64_t& id,
889 ResourceType& type,
890 std::string& parentPublicId,
891 const std::string& publicId) ORTHANC_OVERRIDE
892 {
893 return ILookupResourceAndParent::Apply(*this, id, type, parentPublicId, publicId);
894 }
895
896
897 virtual bool LookupResource(int64_t& id,
898 ResourceType& type,
899 const std::string& publicId) ORTHANC_OVERRIDE
900 {
901 SQLite::Statement s(db_, SQLITE_FROM_HERE,
902 "SELECT internalId, resourceType FROM Resources WHERE publicId=?");
903 s.BindString(0, publicId);
904
905 if (!s.Step())
906 {
907 return false;
908 }
909 else
910 {
911 id = s.ColumnInt(0);
912 type = static_cast<ResourceType>(s.ColumnInt(1));
913
914 // Check whether there is a single resource with this public id
915 assert(!s.Step());
916
917 return true;
918 }
919 }
920
921
922 virtual bool SelectPatientToRecycle(int64_t& internalId) ORTHANC_OVERRIDE
923 {
924 SQLite::Statement s(db_, SQLITE_FROM_HERE,
925 "SELECT patientId FROM PatientRecyclingOrder ORDER BY seq ASC LIMIT 1");
926
927 if (!s.Step())
928 {
929 // No patient remaining or all the patients are protected
930 return false;
931 }
932 else
933 {
934 internalId = s.ColumnInt(0);
935 return true;
936 }
937 }
938
939
940 virtual bool SelectPatientToRecycle(int64_t& internalId,
941 int64_t patientIdToAvoid) ORTHANC_OVERRIDE
942 {
943 SQLite::Statement s(db_, SQLITE_FROM_HERE,
944 "SELECT patientId FROM PatientRecyclingOrder "
945 "WHERE patientId != ? ORDER BY seq ASC LIMIT 1");
946 s.BindInt64(0, patientIdToAvoid);
947
948 if (!s.Step())
949 {
950 // No patient remaining or all the patients are protected
951 return false;
952 }
953 else
954 {
955 internalId = s.ColumnInt(0);
956 return true;
957 }
958 }
959
960
961 virtual void SetGlobalProperty(GlobalProperty property,
962 const std::string& value) ORTHANC_OVERRIDE
963 {
964 SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT OR REPLACE INTO GlobalProperties VALUES(?, ?)");
965 s.BindInt(0, property);
966 s.BindString(1, value);
967 s.Run();
968 }
969
970
971 virtual void SetIdentifierTag(int64_t id,
972 const DicomTag& tag,
973 const std::string& value) ORTHANC_OVERRIDE
974 {
975 SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO DicomIdentifiers VALUES(?, ?, ?, ?)");
976 s.BindInt64(0, id);
977 s.BindInt(1, tag.GetGroup());
978 s.BindInt(2, tag.GetElement());
979 s.BindString(3, value);
980 s.Run();
981 }
982
983
984 virtual void SetProtectedPatient(int64_t internalId,
985 bool isProtected) ORTHANC_OVERRIDE
986 {
987 if (isProtected)
988 {
989 SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM PatientRecyclingOrder WHERE patientId=?");
990 s.BindInt64(0, internalId);
991 s.Run();
992 }
993 else if (IsProtectedPatient(internalId))
994 {
995 SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO PatientRecyclingOrder VALUES(NULL, ?)");
996 s.BindInt64(0, internalId);
997 s.Run();
998 }
999 else
1000 {
1001 // Nothing to do: The patient is already unprotected
1002 }
1003 }
1004
1005
1006 virtual void SetMainDicomTag(int64_t id,
1007 const DicomTag& tag,
1008 const std::string& value) ORTHANC_OVERRIDE
1009 {
1010 SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO MainDicomTags VALUES(?, ?, ?, ?)");
1011 s.BindInt64(0, id);
1012 s.BindInt(1, tag.GetGroup());
1013 s.BindInt(2, tag.GetElement());
1014 s.BindString(3, value);
1015 s.Run();
1016 }
1017
1018
1019 virtual void SetMetadata(int64_t id,
1020 MetadataType type,
1021 const std::string& value) ORTHANC_OVERRIDE
1022 {
1023 SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT OR REPLACE INTO Metadata VALUES(?, ?, ?)");
1024 s.BindInt64(0, id);
1025 s.BindInt(1, type);
1026 s.BindString(2, value);
1027 s.Run();
1028 }
1029
1030
1031 virtual void SetResourcesContent(const Orthanc::ResourcesContent& content) ORTHANC_OVERRIDE
1032 {
1033 ISetResourcesContent::Apply(*this, content);
1034 }
1035
1036
1037 virtual void TagMostRecentPatient(int64_t patient) ORTHANC_OVERRIDE
1038 {
1039 {
1040 SQLite::Statement s(db_, SQLITE_FROM_HERE,
1041 "DELETE FROM PatientRecyclingOrder WHERE patientId=?");
1042 s.BindInt64(0, patient);
1043 s.Run();
1044
1045 assert(db_.GetLastChangeCount() == 0 ||
1046 db_.GetLastChangeCount() == 1);
1047
1048 if (db_.GetLastChangeCount() == 0)
1049 {
1050 // The patient was protected, there was nothing to delete from the recycling order
1051 return;
1052 }
1053 }
1054
1055 {
1056 SQLite::Statement s(db_, SQLITE_FROM_HERE,
1057 "INSERT INTO PatientRecyclingOrder VALUES(NULL, ?)");
1058 s.BindInt64(0, patient);
1059 s.Run();
1060 }
1061 }
1062 };
1063
1064
50 class SQLiteDatabaseWrapper::SignalFileDeleted : public SQLite::IScalarFunction 1065 class SQLiteDatabaseWrapper::SignalFileDeleted : public SQLite::IScalarFunction
51 { 1066 {
52 private: 1067 private:
53 SQLiteDatabaseWrapper& sqlite_; 1068 SQLiteDatabaseWrapper& sqlite_;
54 1069
68 return 7; 1083 return 7;
69 } 1084 }
70 1085
71 virtual void Compute(SQLite::FunctionContext& context) ORTHANC_OVERRIDE 1086 virtual void Compute(SQLite::FunctionContext& context) ORTHANC_OVERRIDE
72 { 1087 {
73 if (sqlite_.listener_ != NULL) 1088 if (sqlite_.activeTransaction_ != NULL)
74 { 1089 {
75 std::string uncompressedMD5, compressedMD5; 1090 std::string uncompressedMD5, compressedMD5;
76 1091
77 if (!context.IsNullValue(5)) 1092 if (!context.IsNullValue(5))
78 { 1093 {
90 uncompressedMD5, 1105 uncompressedMD5,
91 static_cast<CompressionType>(context.GetIntValue(3)), 1106 static_cast<CompressionType>(context.GetIntValue(3)),
92 static_cast<uint64_t>(context.GetInt64Value(4)), 1107 static_cast<uint64_t>(context.GetInt64Value(4)),
93 compressedMD5); 1108 compressedMD5);
94 1109
95 sqlite_.listener_->SignalAttachmentDeleted(info); 1110 sqlite_.activeTransaction_->GetListener().SignalAttachmentDeleted(info);
96 } 1111 }
97 } 1112 }
98 }; 1113 };
99 1114
100 1115
119 return 2; 1134 return 2;
120 } 1135 }
121 1136
122 virtual void Compute(SQLite::FunctionContext& context) ORTHANC_OVERRIDE 1137 virtual void Compute(SQLite::FunctionContext& context) ORTHANC_OVERRIDE
123 { 1138 {
124 if (sqlite_.listener_ != NULL) 1139 if (sqlite_.activeTransaction_ != NULL)
125 { 1140 {
126 sqlite_.listener_->SignalResourceDeleted(static_cast<ResourceType>(context.GetIntValue(1)), 1141 sqlite_.activeTransaction_->GetListener().
127 context.GetStringValue(0)); 1142 SignalResourceDeleted(static_cast<ResourceType>(context.GetIntValue(1)),
1143 context.GetStringValue(0));
128 } 1144 }
129 } 1145 }
130 }; 1146 };
131 1147
132 1148
133 class SQLiteDatabaseWrapper::SignalRemainingAncestor : public SQLite::IScalarFunction 1149 class SQLiteDatabaseWrapper::ReadWriteTransaction : public SQLiteDatabaseWrapper::TransactionBase
134 { 1150 {
135 private: 1151 private:
136 bool hasRemainingAncestor_; 1152 SQLiteDatabaseWrapper& that_;
137 std::string remainingPublicId_; 1153 std::unique_ptr<SQLite::Transaction> transaction_;
138 ResourceType remainingType_; 1154 int64_t initialDiskSize_;
139 1155
140 public: 1156 public:
141 SignalRemainingAncestor() : 1157 ReadWriteTransaction(SQLiteDatabaseWrapper& that,
142 hasRemainingAncestor_(false) 1158 IDatabaseListener& listener) :
143 { 1159 TransactionBase(that.db_, listener, *that.signalRemainingAncestor_),
144 } 1160 that_(that),
145 1161 transaction_(new SQLite::Transaction(that_.db_))
146 void Reset() 1162 {
147 { 1163 if (that_.activeTransaction_ != NULL)
148 hasRemainingAncestor_ = false; 1164 {
149 } 1165 throw OrthancException(ErrorCode_InternalError);
150 1166 }
151 virtual const char* GetName() const ORTHANC_OVERRIDE 1167
152 { 1168 that_.activeTransaction_ = this;
153 return "SignalRemainingAncestor"; 1169
154 } 1170 #if defined(NDEBUG)
155 1171 // Release mode
156 virtual unsigned int GetCardinality() const ORTHANC_OVERRIDE 1172 initialDiskSize_ = 0;
157 { 1173 #else
158 return 2; 1174 // Debug mode
159 } 1175 initialDiskSize_ = static_cast<int64_t>(GetTotalCompressedSize());
160 1176 #endif
161 virtual void Compute(SQLite::FunctionContext& context) ORTHANC_OVERRIDE 1177 }
162 { 1178
163 CLOG(TRACE, SQLITE) << "There exists a remaining ancestor with public ID \"" 1179 virtual ~ReadWriteTransaction()
164 << context.GetStringValue(0) << "\" of type " 1180 {
165 << context.GetIntValue(1); 1181 assert(that_.activeTransaction_ != NULL);
166 1182 that_.activeTransaction_ = NULL;
167 if (!hasRemainingAncestor_ || 1183 }
168 remainingType_ >= context.GetIntValue(1)) 1184
169 { 1185 void Begin()
170 hasRemainingAncestor_ = true; 1186 {
171 remainingPublicId_ = context.GetStringValue(0); 1187 transaction_->Begin();
172 remainingType_ = static_cast<ResourceType>(context.GetIntValue(1)); 1188 }
173 } 1189
174 } 1190 virtual void Rollback() ORTHANC_OVERRIDE
175 1191 {
176 bool HasRemainingAncestor() const 1192 transaction_->Rollback();
177 { 1193 }
178 return hasRemainingAncestor_; 1194
179 } 1195 virtual void Commit(int64_t fileSizeDelta /* only used in debug */) ORTHANC_OVERRIDE
180 1196 {
181 const std::string& GetRemainingAncestorId() const 1197 transaction_->Commit();
182 { 1198
183 assert(hasRemainingAncestor_); 1199 assert(initialDiskSize_ + fileSizeDelta >= 0 &&
184 return remainingPublicId_; 1200 initialDiskSize_ + fileSizeDelta == static_cast<int64_t>(GetTotalCompressedSize()));
185 }
186
187 ResourceType GetRemainingAncestorType() const
188 {
189 assert(hasRemainingAncestor_);
190 return remainingType_;
191 } 1201 }
192 }; 1202 };
193 1203
194 1204
195 void SQLiteDatabaseWrapper::GetChangesInternal(std::list<ServerIndexChange>& target, 1205 class SQLiteDatabaseWrapper::ReadOnlyTransaction : public SQLiteDatabaseWrapper::TransactionBase
196 bool& done, 1206 {
197 SQLite::Statement& s, 1207 private:
198 uint32_t maxResults) 1208 SQLiteDatabaseWrapper& that_;
199 { 1209
200 target.clear(); 1210 public:
201 1211 ReadOnlyTransaction(SQLiteDatabaseWrapper& that,
202 while (target.size() < maxResults && s.Step()) 1212 IDatabaseListener& listener) :
203 { 1213 TransactionBase(that.db_, listener, *that.signalRemainingAncestor_),
204 int64_t seq = s.ColumnInt64(0); 1214 that_(that)
205 ChangeType changeType = static_cast<ChangeType>(s.ColumnInt(1)); 1215 {
206 ResourceType resourceType = static_cast<ResourceType>(s.ColumnInt(3)); 1216 if (that_.activeTransaction_ != NULL)
207 const std::string& date = s.ColumnString(4); 1217 {
208 1218 throw OrthancException(ErrorCode_InternalError);
209 int64_t internalId = s.ColumnInt64(2); 1219 }
210 std::string publicId = GetPublicId(internalId); 1220
211 1221 that_.activeTransaction_ = this;
212 target.push_back(ServerIndexChange(seq, changeType, resourceType, publicId, date)); 1222 }
213 } 1223
214 1224 virtual ~ReadOnlyTransaction()
215 done = !(target.size() == maxResults && s.Step()); 1225 {
1226 assert(that_.activeTransaction_ != NULL);
1227 that_.activeTransaction_ = NULL;
1228 }
1229
1230 virtual void Rollback() ORTHANC_OVERRIDE
1231 {
1232 }
1233
1234 virtual void Commit(int64_t fileSizeDelta /* only used in debug */) ORTHANC_OVERRIDE
1235 {
1236 if (fileSizeDelta != 0)
1237 {
1238 throw OrthancException(ErrorCode_InternalError);
1239 }
1240 }
1241 };
1242
1243
1244 SQLiteDatabaseWrapper::SQLiteDatabaseWrapper(const std::string& path) :
1245 activeTransaction_(NULL),
1246 signalRemainingAncestor_(NULL),
1247 version_(0)
1248 {
1249 db_.Open(path);
216 } 1250 }
217 1251
218 1252
219 void SQLiteDatabaseWrapper::GetExportedResourcesInternal(std::list<ExportedResource>& target, 1253 SQLiteDatabaseWrapper::SQLiteDatabaseWrapper() :
220 bool& done, 1254 activeTransaction_(NULL),
221 SQLite::Statement& s, 1255 signalRemainingAncestor_(NULL),
222 uint32_t maxResults) 1256 version_(0)
223 { 1257 {
224 target.clear(); 1258 db_.OpenInMemory();
225
226 while (target.size() < maxResults && s.Step())
227 {
228 int64_t seq = s.ColumnInt64(0);
229 ResourceType resourceType = static_cast<ResourceType>(s.ColumnInt(1));
230 std::string publicId = s.ColumnString(2);
231
232 ExportedResource resource(seq,
233 resourceType,
234 publicId,
235 s.ColumnString(3), // modality
236 s.ColumnString(8), // date
237 s.ColumnString(4), // patient ID
238 s.ColumnString(5), // study instance UID
239 s.ColumnString(6), // series instance UID
240 s.ColumnString(7)); // sop instance UID
241
242 target.push_back(resource);
243 }
244
245 done = !(target.size() == maxResults && s.Step());
246 } 1259 }
247 1260
248 1261 SQLiteDatabaseWrapper::~SQLiteDatabaseWrapper()
249 void SQLiteDatabaseWrapper::GetChildren(std::list<std::string>& childrenPublicIds, 1262 {
250 int64_t id) 1263 if (activeTransaction_ != NULL)
251 { 1264 {
252 SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT publicId FROM Resources WHERE parentId=?"); 1265 LOG(ERROR) << "A SQLite transaction is still active in the SQLiteDatabaseWrapper destructor: Expect a crash";
1266 }
1267 }
1268
1269
1270 void SQLiteDatabaseWrapper::Open()
1271 {
1272 if (signalRemainingAncestor_ != NULL)
1273 {
1274 throw OrthancException(ErrorCode_BadSequenceOfCalls); // Cannot open twice
1275 }
1276
1277 signalRemainingAncestor_ = dynamic_cast<SignalRemainingAncestor*>(db_.Register(new SignalRemainingAncestor));
1278 db_.Register(new SignalFileDeleted(*this));
1279 db_.Register(new SignalResourceDeleted(*this));
1280
1281 db_.Execute("PRAGMA ENCODING=\"UTF-8\";");
1282
1283 // Performance tuning of SQLite with PRAGMAs
1284 // http://www.sqlite.org/pragma.html
1285 db_.Execute("PRAGMA SYNCHRONOUS=NORMAL;");
1286 db_.Execute("PRAGMA JOURNAL_MODE=WAL;");
1287 db_.Execute("PRAGMA LOCKING_MODE=EXCLUSIVE;");
1288 db_.Execute("PRAGMA WAL_AUTOCHECKPOINT=1000;");
1289 //db_.Execute("PRAGMA TEMP_STORE=memory");
1290
1291 // Make "LIKE" case-sensitive in SQLite
1292 db_.Execute("PRAGMA case_sensitive_like = true;");
1293
1294 VoidDatabaseListener listener;
1295
1296 {
1297 std::unique_ptr<ITransaction> transaction(StartTransaction(TransactionType_ReadOnly, listener));
1298
1299 if (!db_.DoesTableExist("GlobalProperties"))
1300 {
1301 LOG(INFO) << "Creating the database";
1302 std::string query;
1303 ServerResources::GetFileResource(query, ServerResources::PREPARE_DATABASE);
1304 db_.Execute(query);
1305 }
1306
1307 // Check the version of the database
1308 std::string tmp;
1309 if (!transaction->LookupGlobalProperty(tmp, GlobalProperty_DatabaseSchemaVersion))
1310 {
1311 tmp = "Unknown";
1312 }
1313
1314 bool ok = false;
1315 try
1316 {
1317 LOG(INFO) << "Version of the Orthanc database: " << tmp;
1318 version_ = boost::lexical_cast<unsigned int>(tmp);
1319 ok = true;
1320 }
1321 catch (boost::bad_lexical_cast&)
1322 {
1323 }
1324
1325 if (!ok)
1326 {
1327 throw OrthancException(ErrorCode_IncompatibleDatabaseVersion,
1328 "Incompatible version of the Orthanc database: " + tmp);
1329 }
1330
1331 // New in Orthanc 1.5.1
1332 if (version_ == 6)
1333 {
1334 if (!transaction->LookupGlobalProperty(tmp, GlobalProperty_GetTotalSizeIsFast) ||
1335 tmp != "1")
1336 {
1337 LOG(INFO) << "Installing the SQLite triggers to track the size of the attachments";
1338 std::string query;
1339 ServerResources::GetFileResource(query, ServerResources::INSTALL_TRACK_ATTACHMENTS_SIZE);
1340 db_.Execute(query);
1341 }
1342 }
1343
1344 transaction->Commit(0);
1345 }
1346 }
1347
1348
1349 static void ExecuteUpgradeScript(SQLite::Connection& db,
1350 ServerResources::FileResourceId script)
1351 {
1352 std::string upgrade;
1353 ServerResources::GetFileResource(upgrade, script);
1354 db.BeginTransaction();
1355 db.Execute(upgrade);
1356 db.CommitTransaction();
1357 }
1358
1359
1360 void SQLiteDatabaseWrapper::Upgrade(unsigned int targetVersion,
1361 IStorageArea& storageArea)
1362 {
1363 if (targetVersion != 6)
1364 {
1365 throw OrthancException(ErrorCode_IncompatibleDatabaseVersion);
1366 }
1367
1368 // This version of Orthanc is only compatible with versions 3, 4,
1369 // 5 and 6 of the DB schema
1370 if (version_ != 3 &&
1371 version_ != 4 &&
1372 version_ != 5 &&
1373 version_ != 6)
1374 {
1375 throw OrthancException(ErrorCode_IncompatibleDatabaseVersion);
1376 }
1377
1378 if (version_ == 3)
1379 {
1380 LOG(WARNING) << "Upgrading database version from 3 to 4";
1381 ExecuteUpgradeScript(db_, ServerResources::UPGRADE_DATABASE_3_TO_4);
1382 version_ = 4;
1383 }
1384
1385 if (version_ == 4)
1386 {
1387 LOG(WARNING) << "Upgrading database version from 4 to 5";
1388 ExecuteUpgradeScript(db_, ServerResources::UPGRADE_DATABASE_4_TO_5);
1389 version_ = 5;
1390 }
1391
1392 if (version_ == 5)
1393 {
1394 LOG(WARNING) << "Upgrading database version from 5 to 6";
1395 // No change in the DB schema, the step from version 5 to 6 only
1396 // consists in reconstructing the main DICOM tags information
1397 // (as more tags got included).
1398
1399 VoidDatabaseListener listener;
1400
1401 {
1402 std::unique_ptr<ITransaction> transaction(StartTransaction(TransactionType_ReadWrite, listener));
1403 ServerToolbox::ReconstructMainDicomTags(*transaction, storageArea, ResourceType_Patient);
1404 ServerToolbox::ReconstructMainDicomTags(*transaction, storageArea, ResourceType_Study);
1405 ServerToolbox::ReconstructMainDicomTags(*transaction, storageArea, ResourceType_Series);
1406 ServerToolbox::ReconstructMainDicomTags(*transaction, storageArea, ResourceType_Instance);
1407 db_.Execute("UPDATE GlobalProperties SET value=\"6\" WHERE property=" +
1408 boost::lexical_cast<std::string>(GlobalProperty_DatabaseSchemaVersion) + ";");
1409 transaction->Commit(0);
1410 }
1411
1412 version_ = 6;
1413 }
1414 }
1415
1416
1417 IDatabaseWrapper::ITransaction* SQLiteDatabaseWrapper::StartTransaction(TransactionType type,
1418 IDatabaseListener& listener)
1419 {
1420 switch (type)
1421 {
1422 case TransactionType_ReadOnly:
1423 return new ReadOnlyTransaction(*this, listener); // This is a no-op transaction in SQLite (thanks to mutex)
1424
1425 case TransactionType_ReadWrite:
1426 {
1427 std::unique_ptr<ReadWriteTransaction> transaction;
1428 transaction.reset(new ReadWriteTransaction(*this, listener));
1429 transaction->Begin();
1430 return transaction.release();
1431 }
1432
1433 default:
1434 throw OrthancException(ErrorCode_InternalError);
1435 }
1436 }
1437
1438
1439 int64_t SQLiteDatabaseWrapper::UnitTestsTransaction::CreateResource(const std::string& publicId,
1440 ResourceType type)
1441 {
1442 SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO Resources VALUES(NULL, ?, ?, NULL)");
1443 s.BindInt(0, type);
1444 s.BindString(1, publicId);
1445 s.Run();
1446 return db_.GetLastInsertRowId();
1447 }
1448
1449
1450 void SQLiteDatabaseWrapper::UnitTestsTransaction::AttachChild(int64_t parent,
1451 int64_t child)
1452 {
1453 SQLite::Statement s(db_, SQLITE_FROM_HERE, "UPDATE Resources SET parentId = ? WHERE internalId = ?");
1454 s.BindInt64(0, parent);
1455 s.BindInt64(1, child);
1456 s.Run();
1457 }
1458
1459
1460 void SQLiteDatabaseWrapper::UnitTestsTransaction::SetIdentifierTag(int64_t id,
1461 const DicomTag& tag,
1462 const std::string& value)
1463 {
1464 SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO DicomIdentifiers VALUES(?, ?, ?, ?)");
253 s.BindInt64(0, id); 1465 s.BindInt64(0, id);
254 1466 s.BindInt(1, tag.GetGroup());
255 childrenPublicIds.clear(); 1467 s.BindInt(2, tag.GetElement());
256 while (s.Step()) 1468 s.BindString(3, value);
257 { 1469 s.Run();
258 childrenPublicIds.push_back(s.ColumnString(0));
259 }
260 } 1470 }
261 1471
262 1472
263 void SQLiteDatabaseWrapper::DeleteResource(int64_t id) 1473 void SQLiteDatabaseWrapper::UnitTestsTransaction::SetMainDicomTag(int64_t id,
264 { 1474 const DicomTag& tag,
265 signalRemainingAncestor_->Reset(); 1475 const std::string& value)
266 1476 {
267 SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM Resources WHERE internalId=?"); 1477 SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO MainDicomTags VALUES(?, ?, ?, ?)");
268 s.BindInt64(0, id); 1478 s.BindInt64(0, id);
1479 s.BindInt(1, tag.GetGroup());
1480 s.BindInt(2, tag.GetElement());
1481 s.BindString(3, value);
269 s.Run(); 1482 s.Run();
270
271 if (signalRemainingAncestor_->HasRemainingAncestor() &&
272 listener_ != NULL)
273 {
274 listener_->SignalRemainingAncestor(signalRemainingAncestor_->GetRemainingAncestorType(),
275 signalRemainingAncestor_->GetRemainingAncestorId());
276 }
277 } 1483 }
278 1484
279 1485
280 bool SQLiteDatabaseWrapper::GetParentPublicId(std::string& target, 1486 int64_t SQLiteDatabaseWrapper::UnitTestsTransaction::GetTableRecordCount(const std::string& table)
281 int64_t id)
282 {
283 SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT a.publicId FROM Resources AS a, Resources AS b "
284 "WHERE a.internalId = b.parentId AND b.internalId = ?");
285 s.BindInt64(0, id);
286
287 if (s.Step())
288 {
289 target = s.ColumnString(0);
290 return true;
291 }
292 else
293 {
294 return false;
295 }
296 }
297
298
299 int64_t SQLiteDatabaseWrapper::GetTableRecordCount(const std::string& table)
300 { 1487 {
301 /** 1488 /**
302 * "Generally one cannot use SQL parameters/placeholders for 1489 * "Generally one cannot use SQL parameters/placeholders for
303 * database identifiers (tables, columns, views, schemas, etc.) or 1490 * database identifiers (tables, columns, views, schemas, etc.) or
304 * database functions (e.g., CURRENT_DATE), but instead only for 1491 * database functions (e.g., CURRENT_DATE), but instead only for
328 { 1515 {
329 throw OrthancException(ErrorCode_InternalError); 1516 throw OrthancException(ErrorCode_InternalError);
330 } 1517 }
331 } 1518 }
332 1519
333 1520
334 SQLiteDatabaseWrapper::SQLiteDatabaseWrapper(const std::string& path) : 1521 bool SQLiteDatabaseWrapper::UnitTestsTransaction::GetParentPublicId(std::string& target,
335 listener_(NULL), 1522 int64_t id)
336 signalRemainingAncestor_(NULL), 1523 {
337 version_(0) 1524 SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT a.publicId FROM Resources AS a, Resources AS b "
338 { 1525 "WHERE a.internalId = b.parentId AND b.internalId = ?");
339 db_.Open(path); 1526 s.BindInt64(0, id);
340 } 1527
341
342
343 SQLiteDatabaseWrapper::SQLiteDatabaseWrapper() :
344 listener_(NULL),
345 signalRemainingAncestor_(NULL),
346 version_(0)
347 {
348 db_.OpenInMemory();
349 }
350
351
352 int SQLiteDatabaseWrapper::GetGlobalIntegerProperty(GlobalProperty property,
353 int defaultValue)
354 {
355 std::string tmp;
356
357 if (!LookupGlobalProperty(tmp, GlobalProperty_DatabasePatchLevel))
358 {
359 return defaultValue;
360 }
361 else
362 {
363 try
364 {
365 return boost::lexical_cast<int>(tmp);
366 }
367 catch (boost::bad_lexical_cast&)
368 {
369 throw OrthancException(ErrorCode_ParameterOutOfRange,
370 "Global property " + boost::lexical_cast<std::string>(property) +
371 " should be an integer, but found: " + tmp);
372 }
373 }
374 }
375
376
377 void SQLiteDatabaseWrapper::Open()
378 {
379 db_.Execute("PRAGMA ENCODING=\"UTF-8\";");
380
381 // Performance tuning of SQLite with PRAGMAs
382 // http://www.sqlite.org/pragma.html
383 db_.Execute("PRAGMA SYNCHRONOUS=NORMAL;");
384 db_.Execute("PRAGMA JOURNAL_MODE=WAL;");
385 db_.Execute("PRAGMA LOCKING_MODE=EXCLUSIVE;");
386 db_.Execute("PRAGMA WAL_AUTOCHECKPOINT=1000;");
387 //db_.Execute("PRAGMA TEMP_STORE=memory");
388
389 // Make "LIKE" case-sensitive in SQLite
390 db_.Execute("PRAGMA case_sensitive_like = true;");
391
392 {
393 SQLite::Transaction t(db_);
394 t.Begin();
395
396 if (!db_.DoesTableExist("GlobalProperties"))
397 {
398 LOG(INFO) << "Creating the database";
399 std::string query;
400 ServerResources::GetFileResource(query, ServerResources::PREPARE_DATABASE);
401 db_.Execute(query);
402 }
403
404 // Check the version of the database
405 std::string tmp;
406 if (!LookupGlobalProperty(tmp, GlobalProperty_DatabaseSchemaVersion))
407 {
408 tmp = "Unknown";
409 }
410
411 bool ok = false;
412 try
413 {
414 LOG(INFO) << "Version of the Orthanc database: " << tmp;
415 version_ = boost::lexical_cast<unsigned int>(tmp);
416 ok = true;
417 }
418 catch (boost::bad_lexical_cast&)
419 {
420 }
421
422 if (!ok)
423 {
424 throw OrthancException(ErrorCode_IncompatibleDatabaseVersion,
425 "Incompatible version of the Orthanc database: " + tmp);
426 }
427
428 // New in Orthanc 1.5.1
429 if (version_ == 6)
430 {
431 if (!LookupGlobalProperty(tmp, GlobalProperty_GetTotalSizeIsFast) ||
432 tmp != "1")
433 {
434 LOG(INFO) << "Installing the SQLite triggers to track the size of the attachments";
435 std::string query;
436 ServerResources::GetFileResource(query, ServerResources::INSTALL_TRACK_ATTACHMENTS_SIZE);
437 db_.Execute(query);
438 }
439 }
440
441 t.Commit();
442 }
443
444 signalRemainingAncestor_ = dynamic_cast<SignalRemainingAncestor*>(db_.Register(new SignalRemainingAncestor));
445 db_.Register(new SignalFileDeleted(*this));
446 db_.Register(new SignalResourceDeleted(*this));
447 }
448
449
450 static void ExecuteUpgradeScript(SQLite::Connection& db,
451 ServerResources::FileResourceId script)
452 {
453 std::string upgrade;
454 ServerResources::GetFileResource(upgrade, script);
455 db.BeginTransaction();
456 db.Execute(upgrade);
457 db.CommitTransaction();
458 }
459
460
461 void SQLiteDatabaseWrapper::Upgrade(unsigned int targetVersion,
462 IStorageArea& storageArea)
463 {
464 if (targetVersion != 6)
465 {
466 throw OrthancException(ErrorCode_IncompatibleDatabaseVersion);
467 }
468
469 // This version of Orthanc is only compatible with versions 3, 4,
470 // 5 and 6 of the DB schema
471 if (version_ != 3 &&
472 version_ != 4 &&
473 version_ != 5 &&
474 version_ != 6)
475 {
476 throw OrthancException(ErrorCode_IncompatibleDatabaseVersion);
477 }
478
479 if (version_ == 3)
480 {
481 LOG(WARNING) << "Upgrading database version from 3 to 4";
482 ExecuteUpgradeScript(db_, ServerResources::UPGRADE_DATABASE_3_TO_4);
483 version_ = 4;
484 }
485
486 if (version_ == 4)
487 {
488 LOG(WARNING) << "Upgrading database version from 4 to 5";
489 ExecuteUpgradeScript(db_, ServerResources::UPGRADE_DATABASE_4_TO_5);
490 version_ = 5;
491 }
492
493 if (version_ == 5)
494 {
495 LOG(WARNING) << "Upgrading database version from 5 to 6";
496 // No change in the DB schema, the step from version 5 to 6 only
497 // consists in reconstructing the main DICOM tags information
498 // (as more tags got included).
499 db_.BeginTransaction();
500 ServerToolbox::ReconstructMainDicomTags(*this, storageArea, ResourceType_Patient);
501 ServerToolbox::ReconstructMainDicomTags(*this, storageArea, ResourceType_Study);
502 ServerToolbox::ReconstructMainDicomTags(*this, storageArea, ResourceType_Series);
503 ServerToolbox::ReconstructMainDicomTags(*this, storageArea, ResourceType_Instance);
504 db_.Execute("UPDATE GlobalProperties SET value=\"6\" WHERE property=" +
505 boost::lexical_cast<std::string>(GlobalProperty_DatabaseSchemaVersion) + ";");
506 db_.CommitTransaction();
507 version_ = 6;
508 }
509 }
510
511
512 void SQLiteDatabaseWrapper::ClearTable(const std::string& tableName)
513 {
514 db_.Execute("DELETE FROM " + tableName);
515 }
516
517
518 bool SQLiteDatabaseWrapper::LookupParent(int64_t& parentId,
519 int64_t resourceId)
520 {
521 SQLite::Statement s(db_, SQLITE_FROM_HERE,
522 "SELECT parentId FROM Resources WHERE internalId=?");
523 s.BindInt64(0, resourceId);
524
525 if (!s.Step())
526 {
527 throw OrthancException(ErrorCode_UnknownResource);
528 }
529
530 if (s.ColumnIsNull(0))
531 {
532 return false;
533 }
534 else
535 {
536 parentId = s.ColumnInt(0);
537 return true;
538 }
539 }
540
541
542 ResourceType SQLiteDatabaseWrapper::GetResourceType(int64_t resourceId)
543 {
544 SQLite::Statement s(db_, SQLITE_FROM_HERE,
545 "SELECT resourceType FROM Resources WHERE internalId=?");
546 s.BindInt64(0, resourceId);
547
548 if (s.Step()) 1528 if (s.Step())
549 {
550 return static_cast<ResourceType>(s.ColumnInt(0));
551 }
552 else
553 {
554 throw OrthancException(ErrorCode_UnknownResource);
555 }
556 }
557
558
559 std::string SQLiteDatabaseWrapper::GetPublicId(int64_t resourceId)
560 {
561 SQLite::Statement s(db_, SQLITE_FROM_HERE,
562 "SELECT publicId FROM Resources WHERE internalId=?");
563 s.BindInt64(0, resourceId);
564
565 if (s.Step())
566 {
567 return s.ColumnString(0);
568 }
569 else
570 {
571 throw OrthancException(ErrorCode_UnknownResource);
572 }
573 }
574
575
576 void SQLiteDatabaseWrapper::GetChanges(std::list<ServerIndexChange>& target /*out*/,
577 bool& done /*out*/,
578 int64_t since,
579 uint32_t maxResults)
580 {
581 SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT * FROM Changes WHERE seq>? ORDER BY seq LIMIT ?");
582 s.BindInt64(0, since);
583 s.BindInt(1, maxResults + 1);
584 GetChangesInternal(target, done, s, maxResults);
585 }
586
587
588 void SQLiteDatabaseWrapper::GetLastChange(std::list<ServerIndexChange>& target /*out*/)
589 {
590 bool done; // Ignored
591 SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT * FROM Changes ORDER BY seq DESC LIMIT 1");
592 GetChangesInternal(target, done, s, 1);
593 }
594
595
596 class SQLiteDatabaseWrapper::ReadWriteTransaction : public IDatabaseWrapper::ITransaction
597 {
598 private:
599 SQLiteDatabaseWrapper& that_;
600 std::unique_ptr<SQLite::Transaction> transaction_;
601 int64_t initialDiskSize_;
602
603 public:
604 ReadWriteTransaction(SQLiteDatabaseWrapper& that,
605 IDatabaseListener& listener) :
606 that_(that),
607 transaction_(new SQLite::Transaction(that_.db_))
608 {
609 assert(that_.listener_ == NULL);
610 that_.listener_ = &listener; // TODO - STORE IN TRANSACTION
611
612 #if defined(NDEBUG)
613 // Release mode
614 initialDiskSize_ = 0;
615 #else
616 // Debug mode
617 initialDiskSize_ = static_cast<int64_t>(that_.GetTotalCompressedSize());
618 #endif
619 }
620
621 virtual ~ReadWriteTransaction()
622 {
623 assert(that_.listener_ != NULL);
624 that_.listener_ = NULL; // TODO - STORE IN TRANSACTION
625 }
626
627 void Begin()
628 {
629 transaction_->Begin();
630 }
631
632 virtual void Rollback() ORTHANC_OVERRIDE
633 {
634 transaction_->Rollback();
635 }
636
637 virtual void Commit(int64_t fileSizeDelta /* only used in debug */) ORTHANC_OVERRIDE
638 {
639 transaction_->Commit();
640
641 assert(initialDiskSize_ + fileSizeDelta >= 0 &&
642 initialDiskSize_ + fileSizeDelta == static_cast<int64_t>(that_.GetTotalCompressedSize()));
643 }
644 };
645
646
647 class SQLiteDatabaseWrapper::ReadOnlyTransaction : public IDatabaseWrapper::ITransaction
648 {
649 private:
650 SQLiteDatabaseWrapper& that_;
651
652 public:
653 ReadOnlyTransaction(SQLiteDatabaseWrapper& that,
654 IDatabaseListener& listener) :
655 that_(that)
656 {
657 assert(that_.listener_ == NULL);
658 that_.listener_ = &listener;
659 }
660
661 virtual ~ReadOnlyTransaction()
662 {
663 assert(that_.listener_ != NULL);
664 that_.listener_ = NULL;
665 }
666
667 virtual void Rollback() ORTHANC_OVERRIDE
668 {
669 }
670
671 virtual void Commit(int64_t fileSizeDelta /* only used in debug */) ORTHANC_OVERRIDE
672 {
673 if (fileSizeDelta != 0)
674 {
675 throw OrthancException(ErrorCode_InternalError);
676 }
677 }
678 };
679
680
681 IDatabaseWrapper::ITransaction* SQLiteDatabaseWrapper::StartTransaction(TransactionType type,
682 IDatabaseListener& listener)
683 {
684 switch (type)
685 {
686 case TransactionType_ReadOnly:
687 return new ReadOnlyTransaction(*this, listener); // This is a no-op transaction in SQLite (thanks to mutex)
688
689 case TransactionType_ReadWrite:
690 {
691 std::unique_ptr<ReadWriteTransaction> transaction;
692 transaction.reset(new ReadWriteTransaction(*this, listener));
693 transaction->Begin();
694 return transaction.release();
695 }
696
697 default:
698 throw OrthancException(ErrorCode_InternalError);
699 }
700 }
701
702
703 void SQLiteDatabaseWrapper::GetAllMetadata(std::map<MetadataType, std::string>& target,
704 int64_t id)
705 {
706 target.clear();
707
708 SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT type, value FROM Metadata WHERE id=?");
709 s.BindInt64(0, id);
710
711 while (s.Step())
712 {
713 MetadataType key = static_cast<MetadataType>(s.ColumnInt(0));
714 target[key] = s.ColumnString(1);
715 }
716 }
717
718
719 void SQLiteDatabaseWrapper::SetGlobalProperty(GlobalProperty property,
720 const std::string& value)
721 {
722 SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT OR REPLACE INTO GlobalProperties VALUES(?, ?)");
723 s.BindInt(0, property);
724 s.BindString(1, value);
725 s.Run();
726 }
727
728
729 bool SQLiteDatabaseWrapper::LookupGlobalProperty(std::string& target,
730 GlobalProperty property)
731 {
732 SQLite::Statement s(db_, SQLITE_FROM_HERE,
733 "SELECT value FROM GlobalProperties WHERE property=?");
734 s.BindInt(0, property);
735
736 if (!s.Step())
737 {
738 return false;
739 }
740 else
741 { 1529 {
742 target = s.ColumnString(0); 1530 target = s.ColumnString(0);
743 return true; 1531 return true;
744 } 1532 }
1533 else
1534 {
1535 return false;
1536 }
745 } 1537 }
746 1538
747 1539
748 int64_t SQLiteDatabaseWrapper::CreateResource(const std::string& publicId, 1540 void SQLiteDatabaseWrapper::UnitTestsTransaction::GetChildren(std::list<std::string>& childrenPublicIds,
749 ResourceType type) 1541 int64_t id)
750 { 1542 {
751 SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO Resources VALUES(NULL, ?, ?, NULL)"); 1543 SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT publicId FROM Resources WHERE parentId=?");
752 s.BindInt(0, type);
753 s.BindString(1, publicId);
754 s.Run();
755 return db_.GetLastInsertRowId();
756 }
757
758
759 bool SQLiteDatabaseWrapper::LookupResource(int64_t& id,
760 ResourceType& type,
761 const std::string& publicId)
762 {
763 SQLite::Statement s(db_, SQLITE_FROM_HERE,
764 "SELECT internalId, resourceType FROM Resources WHERE publicId=?");
765 s.BindString(0, publicId);
766
767 if (!s.Step())
768 {
769 return false;
770 }
771 else
772 {
773 id = s.ColumnInt(0);
774 type = static_cast<ResourceType>(s.ColumnInt(1));
775
776 // Check whether there is a single resource with this public id
777 assert(!s.Step());
778
779 return true;
780 }
781 }
782
783
784 void SQLiteDatabaseWrapper::AttachChild(int64_t parent,
785 int64_t child)
786 {
787 SQLite::Statement s(db_, SQLITE_FROM_HERE, "UPDATE Resources SET parentId = ? WHERE internalId = ?");
788 s.BindInt64(0, parent);
789 s.BindInt64(1, child);
790 s.Run();
791 }
792
793
794 void SQLiteDatabaseWrapper::SetMetadata(int64_t id,
795 MetadataType type,
796 const std::string& value)
797 {
798 SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT OR REPLACE INTO Metadata VALUES(?, ?, ?)");
799 s.BindInt64(0, id); 1544 s.BindInt64(0, id);
800 s.BindInt(1, type); 1545
801 s.BindString(2, value); 1546 childrenPublicIds.clear();
802 s.Run();
803 }
804
805
806 void SQLiteDatabaseWrapper::DeleteMetadata(int64_t id,
807 MetadataType type)
808 {
809 SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM Metadata WHERE id=? and type=?");
810 s.BindInt64(0, id);
811 s.BindInt(1, type);
812 s.Run();
813 }
814
815
816 bool SQLiteDatabaseWrapper::LookupMetadata(std::string& target,
817 int64_t id,
818 MetadataType type)
819 {
820 SQLite::Statement s(db_, SQLITE_FROM_HERE,
821 "SELECT value FROM Metadata WHERE id=? AND type=?");
822 s.BindInt64(0, id);
823 s.BindInt(1, type);
824
825 if (!s.Step())
826 {
827 return false;
828 }
829 else
830 {
831 target = s.ColumnString(0);
832 return true;
833 }
834 }
835
836
837 void SQLiteDatabaseWrapper::AddAttachment(int64_t id,
838 const FileInfo& attachment)
839 {
840 SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO AttachedFiles VALUES(?, ?, ?, ?, ?, ?, ?, ?)");
841 s.BindInt64(0, id);
842 s.BindInt(1, attachment.GetContentType());
843 s.BindString(2, attachment.GetUuid());
844 s.BindInt64(3, attachment.GetCompressedSize());
845 s.BindInt64(4, attachment.GetUncompressedSize());
846 s.BindInt(5, attachment.GetCompressionType());
847 s.BindString(6, attachment.GetUncompressedMD5());
848 s.BindString(7, attachment.GetCompressedMD5());
849 s.Run();
850 }
851
852
853 void SQLiteDatabaseWrapper::DeleteAttachment(int64_t id,
854 FileContentType attachment)
855 {
856 SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM AttachedFiles WHERE id=? AND fileType=?");
857 s.BindInt64(0, id);
858 s.BindInt(1, attachment);
859 s.Run();
860 }
861
862
863 void SQLiteDatabaseWrapper::ListAvailableAttachments(std::set<FileContentType>& target,
864 int64_t id)
865 {
866 target.clear();
867
868 SQLite::Statement s(db_, SQLITE_FROM_HERE,
869 "SELECT fileType FROM AttachedFiles WHERE id=?");
870 s.BindInt64(0, id);
871
872 while (s.Step()) 1547 while (s.Step())
873 { 1548 {
874 target.insert(static_cast<FileContentType>(s.ColumnInt(0))); 1549 childrenPublicIds.push_back(s.ColumnString(0));
875 }
876 }
877
878 bool SQLiteDatabaseWrapper::LookupAttachment(FileInfo& attachment,
879 int64_t id,
880 FileContentType contentType)
881 {
882 SQLite::Statement s(db_, SQLITE_FROM_HERE,
883 "SELECT uuid, uncompressedSize, compressionType, compressedSize, "
884 "uncompressedMD5, compressedMD5 FROM AttachedFiles WHERE id=? AND fileType=?");
885 s.BindInt64(0, id);
886 s.BindInt(1, contentType);
887
888 if (!s.Step())
889 {
890 return false;
891 }
892 else
893 {
894 attachment = FileInfo(s.ColumnString(0),
895 contentType,
896 s.ColumnInt64(1),
897 s.ColumnString(4),
898 static_cast<CompressionType>(s.ColumnInt(2)),
899 s.ColumnInt64(3),
900 s.ColumnString(5));
901 return true;
902 }
903 }
904
905
906 void SQLiteDatabaseWrapper::ClearMainDicomTags(int64_t id)
907 {
908 {
909 SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM DicomIdentifiers WHERE id=?");
910 s.BindInt64(0, id);
911 s.Run();
912 }
913
914 {
915 SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM MainDicomTags WHERE id=?");
916 s.BindInt64(0, id);
917 s.Run();
918 }
919 }
920
921
922 void SQLiteDatabaseWrapper::SetMainDicomTag(int64_t id,
923 const DicomTag& tag,
924 const std::string& value)
925 {
926 SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO MainDicomTags VALUES(?, ?, ?, ?)");
927 s.BindInt64(0, id);
928 s.BindInt(1, tag.GetGroup());
929 s.BindInt(2, tag.GetElement());
930 s.BindString(3, value);
931 s.Run();
932 }
933
934
935 void SQLiteDatabaseWrapper::SetIdentifierTag(int64_t id,
936 const DicomTag& tag,
937 const std::string& value)
938 {
939 SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO DicomIdentifiers VALUES(?, ?, ?, ?)");
940 s.BindInt64(0, id);
941 s.BindInt(1, tag.GetGroup());
942 s.BindInt(2, tag.GetElement());
943 s.BindString(3, value);
944 s.Run();
945 }
946
947
948 void SQLiteDatabaseWrapper::GetMainDicomTags(DicomMap& map,
949 int64_t id)
950 {
951 map.Clear();
952
953 SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT * FROM MainDicomTags WHERE id=?");
954 s.BindInt64(0, id);
955 while (s.Step())
956 {
957 map.SetValue(s.ColumnInt(1),
958 s.ColumnInt(2),
959 s.ColumnString(3), false);
960 }
961 }
962
963
964 void SQLiteDatabaseWrapper::GetChildrenPublicId(std::list<std::string>& target,
965 int64_t id)
966 {
967 SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT a.publicId FROM Resources AS a, Resources AS b "
968 "WHERE a.parentId = b.internalId AND b.internalId = ?");
969 s.BindInt64(0, id);
970
971 target.clear();
972
973 while (s.Step())
974 {
975 target.push_back(s.ColumnString(0));
976 }
977 }
978
979
980 void SQLiteDatabaseWrapper::GetChildrenInternalId(std::list<int64_t>& target,
981 int64_t id)
982 {
983 SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT a.internalId FROM Resources AS a, Resources AS b "
984 "WHERE a.parentId = b.internalId AND b.internalId = ?");
985 s.BindInt64(0, id);
986
987 target.clear();
988
989 while (s.Step())
990 {
991 target.push_back(s.ColumnInt64(0));
992 }
993 }
994
995
996 void SQLiteDatabaseWrapper::LogChange(int64_t internalId,
997 const ServerIndexChange& change)
998 {
999 SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO Changes VALUES(NULL, ?, ?, ?, ?)");
1000 s.BindInt(0, change.GetChangeType());
1001 s.BindInt64(1, internalId);
1002 s.BindInt(2, change.GetResourceType());
1003 s.BindString(3, change.GetDate());
1004 s.Run();
1005 }
1006
1007
1008 void SQLiteDatabaseWrapper::LogExportedResource(const ExportedResource& resource)
1009 {
1010 SQLite::Statement s(db_, SQLITE_FROM_HERE,
1011 "INSERT INTO ExportedResources VALUES(NULL, ?, ?, ?, ?, ?, ?, ?, ?)");
1012
1013 s.BindInt(0, resource.GetResourceType());
1014 s.BindString(1, resource.GetPublicId());
1015 s.BindString(2, resource.GetModality());
1016 s.BindString(3, resource.GetPatientId());
1017 s.BindString(4, resource.GetStudyInstanceUid());
1018 s.BindString(5, resource.GetSeriesInstanceUid());
1019 s.BindString(6, resource.GetSopInstanceUid());
1020 s.BindString(7, resource.GetDate());
1021 s.Run();
1022 }
1023
1024
1025 void SQLiteDatabaseWrapper::GetExportedResources(std::list<ExportedResource>& target,
1026 bool& done,
1027 int64_t since,
1028 uint32_t maxResults)
1029 {
1030 SQLite::Statement s(db_, SQLITE_FROM_HERE,
1031 "SELECT * FROM ExportedResources WHERE seq>? ORDER BY seq LIMIT ?");
1032 s.BindInt64(0, since);
1033 s.BindInt(1, maxResults + 1);
1034 GetExportedResourcesInternal(target, done, s, maxResults);
1035 }
1036
1037
1038 void SQLiteDatabaseWrapper::GetLastExportedResource(std::list<ExportedResource>& target)
1039 {
1040 bool done; // Ignored
1041 SQLite::Statement s(db_, SQLITE_FROM_HERE,
1042 "SELECT * FROM ExportedResources ORDER BY seq DESC LIMIT 1");
1043 GetExportedResourcesInternal(target, done, s, 1);
1044 }
1045
1046
1047 uint64_t SQLiteDatabaseWrapper::GetTotalCompressedSize()
1048 {
1049 // Old SQL query that was used in Orthanc <= 1.5.0:
1050 // SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT SUM(compressedSize) FROM AttachedFiles");
1051
1052 SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT value FROM GlobalIntegers WHERE key=0");
1053 s.Run();
1054 return static_cast<uint64_t>(s.ColumnInt64(0));
1055 }
1056
1057
1058 uint64_t SQLiteDatabaseWrapper::GetTotalUncompressedSize()
1059 {
1060 // Old SQL query that was used in Orthanc <= 1.5.0:
1061 // SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT SUM(uncompressedSize) FROM AttachedFiles");
1062
1063 SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT value FROM GlobalIntegers WHERE key=1");
1064 s.Run();
1065 return static_cast<uint64_t>(s.ColumnInt64(0));
1066 }
1067
1068
1069 uint64_t SQLiteDatabaseWrapper::GetResourceCount(ResourceType resourceType)
1070 {
1071 SQLite::Statement s(db_, SQLITE_FROM_HERE,
1072 "SELECT COUNT(*) FROM Resources WHERE resourceType=?");
1073 s.BindInt(0, resourceType);
1074
1075 if (!s.Step())
1076 {
1077 return 0;
1078 }
1079 else
1080 {
1081 int64_t c = s.ColumnInt(0);
1082 assert(!s.Step());
1083 return c;
1084 }
1085 }
1086
1087
1088 void SQLiteDatabaseWrapper::GetAllPublicIds(std::list<std::string>& target,
1089 ResourceType resourceType)
1090 {
1091 SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT publicId FROM Resources WHERE resourceType=?");
1092 s.BindInt(0, resourceType);
1093
1094 target.clear();
1095 while (s.Step())
1096 {
1097 target.push_back(s.ColumnString(0));
1098 }
1099 }
1100
1101
1102 void SQLiteDatabaseWrapper::GetAllPublicIds(std::list<std::string>& target,
1103 ResourceType resourceType,
1104 size_t since,
1105 size_t limit)
1106 {
1107 if (limit == 0)
1108 {
1109 target.clear();
1110 return;
1111 }
1112
1113 SQLite::Statement s(db_, SQLITE_FROM_HERE,
1114 "SELECT publicId FROM Resources WHERE "
1115 "resourceType=? LIMIT ? OFFSET ?");
1116 s.BindInt(0, resourceType);
1117 s.BindInt64(1, limit);
1118 s.BindInt64(2, since);
1119
1120 target.clear();
1121 while (s.Step())
1122 {
1123 target.push_back(s.ColumnString(0));
1124 }
1125 }
1126
1127
1128 bool SQLiteDatabaseWrapper::SelectPatientToRecycle(int64_t& internalId)
1129 {
1130 SQLite::Statement s(db_, SQLITE_FROM_HERE,
1131 "SELECT patientId FROM PatientRecyclingOrder ORDER BY seq ASC LIMIT 1");
1132
1133 if (!s.Step())
1134 {
1135 // No patient remaining or all the patients are protected
1136 return false;
1137 }
1138 else
1139 {
1140 internalId = s.ColumnInt(0);
1141 return true;
1142 }
1143 }
1144
1145
1146 bool SQLiteDatabaseWrapper::SelectPatientToRecycle(int64_t& internalId,
1147 int64_t patientIdToAvoid)
1148 {
1149 SQLite::Statement s(db_, SQLITE_FROM_HERE,
1150 "SELECT patientId FROM PatientRecyclingOrder "
1151 "WHERE patientId != ? ORDER BY seq ASC LIMIT 1");
1152 s.BindInt64(0, patientIdToAvoid);
1153
1154 if (!s.Step())
1155 {
1156 // No patient remaining or all the patients are protected
1157 return false;
1158 }
1159 else
1160 {
1161 internalId = s.ColumnInt(0);
1162 return true;
1163 }
1164 }
1165
1166
1167 bool SQLiteDatabaseWrapper::IsProtectedPatient(int64_t internalId)
1168 {
1169 SQLite::Statement s(db_, SQLITE_FROM_HERE,
1170 "SELECT * FROM PatientRecyclingOrder WHERE patientId = ?");
1171 s.BindInt64(0, internalId);
1172 return !s.Step();
1173 }
1174
1175
1176 void SQLiteDatabaseWrapper::SetProtectedPatient(int64_t internalId,
1177 bool isProtected)
1178 {
1179 if (isProtected)
1180 {
1181 SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM PatientRecyclingOrder WHERE patientId=?");
1182 s.BindInt64(0, internalId);
1183 s.Run();
1184 }
1185 else if (IsProtectedPatient(internalId))
1186 {
1187 SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO PatientRecyclingOrder VALUES(NULL, ?)");
1188 s.BindInt64(0, internalId);
1189 s.Run();
1190 }
1191 else
1192 {
1193 // Nothing to do: The patient is already unprotected
1194 }
1195 }
1196
1197
1198 bool SQLiteDatabaseWrapper::IsExistingResource(int64_t internalId)
1199 {
1200 SQLite::Statement s(db_, SQLITE_FROM_HERE,
1201 "SELECT * FROM Resources WHERE internalId=?");
1202 s.BindInt64(0, internalId);
1203 return s.Step();
1204 }
1205
1206
1207 bool SQLiteDatabaseWrapper::IsDiskSizeAbove(uint64_t threshold)
1208 {
1209 return GetTotalCompressedSize() > threshold;
1210 }
1211
1212
1213
1214 class SQLiteDatabaseWrapper::LookupFormatter : public ISqlLookupFormatter
1215 {
1216 private:
1217 std::list<std::string> values_;
1218
1219 public:
1220 virtual std::string GenerateParameter(const std::string& value) ORTHANC_OVERRIDE
1221 {
1222 values_.push_back(value);
1223 return "?";
1224 }
1225
1226 virtual std::string FormatResourceType(ResourceType level) ORTHANC_OVERRIDE
1227 {
1228 return boost::lexical_cast<std::string>(level);
1229 }
1230
1231 virtual std::string FormatWildcardEscape() ORTHANC_OVERRIDE
1232 {
1233 return "ESCAPE '\\'";
1234 }
1235
1236 void Bind(SQLite::Statement& statement) const
1237 {
1238 size_t pos = 0;
1239
1240 for (std::list<std::string>::const_iterator
1241 it = values_.begin(); it != values_.end(); ++it, pos++)
1242 {
1243 statement.BindString(pos, *it);
1244 }
1245 }
1246 };
1247
1248
1249 static void AnswerLookup(std::list<std::string>& resourcesId,
1250 std::list<std::string>& instancesId,
1251 SQLite::Connection& db,
1252 ResourceType level)
1253 {
1254 resourcesId.clear();
1255 instancesId.clear();
1256
1257 std::unique_ptr<SQLite::Statement> statement;
1258
1259 switch (level)
1260 {
1261 case ResourceType_Patient:
1262 {
1263 statement.reset(
1264 new SQLite::Statement(
1265 db, SQLITE_FROM_HERE,
1266 "SELECT patients.publicId, instances.publicID FROM Lookup AS patients "
1267 "INNER JOIN Resources studies ON patients.internalId=studies.parentId "
1268 "INNER JOIN Resources series ON studies.internalId=series.parentId "
1269 "INNER JOIN Resources instances ON series.internalId=instances.parentId "
1270 "GROUP BY patients.publicId"));
1271
1272 break;
1273 }
1274
1275 case ResourceType_Study:
1276 {
1277 statement.reset(
1278 new SQLite::Statement(
1279 db, SQLITE_FROM_HERE,
1280 "SELECT studies.publicId, instances.publicID FROM Lookup AS studies "
1281 "INNER JOIN Resources series ON studies.internalId=series.parentId "
1282 "INNER JOIN Resources instances ON series.internalId=instances.parentId "
1283 "GROUP BY studies.publicId"));
1284
1285 break;
1286 }
1287
1288 case ResourceType_Series:
1289 {
1290 statement.reset(
1291 new SQLite::Statement(
1292 db, SQLITE_FROM_HERE,
1293 "SELECT series.publicId, instances.publicID FROM Lookup AS series "
1294 "INNER JOIN Resources instances ON series.internalId=instances.parentId "
1295 "GROUP BY series.publicId"));
1296
1297 break;
1298 }
1299
1300 case ResourceType_Instance:
1301 {
1302 statement.reset(
1303 new SQLite::Statement(
1304 db, SQLITE_FROM_HERE, "SELECT publicId, publicId FROM Lookup"));
1305
1306 break;
1307 }
1308
1309 default:
1310 throw OrthancException(ErrorCode_InternalError);
1311 }
1312
1313 assert(statement.get() != NULL);
1314
1315 while (statement->Step())
1316 {
1317 resourcesId.push_back(statement->ColumnString(0));
1318 instancesId.push_back(statement->ColumnString(1));
1319 }
1320 }
1321
1322
1323 void SQLiteDatabaseWrapper::ApplyLookupResources(std::list<std::string>& resourcesId,
1324 std::list<std::string>* instancesId,
1325 const std::vector<DatabaseConstraint>& lookup,
1326 ResourceType queryLevel,
1327 size_t limit)
1328 {
1329 LookupFormatter formatter;
1330
1331 std::string sql;
1332 LookupFormatter::Apply(sql, formatter, lookup, queryLevel, limit);
1333
1334 sql = "CREATE TEMPORARY TABLE Lookup AS " + sql;
1335
1336 {
1337 SQLite::Statement s(db_, SQLITE_FROM_HERE, "DROP TABLE IF EXISTS Lookup");
1338 s.Run();
1339 }
1340
1341 {
1342 SQLite::Statement statement(db_, sql);
1343 formatter.Bind(statement);
1344 statement.Run();
1345 }
1346
1347 if (instancesId != NULL)
1348 {
1349 AnswerLookup(resourcesId, *instancesId, db_, queryLevel);
1350 }
1351 else
1352 {
1353 resourcesId.clear();
1354
1355 SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT publicId FROM Lookup");
1356
1357 while (s.Step())
1358 {
1359 resourcesId.push_back(s.ColumnString(0));
1360 }
1361 }
1362 }
1363
1364
1365 int64_t SQLiteDatabaseWrapper::GetLastChangeIndex()
1366 {
1367 SQLite::Statement s(db_, SQLITE_FROM_HERE,
1368 "SELECT seq FROM sqlite_sequence WHERE name='Changes'");
1369
1370 if (s.Step())
1371 {
1372 int64_t c = s.ColumnInt(0);
1373 assert(!s.Step());
1374 return c;
1375 }
1376 else
1377 {
1378 // No change has been recorded so far in the database
1379 return 0;
1380 }
1381 }
1382
1383
1384 void SQLiteDatabaseWrapper::TagMostRecentPatient(int64_t patient)
1385 {
1386 {
1387 SQLite::Statement s(db_, SQLITE_FROM_HERE,
1388 "DELETE FROM PatientRecyclingOrder WHERE patientId=?");
1389 s.BindInt64(0, patient);
1390 s.Run();
1391
1392 assert(db_.GetLastChangeCount() == 0 ||
1393 db_.GetLastChangeCount() == 1);
1394
1395 if (db_.GetLastChangeCount() == 0)
1396 {
1397 // The patient was protected, there was nothing to delete from the recycling order
1398 return;
1399 }
1400 }
1401
1402 {
1403 SQLite::Statement s(db_, SQLITE_FROM_HERE,
1404 "INSERT INTO PatientRecyclingOrder VALUES(NULL, ?)");
1405 s.BindInt64(0, patient);
1406 s.Run();
1407 } 1550 }
1408 } 1551 }
1409 } 1552 }