changeset 5425:e2c9f9d9700e

merge new MemoryStringCache
author Alain Mazy <am@osimis.io>
date Wed, 15 Nov 2023 08:58:45 +0100
parents 630d0195e3bb (diff) d37dff2c0028 (current diff)
children c65e036d649b
files
diffstat 5 files changed, 315 insertions(+), 15 deletions(-) [+]
line wrap: on
line diff
--- a/CITATION.cff	Mon Nov 13 17:01:59 2023 +0100
+++ b/CITATION.cff	Wed Nov 15 08:58:45 2023 +0100
@@ -9,6 +9,6 @@
     given-names: "Sébastien"
 doi: "10.1007/s10278-018-0082-y"
 license: "GPL-3.0-or-later"
-repository-code: "https://hg.orthanc-server.com/orthanc/"
+repository-code: "https://orthanc.uclouvain.be/hg/orthanc/"
 version: 1.12.1
 date-released: 2023-07-04
--- a/OrthancFramework/Resources/CMake/DownloadOrthancFramework.cmake	Mon Nov 13 17:01:59 2023 +0100
+++ b/OrthancFramework/Resources/CMake/DownloadOrthancFramework.cmake	Wed Nov 15 08:58:45 2023 +0100
@@ -271,7 +271,7 @@
   else()
     message("Forking the Orthanc source repository using Mercurial")
     execute_process(
-      COMMAND ${ORTHANC_FRAMEWORK_HG} clone "https://hg.orthanc-server.com/orthanc/"
+      COMMAND ${ORTHANC_FRAMEWORK_HG} clone "https://orthanc.uclouvain.be/hg/orthanc/"
       WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
       RESULT_VARIABLE Failure
       )    
--- a/OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp	Mon Nov 13 17:01:59 2023 +0100
+++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp	Wed Nov 15 08:58:45 2023 +0100
@@ -2263,8 +2263,6 @@
         operations.append("verify-md5");
       }
 
-      operations.append("uuid");
-
       call.GetOutput().AnswerJson(operations);
     }
   }
--- a/OrthancServer/Sources/Search/ISqlLookupFormatter.cpp	Mon Nov 13 17:01:59 2023 +0100
+++ b/OrthancServer/Sources/Search/ISqlLookupFormatter.cpp	Wed Nov 15 08:58:45 2023 +0100
@@ -32,8 +32,10 @@
 
 #if ORTHANC_BUILDING_SERVER_LIBRARY == 1
 #  include "../../../OrthancFramework/Sources/OrthancException.h"
+#  include "../../../OrthancFramework/Sources/Toolbox.h"
 #else
 #  include <OrthancException.h>
+#  include <Toolbox.h>
 #endif
 
 #include "DatabaseConstraint.h"
@@ -301,22 +303,184 @@
       return s;
     }
   }
-  
+
+  static bool FormatComparison2(std::string& target,
+                                ISqlLookupFormatter& formatter,
+                                const DatabaseConstraint& constraint,
+                                bool escapeBrackets)
+  {
+    std::string comparison;
+    std::string tagFilter = ("tagGroup = " + boost::lexical_cast<std::string>(constraint.GetTag().GetGroup())
+                              + " AND tagElement = " + boost::lexical_cast<std::string>(constraint.GetTag().GetElement()));
+
+    switch (constraint.GetConstraintType())
+    {
+      case ConstraintType_Equal:
+      case ConstraintType_SmallerOrEqual:
+      case ConstraintType_GreaterOrEqual:
+      {
+        std::string op;
+        switch (constraint.GetConstraintType())
+        {
+          case ConstraintType_Equal:
+            op = "=";
+            break;
+          
+          case ConstraintType_SmallerOrEqual:
+            op = "<=";
+            break;
+          
+          case ConstraintType_GreaterOrEqual:
+            op = ">=";
+            break;
+          
+          default:
+            throw OrthancException(ErrorCode_InternalError);
+        }
+
+        std::string parameter = formatter.GenerateParameter(constraint.GetSingleValue());
+
+        if (constraint.IsCaseSensitive())
+        {
+          comparison = " AND value " + op + " " + parameter;
+        }
+        else
+        {
+          comparison = " AND lower(value) " + op + " lower(" + parameter + ")";
+        }
+
+        break;
+      }
+
+      case ConstraintType_List:
+      {
+        std::vector<std::string> comparisonValues;
+        for (size_t i = 0; i < constraint.GetValuesCount(); i++)
+        {
+          std::string parameter = formatter.GenerateParameter(constraint.GetValue(i));
+
+          if (constraint.IsCaseSensitive())
+          {
+            comparisonValues.push_back(parameter);
+          }
+          else
+          {
+            comparisonValues.push_back("lower(" + parameter + ")");
+          }
+        }
+
+        std::string values;
+        Toolbox::JoinStrings(values, comparisonValues, ", ");
+
+        if (constraint.IsCaseSensitive())
+        {
+          comparison = " AND value IN (" + values + ")";
+        }
+        else
+        {
+          comparison = " AND lower(value) IN (" + values + ")";
+        }
+            
+        break;
+      }
+
+      case ConstraintType_Wildcard:
+      {
+        const std::string value = constraint.GetSingleValue();
 
-  void ISqlLookupFormatter::Apply(std::string& sql,
-                                  ISqlLookupFormatter& formatter,
-                                  const std::vector<DatabaseConstraint>& lookup,
-                                  ResourceType queryLevel,
-                                  const std::set<std::string>& labels,
-                                  LabelsConstraint labelsConstraint,
-                                  size_t limit)
+        if (value == "*")
+        {
+          if (!constraint.IsMandatory())
+          {
+            // Universal constraint on an optional tag, ignore it
+            return false;
+          }
+        }
+        else
+        {
+          std::string escaped;
+          escaped.reserve(value.size());
+
+          for (size_t i = 0; i < value.size(); i++)
+          {
+            if (value[i] == '*')
+            {
+              escaped += "%";
+            }
+            else if (value[i] == '?')
+            {
+              escaped += "_";
+            }
+            else if (value[i] == '%')
+            {
+              escaped += "\\%";
+            }
+            else if (value[i] == '_')
+            {
+              escaped += "\\_";
+            }
+            else if (value[i] == '\\')
+            {
+              escaped += "\\\\";
+            }
+            else if (escapeBrackets && value[i] == '[')
+            {
+              escaped += "\\[";
+            }
+            else if (escapeBrackets && value[i] == ']')
+            {
+              escaped += "\\]";
+            }
+            else
+            {
+              escaped += value[i];
+            }               
+          }
+
+          std::string parameter = formatter.GenerateParameter(escaped);
+
+          if (constraint.IsCaseSensitive())
+          {
+            comparison = " AND value LIKE " + parameter + " " + formatter.FormatWildcardEscape();
+          }
+          else
+          {
+            comparison = " AND lower(value) LIKE lower(" + parameter + ") " + formatter.FormatWildcardEscape();
+          }
+        }
+          
+        break;
+      }
+
+      default:
+        return false;
+    }
+
+    if (constraint.IsMandatory())
+    {
+      target = tagFilter + comparison;
+    }
+    else if (comparison.empty())
+    {
+      target = tagFilter + " AND value IS NULL";
+    }
+    else
+    {
+      target = tagFilter + " AND value IS NULL OR " + comparison;
+    }
+
+    return true;
+  }
+
+
+  void ISqlLookupFormatter::GetLookupLevels(ResourceType& lowerLevel, ResourceType& upperLevel, const ResourceType& queryLevel, const std::vector<DatabaseConstraint>& lookup)
   {
     assert(ResourceType_Patient < ResourceType_Study &&
            ResourceType_Study < ResourceType_Series &&
            ResourceType_Series < ResourceType_Instance);
     
-    ResourceType upperLevel = queryLevel;
-    ResourceType lowerLevel = queryLevel;
+    lowerLevel = queryLevel;
+    upperLevel = queryLevel;
 
     for (size_t i = 0; i < lookup.size(); i++)
     {
@@ -332,7 +496,20 @@
         lowerLevel = level;
       }
     }
-    
+  }
+  
+
+  void ISqlLookupFormatter::Apply(std::string& sql,
+                                  ISqlLookupFormatter& formatter,
+                                  const std::vector<DatabaseConstraint>& lookup,
+                                  ResourceType queryLevel,
+                                  const std::set<std::string>& labels,
+                                  LabelsConstraint labelsConstraint,
+                                  size_t limit)
+  {
+    ResourceType lowerLevel, upperLevel;
+    GetLookupLevels(lowerLevel, upperLevel, queryLevel, lookup);
+
     assert(upperLevel <= queryLevel &&
            queryLevel <= lowerLevel);
 
@@ -431,4 +608,119 @@
       sql += " LIMIT " + boost::lexical_cast<std::string>(limit);
     }
   }
+
+
+  void ISqlLookupFormatter::ApplySingleLevel(std::string& sql,
+                                             ISqlLookupFormatter& formatter,
+                                             const std::vector<DatabaseConstraint>& lookup,
+                                             ResourceType queryLevel,
+                                             const std::set<std::string>& labels,
+                                             LabelsConstraint labelsConstraint,
+                                             size_t limit
+                                             )
+  {
+    ResourceType lowerLevel, upperLevel;
+    GetLookupLevels(lowerLevel, upperLevel, queryLevel, lookup);
+    
+    assert(upperLevel == queryLevel &&
+           queryLevel == lowerLevel);
+
+    const bool escapeBrackets = formatter.IsEscapeBrackets();
+    
+    std::vector<std::string> mainDicomTagsComparisons, dicomIdentifiersComparisons;
+
+    for (size_t i = 0; i < lookup.size(); i++)
+    {
+      std::string comparison;
+      
+      if (FormatComparison2(comparison, formatter, lookup[i], escapeBrackets))
+      {
+        if (!comparison.empty())
+        {
+          if (lookup[i].IsIdentifier())
+          {
+            dicomIdentifiersComparisons.push_back(comparison);
+          }
+          else
+          {
+            mainDicomTagsComparisons.push_back(comparison);
+          }
+        }
+      }
+    }
+
+    sql = ("SELECT publicId, internalId "
+           "FROM Resources "
+           "WHERE resourceType = " + formatter.FormatResourceType(queryLevel) 
+            + " ");
+
+    if (dicomIdentifiersComparisons.size() > 0)
+    {
+      for (std::vector<std::string>::const_iterator it = dicomIdentifiersComparisons.begin(); it < dicomIdentifiersComparisons.end(); ++it)
+      {
+        sql += (" AND internalId IN (SELECT id FROM DicomIdentifiers WHERE " + *it + ") ");
+      }
+    }
+
+    if (mainDicomTagsComparisons.size() > 0)
+    {
+      std::string comparisons;
+      for (std::vector<std::string>::const_iterator it = mainDicomTagsComparisons.begin(); it < mainDicomTagsComparisons.end(); ++it)
+      {
+        sql += (" AND internalId IN (SELECT id FROM MainDicomTags WHERE " + *it + ") ");
+      }
+    }
+
+    if (!labels.empty())
+    {
+      /**
+       * "In SQL Server, NOT EXISTS and NOT IN predicates are the best
+       * way to search for missing values, as long as both columns in
+       * question are NOT NULL."
+       * https://explainextended.com/2009/09/15/not-in-vs-not-exists-vs-left-join-is-null-sql-server/
+       **/
+
+      std::list<std::string> formattedLabels;
+      for (std::set<std::string>::const_iterator it = labels.begin(); it != labels.end(); ++it)
+      {
+        formattedLabels.push_back(formatter.GenerateParameter(*it));
+      }
+
+      std::string condition;
+      std::string inOrNotIn;
+      switch (labelsConstraint)
+      {
+        case LabelsConstraint_Any:
+          condition = "> 0";
+          inOrNotIn = "IN";
+          break;
+          
+        case LabelsConstraint_All:
+          condition = "= " + boost::lexical_cast<std::string>(labels.size());
+          inOrNotIn = "IN";
+          break;
+          
+        case LabelsConstraint_None:
+          condition = "> 0";
+          inOrNotIn = "NOT IN";
+          break;
+          
+        default:
+          throw OrthancException(ErrorCode_ParameterOutOfRange);
+      }
+      
+      sql += (" AND internalId " + inOrNotIn + " (SELECT id"
+                                 " FROM (SELECT id, COUNT(1) AS labelsCount "
+                                        "FROM Labels "
+                                        "WHERE label IN (" + Join(formattedLabels, "", ", ") + ") GROUP BY id"
+                                        ") AS temp "
+                                 " WHERE labelsCount " + condition + ")");
+    }
+
+    if (limit != 0)
+    {
+      sql += " LIMIT " + boost::lexical_cast<std::string>(limit);
+    }
+  }
+
 }
--- a/OrthancServer/Sources/Search/ISqlLookupFormatter.h	Mon Nov 13 17:01:59 2023 +0100
+++ b/OrthancServer/Sources/Search/ISqlLookupFormatter.h	Wed Nov 15 08:58:45 2023 +0100
@@ -63,6 +63,8 @@
      **/
     virtual bool IsEscapeBrackets() const = 0;
 
+    static void GetLookupLevels(ResourceType& lowerLevel, ResourceType& upperLevel, const ResourceType& queryLevel, const std::vector<DatabaseConstraint>& lookup);
+
     static void Apply(std::string& sql,
                       ISqlLookupFormatter& formatter,
                       const std::vector<DatabaseConstraint>& lookup,
@@ -70,5 +72,13 @@
                       const std::set<std::string>& labels,  // New in Orthanc 1.12.0
                       LabelsConstraint labelsConstraint,    // New in Orthanc 1.12.0
                       size_t limit);
+
+    static void ApplySingleLevel(std::string& sql,
+                                 ISqlLookupFormatter& formatter,
+                                 const std::vector<DatabaseConstraint>& lookup,
+                                 ResourceType queryLevel,
+                                 const std::set<std::string>& labels,  // New in Orthanc 1.12.0
+                                 LabelsConstraint labelsConstraint,    // New in Orthanc 1.12.0
+                                 size_t limit);
   };
 }