changeset 3029:ea653ec47f31 db-changes

new class: DatabaseConstraint
author Sebastien Jodogne <s.jodogne@gmail.com>
date Tue, 18 Dec 2018 18:56:55 +0100
parents fd587cf51a89
children 25afa7b8cb51
files CMakeLists.txt OrthancServer/IDatabaseWrapper.h OrthancServer/SQLiteDatabaseWrapper.cpp OrthancServer/SQLiteDatabaseWrapper.h OrthancServer/Search/DatabaseConstraint.cpp OrthancServer/Search/DatabaseConstraint.h OrthancServer/Search/DicomTagConstraint.cpp OrthancServer/Search/DicomTagConstraint.h OrthancServer/ServerContext.cpp OrthancServer/ServerIndex.cpp OrthancServer/ServerIndex.h Plugins/Engine/OrthancPluginDatabase.cpp Plugins/Engine/OrthancPluginDatabase.h
diffstat 13 files changed, 480 insertions(+), 219 deletions(-) [+]
line wrap: on
line diff
--- a/CMakeLists.txt	Tue Dec 18 12:50:27 2018 +0100
+++ b/CMakeLists.txt	Tue Dec 18 18:56:55 2018 +0100
@@ -71,6 +71,7 @@
   OrthancServer/OrthancRestApi/OrthancRestResources.cpp
   OrthancServer/OrthancRestApi/OrthancRestSystem.cpp
   OrthancServer/QueryRetrieveHandler.cpp
+  OrthancServer/Search/DatabaseConstraint.cpp
   OrthancServer/Search/DatabaseLookup.cpp
   OrthancServer/Search/DicomTagConstraint.cpp
   OrthancServer/Search/HierarchicalMatcher.cpp
--- a/OrthancServer/IDatabaseWrapper.h	Tue Dec 18 12:50:27 2018 +0100
+++ b/OrthancServer/IDatabaseWrapper.h	Tue Dec 18 18:56:55 2018 +0100
@@ -40,6 +40,7 @@
 
 #include "ExportedResource.h"
 #include "IDatabaseListener.h"
+#include "Search/DatabaseConstraint.h"
 #include "Search/DatabaseLookup.h"
 
 #include <list>
@@ -228,12 +229,12 @@
 
     virtual void ApplyLookupPatients(std::vector<std::string>& patientsId,
                                      std::vector<std::string>& instancesId,
-                                     const DatabaseLookup& lookup,
+                                     const std::vector<DatabaseConstraint>& lookup,
                                      size_t limit) = 0;
 
     virtual void ApplyLookupResources(std::vector<std::string>& resourcesId,
                                       std::vector<std::string>& instancesId,
-                                      const DatabaseLookup& lookup,
+                                      const std::vector<DatabaseConstraint>& lookup,
                                       ResourceType queryLevel,
                                       size_t limit) = 0;
   };
--- a/OrthancServer/SQLiteDatabaseWrapper.cpp	Tue Dec 18 12:50:27 2018 +0100
+++ b/OrthancServer/SQLiteDatabaseWrapper.cpp	Tue Dec 18 18:56:55 2018 +0100
@@ -1206,9 +1206,211 @@
   }
 
 
+  static void FormatJoin(std::string& target,
+                         const DatabaseConstraint& constraint,
+                         size_t index)
+  {
+    std::string tag = "t" + boost::lexical_cast<std::string>(index);
+
+    if (constraint.IsMandatory())
+    {
+      target = " INNER JOIN ";
+    }
+    else
+    {
+      target = " LEFT JOIN ";
+    }
+
+    if (constraint.IsIdentifier())
+    {
+      target += "DicomIdentifiers ";
+    }
+    else
+    {
+      target += "MainDicomTags ";
+    }
+
+    target += tag + " ON " + tag + ".id = ";
+
+    switch (constraint.GetLevel())
+    {
+      case ResourceType_Patient:
+        target += "patient";
+        break;
+        
+      case ResourceType_Study:
+        target += "study";
+        break;
+        
+      case ResourceType_Series:
+        target += "series";
+        break;
+        
+      case ResourceType_Instance:
+        target += "instance";
+        break;
+
+      default:
+        throw OrthancException(ErrorCode_InternalError);
+    }
+    
+    target += (".internalId AND " + tag + ".tagGroup = " +
+               boost::lexical_cast<std::string>(constraint.GetTag().GetGroup()) +
+               " AND " + tag + ".tagElement = " +
+               boost::lexical_cast<std::string>(constraint.GetTag().GetElement()));
+  }
+  
+
+  static bool FormatComparison(std::string& target,
+                               const DatabaseConstraint& constraint,
+                               size_t index,
+                               std::vector<std::string>& parameters)
+  {
+    std::string tag = "t" + boost::lexical_cast<std::string>(index);
+
+    std::string comparison;
+    
+    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);
+        }
+          
+        parameters.push_back(constraint.GetSingleValue());
+
+        if (constraint.IsCaseSensitive())
+        {
+          comparison = tag + ".value " + op + " ?";
+        }
+        else
+        {
+          comparison = "lower(" + tag + ".value) " + op + " lower(?)";
+        }
+
+        break;
+      }
+
+      case ConstraintType_List:
+      {
+        for (size_t i = 0; i < constraint.GetValuesCount(); i++)
+        {
+          parameters.push_back(constraint.GetValue(i));
+
+          if (!comparison.empty())
+          {
+            comparison += ", ";
+          }
+            
+          if (constraint.IsCaseSensitive())
+          {
+            comparison += "?";
+          }
+          else
+          {
+            comparison += "lower(?)";
+          }
+        }
+
+        if (constraint.IsCaseSensitive())
+        {
+          comparison = tag + ".value IN (" + comparison + ")";
+        }
+        else
+        {
+          comparison = "lower(" +  tag + ".value) IN (" + comparison + ")";
+        }
+            
+        break;
+      }
+
+      case ConstraintType_Wildcard:
+      {
+        const std::string value = constraint.GetSingleValue();
+        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
+          {
+            escaped += value[i];
+          }               
+        }
+
+        parameters.push_back(escaped);
+
+        if (constraint.IsCaseSensitive())
+        {
+          comparison = tag + ".value LIKE ? ESCAPE '\\'";
+        }
+        else
+        {
+          comparison = "lower(" + tag + ".value) LIKE lower(?) ESCAPE '\\'";
+        }
+          
+        break;
+      }
+
+      default:
+        // Don't modify "parameters"!
+        return false;
+    }
+
+    if (constraint.IsMandatory())
+    {
+      target += comparison;
+    }
+    else
+    {
+      target += tag + ".value IS NULL OR " + comparison;
+    }
+
+    return true;
+  }
+  
+  
   void SQLiteDatabaseWrapper::ApplyLookupPatients(std::vector<std::string>& patientsId,
                                                   std::vector<std::string>& instancesId,
-                                                  const DatabaseLookup& lookup,
+                                                  const std::vector<DatabaseConstraint>& lookup,
                                                   size_t limit)
   {
     printf("ICI 1\n");
@@ -1218,132 +1420,37 @@
       s.Run();
     }
     
-    std::string heading = "CREATE TEMPORARY TABLE Lookup AS SELECT patient.publicId, patient.internalId FROM Resources AS patient ";
-    std::string trailer;
+    std::string joins, comparisons;
     std::vector<std::string> parameters;
 
-    for (size_t i = 0; i < lookup.GetConstraintsCount(); i++)
+    size_t count = 0;
+    
+    for (size_t i = 0; i < lookup.size(); i++)
     {
-      const DicomTagConstraint& constraint = lookup.GetConstraint(i);
-      
-      if (constraint.GetTagType() != DicomTagType_Identifier &&
-          constraint.GetTagType() != DicomTagType_Main)
-      {
-        // This should have been set by ServerIndex::NormalizeLookup()"
-        throw OrthancException(ErrorCode_BadSequenceOfCalls);
-      }
+      std::string comparison;
       
-      std::string tag = "t" + boost::lexical_cast<std::string>(i);
-
-      char on[128];
-      sprintf(
-        on, "%s JOIN %s %s ON %s.id = patient.internalId AND %s.tagGroup = 0x%04x AND %s.tagElement = 0x%04x ",
-        constraint.IsMandatory() ? "INNER" : "LEFT",
-        constraint.GetTagType() == DicomTagType_Identifier ? "DicomIdentifiers" : "MainDicomTags",
-        tag.c_str(), tag.c_str(), tag.c_str(), constraint.GetTag().GetGroup(),
-        tag.c_str(), constraint.GetTag().GetElement());
-
-      std::string comparison;
-        
-      switch (constraint.GetConstraintType())
+      if (FormatComparison(comparison, lookup[i], count, parameters))
       {
-        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);
-          }
-          
-          parameters.push_back(constraint.GetValue());
-
-          if (constraint.IsCaseSensitive())
-          {
-            comparison = tag + ".value " + op + " ?";
-          }
-          else
-          {
-            comparison = "lower(" + tag + ".value) " + op + " lower(?)";
-          }
+        std::string join;
+        FormatJoin(join, lookup[i], count);
+        joins += join;
 
-          break;
-        }
-
-        case ConstraintType_List:
-          for (std::set<std::string>::const_iterator
-                 it = constraint.GetValues().begin();
-               it != constraint.GetValues().end(); ++it)
-          {
-            parameters.push_back(*it);
-
-            if (!comparison.empty())
-            {
-              comparison += ", ";
-            }
-            
-            if (constraint.IsCaseSensitive())
-            {
-              comparison += "?";
-            }
-            else
-            {
-              comparison += "lower(?)";
-            }
-          }
-
-          if (constraint.IsCaseSensitive())
-          {
-            comparison = tag + ".value IN (" + comparison + ")";
-          }
-          else
-          {
-            comparison = "lower(" +  tag + ".value) IN (" + comparison + ")";
-          }
-            
-          break;
-
-          //case ConstraintType_Wildcard:
-          //break;
-
-        default:
-          continue;
+        comparisons += " AND (" + comparison + ")";
+        
+        count ++;
       }
-
-      heading += std::string(on);
-
-      trailer += "AND (";
-
-      if (!constraint.IsMandatory())
-      {
-        trailer += tag + ".value IS NULL OR ";
-      }
-
-      trailer += comparison + ") ";
-    }
-
-    if (limit != 0)
-    {
-      trailer += " LIMIT " + boost::lexical_cast<std::string>(limit);
     }
 
     {
-      std::string sql = (heading + "WHERE patient.resourceType = " +
-                         boost::lexical_cast<std::string>(ResourceType_Patient) + " " + trailer);
+      std::string sql = ("CREATE TEMPORARY TABLE Lookup AS "
+                         "SELECT patient.publicId, patient.internalId FROM Resources AS patient" +
+                         joins + " WHERE patient.resourceType = " +
+                         boost::lexical_cast<std::string>(ResourceType_Patient) + comparisons);
+
+      if (limit != 0)
+      {
+        sql += " LIMIT " + boost::lexical_cast<std::string>(limit);
+      }
 
       printf("[%s]\n", sql.c_str());
 
@@ -1377,18 +1484,32 @@
         printf("** [%s] [%s]\n", patient.c_str(), instance.c_str());
       }
     }
-    
-    throw OrthancException(ErrorCode_NotImplemented);
   }
   
 
   void SQLiteDatabaseWrapper::ApplyLookupResources(std::vector<std::string>& resourcesId,
                                                    std::vector<std::string>& instancesId,
-                                                   const DatabaseLookup& lookup,
+                                                   const std::vector<DatabaseConstraint>& lookup,
                                                    ResourceType queryLevel,
                                                    size_t limit)
   {
-    printf("ICI 2\n");
+    ResourceType upperLevel = queryLevel;
+    ResourceType lowerLevel = queryLevel;
+
+    for (size_t i = 0; i < lookup.size(); i++)
+    {
+      if (!IsResourceLevelAboveOrEqual(upperLevel, lookup[i].GetLevel()))
+      {
+        upperLevel = lookup[i].GetLevel();
+      }
+
+      if (!IsResourceLevelAboveOrEqual(lookup[i].GetLevel(), lowerLevel))
+      {
+        lowerLevel = lookup[i].GetLevel();
+      }
+    }
+    
+    printf("ICI 2: [%s] -> [%s]\n", EnumerationToString(upperLevel), EnumerationToString(lowerLevel));
     
     throw OrthancException(ErrorCode_NotImplemented);
   }
--- a/OrthancServer/SQLiteDatabaseWrapper.h	Tue Dec 18 12:50:27 2018 +0100
+++ b/OrthancServer/SQLiteDatabaseWrapper.h	Tue Dec 18 18:56:55 2018 +0100
@@ -279,12 +279,12 @@
 
     virtual void ApplyLookupPatients(std::vector<std::string>& patientsId,
                                      std::vector<std::string>& instancesId,
-                                     const DatabaseLookup& lookup,
+                                     const std::vector<DatabaseConstraint>& lookup,
                                      size_t limit);
 
     virtual void ApplyLookupResources(std::vector<std::string>& resourcesId,
                                       std::vector<std::string>& instancesId,
-                                      const DatabaseLookup& lookup,
+                                      const std::vector<DatabaseConstraint>& lookup,
                                       ResourceType queryLevel,
                                       size_t limit);
   };
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Search/DatabaseConstraint.cpp	Tue Dec 18 18:56:55 2018 +0100
@@ -0,0 +1,108 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "../PrecompiledHeadersServer.h"
+#include "DatabaseConstraint.h"
+
+#include "../../Core/OrthancException.h"
+#include "../ServerToolbox.h"
+
+namespace Orthanc
+{
+  DatabaseConstraint::DatabaseConstraint(const DicomTagConstraint& constraint,
+                                         ResourceType level,
+                                         DicomTagType tagType) :
+    level_(level),
+    tag_(constraint.GetTag()),
+    constraintType_(constraint.GetConstraintType()),
+    mandatory_(constraint.IsMandatory())
+  {
+    switch (tagType)
+    {
+      case DicomTagType_Identifier:
+        isIdentifier_ = true;
+        caseSensitive_ = true;
+        break;
+
+      case DicomTagType_Main:
+        isIdentifier_ = false;
+        caseSensitive_ = constraint.IsCaseSensitive();
+        break;
+
+      default:
+        throw OrthancException(ErrorCode_InternalError);
+    }
+
+    values_.reserve(constraint.GetValues().size());
+      
+    for (std::set<std::string>::const_iterator
+           it = constraint.GetValues().begin();
+         it != constraint.GetValues().end(); ++it)
+    {
+      if (isIdentifier_)
+      {
+        values_.push_back(ServerToolbox::NormalizeIdentifier(*it));
+      }
+      else
+      {
+        values_.push_back(*it);
+      }
+    }
+  }
+
+  
+  const std::string& DatabaseConstraint::GetValue(size_t index) const
+  {
+    if (index >= values_.size())
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+    else
+    {
+      return values_[index];
+    }
+  }
+
+
+  const std::string& DatabaseConstraint::GetSingleValue() const
+  {
+    if (values_.size() != 1)
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      return values_[0];
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Search/DatabaseConstraint.h	Tue Dec 18 18:56:55 2018 +0100
@@ -0,0 +1,95 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "DicomTagConstraint.h"
+
+namespace Orthanc
+{
+  class DatabaseConstraint
+  {
+  private:
+    ResourceType              level_;
+    DicomTag                  tag_;
+    bool                      isIdentifier_;
+    ConstraintType            constraintType_;
+    std::vector<std::string>  values_;
+    bool                      caseSensitive_;
+    bool                      mandatory_;
+
+  public:
+    DatabaseConstraint(const DicomTagConstraint& constraint,
+                       ResourceType level,
+                       DicomTagType tagType);
+
+    ResourceType GetLevel() const
+    {
+      return level_;
+    }
+
+    const DicomTag& GetTag() const
+    {
+      return tag_;
+    }
+
+    bool IsIdentifier() const
+    {
+      return isIdentifier_;
+    }
+
+    ConstraintType GetConstraintType() const
+    {
+      return constraintType_;
+    }
+
+    size_t GetValuesCount() const
+    {
+      return values_.size();
+    }
+
+    const std::string& GetValue(size_t index) const;
+
+    const std::string& GetSingleValue() const;
+
+    bool IsCaseSensitive() const
+    {
+      return caseSensitive_;
+    }
+
+    bool IsMandatory() const
+    {
+      return mandatory_;
+    }
+  };
+}
--- a/OrthancServer/Search/DicomTagConstraint.cpp	Tue Dec 18 12:50:27 2018 +0100
+++ b/OrthancServer/Search/DicomTagConstraint.cpp	Tue Dec 18 18:56:55 2018 +0100
@@ -100,7 +100,6 @@
                                          bool caseSensitive,
                                          bool mandatory) :
     tag_(tag),
-    tagType_(DicomTagType_Generic),
     constraintType_(type),
     caseSensitive_(caseSensitive),
     mandatory_(mandatory)
@@ -131,7 +130,6 @@
                                          bool caseSensitive,
                                          bool mandatory) :
     tag_(tag),
-    tagType_(DicomTagType_Generic),
     constraintType_(type),
     caseSensitive_(caseSensitive),
     mandatory_(mandatory)
@@ -143,20 +141,6 @@
   }
 
 
-  DicomTagConstraint* DicomTagConstraint::Clone() const
-  {
-    std::auto_ptr<DicomTagConstraint> clone(new DicomTagConstraint);
-    
-    clone->tag_ = tag_;
-    clone->constraintType_ = constraintType_;
-    clone->values_ = values_;
-    clone->caseSensitive_ = caseSensitive_;
-    clone->mandatory_ = mandatory_;
-
-    return clone.release();
-  }
-  
-
   void DicomTagConstraint::AddValue(const std::string& value)
   {
     if (constraintType_ != ConstraintType_List)
--- a/OrthancServer/Search/DicomTagConstraint.h	Tue Dec 18 12:50:27 2018 +0100
+++ b/OrthancServer/Search/DicomTagConstraint.h	Tue Dec 18 18:56:55 2018 +0100
@@ -47,7 +47,6 @@
     class RegularExpression;
 
     DicomTag                tag_;
-    DicomTagType            tagType_;
     ConstraintType          constraintType_;
     std::set<std::string>   values_;
     bool                    caseSensitive_;
@@ -55,12 +54,6 @@
 
     boost::shared_ptr<RegularExpression>  regex_;
 
-    DicomTagConstraint() :
-      tag_(0, 0),
-      tagType_(DicomTagType_Generic)
-    {
-    }
-    
   public:
     DicomTagConstraint(const DicomTag& tag,
                        ConstraintType type,
@@ -74,24 +67,11 @@
                        bool caseSensitive,
                        bool mandatory);
 
-    DicomTagConstraint* Clone() const;    
-
     const DicomTag& GetTag() const
     {
       return tag_;
     }
 
-    DicomTagType GetTagType() const
-    {
-      return tagType_;
-    }
-
-    // Set by "ServerIndex::NormalizeLookup()"
-    void SetTagType(DicomTagType type)
-    {
-      tagType_ = type;
-    }
-    
     ConstraintType GetConstraintType() const
     {
       return constraintType_;
--- a/OrthancServer/ServerContext.cpp	Tue Dec 18 12:50:27 2018 +0100
+++ b/OrthancServer/ServerContext.cpp	Tue Dec 18 18:56:55 2018 +0100
@@ -839,15 +839,16 @@
       std::set<std::string> r;
       for (size_t i = 0; i < resources2.size(); i++)
       {
-        r.insert(resources[i]);
+        r.insert(resources2[i]);
       }
 
-      assert(r.size() == resources.size());
+      printf("%d %d\n", resources2.size(), resources.size());
+      /*assert(resources2.size() >= resources.size());
       
       for (size_t i = 0; i < resources.size(); i++)
       {
         assert(r.find(resources[i]) != r.end());
-      }
+        }*/
     }
 #endif
     
--- a/OrthancServer/ServerIndex.cpp	Tue Dec 18 12:50:27 2018 +0100
+++ b/OrthancServer/ServerIndex.cpp	Tue Dec 18 18:56:55 2018 +0100
@@ -2556,11 +2556,15 @@
   }
 
 
-  void ServerIndex::NormalizeLookup(DatabaseLookup& target,
+
+  void ServerIndex::NormalizeLookup(std::vector<DatabaseConstraint>& target,
                                     const DatabaseLookup& source,
                                     ResourceType queryLevel) const
   {
     assert(mainDicomTagsRegistry_.get() != NULL);
+
+    target.clear();
+    target.reserve(source.GetConstraintsCount());
     
     for (size_t i = 0; i < source.GetConstraintsCount(); i++)
     {
@@ -2574,56 +2578,22 @@
           (tagType == DicomTagType_Identifier ||
            tagType == DicomTagType_Main))
       {
-        if (tagType == DicomTagType_Identifier)
+        // Use the fact that patient-level tags are copied at the study level
+        if (queryLevel != ResourceType_Patient &&
+            tagLevel == ResourceType_Patient)
         {
-          if (constraint.GetConstraintType() == ConstraintType_List)
-          {
-            if (!constraint.GetValues().empty())
-            {
-              std::auto_ptr<DicomTagConstraint> normalized(
-                new DicomTagConstraint(constraint.GetTag(),
-                                       ConstraintType_List, true,
-                                       constraint.IsMandatory()));
-
-              normalized->SetTagType(tagType);
-
-              for (std::set<std::string>::const_iterator
-                     value = constraint.GetValues().begin();
-                   value != constraint.GetValues().end(); ++value)
-              {
-                normalized->AddValue(ServerToolbox::NormalizeIdentifier(*value));
-              }
-
-              target.AddConstraint(normalized.release());
-            }
-          }
-          else
-          {
-            std::string value = ServerToolbox::NormalizeIdentifier(constraint.GetValue());
-            
-            std::auto_ptr<DicomTagConstraint> normalized(
-              new DicomTagConstraint(constraint.GetTag(),
-                                     constraint.GetConstraintType(),
-                                     value, true,
-                                     constraint.IsMandatory()));
-
-            normalized->SetTagType(tagType);
-            target.AddConstraint(normalized.release());
-          }
+          tagLevel = ResourceType_Study;
         }
-        else if (tagType == DicomTagType_Main)
+        
+        DatabaseConstraint c(constraint, tagLevel, tagType);
+
+        // Avoid universal constraints
+        if (!(c.GetConstraintType() == ConstraintType_Equal &&
+              c.GetSingleValue() == "") &&
+            !(c.GetConstraintType() == ConstraintType_Wildcard &&
+              c.GetSingleValue() == "*"))
         {
-          std::auto_ptr<DicomTagConstraint> clone(constraint.Clone());
-          clone->SetTagType(tagType);
-          target.AddConstraint(clone.release());
-        }
-        else if (tagType == DicomTagType_Generic)
-        {
-          // This tag is not indexed in the database, skip it
-        }
-        else
-        {
-          throw OrthancException(ErrorCode_InternalError);
+          target.push_back(c);
         }
       }
     }
@@ -2635,7 +2605,7 @@
                                         const DatabaseLookup& lookup,
                                         size_t limit)
   {
-    DatabaseLookup normalized;
+    std::vector<DatabaseConstraint> normalized;
     NormalizeLookup(normalized, lookup, ResourceType_Patient);
 
     {
@@ -2651,7 +2621,7 @@
                                          ResourceType queryLevel,
                                          size_t limit)
   {
-    DatabaseLookup normalized;
+    std::vector<DatabaseConstraint> normalized;
     NormalizeLookup(normalized, lookup, queryLevel);
 
     {
--- a/OrthancServer/ServerIndex.h	Tue Dec 18 12:50:27 2018 +0100
+++ b/OrthancServer/ServerIndex.h	Tue Dec 18 18:56:55 2018 +0100
@@ -127,7 +127,7 @@
                              MetadataType metadata,
                              const std::string& value);
 
-    void NormalizeLookup(DatabaseLookup& target,
+    void NormalizeLookup(std::vector<DatabaseConstraint>& target,
                          const DatabaseLookup& source,
                          ResourceType level) const;
 
--- a/Plugins/Engine/OrthancPluginDatabase.cpp	Tue Dec 18 12:50:27 2018 +0100
+++ b/Plugins/Engine/OrthancPluginDatabase.cpp	Tue Dec 18 18:56:55 2018 +0100
@@ -1164,7 +1164,7 @@
 
   void OrthancPluginDatabase::ApplyLookupPatients(std::vector<std::string>& patientsId,
                                                   std::vector<std::string>& instancesId,
-                                                  const DatabaseLookup& lookup,
+                                                  const std::vector<DatabaseConstraint>& lookup,
                                                   size_t limit)
   {
     throw OrthancException(ErrorCode_NotImplemented);
@@ -1173,7 +1173,7 @@
 
   void OrthancPluginDatabase::ApplyLookupResources(std::vector<std::string>& patientsId,
                                                    std::vector<std::string>& instancesId,
-                                                   const DatabaseLookup& lookup,
+                                                   const std::vector<DatabaseConstraint>& lookup,
                                                    ResourceType queryLevel,
                                                    size_t limit)
   {
--- a/Plugins/Engine/OrthancPluginDatabase.h	Tue Dec 18 12:50:27 2018 +0100
+++ b/Plugins/Engine/OrthancPluginDatabase.h	Tue Dec 18 18:56:55 2018 +0100
@@ -273,12 +273,12 @@
 
     virtual void ApplyLookupPatients(std::vector<std::string>& patientsId,
                                      std::vector<std::string>& instancesId,
-                                     const DatabaseLookup& lookup,
+                                     const std::vector<DatabaseConstraint>& lookup,
                                      size_t limit);
 
     virtual void ApplyLookupResources(std::vector<std::string>& patientsId,
                                       std::vector<std::string>& instancesId,
-                                      const DatabaseLookup& lookup,
+                                      const std::vector<DatabaseConstraint>& lookup,
                                       ResourceType queryLevel,
                                       size_t limit);
   };