Mercurial > hg > orthanc
comparison OrthancServer/Sources/Database/SQLiteDatabaseWrapper.cpp @ 5809:023a99146dd0 attach-custom-data
merged find-refactoring -> attach-custom-data
author | Alain Mazy <am@orthanc.team> |
---|---|
date | Tue, 24 Sep 2024 12:53:43 +0200 |
parents | 8279eaab0d1d 8a8756b2dd0b |
children |
comparison
equal
deleted
inserted
replaced
5808:63c025cf6958 | 5809:023a99146dd0 |
---|---|
27 #include "../../../OrthancFramework/Sources/DicomFormat/DicomArray.h" | 27 #include "../../../OrthancFramework/Sources/DicomFormat/DicomArray.h" |
28 #include "../../../OrthancFramework/Sources/Logging.h" | 28 #include "../../../OrthancFramework/Sources/Logging.h" |
29 #include "../../../OrthancFramework/Sources/SQLite/Transaction.h" | 29 #include "../../../OrthancFramework/Sources/SQLite/Transaction.h" |
30 #include "../Search/ISqlLookupFormatter.h" | 30 #include "../Search/ISqlLookupFormatter.h" |
31 #include "../ServerToolbox.h" | 31 #include "../ServerToolbox.h" |
32 #include "Compatibility/GenericFind.h" | |
32 #include "Compatibility/ICreateInstance.h" | 33 #include "Compatibility/ICreateInstance.h" |
33 #include "Compatibility/IGetChildrenMetadata.h" | 34 #include "Compatibility/IGetChildrenMetadata.h" |
34 #include "Compatibility/ILookupResourceAndParent.h" | 35 #include "Compatibility/ILookupResourceAndParent.h" |
35 #include "Compatibility/ISetResourcesContent.h" | 36 #include "Compatibility/ISetResourcesContent.h" |
36 #include "VoidDatabaseListener.h" | 37 #include "VoidDatabaseListener.h" |
42 | 43 |
43 static std::map<std::string, std::string> filesToDeleteCustomData; | 44 static std::map<std::string, std::string> filesToDeleteCustomData; |
44 | 45 |
45 namespace Orthanc | 46 namespace Orthanc |
46 { | 47 { |
48 static std::string JoinRequestedMetadata(const FindRequest::ChildrenSpecification& childrenSpec) | |
49 { | |
50 std::set<std::string> metadataTypes; | |
51 for (std::set<MetadataType>::const_iterator it = childrenSpec.GetMetadata().begin(); it != childrenSpec.GetMetadata().end(); ++it) | |
52 { | |
53 metadataTypes.insert(boost::lexical_cast<std::string>(*it)); | |
54 } | |
55 std::string joinedMetadataTypes; | |
56 Orthanc::Toolbox::JoinStrings(joinedMetadataTypes, metadataTypes, ", "); | |
57 | |
58 return joinedMetadataTypes; | |
59 } | |
60 | |
61 static std::string JoinRequestedTags(const FindRequest::ChildrenSpecification& childrenSpec) | |
62 { | |
63 // note: SQLite does not seem to support (tagGroup, tagElement) in ((x, y), (z, w)) in complex subqueries. | |
64 // Therefore, since we expect the requested tag list to be short, we write it as | |
65 // ((tagGroup = x AND tagElement = y ) OR (tagGroup = z AND tagElement = w)) | |
66 | |
67 std::string sql = " ("; | |
68 std::set<std::string> tags; | |
69 for (std::set<DicomTag>::const_iterator it = childrenSpec.GetMainDicomTags().begin(); it != childrenSpec.GetMainDicomTags().end(); ++it) | |
70 { | |
71 tags.insert("(tagGroup = " + boost::lexical_cast<std::string>(it->GetGroup()) | |
72 + " AND tagElement = " + boost::lexical_cast<std::string>(it->GetElement()) + ")"); | |
73 } | |
74 std::string joinedTags; | |
75 Orthanc::Toolbox::JoinStrings(joinedTags, tags, " OR "); | |
76 | |
77 sql += joinedTags + ") "; | |
78 return sql; | |
79 } | |
80 | |
81 static std::string JoinChanges(const std::set<ChangeType>& changeTypes) | |
82 { | |
83 std::set<std::string> changeTypesString; | |
84 for (std::set<ChangeType>::const_iterator it = changeTypes.begin(); it != changeTypes.end(); ++it) | |
85 { | |
86 changeTypesString.insert(boost::lexical_cast<std::string>(static_cast<uint32_t>(*it))); | |
87 } | |
88 | |
89 std::string joinedChangesTypes; | |
90 Orthanc::Toolbox::JoinStrings(joinedChangesTypes, changeTypesString, ", "); | |
91 | |
92 return joinedChangesTypes; | |
93 } | |
94 | |
47 class SQLiteDatabaseWrapper::LookupFormatter : public ISqlLookupFormatter | 95 class SQLiteDatabaseWrapper::LookupFormatter : public ISqlLookupFormatter |
48 { | 96 { |
49 private: | 97 private: |
50 std::list<std::string> values_; | 98 std::list<std::string> values_; |
51 | 99 |
62 } | 110 } |
63 | 111 |
64 virtual std::string FormatWildcardEscape() ORTHANC_OVERRIDE | 112 virtual std::string FormatWildcardEscape() ORTHANC_OVERRIDE |
65 { | 113 { |
66 return "ESCAPE '\\'"; | 114 return "ESCAPE '\\'"; |
115 } | |
116 | |
117 virtual std::string FormatLimits(uint64_t since, uint64_t count) ORTHANC_OVERRIDE | |
118 { | |
119 std::string sql; | |
120 | |
121 if (count > 0) | |
122 { | |
123 sql += " LIMIT " + boost::lexical_cast<std::string>(count); | |
124 } | |
125 | |
126 if (since > 0) | |
127 { | |
128 if (count == 0) | |
129 { | |
130 sql += " LIMIT -1"; // In SQLite, "OFFSET" cannot appear without "LIMIT" | |
131 } | |
132 | |
133 sql += " OFFSET " + boost::lexical_cast<std::string>(since); | |
134 } | |
135 | |
136 return sql; | |
67 } | 137 } |
68 | 138 |
69 virtual bool IsEscapeBrackets() const ORTHANC_OVERRIDE | 139 virtual bool IsEscapeBrackets() const ORTHANC_OVERRIDE |
70 { | 140 { |
71 return false; | 141 return false; |
234 | 304 |
235 | 305 |
236 void GetChangesInternal(std::list<ServerIndexChange>& target, | 306 void GetChangesInternal(std::list<ServerIndexChange>& target, |
237 bool& done, | 307 bool& done, |
238 SQLite::Statement& s, | 308 SQLite::Statement& s, |
239 uint32_t limit) | 309 uint32_t limit, |
310 bool returnFirstResults) // the statement usually returns limit+1 results while we only need the limit results -> we need to know which ones to return, the firsts or the lasts | |
240 { | 311 { |
241 target.clear(); | 312 target.clear(); |
242 | 313 |
243 while (target.size() < limit && s.Step()) | 314 while (s.Step()) |
244 { | 315 { |
245 int64_t seq = s.ColumnInt64(0); | 316 int64_t seq = s.ColumnInt64(0); |
246 ChangeType changeType = static_cast<ChangeType>(s.ColumnInt(1)); | 317 ChangeType changeType = static_cast<ChangeType>(s.ColumnInt(1)); |
247 ResourceType resourceType = static_cast<ResourceType>(s.ColumnInt(3)); | 318 ResourceType resourceType = static_cast<ResourceType>(s.ColumnInt(3)); |
248 const std::string& date = s.ColumnString(4); | 319 const std::string& date = s.ColumnString(4); |
251 std::string publicId = GetPublicId(internalId); | 322 std::string publicId = GetPublicId(internalId); |
252 | 323 |
253 target.push_back(ServerIndexChange(seq, changeType, resourceType, publicId, date)); | 324 target.push_back(ServerIndexChange(seq, changeType, resourceType, publicId, date)); |
254 } | 325 } |
255 | 326 |
256 done = !(target.size() == limit && s.Step()); | 327 done = target.size() <= limit; // 'done' means "there are no more other changes of this type in that direction (depending on since/to)" |
328 | |
329 // if we have retrieved more changes than requested -> cleanup | |
330 if (target.size() > limit) | |
331 { | |
332 assert(target.size() == limit+1); // the statement should only request 1 element more | |
333 | |
334 if (returnFirstResults) | |
335 { | |
336 target.pop_back(); | |
337 } | |
338 else | |
339 { | |
340 target.pop_front(); | |
341 } | |
342 } | |
257 } | 343 } |
258 | 344 |
259 | 345 |
260 void GetExportedResourcesInternal(std::list<ExportedResource>& target, | 346 void GetExportedResourcesInternal(std::list<ExportedResource>& target, |
261 bool& done, | 347 bool& done, |
353 LookupFormatter formatter; | 439 LookupFormatter formatter; |
354 | 440 |
355 std::string sql; | 441 std::string sql; |
356 LookupFormatter::Apply(sql, formatter, lookup, queryLevel, labels, labelsConstraint, limit); | 442 LookupFormatter::Apply(sql, formatter, lookup, queryLevel, labels, labelsConstraint, limit); |
357 | 443 |
358 sql = "CREATE TEMPORARY TABLE Lookup AS " + sql; | 444 sql = "CREATE TEMPORARY TABLE Lookup AS " + sql; // TODO-FIND: use a CTE (or is this method obsolete ?) |
359 | 445 |
360 { | 446 { |
361 SQLite::Statement s(db_, SQLITE_FROM_HERE, "DROP TABLE IF EXISTS Lookup"); | 447 SQLite::Statement s(db_, SQLITE_FROM_HERE, "DROP TABLE IF EXISTS Lookup"); |
362 s.Run(); | 448 s.Run(); |
363 } | 449 } |
383 resourcesId.push_back(s.ColumnString(0)); | 469 resourcesId.push_back(s.ColumnString(0)); |
384 } | 470 } |
385 } | 471 } |
386 } | 472 } |
387 | 473 |
474 #define C0_QUERY_ID 0 | |
475 #define C1_INTERNAL_ID 1 | |
476 #define C2_ROW_NUMBER 2 | |
477 #define C3_STRING_1 3 | |
478 #define C4_STRING_2 4 | |
479 #define C5_STRING_3 5 | |
480 #define C6_STRING_4 6 | |
481 #define C7_INT_1 7 | |
482 #define C8_INT_2 8 | |
483 #define C9_BIG_INT_1 9 | |
484 #define C10_BIG_INT_2 10 | |
485 | |
486 #define QUERY_LOOKUP 1 | |
487 #define QUERY_MAIN_DICOM_TAGS 2 | |
488 #define QUERY_ATTACHMENTS 3 | |
489 #define QUERY_METADATA 4 | |
490 #define QUERY_LABELS 5 | |
491 #define QUERY_PARENT_MAIN_DICOM_TAGS 10 | |
492 #define QUERY_PARENT_IDENTIFIER 11 | |
493 #define QUERY_PARENT_METADATA 12 | |
494 #define QUERY_GRAND_PARENT_MAIN_DICOM_TAGS 15 | |
495 #define QUERY_GRAND_PARENT_METADATA 16 | |
496 #define QUERY_CHILDREN_IDENTIFIERS 20 | |
497 #define QUERY_CHILDREN_MAIN_DICOM_TAGS 21 | |
498 #define QUERY_CHILDREN_METADATA 22 | |
499 #define QUERY_GRAND_CHILDREN_IDENTIFIERS 30 | |
500 #define QUERY_GRAND_CHILDREN_MAIN_DICOM_TAGS 31 | |
501 #define QUERY_GRAND_CHILDREN_METADATA 32 | |
502 #define QUERY_GRAND_GRAND_CHILDREN_IDENTIFIERS 40 | |
503 #define QUERY_ONE_INSTANCE_IDENTIFIER 50 | |
504 #define QUERY_ONE_INSTANCE_METADATA 51 | |
505 #define QUERY_ONE_INSTANCE_ATTACHMENTS 52 | |
506 | |
507 #define STRINGIFY(x) #x | |
508 #define TOSTRING(x) STRINGIFY(x) | |
509 | |
510 | |
511 virtual void ExecuteFind(FindResponse& response, | |
512 const FindRequest& request, | |
513 const Capabilities& capabilities) ORTHANC_OVERRIDE | |
514 { | |
515 LookupFormatter formatter; | |
516 std::string sql; | |
517 const ResourceType requestLevel = request.GetLevel(); | |
518 | |
519 std::string lookupSql; | |
520 LookupFormatter::Apply(lookupSql, formatter, request); | |
521 | |
522 // base query, retrieve the ordered internalId and publicId of the selected resources | |
523 sql = "WITH Lookup AS (" + lookupSql + ") "; | |
524 | |
525 // in SQLite, all CTEs must be created at the beginning of the query, you can not define local CTE inside subqueries | |
526 // need one instance info ? (part 1: create the CTE) | |
527 if (request.GetLevel() != ResourceType_Instance && | |
528 request.IsRetrieveOneInstanceMetadataAndAttachments()) | |
529 { | |
530 // Here, we create a nested CTE 'OneInstance' with one instance ID to join with metadata and main | |
531 sql += ", OneInstance AS"; | |
532 | |
533 switch (requestLevel) | |
534 { | |
535 case ResourceType_Series: | |
536 { | |
537 sql+= " (SELECT Lookup.internalId AS parentInternalId, childLevel.publicId AS instancePublicId, childLevel.internalId AS instanceInternalId" | |
538 " FROM Resources AS childLevel " | |
539 " INNER JOIN Lookup ON childLevel.parentId = Lookup.internalId GROUP BY Lookup.internalId) "; | |
540 break; | |
541 } | |
542 | |
543 case ResourceType_Study: | |
544 { | |
545 sql+= " (SELECT Lookup.internalId AS parentInternalId, grandChildLevel.publicId AS instancePublicId, grandChildLevel.internalId AS instanceInternalId" | |
546 " FROM Resources AS grandChildLevel " | |
547 " INNER JOIN Resources childLevel ON grandChildLevel.parentId = childLevel.internalId " | |
548 " INNER JOIN Lookup ON childLevel.parentId = Lookup.internalId GROUP BY Lookup.internalId) "; | |
549 break; | |
550 } | |
551 | |
552 case ResourceType_Patient: | |
553 { | |
554 sql+= " (SELECT Lookup.internalId AS parentInternalId, grandGrandChildLevel.publicId AS instancePublicId, grandGrandChildLevel.internalId AS instanceInternalId" | |
555 " FROM Resources AS grandGrandChildLevel " | |
556 " INNER JOIN Resources grandChildLevel ON grandGrandChildLevel.parentId = grandChildLevel.internalId " | |
557 " INNER JOIN Resources childLevel ON grandChildLevel.parentId = childLevel.internalId " | |
558 " INNER JOIN Lookup ON childLevel.parentId = Lookup.internalId GROUP BY Lookup.internalId) "; | |
559 break; | |
560 } | |
561 | |
562 default: | |
563 throw OrthancException(ErrorCode_InternalError); | |
564 } | |
565 } | |
566 | |
567 sql += "SELECT " | |
568 " " TOSTRING(QUERY_LOOKUP) " AS c0_queryId, " | |
569 " Lookup.internalId AS c1_internalId, " | |
570 " Lookup.rowNumber AS c2_rowNumber, " | |
571 " Lookup.publicId AS c3_string1, " | |
572 " NULL AS c4_string2, " | |
573 " NULL AS c5_string3, " | |
574 " NULL AS c6_string4, " | |
575 " NULL AS c7_int1, " | |
576 " NULL AS c8_int2, " | |
577 " NULL AS c9_big_int1, " | |
578 " NULL AS c10_big_int2 " | |
579 " FROM Lookup "; | |
580 | |
581 // need one instance info ? (part 2: execute the queries) | |
582 if (request.GetLevel() != ResourceType_Instance && | |
583 request.IsRetrieveOneInstanceMetadataAndAttachments()) | |
584 { | |
585 sql += " UNION SELECT" | |
586 " " TOSTRING(QUERY_ONE_INSTANCE_IDENTIFIER) " AS c0_queryId, " | |
587 " parentInternalId AS c1_internalId, " | |
588 " NULL AS c2_rowNumber, " | |
589 " instancePublicId AS c3_string1, " | |
590 " NULL AS c4_string2, " | |
591 " NULL AS c5_string3, " | |
592 " NULL AS c6_string4, " | |
593 " NULL AS c7_int1, " | |
594 " NULL AS c8_int2, " | |
595 " instanceInternalId AS c9_big_int1, " | |
596 " NULL AS c10_big_int2 " | |
597 " FROM OneInstance "; | |
598 | |
599 sql += " UNION SELECT" | |
600 " " TOSTRING(QUERY_ONE_INSTANCE_METADATA) " AS c0_queryId, " | |
601 " parentInternalId AS c1_internalId, " | |
602 " NULL AS c2_rowNumber, " | |
603 " Metadata.value AS c3_string1, " | |
604 " NULL AS c4_string2, " | |
605 " NULL AS c5_string3, " | |
606 " NULL AS c6_string4, " | |
607 " Metadata.type AS c7_int1, " | |
608 " NULL AS c8_int2, " | |
609 " NULL AS c9_big_int1, " | |
610 " NULL AS c10_big_int2 " | |
611 " FROM OneInstance " | |
612 " INNER JOIN Metadata ON Metadata.id = OneInstance.instanceInternalId "; | |
613 | |
614 sql += " UNION SELECT" | |
615 " " TOSTRING(QUERY_ONE_INSTANCE_ATTACHMENTS) " AS c0_queryId, " | |
616 " parentInternalId AS c1_internalId, " | |
617 " NULL AS c2_rowNumber, " | |
618 " uuid AS c3_string1, " | |
619 " uncompressedMD5 AS c4_string2, " | |
620 " compressedMD5 AS c5_string3, " | |
621 " customData AS c6_string4, " | |
622 " fileType AS c7_int1, " | |
623 " compressionType AS c8_int2, " | |
624 " compressedSize AS c9_big_int1, " | |
625 " uncompressedSize AS c10_big_int2 " | |
626 " FROM OneInstance " | |
627 " INNER JOIN AttachedFiles ON AttachedFiles.id = OneInstance.instanceInternalId "; | |
628 | |
629 } | |
630 | |
631 // need MainDicomTags from resource ? | |
632 if (request.IsRetrieveMainDicomTags()) | |
633 { | |
634 sql += "UNION SELECT " | |
635 " " TOSTRING(QUERY_MAIN_DICOM_TAGS) " AS c0_queryId, " | |
636 " Lookup.internalId AS c1_internalId, " | |
637 " NULL AS c2_rowNumber, " | |
638 " value AS c3_string1, " | |
639 " NULL AS c4_string2, " | |
640 " NULL AS c5_string3, " | |
641 " NULL AS c6_string4, " | |
642 " tagGroup AS c7_int1, " | |
643 " tagElement AS c8_int2, " | |
644 " NULL AS c9_big_int1, " | |
645 " NULL AS c10_big_int2 " | |
646 "FROM Lookup " | |
647 "INNER JOIN MainDicomTags ON MainDicomTags.id = Lookup.internalId "; | |
648 } | |
649 | |
650 // need resource metadata ? | |
651 if (request.IsRetrieveMetadata()) | |
652 { | |
653 sql += "UNION SELECT " | |
654 " " TOSTRING(QUERY_METADATA) " AS c0_queryId, " | |
655 " Lookup.internalId AS c1_internalId, " | |
656 " NULL AS c2_rowNumber, " | |
657 " value AS c3_string1, " | |
658 " NULL AS c4_string2, " | |
659 " NULL AS c5_string3, " | |
660 " NULL AS c6_string4, " | |
661 " type AS c7_int1, " | |
662 " NULL AS c8_int2, " | |
663 " NULL AS c9_big_int1, " | |
664 " NULL AS c10_big_int2 " | |
665 "FROM Lookup " | |
666 "INNER JOIN Metadata ON Metadata.id = Lookup.internalId "; | |
667 } | |
668 | |
669 // need resource attachments ? | |
670 if (request.IsRetrieveAttachments()) | |
671 { | |
672 sql += "UNION SELECT " | |
673 " " TOSTRING(QUERY_ATTACHMENTS) " AS c0_queryId, " | |
674 " Lookup.internalId AS c1_internalId, " | |
675 " NULL AS c2_rowNumber, " | |
676 " uuid AS c3_string1, " | |
677 " uncompressedMD5 AS c4_string2, " | |
678 " compressedMD5 AS c5_string3, " | |
679 " customData AS c6_string4, " | |
680 " fileType AS c7_int1, " | |
681 " compressionType AS c8_int2, " | |
682 " compressedSize AS c9_big_int1, " | |
683 " uncompressedSize AS c10_big_int2 " | |
684 "FROM Lookup " | |
685 "INNER JOIN AttachedFiles ON AttachedFiles.id = Lookup.internalId "; | |
686 } | |
687 | |
688 | |
689 // need resource labels ? | |
690 if (request.IsRetrieveLabels()) | |
691 { | |
692 sql += "UNION SELECT " | |
693 " " TOSTRING(QUERY_LABELS) " AS c0_queryId, " | |
694 " Lookup.internalId AS c1_internalId, " | |
695 " NULL AS c2_rowNumber, " | |
696 " label AS c3_string1, " | |
697 " NULL AS c4_string2, " | |
698 " NULL AS c5_string3, " | |
699 " NULL AS c6_string4, " | |
700 " NULL AS c7_int1, " | |
701 " NULL AS c8_int2, " | |
702 " NULL AS c9_big_int1, " | |
703 " NULL AS c10_big_int2 " | |
704 "FROM Lookup " | |
705 "INNER JOIN Labels ON Labels.id = Lookup.internalId "; | |
706 } | |
707 | |
708 if (requestLevel > ResourceType_Patient) | |
709 { | |
710 // need MainDicomTags from parent ? | |
711 if (request.GetParentSpecification(static_cast<ResourceType>(requestLevel - 1)).IsRetrieveMainDicomTags()) | |
712 { | |
713 sql += "UNION SELECT " | |
714 " " TOSTRING(QUERY_PARENT_MAIN_DICOM_TAGS) " AS c0_queryId, " | |
715 " Lookup.internalId AS c1_internalId, " | |
716 " NULL AS c2_rowNumber, " | |
717 " value AS c3_string1, " | |
718 " NULL AS c4_string2, " | |
719 " NULL AS c5_string3, " | |
720 " NULL AS c6_string4, " | |
721 " tagGroup AS c7_int1, " | |
722 " tagElement AS c8_int2, " | |
723 " NULL AS c9_big_int1, " | |
724 " NULL AS c10_big_int2 " | |
725 "FROM Lookup " | |
726 "INNER JOIN Resources currentLevel ON Lookup.internalId = currentLevel.internalId " | |
727 "INNER JOIN MainDicomTags ON MainDicomTags.id = currentLevel.parentId "; | |
728 } | |
729 | |
730 // need metadata from parent ? | |
731 if (request.GetParentSpecification(static_cast<ResourceType>(requestLevel - 1)).IsRetrieveMetadata()) | |
732 { | |
733 sql += "UNION SELECT " | |
734 " " TOSTRING(QUERY_PARENT_METADATA) " AS c0_queryId, " | |
735 " Lookup.internalId AS c1_internalId, " | |
736 " NULL AS c2_rowNumber, " | |
737 " value AS c3_string1, " | |
738 " NULL AS c4_string2, " | |
739 " NULL AS c5_string3, " | |
740 " NULL AS c6_string4, " | |
741 " type AS c7_int1, " | |
742 " NULL AS c8_int2, " | |
743 " NULL AS c9_big_int1, " | |
744 " NULL AS c10_big_int2 " | |
745 "FROM Lookup " | |
746 "INNER JOIN Resources currentLevel ON Lookup.internalId = currentLevel.internalId " | |
747 "INNER JOIN Metadata ON Metadata.id = currentLevel.parentId "; | |
748 } | |
749 | |
750 if (requestLevel > ResourceType_Study) | |
751 { | |
752 // need MainDicomTags from grandparent ? | |
753 if (request.GetParentSpecification(static_cast<ResourceType>(requestLevel - 2)).IsRetrieveMainDicomTags()) | |
754 { | |
755 sql += "UNION SELECT " | |
756 " " TOSTRING(QUERY_GRAND_PARENT_MAIN_DICOM_TAGS) " AS c0_queryId, " | |
757 " Lookup.internalId AS c1_internalId, " | |
758 " NULL AS c2_rowNumber, " | |
759 " value AS c3_string1, " | |
760 " NULL AS c4_string2, " | |
761 " NULL AS c5_string3, " | |
762 " NULL AS c6_string4, " | |
763 " tagGroup AS c7_int1, " | |
764 " tagElement AS c8_int2, " | |
765 " NULL AS c9_big_int1, " | |
766 " NULL AS c10_big_int2 " | |
767 "FROM Lookup " | |
768 "INNER JOIN Resources currentLevel ON Lookup.internalId = currentLevel.internalId " | |
769 "INNER JOIN Resources parentLevel ON currentLevel.parentId = parentLevel.internalId " | |
770 "INNER JOIN MainDicomTags ON MainDicomTags.id = parentLevel.parentId "; | |
771 } | |
772 | |
773 // need metadata from grandparent ? | |
774 if (request.GetParentSpecification(static_cast<ResourceType>(requestLevel - 2)).IsRetrieveMetadata()) | |
775 { | |
776 sql += "UNION SELECT " | |
777 " " TOSTRING(QUERY_GRAND_PARENT_METADATA) " AS c0_queryId, " | |
778 " Lookup.internalId AS c1_internalId, " | |
779 " NULL AS c2_rowNumber, " | |
780 " value AS c3_string1, " | |
781 " NULL AS c4_string2, " | |
782 " NULL AS c5_string3, " | |
783 " NULL AS c6_string4, " | |
784 " type AS c7_int1, " | |
785 " NULL AS c8_int2, " | |
786 " NULL AS c9_big_int1, " | |
787 " NULL AS c10_big_int2 " | |
788 "FROM Lookup " | |
789 "INNER JOIN Resources currentLevel ON Lookup.internalId = currentLevel.internalId " | |
790 "INNER JOIN Resources parentLevel ON currentLevel.parentId = parentLevel.internalId " | |
791 "INNER JOIN Metadata ON Metadata.id = parentLevel.parentId "; | |
792 } | |
793 } | |
794 } | |
795 | |
796 // need MainDicomTags from children ? | |
797 if (requestLevel <= ResourceType_Series && request.GetChildrenSpecification(static_cast<ResourceType>(requestLevel + 1)).GetMainDicomTags().size() > 0) | |
798 { | |
799 sql += "UNION SELECT " | |
800 " " TOSTRING(QUERY_CHILDREN_MAIN_DICOM_TAGS) " AS c0_queryId, " | |
801 " Lookup.internalId AS c1_internalId, " | |
802 " NULL AS c2_rowNumber, " | |
803 " value AS c3_string1, " | |
804 " NULL AS c4_string2, " | |
805 " NULL AS c5_string3, " | |
806 " NULL AS c6_string4, " | |
807 " tagGroup AS c7_int1, " | |
808 " tagElement AS c8_int2, " | |
809 " NULL AS c9_big_int1, " | |
810 " NULL AS c10_big_int2 " | |
811 "FROM Lookup " | |
812 " INNER JOIN Resources childLevel ON childLevel.parentId = Lookup.internalId " | |
813 " INNER JOIN MainDicomTags ON MainDicomTags.id = childLevel.internalId AND " + JoinRequestedTags(request.GetChildrenSpecification(static_cast<ResourceType>(requestLevel + 1))); | |
814 } | |
815 | |
816 // need MainDicomTags from grandchildren ? | |
817 if (requestLevel <= ResourceType_Study && request.GetChildrenSpecification(static_cast<ResourceType>(requestLevel + 2)).GetMainDicomTags().size() > 0) | |
818 { | |
819 sql += "UNION SELECT " | |
820 " " TOSTRING(QUERY_GRAND_CHILDREN_MAIN_DICOM_TAGS) " AS c0_queryId, " | |
821 " Lookup.internalId AS c1_internalId, " | |
822 " NULL AS c2_rowNumber, " | |
823 " value AS c3_string1, " | |
824 " NULL AS c4_string2, " | |
825 " NULL AS c5_string3, " | |
826 " NULL AS c6_string4, " | |
827 " tagGroup AS c7_int1, " | |
828 " tagElement AS c8_int2, " | |
829 " NULL AS c9_big_int1, " | |
830 " NULL AS c10_big_int2 " | |
831 "FROM Lookup " | |
832 " INNER JOIN Resources childLevel ON childLevel.parentId = Lookup.internalId " | |
833 " INNER JOIN Resources grandChildLevel ON grandChildLevel.parentId = childLevel.internalId " | |
834 " INNER JOIN MainDicomTags ON MainDicomTags.id = grandChildLevel.internalId AND " + JoinRequestedTags(request.GetChildrenSpecification(static_cast<ResourceType>(requestLevel + 2))); | |
835 } | |
836 | |
837 // need parent identifier ? | |
838 if (request.IsRetrieveParentIdentifier()) | |
839 { | |
840 sql += "UNION SELECT " | |
841 " " TOSTRING(QUERY_PARENT_IDENTIFIER) " AS c0_queryId, " | |
842 " Lookup.internalId AS c1_internalId, " | |
843 " NULL AS c2_rowNumber, " | |
844 " parentLevel.publicId AS c3_string1, " | |
845 " NULL AS c4_string2, " | |
846 " NULL AS c5_string3, " | |
847 " NULL AS c6_string4, " | |
848 " NULL AS c7_int1, " | |
849 " NULL AS c8_int2, " | |
850 " NULL AS c9_big_int1, " | |
851 " NULL AS c10_big_int2 " | |
852 "FROM Lookup " | |
853 " INNER JOIN Resources currentLevel ON currentLevel.internalId = Lookup.internalId " | |
854 " INNER JOIN Resources parentLevel ON currentLevel.parentId = parentLevel.internalId "; | |
855 } | |
856 | |
857 // need children metadata ? | |
858 if (requestLevel <= ResourceType_Series && request.GetChildrenSpecification(static_cast<ResourceType>(requestLevel + 1)).GetMetadata().size() > 0) | |
859 { | |
860 sql += "UNION SELECT " | |
861 " " TOSTRING(QUERY_CHILDREN_METADATA) " AS c0_queryId, " | |
862 " Lookup.internalId AS c1_internalId, " | |
863 " NULL AS c2_rowNumber, " | |
864 " value AS c3_string1, " | |
865 " NULL AS c4_string2, " | |
866 " NULL AS c5_string3, " | |
867 " NULL AS c6_string4, " | |
868 " type AS c7_int1, " | |
869 " NULL AS c8_int2, " | |
870 " NULL AS c9_big_int1, " | |
871 " NULL AS c10_big_int2 " | |
872 "FROM Lookup " | |
873 " INNER JOIN Resources childLevel ON childLevel.parentId = Lookup.internalId " | |
874 " INNER JOIN Metadata ON Metadata.id = childLevel.internalId AND Metadata.type IN (" + JoinRequestedMetadata(request.GetChildrenSpecification(static_cast<ResourceType>(requestLevel + 1))) + ") "; | |
875 } | |
876 | |
877 // need grandchildren metadata ? | |
878 if (requestLevel <= ResourceType_Study && request.GetChildrenSpecification(static_cast<ResourceType>(requestLevel + 2)).GetMetadata().size() > 0) | |
879 { | |
880 sql += "UNION SELECT " | |
881 " " TOSTRING(QUERY_GRAND_CHILDREN_METADATA) " AS c0_queryId, " | |
882 " Lookup.internalId AS c1_internalId, " | |
883 " NULL AS c2_rowNumber, " | |
884 " value AS c3_string1, " | |
885 " NULL AS c4_string2, " | |
886 " NULL AS c5_string3, " | |
887 " NULL AS c6_string4, " | |
888 " type AS c7_int1, " | |
889 " NULL AS c8_int2, " | |
890 " NULL AS c9_big_int1, " | |
891 " NULL AS c10_big_int2 " | |
892 "FROM Lookup " | |
893 " INNER JOIN Resources childLevel ON childLevel.parentId = Lookup.internalId " | |
894 " INNER JOIN Resources grandChildLevel ON grandChildLevel.parentId = childLevel.internalId " | |
895 " INNER JOIN Metadata ON Metadata.id = grandChildLevel.internalId AND Metadata.type IN (" + JoinRequestedMetadata(request.GetChildrenSpecification(static_cast<ResourceType>(requestLevel + 2))) + ") "; | |
896 } | |
897 | |
898 // need children identifiers ? | |
899 if ((requestLevel == ResourceType_Patient && request.GetChildrenSpecification(ResourceType_Study).IsRetrieveIdentifiers()) || | |
900 (requestLevel == ResourceType_Study && request.GetChildrenSpecification(ResourceType_Series).IsRetrieveIdentifiers()) || | |
901 (requestLevel == ResourceType_Series && request.GetChildrenSpecification(ResourceType_Instance).IsRetrieveIdentifiers())) | |
902 { | |
903 sql += "UNION SELECT " | |
904 " " TOSTRING(QUERY_CHILDREN_IDENTIFIERS) " AS c0_queryId, " | |
905 " Lookup.internalId AS c1_internalId, " | |
906 " NULL AS c2_rowNumber, " | |
907 " childLevel.publicId AS c3_string1, " | |
908 " NULL AS c4_string2, " | |
909 " NULL AS c5_string3, " | |
910 " NULL AS c6_string4, " | |
911 " NULL AS c7_int1, " | |
912 " NULL AS c8_int2, " | |
913 " NULL AS c9_big_int1, " | |
914 " NULL AS c10_big_int2 " | |
915 "FROM Lookup " | |
916 " INNER JOIN Resources childLevel ON Lookup.internalId = childLevel.parentId "; | |
917 } | |
918 | |
919 // need grandchildren identifiers ? | |
920 if ((requestLevel == ResourceType_Patient && request.GetChildrenSpecification(ResourceType_Series).IsRetrieveIdentifiers()) || | |
921 (requestLevel == ResourceType_Study && request.GetChildrenSpecification(ResourceType_Instance).IsRetrieveIdentifiers())) | |
922 { | |
923 sql += "UNION SELECT " | |
924 " " TOSTRING(QUERY_GRAND_CHILDREN_IDENTIFIERS) " AS c0_queryId, " | |
925 " Lookup.internalId AS c1_internalId, " | |
926 " NULL AS c2_rowNumber, " | |
927 " grandChildLevel.publicId AS c3_string1, " | |
928 " NULL AS c4_string2, " | |
929 " NULL AS c5_string3, " | |
930 " NULL AS c6_string4, " | |
931 " NULL AS c7_int1, " | |
932 " NULL AS c8_int2, " | |
933 " NULL AS c9_big_int1, " | |
934 " NULL AS c10_big_int2 " | |
935 "FROM Lookup " | |
936 "INNER JOIN Resources childLevel ON Lookup.internalId = childLevel.parentId " | |
937 "INNER JOIN Resources grandChildLevel ON childLevel.internalId = grandChildLevel.parentId "; | |
938 } | |
939 | |
940 // need grandgrandchildren identifiers ? | |
941 if (requestLevel == ResourceType_Patient && request.GetChildrenSpecification(ResourceType_Instance).IsRetrieveIdentifiers()) | |
942 { | |
943 sql += "UNION SELECT " | |
944 " " TOSTRING(QUERY_GRAND_GRAND_CHILDREN_IDENTIFIERS) " AS c0_queryId, " | |
945 " Lookup.internalId AS c1_internalId, " | |
946 " NULL AS c2_rowNumber, " | |
947 " grandGrandChildLevel.publicId AS c3_string1, " | |
948 " NULL AS c4_string2, " | |
949 " NULL AS c5_string3, " | |
950 " NULL AS c6_string4, " | |
951 " NULL AS c7_int1, " | |
952 " NULL AS c8_int2, " | |
953 " NULL AS c9_big_int1, " | |
954 " NULL AS c10_big_int2 " | |
955 "FROM Lookup " | |
956 "INNER JOIN Resources childLevel ON Lookup.internalId = childLevel.parentId " | |
957 "INNER JOIN Resources grandChildLevel ON childLevel.internalId = grandChildLevel.parentId " | |
958 "INNER JOIN Resources grandGrandChildLevel ON grandChildLevel.internalId = grandGrandChildLevel.parentId "; | |
959 } | |
960 | |
961 | |
962 sql += " ORDER BY c0_queryId, c2_rowNumber"; // this is really important to make sure that the Lookup query is the first one to provide results since we use it to create the responses element ! | |
963 | |
964 SQLite::Statement s(db_, SQLITE_FROM_HERE_DYNAMIC(sql), sql); | |
965 formatter.Bind(s); | |
966 | |
967 while (s.Step()) | |
968 { | |
969 int queryId = s.ColumnInt(C0_QUERY_ID); | |
970 int64_t internalId = s.ColumnInt64(C1_INTERNAL_ID); | |
971 | |
972 // LOG(INFO) << queryId << ": " << internalId; | |
973 // continue; | |
974 | |
975 assert(queryId == QUERY_LOOKUP || response.HasResource(internalId)); // the QUERY_LOOKUP must be read first and must create the response before any other query tries to populate the fields | |
976 | |
977 switch (queryId) | |
978 { | |
979 case QUERY_LOOKUP: | |
980 response.Add(new FindResponse::Resource(requestLevel, internalId, s.ColumnString(C3_STRING_1))); | |
981 break; | |
982 | |
983 case QUERY_LABELS: | |
984 { | |
985 FindResponse::Resource& res = response.GetResourceByInternalId(internalId); | |
986 res.AddLabel(s.ColumnString(C3_STRING_1)); | |
987 }; break; | |
988 | |
989 case QUERY_ATTACHMENTS: | |
990 { | |
991 FindResponse::Resource& res = response.GetResourceByInternalId(internalId); | |
992 FileInfo file(s.ColumnString(C3_STRING_1), static_cast<FileContentType>(s.ColumnInt(C7_INT_1)), | |
993 s.ColumnInt64(C9_BIG_INT_1), s.ColumnString(C4_STRING_2), | |
994 static_cast<CompressionType>(s.ColumnInt(C8_INT_2)), | |
995 s.ColumnInt64(C10_BIG_INT_2), s.ColumnString(C5_STRING_3), s.ColumnString(C6_STRING_4)); | |
996 res.AddAttachment(file); | |
997 }; break; | |
998 | |
999 case QUERY_MAIN_DICOM_TAGS: | |
1000 { | |
1001 FindResponse::Resource& res = response.GetResourceByInternalId(internalId); | |
1002 res.AddStringDicomTag(requestLevel, | |
1003 static_cast<uint16_t>(s.ColumnInt(C7_INT_1)), | |
1004 static_cast<uint16_t>(s.ColumnInt(C8_INT_2)), | |
1005 s.ColumnString(C3_STRING_1)); | |
1006 }; break; | |
1007 | |
1008 case QUERY_PARENT_MAIN_DICOM_TAGS: | |
1009 { | |
1010 FindResponse::Resource& res = response.GetResourceByInternalId(internalId); | |
1011 res.AddStringDicomTag(static_cast<ResourceType>(requestLevel - 1), | |
1012 static_cast<uint16_t>(s.ColumnInt(C7_INT_1)), | |
1013 static_cast<uint16_t>(s.ColumnInt(C8_INT_2)), | |
1014 s.ColumnString(C3_STRING_1)); | |
1015 }; break; | |
1016 | |
1017 case QUERY_GRAND_PARENT_MAIN_DICOM_TAGS: | |
1018 { | |
1019 FindResponse::Resource& res = response.GetResourceByInternalId(internalId); | |
1020 res.AddStringDicomTag(static_cast<ResourceType>(requestLevel - 2), | |
1021 static_cast<uint16_t>(s.ColumnInt(C7_INT_1)), | |
1022 static_cast<uint16_t>(s.ColumnInt(C8_INT_2)), | |
1023 s.ColumnString(C3_STRING_1)); | |
1024 }; break; | |
1025 | |
1026 case QUERY_CHILDREN_MAIN_DICOM_TAGS: | |
1027 { | |
1028 FindResponse::Resource& res = response.GetResourceByInternalId(internalId); | |
1029 res.AddChildrenMainDicomTagValue(static_cast<ResourceType>(requestLevel + 1), | |
1030 DicomTag(static_cast<uint16_t>(s.ColumnInt(C7_INT_1)), static_cast<uint16_t>(s.ColumnInt(C8_INT_2))), | |
1031 s.ColumnString(C3_STRING_1)); | |
1032 }; break; | |
1033 | |
1034 case QUERY_GRAND_CHILDREN_MAIN_DICOM_TAGS: | |
1035 { | |
1036 FindResponse::Resource& res = response.GetResourceByInternalId(internalId); | |
1037 res.AddChildrenMainDicomTagValue(static_cast<ResourceType>(requestLevel + 2), | |
1038 DicomTag(static_cast<uint16_t>(s.ColumnInt(C7_INT_1)), static_cast<uint16_t>(s.ColumnInt(C8_INT_2))), | |
1039 s.ColumnString(C3_STRING_1)); | |
1040 }; break; | |
1041 | |
1042 case QUERY_METADATA: | |
1043 { | |
1044 FindResponse::Resource& res = response.GetResourceByInternalId(internalId); | |
1045 res.AddMetadata(static_cast<ResourceType>(requestLevel), | |
1046 static_cast<MetadataType>(s.ColumnInt(C7_INT_1)), | |
1047 s.ColumnString(C3_STRING_1)); | |
1048 }; break; | |
1049 | |
1050 case QUERY_PARENT_METADATA: | |
1051 { | |
1052 FindResponse::Resource& res = response.GetResourceByInternalId(internalId); | |
1053 res.AddMetadata(static_cast<ResourceType>(requestLevel - 1), | |
1054 static_cast<MetadataType>(s.ColumnInt(C7_INT_1)), | |
1055 s.ColumnString(C3_STRING_1)); | |
1056 }; break; | |
1057 | |
1058 case QUERY_GRAND_PARENT_METADATA: | |
1059 { | |
1060 FindResponse::Resource& res = response.GetResourceByInternalId(internalId); | |
1061 res.AddMetadata(static_cast<ResourceType>(requestLevel - 2), | |
1062 static_cast<MetadataType>(s.ColumnInt(C7_INT_1)), | |
1063 s.ColumnString(C3_STRING_1)); | |
1064 }; break; | |
1065 | |
1066 case QUERY_CHILDREN_METADATA: | |
1067 { | |
1068 FindResponse::Resource& res = response.GetResourceByInternalId(internalId); | |
1069 res.AddChildrenMetadataValue(static_cast<ResourceType>(requestLevel + 1), | |
1070 static_cast<MetadataType>(s.ColumnInt(C7_INT_1)), | |
1071 s.ColumnString(C3_STRING_1)); | |
1072 }; break; | |
1073 | |
1074 case QUERY_GRAND_CHILDREN_METADATA: | |
1075 { | |
1076 FindResponse::Resource& res = response.GetResourceByInternalId(internalId); | |
1077 res.AddChildrenMetadataValue(static_cast<ResourceType>(requestLevel + 2), | |
1078 static_cast<MetadataType>(s.ColumnInt(C7_INT_1)), | |
1079 s.ColumnString(C3_STRING_1)); | |
1080 }; break; | |
1081 | |
1082 case QUERY_PARENT_IDENTIFIER: | |
1083 { | |
1084 FindResponse::Resource& res = response.GetResourceByInternalId(internalId); | |
1085 res.SetParentIdentifier(s.ColumnString(C3_STRING_1)); | |
1086 }; break; | |
1087 | |
1088 case QUERY_CHILDREN_IDENTIFIERS: | |
1089 { | |
1090 FindResponse::Resource& res = response.GetResourceByInternalId(internalId); | |
1091 res.AddChildIdentifier(static_cast<ResourceType>(requestLevel + 1), | |
1092 s.ColumnString(C3_STRING_1)); | |
1093 }; break; | |
1094 | |
1095 case QUERY_GRAND_CHILDREN_IDENTIFIERS: | |
1096 { | |
1097 FindResponse::Resource& res = response.GetResourceByInternalId(internalId); | |
1098 res.AddChildIdentifier(static_cast<ResourceType>(requestLevel + 2), | |
1099 s.ColumnString(C3_STRING_1)); | |
1100 }; break; | |
1101 | |
1102 case QUERY_GRAND_GRAND_CHILDREN_IDENTIFIERS: | |
1103 { | |
1104 FindResponse::Resource& res = response.GetResourceByInternalId(internalId); | |
1105 res.AddChildIdentifier(static_cast<ResourceType>(requestLevel + 3), | |
1106 s.ColumnString(C3_STRING_1)); | |
1107 }; break; | |
1108 | |
1109 case QUERY_ONE_INSTANCE_IDENTIFIER: | |
1110 { | |
1111 FindResponse::Resource& res = response.GetResourceByInternalId(internalId); | |
1112 res.SetOneInstancePublicId(s.ColumnString(C3_STRING_1)); | |
1113 }; break; | |
1114 | |
1115 case QUERY_ONE_INSTANCE_METADATA: | |
1116 { | |
1117 FindResponse::Resource& res = response.GetResourceByInternalId(internalId); | |
1118 res.AddOneInstanceMetadata(static_cast<MetadataType>(s.ColumnInt(C7_INT_1)), s.ColumnString(C3_STRING_1)); | |
1119 }; break; | |
1120 | |
1121 case QUERY_ONE_INSTANCE_ATTACHMENTS: | |
1122 { | |
1123 FindResponse::Resource& res = response.GetResourceByInternalId(internalId); | |
1124 FileInfo file(s.ColumnString(C3_STRING_1), static_cast<FileContentType>(s.ColumnInt(C7_INT_1)), | |
1125 s.ColumnInt64(C9_BIG_INT_1), s.ColumnString(C4_STRING_2), | |
1126 static_cast<CompressionType>(s.ColumnInt(C8_INT_2)), | |
1127 s.ColumnInt64(C10_BIG_INT_2), s.ColumnString(C5_STRING_3), s.ColumnString(C6_STRING_4)); | |
1128 res.AddOneInstanceAttachment(file); | |
1129 }; break; | |
1130 | |
1131 default: | |
1132 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); | |
1133 } | |
1134 } | |
1135 } | |
388 | 1136 |
389 // From the "ICreateInstance" interface | 1137 // From the "ICreateInstance" interface |
390 virtual void AttachChild(int64_t parent, | 1138 virtual void AttachChild(int64_t parent, |
391 int64_t child) ORTHANC_OVERRIDE | 1139 int64_t child) ORTHANC_OVERRIDE |
392 { | 1140 { |
539 virtual void GetAllPublicIds(std::list<std::string>& target, | 1287 virtual void GetAllPublicIds(std::list<std::string>& target, |
540 ResourceType resourceType, | 1288 ResourceType resourceType, |
541 int64_t since, | 1289 int64_t since, |
542 uint32_t limit) ORTHANC_OVERRIDE | 1290 uint32_t limit) ORTHANC_OVERRIDE |
543 { | 1291 { |
544 if (limit == 0) | |
545 { | |
546 target.clear(); | |
547 return; | |
548 } | |
549 | |
550 SQLite::Statement s(db_, SQLITE_FROM_HERE, | 1292 SQLite::Statement s(db_, SQLITE_FROM_HERE, |
551 "SELECT publicId FROM Resources WHERE " | 1293 "SELECT publicId FROM Resources WHERE " |
552 "resourceType=? LIMIT ? OFFSET ?"); | 1294 "resourceType=? LIMIT ? OFFSET ?"); |
553 s.BindInt(0, resourceType); | 1295 s.BindInt(0, resourceType); |
554 s.BindInt64(1, limit); | 1296 s.BindInt64(1, limit == 0 ? -1 : limit); // In SQLite, setting "LIMIT" to "-1" means "no limit" |
555 s.BindInt64(2, since); | 1297 s.BindInt64(2, since); |
556 | 1298 |
557 target.clear(); | 1299 target.clear(); |
558 while (s.Step()) | 1300 while (s.Step()) |
559 { | 1301 { |
565 virtual void GetChanges(std::list<ServerIndexChange>& target /*out*/, | 1307 virtual void GetChanges(std::list<ServerIndexChange>& target /*out*/, |
566 bool& done /*out*/, | 1308 bool& done /*out*/, |
567 int64_t since, | 1309 int64_t since, |
568 uint32_t limit) ORTHANC_OVERRIDE | 1310 uint32_t limit) ORTHANC_OVERRIDE |
569 { | 1311 { |
570 SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT * FROM Changes WHERE seq>? ORDER BY seq LIMIT ?"); | 1312 std::set<ChangeType> filter; |
571 s.BindInt64(0, since); | 1313 GetChangesExtended(target, done, since, -1, limit, filter); |
572 s.BindInt(1, limit + 1); | 1314 } |
573 GetChangesInternal(target, done, s, limit); | 1315 |
1316 virtual void GetChangesExtended(std::list<ServerIndexChange>& target /*out*/, | |
1317 bool& done /*out*/, | |
1318 int64_t since, | |
1319 int64_t to, | |
1320 uint32_t limit, | |
1321 const std::set<ChangeType>& filterType) ORTHANC_OVERRIDE | |
1322 { | |
1323 std::vector<std::string> filters; | |
1324 bool hasSince = false; | |
1325 bool hasTo = false; | |
1326 | |
1327 if (since > 0) | |
1328 { | |
1329 hasSince = true; | |
1330 filters.push_back("seq>?"); | |
1331 } | |
1332 if (to != -1) | |
1333 { | |
1334 hasTo = true; | |
1335 filters.push_back("seq<=?"); | |
1336 } | |
1337 if (filterType.size() != 0) | |
1338 { | |
1339 filters.push_back("changeType IN ( " + JoinChanges(filterType) + " )"); | |
1340 } | |
1341 | |
1342 std::string filtersString; | |
1343 if (filters.size() > 0) | |
1344 { | |
1345 Toolbox::JoinStrings(filtersString, filters, " AND "); | |
1346 filtersString = "WHERE " + filtersString; | |
1347 } | |
1348 | |
1349 std::string sql; | |
1350 bool returnFirstResults; | |
1351 if (hasTo && !hasSince) | |
1352 { | |
1353 // in this case, we want the largest values in the LIMIT clause but we want them ordered in ascending order | |
1354 sql = "SELECT * FROM (SELECT * FROM Changes " + filtersString + " ORDER BY seq DESC LIMIT ?) ORDER BY seq ASC"; | |
1355 returnFirstResults = false; | |
1356 } | |
1357 else | |
1358 { | |
1359 // default query: we want the smallest values ordered in ascending order | |
1360 sql = "SELECT * FROM Changes " + filtersString + " ORDER BY seq ASC LIMIT ?"; | |
1361 returnFirstResults = true; | |
1362 } | |
1363 | |
1364 SQLite::Statement s(db_, SQLITE_FROM_HERE_DYNAMIC(sql), sql); | |
1365 | |
1366 int paramCounter = 0; | |
1367 if (hasSince) | |
1368 { | |
1369 s.BindInt64(paramCounter++, since); | |
1370 } | |
1371 if (hasTo) | |
1372 { | |
1373 s.BindInt64(paramCounter++, to); | |
1374 } | |
1375 | |
1376 s.BindInt(paramCounter++, limit + 1); // we take limit+1 because we use the +1 to know if "Done" must be set to true | |
1377 GetChangesInternal(target, done, s, limit, returnFirstResults); | |
574 } | 1378 } |
575 | 1379 |
576 | 1380 |
577 virtual void GetChildrenMetadata(std::list<std::string>& target, | 1381 virtual void GetChildrenMetadata(std::list<std::string>& target, |
578 int64_t resourceId, | 1382 int64_t resourceId, |
629 | 1433 |
630 virtual void GetLastChange(std::list<ServerIndexChange>& target /*out*/) ORTHANC_OVERRIDE | 1434 virtual void GetLastChange(std::list<ServerIndexChange>& target /*out*/) ORTHANC_OVERRIDE |
631 { | 1435 { |
632 bool done; // Ignored | 1436 bool done; // Ignored |
633 SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT * FROM Changes ORDER BY seq DESC LIMIT 1"); | 1437 SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT * FROM Changes ORDER BY seq DESC LIMIT 1"); |
634 GetChangesInternal(target, done, s, 1); | 1438 GetChangesInternal(target, done, s, 1, true); |
635 } | 1439 } |
636 | 1440 |
637 | 1441 |
638 int64_t GetLastChangeIndex() ORTHANC_OVERRIDE | 1442 int64_t GetLastChangeIndex() ORTHANC_OVERRIDE |
639 { | 1443 { |
1360 version_(0) | 2164 version_(0) |
1361 { | 2165 { |
1362 // TODO: implement revisions in SQLite | 2166 // TODO: implement revisions in SQLite |
1363 dbCapabilities_.SetFlushToDisk(true); | 2167 dbCapabilities_.SetFlushToDisk(true); |
1364 dbCapabilities_.SetLabelsSupport(true); | 2168 dbCapabilities_.SetLabelsSupport(true); |
2169 dbCapabilities_.SetHasExtendedChanges(true); | |
2170 dbCapabilities_.SetHasFindSupport(true); | |
1365 db_.Open(path); | 2171 db_.Open(path); |
1366 } | 2172 } |
1367 | 2173 |
1368 | 2174 |
1369 SQLiteDatabaseWrapper::SQLiteDatabaseWrapper() : | 2175 SQLiteDatabaseWrapper::SQLiteDatabaseWrapper() : |
1372 version_(0) | 2178 version_(0) |
1373 { | 2179 { |
1374 // TODO: implement revisions in SQLite | 2180 // TODO: implement revisions in SQLite |
1375 dbCapabilities_.SetFlushToDisk(true); | 2181 dbCapabilities_.SetFlushToDisk(true); |
1376 dbCapabilities_.SetLabelsSupport(true); | 2182 dbCapabilities_.SetLabelsSupport(true); |
2183 dbCapabilities_.SetHasExtendedChanges(true); | |
2184 dbCapabilities_.SetHasFindSupport(true); | |
1377 db_.OpenInMemory(); | 2185 db_.OpenInMemory(); |
1378 } | 2186 } |
1379 | 2187 |
1380 SQLiteDatabaseWrapper::~SQLiteDatabaseWrapper() | 2188 SQLiteDatabaseWrapper::~SQLiteDatabaseWrapper() |
1381 { | 2189 { |