Mercurial > hg > orthanc-databases
comparison Resources/Orthanc/Databases/ISqlLookupFormatter.cpp @ 536:4ecf50a4521c find-refactoring
sync ISqlLookupFormatter from Orthanc + fix bug 224: LIMIT shall not be used with MSSQL
author | Alain Mazy <am@orthanc.team> |
---|---|
date | Fri, 06 Sep 2024 16:56:37 +0200 |
parents | 54d518dcd74a |
children | 12f1c5265081 |
comparison
equal
deleted
inserted
replaced
535:03a4a1bc852a | 536:4ecf50a4521c |
---|---|
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 |
473 | 474 |
474 return true; | 475 return true; |
475 } | 476 } |
476 | 477 |
477 | 478 |
478 void ISqlLookupFormatter::GetLookupLevels(ResourceType& lowerLevel, ResourceType& upperLevel, const ResourceType& queryLevel, const std::vector<DatabaseConstraint>& lookup) | 479 void ISqlLookupFormatter::GetLookupLevels(ResourceType& lowerLevel, |
480 ResourceType& upperLevel, | |
481 const ResourceType& queryLevel, | |
482 const DatabaseConstraints& lookup) | |
479 { | 483 { |
480 assert(ResourceType_Patient < ResourceType_Study && | 484 assert(ResourceType_Patient < ResourceType_Study && |
481 ResourceType_Study < ResourceType_Series && | 485 ResourceType_Study < ResourceType_Series && |
482 ResourceType_Series < ResourceType_Instance); | 486 ResourceType_Series < ResourceType_Instance); |
483 | 487 |
484 lowerLevel = queryLevel; | 488 lowerLevel = queryLevel; |
485 upperLevel = queryLevel; | 489 upperLevel = queryLevel; |
486 | 490 |
487 for (size_t i = 0; i < lookup.size(); i++) | 491 for (size_t i = 0; i < lookup.GetSize(); i++) |
488 { | 492 { |
489 ResourceType level = lookup[i].GetLevel(); | 493 ResourceType level = lookup.GetConstraint(i).GetLevel(); |
490 | 494 |
491 if (level < upperLevel) | 495 if (level < upperLevel) |
492 { | 496 { |
493 upperLevel = level; | 497 upperLevel = level; |
494 } | 498 } |
501 } | 505 } |
502 | 506 |
503 | 507 |
504 void ISqlLookupFormatter::Apply(std::string& sql, | 508 void ISqlLookupFormatter::Apply(std::string& sql, |
505 ISqlLookupFormatter& formatter, | 509 ISqlLookupFormatter& formatter, |
506 const std::vector<DatabaseConstraint>& lookup, | 510 const DatabaseConstraints& lookup, |
507 ResourceType queryLevel, | 511 ResourceType queryLevel, |
508 const std::set<std::string>& labels, | 512 const std::set<std::string>& labels, |
509 LabelsConstraint labelsConstraint, | 513 LabelsConstraint labelsConstraint, |
510 size_t limit) | 514 size_t limit) |
511 { | 515 { |
519 | 523 |
520 std::string joins, comparisons; | 524 std::string joins, comparisons; |
521 | 525 |
522 size_t count = 0; | 526 size_t count = 0; |
523 | 527 |
524 for (size_t i = 0; i < lookup.size(); i++) | 528 for (size_t i = 0; i < lookup.GetSize(); i++) |
525 { | 529 { |
530 const DatabaseConstraint& constraint = lookup.GetConstraint(i); | |
531 | |
526 std::string comparison; | 532 std::string comparison; |
527 | 533 |
528 if (FormatComparison(comparison, formatter, lookup[i], count, escapeBrackets)) | 534 if (FormatComparison(comparison, formatter, constraint, count, escapeBrackets)) |
529 { | 535 { |
530 std::string join; | 536 std::string join; |
531 FormatJoin(join, lookup[i], count); | 537 FormatJoin(join, constraint, count); |
532 joins += join; | 538 joins += join; |
533 | 539 |
534 if (!comparison.empty()) | 540 if (!comparison.empty()) |
535 { | 541 { |
536 comparisons += " AND " + comparison; | 542 comparisons += " AND " + comparison; |
609 { | 615 { |
610 sql += " LIMIT " + boost::lexical_cast<std::string>(limit); | 616 sql += " LIMIT " + boost::lexical_cast<std::string>(limit); |
611 } | 617 } |
612 } | 618 } |
613 | 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 ResourceType lowerLevel, upperLevel; | |
630 GetLookupLevels(lowerLevel, upperLevel, queryLevel, request.GetDicomTagConstraints()); | |
631 | |
632 assert(upperLevel <= queryLevel && | |
633 queryLevel <= lowerLevel); | |
634 | |
635 | |
636 sql = ("SELECT " + | |
637 strQueryLevel + ".publicId, " + | |
638 strQueryLevel + ".internalId" + | |
639 " FROM Resources AS " + strQueryLevel); | |
640 | |
641 | |
642 std::string joins, comparisons; | |
643 | |
644 if (request.GetOrthancIdentifiers().IsDefined() && request.GetOrthancIdentifiers().DetectLevel() <= queryLevel) | |
645 { | |
646 // single child resource matching, there should not be other constraints (at least for now) | |
647 assert(request.GetDicomTagConstraints().GetSize() == 0); | |
648 assert(request.GetLabels().size() == 0); | |
649 assert(request.HasLimits() == false); | |
650 | |
651 ResourceType topParentLevel = request.GetOrthancIdentifiers().DetectLevel(); | |
652 const std::string& strTopParentLevel = FormatLevel(topParentLevel); | |
653 | |
654 comparisons = " AND " + strTopParentLevel + ".publicId = " + formatter.GenerateParameter(request.GetOrthancIdentifiers().GetLevel(topParentLevel)); | |
655 | |
656 for (int level = queryLevel; level > topParentLevel; level--) | |
657 { | |
658 sql += (" INNER JOIN Resources " + | |
659 FormatLevel(static_cast<ResourceType>(level - 1)) + " ON " + | |
660 FormatLevel(static_cast<ResourceType>(level - 1)) + ".internalId=" + | |
661 FormatLevel(static_cast<ResourceType>(level)) + ".parentId"); | |
662 } | |
663 } | |
664 else | |
665 { | |
666 size_t count = 0; | |
667 | |
668 const DatabaseConstraints& dicomTagsConstraints = request.GetDicomTagConstraints(); | |
669 for (size_t i = 0; i < dicomTagsConstraints.GetSize(); i++) | |
670 { | |
671 const DatabaseConstraint& constraint = dicomTagsConstraints.GetConstraint(i); | |
672 | |
673 std::string comparison; | |
674 | |
675 if (FormatComparison(comparison, formatter, constraint, count, escapeBrackets)) | |
676 { | |
677 std::string join; | |
678 FormatJoin(join, constraint, count); | |
679 joins += join; | |
680 | |
681 if (!comparison.empty()) | |
682 { | |
683 comparisons += " AND " + comparison; | |
684 } | |
685 | |
686 count ++; | |
687 } | |
688 } | |
689 } | |
690 | |
691 for (int level = queryLevel - 1; level >= upperLevel; level--) | |
692 { | |
693 sql += (" INNER JOIN Resources " + | |
694 FormatLevel(static_cast<ResourceType>(level)) + " ON " + | |
695 FormatLevel(static_cast<ResourceType>(level)) + ".internalId=" + | |
696 FormatLevel(static_cast<ResourceType>(level + 1)) + ".parentId"); | |
697 } | |
698 | |
699 for (int level = queryLevel + 1; level <= lowerLevel; level++) | |
700 { | |
701 sql += (" INNER JOIN Resources " + | |
702 FormatLevel(static_cast<ResourceType>(level)) + " ON " + | |
703 FormatLevel(static_cast<ResourceType>(level - 1)) + ".internalId=" + | |
704 FormatLevel(static_cast<ResourceType>(level)) + ".parentId"); | |
705 } | |
706 | |
707 std::list<std::string> where; | |
708 where.push_back(strQueryLevel + ".resourceType = " + | |
709 formatter.FormatResourceType(queryLevel) + comparisons); | |
710 | |
711 | |
712 if (!request.GetLabels().empty()) | |
713 { | |
714 /** | |
715 * "In SQL Server, NOT EXISTS and NOT IN predicates are the best | |
716 * way to search for missing values, as long as both columns in | |
717 * question are NOT NULL." | |
718 * https://explainextended.com/2009/09/15/not-in-vs-not-exists-vs-left-join-is-null-sql-server/ | |
719 **/ | |
720 | |
721 const std::set<std::string>& labels = request.GetLabels(); | |
722 std::list<std::string> formattedLabels; | |
723 for (std::set<std::string>::const_iterator it = labels.begin(); it != labels.end(); ++it) | |
724 { | |
725 formattedLabels.push_back(formatter.GenerateParameter(*it)); | |
726 } | |
727 | |
728 std::string condition; | |
729 switch (request.GetLabelsConstraint()) | |
730 { | |
731 case LabelsConstraint_Any: | |
732 condition = "> 0"; | |
733 break; | |
734 | |
735 case LabelsConstraint_All: | |
736 condition = "= " + boost::lexical_cast<std::string>(labels.size()); | |
737 break; | |
738 | |
739 case LabelsConstraint_None: | |
740 condition = "= 0"; | |
741 break; | |
742 | |
743 default: | |
744 throw OrthancException(ErrorCode_ParameterOutOfRange); | |
745 } | |
746 | |
747 where.push_back("(SELECT COUNT(1) FROM Labels AS selectedLabels WHERE selectedLabels.id = " + strQueryLevel + | |
748 ".internalId AND selectedLabels.label IN (" + Join(formattedLabels, "", ", ") + ")) " + condition); | |
749 } | |
750 | |
751 sql += joins + Join(where, " WHERE ", " AND "); | |
752 | |
753 if (request.HasLimits()) | |
754 { | |
755 sql += formatter.FormatLimits(request.GetLimitsSince(), request.GetLimitsCount()); | |
756 } | |
757 | |
758 } | |
759 #endif | |
760 | |
614 | 761 |
615 void ISqlLookupFormatter::ApplySingleLevel(std::string& sql, | 762 void ISqlLookupFormatter::ApplySingleLevel(std::string& sql, |
616 ISqlLookupFormatter& formatter, | 763 ISqlLookupFormatter& formatter, |
617 const std::vector<DatabaseConstraint>& lookup, | 764 const DatabaseConstraints& lookup, |
618 ResourceType queryLevel, | 765 ResourceType queryLevel, |
619 const std::set<std::string>& labels, | 766 const std::set<std::string>& labels, |
620 LabelsConstraint labelsConstraint, | 767 LabelsConstraint labelsConstraint, |
621 size_t limit | 768 size_t limit |
622 ) | 769 ) |
629 | 776 |
630 const bool escapeBrackets = formatter.IsEscapeBrackets(); | 777 const bool escapeBrackets = formatter.IsEscapeBrackets(); |
631 | 778 |
632 std::vector<std::string> mainDicomTagsComparisons, dicomIdentifiersComparisons; | 779 std::vector<std::string> mainDicomTagsComparisons, dicomIdentifiersComparisons; |
633 | 780 |
634 for (size_t i = 0; i < lookup.size(); i++) | 781 for (size_t i = 0; i < lookup.GetSize(); i++) |
635 { | 782 { |
783 const DatabaseConstraint& constraint = lookup.GetConstraint(i); | |
784 | |
636 std::string comparison; | 785 std::string comparison; |
637 | 786 |
638 if (FormatComparison2(comparison, formatter, lookup[i], escapeBrackets)) | 787 if (FormatComparison2(comparison, formatter, constraint, escapeBrackets)) |
639 { | 788 { |
640 if (!comparison.empty()) | 789 if (!comparison.empty()) |
641 { | 790 { |
642 if (lookup[i].IsIdentifier()) | 791 if (constraint.IsIdentifier()) |
643 { | 792 { |
644 dicomIdentifiersComparisons.push_back(comparison); | 793 dicomIdentifiersComparisons.push_back(comparison); |
645 } | 794 } |
646 else | 795 else |
647 { | 796 { |