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,