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 {