Mercurial > hg > orthanc
comparison OrthancServer/Sources/Search/ISqlLookupFormatter.cpp @ 5748:4bc650d88463 find-refactoring
WIP: started to implement IntegratedFind in SQLite
author | Alain Mazy <am@orthanc.team> |
---|---|
date | Fri, 30 Aug 2024 18:03:37 +0200 |
parents | 68fc5af30c03 |
children | f39406a9eda4 |
comparison
equal
deleted
inserted
replaced
5747:796cb17db15c | 5748:4bc650d88463 |
---|---|
32 #include "ISqlLookupFormatter.h" | 32 #include "ISqlLookupFormatter.h" |
33 | 33 |
34 #if ORTHANC_BUILDING_SERVER_LIBRARY == 1 | 34 #if ORTHANC_BUILDING_SERVER_LIBRARY == 1 |
35 # include "../../../OrthancFramework/Sources/OrthancException.h" | 35 # include "../../../OrthancFramework/Sources/OrthancException.h" |
36 # include "../../../OrthancFramework/Sources/Toolbox.h" | 36 # include "../../../OrthancFramework/Sources/Toolbox.h" |
37 # include "../Database/FindRequest.h" | |
37 #else | 38 #else |
38 # include <OrthancException.h> | 39 # include <OrthancException.h> |
39 # include <Toolbox.h> | 40 # include <Toolbox.h> |
40 #endif | 41 #endif |
41 | 42 |
613 if (limit != 0) | 614 if (limit != 0) |
614 { | 615 { |
615 sql += " LIMIT " + boost::lexical_cast<std::string>(limit); | 616 sql += " LIMIT " + boost::lexical_cast<std::string>(limit); |
616 } | 617 } |
617 } | 618 } |
619 | |
620 #if ORTHANC_BUILDING_SERVER_LIBRARY == 1 | |
621 void ISqlLookupFormatter::Apply(std::string& sql, | |
622 ISqlLookupFormatter& formatter, | |
623 const FindRequest& request) | |
624 { | |
625 const bool escapeBrackets = formatter.IsEscapeBrackets(); | |
626 ResourceType queryLevel = request.GetLevel(); | |
627 const std::string& strQueryLevel = FormatLevel(queryLevel); | |
628 | |
629 std::string joins, comparisons; | |
630 | |
631 if (request.GetOrthancIdentifiers().IsDefined() && request.GetOrthancIdentifiers().DetectLevel() == queryLevel) | |
632 { | |
633 // single resource matching, there should not be other constraints | |
634 assert(request.GetDicomTagConstraints().GetSize() == 0); | |
635 assert(request.GetLabels().size() == 0); | |
636 assert(request.HasLimits() == false); | |
637 | |
638 comparisons = " AND " + strQueryLevel + ".publicId = " + formatter.GenerateParameter(request.GetOrthancIdentifiers().GetLevel(queryLevel)); | |
639 } | |
640 else if (request.GetOrthancIdentifiers().IsDefined() && request.GetOrthancIdentifiers().DetectLevel() == queryLevel - 1) | |
641 { | |
642 // single child resource matching, there should not be other constraints (at least for now) | |
643 assert(request.GetDicomTagConstraints().GetSize() == 0); | |
644 assert(request.GetLabels().size() == 0); | |
645 assert(request.HasLimits() == false); | |
646 | |
647 ResourceType parentLevel = static_cast<ResourceType>(queryLevel - 1); | |
648 const std::string& strParentLevel = FormatLevel(parentLevel); | |
649 | |
650 comparisons = " AND " + strParentLevel + ".publicId = " + formatter.GenerateParameter(request.GetOrthancIdentifiers().GetLevel(parentLevel)); | |
651 joins += (" INNER JOIN Resources " + | |
652 strParentLevel + " ON " + | |
653 strQueryLevel + ".parentId=" + | |
654 strParentLevel + ".internalId"); | |
655 } | |
656 // TODO-FIND other levels (there's probably a way to make it generic as it was done before !!!): | |
657 // /studies/../instances | |
658 // /patients/../instances | |
659 // /series/../study | |
660 // /instances/../study | |
661 // ... | |
662 else | |
663 { | |
664 size_t count = 0; | |
665 | |
666 const DatabaseConstraints& dicomTagsConstraints = request.GetDicomTagConstraints(); | |
667 for (size_t i = 0; i < dicomTagsConstraints.GetSize(); i++) | |
668 { | |
669 const DatabaseConstraint& constraint = dicomTagsConstraints.GetConstraint(i); | |
670 | |
671 std::string comparison; | |
672 | |
673 if (FormatComparison(comparison, formatter, constraint, count, escapeBrackets)) | |
674 { | |
675 std::string join; | |
676 FormatJoin(join, constraint, count); | |
677 joins += join; | |
678 | |
679 if (!comparison.empty()) | |
680 { | |
681 comparisons += " AND " + comparison; | |
682 } | |
683 | |
684 count ++; | |
685 } | |
686 } | |
687 } | |
688 | |
689 sql = ("SELECT " + | |
690 strQueryLevel + ".publicId, " + | |
691 strQueryLevel + ".internalId" + | |
692 " FROM Resources AS " + strQueryLevel); | |
693 | |
694 // for (int level = queryLevel - 1; level >= upperLevel; level--) | |
695 // { | |
696 // sql += (" INNER JOIN Resources " + | |
697 // FormatLevel(static_cast<ResourceType>(level)) + " ON " + | |
698 // FormatLevel(static_cast<ResourceType>(level)) + ".internalId=" + | |
699 // FormatLevel(static_cast<ResourceType>(level + 1)) + ".parentId"); | |
700 // } | |
701 | |
702 // for (int level = queryLevel + 1; level <= lowerLevel; level++) | |
703 // { | |
704 // sql += (" INNER JOIN Resources " + | |
705 // FormatLevel(static_cast<ResourceType>(level)) + " ON " + | |
706 // FormatLevel(static_cast<ResourceType>(level - 1)) + ".internalId=" + | |
707 // FormatLevel(static_cast<ResourceType>(level)) + ".parentId"); | |
708 // } | |
709 | |
710 std::list<std::string> where; | |
711 where.push_back(strQueryLevel + ".resourceType = " + | |
712 formatter.FormatResourceType(queryLevel) + comparisons); | |
713 | |
714 | |
715 if (!request.GetLabels().empty()) | |
716 { | |
717 /** | |
718 * "In SQL Server, NOT EXISTS and NOT IN predicates are the best | |
719 * way to search for missing values, as long as both columns in | |
720 * question are NOT NULL." | |
721 * https://explainextended.com/2009/09/15/not-in-vs-not-exists-vs-left-join-is-null-sql-server/ | |
722 **/ | |
723 | |
724 const std::set<std::string>& labels = request.GetLabels(); | |
725 std::list<std::string> formattedLabels; | |
726 for (std::set<std::string>::const_iterator it = labels.begin(); it != labels.end(); ++it) | |
727 { | |
728 formattedLabels.push_back(formatter.GenerateParameter(*it)); | |
729 } | |
730 | |
731 std::string condition; | |
732 switch (request.GetLabelsConstraint()) | |
733 { | |
734 case LabelsConstraint_Any: | |
735 condition = "> 0"; | |
736 break; | |
737 | |
738 case LabelsConstraint_All: | |
739 condition = "= " + boost::lexical_cast<std::string>(labels.size()); | |
740 break; | |
741 | |
742 case LabelsConstraint_None: | |
743 condition = "= 0"; | |
744 break; | |
745 | |
746 default: | |
747 throw OrthancException(ErrorCode_ParameterOutOfRange); | |
748 } | |
749 | |
750 where.push_back("(SELECT COUNT(1) FROM Labels AS selectedLabels WHERE selectedLabels.id = " + strQueryLevel + | |
751 ".internalId AND selectedLabels.label IN (" + Join(formattedLabels, "", ", ") + ")) " + condition); | |
752 } | |
753 | |
754 sql += joins + Join(where, " WHERE ", " AND "); | |
755 | |
756 if (request.HasLimits()) | |
757 { | |
758 sql += " LIMIT " + boost::lexical_cast<std::string>(request.GetLimitsCount()); | |
759 sql += " OFFSET " + boost::lexical_cast<std::string>(request.GetLimitsSince()); | |
760 } | |
761 | |
762 } | |
763 #endif | |
618 | 764 |
619 | 765 |
620 void ISqlLookupFormatter::ApplySingleLevel(std::string& sql, | 766 void ISqlLookupFormatter::ApplySingleLevel(std::string& sql, |
621 ISqlLookupFormatter& formatter, | 767 ISqlLookupFormatter& formatter, |
622 const DatabaseConstraints& lookup, | 768 const DatabaseConstraints& lookup, |