changeset 3025:039a9d262d64 db-changes

preparing to speed up find in databases
author Sebastien Jodogne <s.jodogne@gmail.com>
date Mon, 17 Dec 2018 17:05:28 +0100
parents ef17a587e10d
children fd587cf51a89
files OrthancServer/IDatabaseWrapper.h OrthancServer/OrthancFindRequestHandler.cpp OrthancServer/OrthancRestApi/OrthancRestResources.cpp OrthancServer/SQLiteDatabaseWrapper.cpp OrthancServer/SQLiteDatabaseWrapper.h OrthancServer/Search/DatabaseLookup.cpp OrthancServer/Search/DatabaseLookup.h OrthancServer/Search/DicomTagConstraint.cpp OrthancServer/Search/DicomTagConstraint.h OrthancServer/ServerContext.cpp OrthancServer/ServerContext.h OrthancServer/ServerIndex.cpp OrthancServer/ServerIndex.h Plugins/Engine/OrthancPluginDatabase.cpp Plugins/Engine/OrthancPluginDatabase.h UnitTestsSources/DatabaseLookupTests.cpp
diffstat 16 files changed, 475 insertions(+), 229 deletions(-) [+]
line wrap: on
line diff
--- a/OrthancServer/IDatabaseWrapper.h	Mon Dec 17 10:26:01 2018 +0100
+++ b/OrthancServer/IDatabaseWrapper.h	Mon Dec 17 17:05:28 2018 +0100
@@ -34,11 +34,13 @@
 #pragma once
 
 #include "../Core/DicomFormat/DicomMap.h"
-#include "../Core/SQLite/ITransaction.h"
+#include "../Core/FileStorage/FileInfo.h"
 #include "../Core/FileStorage/IStorageArea.h"
-#include "../Core/FileStorage/FileInfo.h"
+#include "../Core/SQLite/ITransaction.h"
+
+#include "ExportedResource.h"
 #include "IDatabaseListener.h"
-#include "ExportedResource.h"
+#include "Search/DatabaseLookup.h"
 
 #include <list>
 #include <boost/noncopyable.hpp>
@@ -223,5 +225,18 @@
                          IStorageArea& storageArea) = 0;
 
     virtual bool IsDiskSizeAbove(uint64_t threshold) = 0;
+
+    virtual void FindOneChildInstance(std::vector<std::string>& instancesId,
+                                      const std::vector<std::string>& resourcesId,
+                                      ResourceType level) = 0;
+
+    virtual void ApplyLookupPatients(std::vector<std::string>& patientsId,
+                                     const DatabaseLookup& lookup,
+                                     size_t limit) = 0;
+
+    virtual void ApplyLookupResources(std::vector<std::string>& resourcesId,
+                                      const DatabaseLookup& lookup,
+                                      ResourceType queryLevel,
+                                      size_t limit) = 0;
   };
 }
--- a/OrthancServer/OrthancFindRequestHandler.cpp	Mon Dec 17 10:26:01 2018 +0100
+++ b/OrthancServer/OrthancFindRequestHandler.cpp	Mon Dec 17 17:05:28 2018 +0100
@@ -615,6 +615,7 @@
      **/
 
     LookupResource lookup(level);
+    DatabaseLookup lookup2;
 
     bool caseSensitivePN;
 
@@ -655,6 +656,7 @@
         }
 
         lookup.AddDicomConstraint(tag, value, sensitive);
+        lookup2.AddDicomConstraint(tag, value, sensitive, true /* mandatory */);
       }
       else
       {
@@ -672,7 +674,7 @@
 
 
     LookupVisitor visitor(answers, context_, level, *filteredInput, sequencesToReturn);
-    context_.Apply(visitor, lookup, 0 /* "since" is not relevant to C-FIND */, limit);
+    context_.Apply(visitor, lookup, lookup2, 0 /* "since" is not relevant to C-FIND */, limit);
   }
 
 
--- a/OrthancServer/OrthancRestApi/OrthancRestResources.cpp	Mon Dec 17 10:26:01 2018 +0100
+++ b/OrthancServer/OrthancRestApi/OrthancRestResources.cpp	Mon Dec 17 17:05:28 2018 +0100
@@ -40,6 +40,7 @@
 #include "../../Core/HttpServer/HttpContentNegociation.h"
 #include "../../Core/Logging.h"
 #include "../OrthancConfiguration.h"
+#include "../Search/DatabaseLookup.h"
 #include "../Search/LookupResource.h"
 #include "../ServerContext.h"
 #include "../ServerToolbox.h"
@@ -1403,6 +1404,7 @@
       std::string level = request[KEY_LEVEL].asString();
 
       LookupResource query(StringToResourceType(level.c_str()));
+      DatabaseLookup query2;
 
       Json::Value::Members members = request[KEY_QUERY].getMemberNames();
       for (size_t i = 0; i < members.size(); i++)
@@ -1416,10 +1418,13 @@
         query.AddDicomConstraint(FromDcmtkBridge::ParseTag(members[i]), 
                                  request[KEY_QUERY][members[i]].asString(),
                                  caseSensitive);
+        query2.AddDicomConstraint(FromDcmtkBridge::ParseTag(members[i]), 
+                                  request[KEY_QUERY][members[i]].asString(),
+                                  caseSensitive, true);
       }
 
       FindVisitor visitor;
-      context.Apply(visitor, query, since, limit);
+      context.Apply(visitor, query, query2, since, limit);
       visitor.Answer(call.GetOutput(), context.GetIndex(), query.GetLevel(), expand);
     }
   }
--- a/OrthancServer/SQLiteDatabaseWrapper.cpp	Mon Dec 17 10:26:01 2018 +0100
+++ b/OrthancServer/SQLiteDatabaseWrapper.cpp	Mon Dec 17 17:05:28 2018 +0100
@@ -54,7 +54,7 @@
 
     public:
       SignalFileDeleted(IDatabaseListener& listener) :
-      listener_(listener)
+        listener_(listener)
       {
       }
 
@@ -101,7 +101,7 @@
 
     public:
       SignalResourceDeleted(IDatabaseListener& listener) :
-      listener_(listener)
+        listener_(listener)
       {
       }
 
@@ -1201,4 +1201,247 @@
   {
     return GetTotalCompressedSize() > threshold;
   }
+
+
+  namespace
+  {
+    class MainDicomTagsRegistry : public boost::noncopyable
+    {
+    private:
+      class TagInfo
+      {
+      private:
+        ResourceType  level_;
+        DicomTagType  type_;
+
+      public:
+        TagInfo()
+        {
+        }
+
+        TagInfo(ResourceType level,
+                DicomTagType type) :
+          level_(level),
+          type_(type)
+        {
+        }
+
+        ResourceType GetLevel() const
+        {
+          return level_;
+        }
+
+        DicomTagType GetType() const
+        {
+          return type_;
+        }
+      };
+      
+      typedef std::map<DicomTag, TagInfo>   Registry;
+
+
+      Registry  registry_;
+      
+      void LoadTags(ResourceType level)
+      {
+        const DicomTag* tags = NULL;
+        size_t size;
+  
+        ServerToolbox::LoadIdentifiers(tags, size, level);
+  
+        for (size_t i = 0; i < size; i++)
+        {
+          if (registry_.find(tags[i]) == registry_.end())
+          {
+            registry_[tags[i]] = TagInfo(level, DicomTagType_Identifier);
+          }
+          else
+          {
+            // These patient-level tags are copied in the study level
+            assert(level == ResourceType_Study &&
+                   (tags[i] == DICOM_TAG_PATIENT_ID ||
+                    tags[i] == DICOM_TAG_PATIENT_NAME ||
+                    tags[i] == DICOM_TAG_PATIENT_BIRTH_DATE));
+          }
+        }
+  
+        DicomMap::LoadMainDicomTags(tags, size, level);
+  
+        for (size_t i = 0; i < size; i++)
+        {
+          if (registry_.find(tags[i]) == registry_.end())
+          {
+            registry_[tags[i]] = TagInfo(level, DicomTagType_Main);
+          }
+        }
+      }
+
+    public:
+      MainDicomTagsRegistry()
+      {
+        LoadTags(ResourceType_Patient);
+        LoadTags(ResourceType_Study);
+        LoadTags(ResourceType_Series);
+        LoadTags(ResourceType_Instance); 
+      }
+
+      void LookupTag(ResourceType& level,
+                     DicomTagType& type,
+                     const DicomTag& tag) const
+      {
+        Registry::const_iterator it = registry_.find(tag);
+
+        if (it == registry_.end())
+        {
+          // Default values
+          level = ResourceType_Instance;
+          type = DicomTagType_Generic;
+        }
+        else
+        {
+          level = it->second.GetLevel();
+          type = it->second.GetType();
+        }
+      }
+    };
+  }
+  
+
+  void SQLiteDatabaseWrapper::FindOneChildInstance(std::vector<std::string>& instancesId,
+                                                   const std::vector<std::string>& resourcesId,
+                                                   ResourceType level)
+  {
+    printf("ICI 3\n");
+
+    throw OrthancException(ErrorCode_NotImplemented);
+  }
+
+
+  void SQLiteDatabaseWrapper::ApplyLookupPatients(std::vector<std::string>& patientsId,
+                                                  const DatabaseLookup& lookup,
+                                                  size_t limit)
+  {
+    static const MainDicomTagsRegistry registry;
+    
+    printf("ICI 1\n");
+
+    std::string heading = "SELECT patient.publicId FROM Resources AS patient ";
+    std::string trailer;
+    std::vector<std::string> parameters;
+
+    for (size_t i = 0; i < lookup.GetConstraintsCount(); i++)
+    {
+      const DicomTagConstraint& constraint = lookup.GetConstraint(i);
+      
+      ResourceType level;
+      DicomTagType type;
+      registry.LookupTag(level, type, constraint.GetTag());
+
+      if (level != ResourceType_Patient)
+      {
+        throw OrthancException(ErrorCode_ParameterOutOfRange,
+                               "Not a patient-level tag: (" +
+                               lookup.GetConstraint(i).GetTag().Format() + ")");
+      }
+
+      if (type == DicomTagType_Identifier ||
+          type == DicomTagType_Main)
+      {
+        std::string table = (type == DicomTagType_Identifier ? "DicomIdentifiers" : "MainDicomTags");
+        std::string tag = "t" + boost::lexical_cast<std::string>(i);
+
+        char on[128];
+        sprintf(on, " %s ON %s.id = patient.internalId AND %s.tagGroup = 0x%04x AND %s.tagElement = 0x%04x ",
+                tag.c_str(), tag.c_str(), tag.c_str(), constraint.GetTag().GetGroup(),
+                tag.c_str(), constraint.GetTag().GetElement());
+        
+        if (constraint.IsMandatory())
+        {
+          heading += "INNER JOIN " + table + std::string(on);
+        }
+        else
+        {
+          heading += "LEFT JOIN " + table + std::string(on);
+        }
+
+        trailer += "AND (";
+
+        if (!constraint.IsMandatory())
+        {
+          trailer += tag + ".value IS NULL OR ";
+        }
+
+        if (constraint.IsCaseSensitive())
+        {
+          trailer += tag + ".value ";
+        }
+        else
+        {
+          trailer += "lower(" + tag + ".value) ";
+        }
+
+        switch (constraint.GetType())
+        {
+          case ConstraintType_Equal:
+            parameters.push_back(constraint.GetValue());
+            
+            if (constraint.IsCaseSensitive())
+            {
+              trailer += "= ?";
+            }
+            else
+            {
+              trailer += "= lower(?)";
+            }
+
+            break;
+
+          default:
+            throw OrthancException(ErrorCode_NotImplemented);
+        }
+
+        trailer += ") ";
+      }
+    }
+
+    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);
+
+    SQLite::Statement s(db_, sql);
+
+    printf("[%s]\n", sql.c_str());
+
+    for (size_t i = 0; i < parameters.size(); i++)
+    {
+      printf("   %d = '%s'\n", i, parameters[i].c_str());
+      s.BindString(i, parameters[i]);
+    }
+
+    patientsId.clear();
+
+    while (s.Step())
+    {
+      std::string publicId = s.ColumnString(0);
+      patientsId.push_back(publicId);
+      printf("** [%s]\n", publicId.c_str());
+    }
+    
+    throw OrthancException(ErrorCode_NotImplemented);
+  }
+  
+
+  void SQLiteDatabaseWrapper::ApplyLookupResources(std::vector<std::string>& resourcesId,
+                                                   const DatabaseLookup& lookup,
+                                                   ResourceType queryLevel,
+                                                   size_t limit)
+  {
+    printf("ICI 2\n");
+    
+    throw OrthancException(ErrorCode_NotImplemented);
+  }
 }
--- a/OrthancServer/SQLiteDatabaseWrapper.h	Mon Dec 17 10:26:01 2018 +0100
+++ b/OrthancServer/SQLiteDatabaseWrapper.h	Mon Dec 17 17:05:28 2018 +0100
@@ -276,5 +276,18 @@
                                        const std::string& end);
 
     virtual bool IsDiskSizeAbove(uint64_t threshold);
+
+    virtual void FindOneChildInstance(std::vector<std::string>& instancesId,
+                                      const std::vector<std::string>& resourcesId,
+                                      ResourceType level);
+
+    virtual void ApplyLookupPatients(std::vector<std::string>& patientsId,
+                                     const DatabaseLookup& lookup,
+                                     size_t limit);
+
+    virtual void ApplyLookupResources(std::vector<std::string>& resourcesId,
+                                      const DatabaseLookup& lookup,
+                                      ResourceType queryLevel,
+                                      size_t limit);
   };
 }
--- a/OrthancServer/Search/DatabaseLookup.cpp	Mon Dec 17 10:26:01 2018 +0100
+++ b/OrthancServer/Search/DatabaseLookup.cpp	Mon Dec 17 17:05:28 2018 +0100
@@ -39,50 +39,6 @@
 
 namespace Orthanc
 {
-  void DatabaseLookup::LoadTags(ResourceType level)
-  {
-    const DicomTag* tags = NULL;
-    size_t size;
-    
-    ServerToolbox::LoadIdentifiers(tags, size, level);
-    
-    for (size_t i = 0; i < size; i++)
-    {
-      if (tags_.find(tags[i]) == tags_.end())
-      {
-        tags_[tags[i]] = TagInfo(DicomTagType_Identifier, level);
-      }
-      else
-      {
-        // These patient-level tags are copied in the study level
-        assert(level == ResourceType_Study &&
-               (tags[i] == DICOM_TAG_PATIENT_ID ||
-                tags[i] == DICOM_TAG_PATIENT_NAME ||
-                tags[i] == DICOM_TAG_PATIENT_BIRTH_DATE));
-      }
-    }
-    
-    DicomMap::LoadMainDicomTags(tags, size, level);
-    
-    for (size_t i = 0; i < size; i++)
-    {
-      if (tags_.find(tags[i]) == tags_.end())
-      {
-        tags_[tags[i]] = TagInfo(DicomTagType_Main, level);
-      }
-    }
-  }
-
-
-  DatabaseLookup::DatabaseLookup()
-  {
-    LoadTags(ResourceType_Patient);
-    LoadTags(ResourceType_Study);
-    LoadTags(ResourceType_Series);
-    LoadTags(ResourceType_Instance);
-  }
-
-
   DatabaseLookup::~DatabaseLookup()
   {
     for (size_t i = 0; i < constraints_.size(); i++)
@@ -116,17 +72,6 @@
     else
     {
       constraints_.push_back(constraint);
-
-      std::map<DicomTag, TagInfo>::const_iterator tag = tags_.find(constraint->GetTag());
-
-      if (tag == tags_.end())
-      {
-        constraint->SetTagInfo(DicomTagType_Generic, ResourceType_Instance);
-      }
-      else
-      {
-        constraint->SetTagInfo(tag->second.GetType(), tag->second.GetLevel());
-      }
     }
   }
 
@@ -148,7 +93,8 @@
 
   void DatabaseLookup::AddDicomConstraint(const DicomTag& tag,
                                           const std::string& dicomQuery,
-                                          bool caseSensitivePN)
+                                          bool caseSensitivePN,
+                                          bool mandatoryTag)
   {
     ValueRepresentation vr = FromDcmtkBridge::LookupValueRepresentation(tag);
 
@@ -214,13 +160,13 @@
       if (!lower.empty())
       {
         AddConstraint(new DicomTagConstraint
-                      (tag, ConstraintType_GreaterOrEqual, lower, caseSensitive));
+                      (tag, ConstraintType_GreaterOrEqual, lower, caseSensitive, mandatoryTag));
       }
 
       if (!upper.empty())
       {
         AddConstraint(new DicomTagConstraint
-                      (tag, ConstraintType_SmallerOrEqual, upper, caseSensitive));
+                      (tag, ConstraintType_SmallerOrEqual, upper, caseSensitive, mandatoryTag));
       }
     }
     else if (dicomQuery.find('\\') != std::string::npos)
@@ -235,7 +181,7 @@
       }
 
       std::auto_ptr<DicomTagConstraint> constraint
-        (new DicomTagConstraint(fixedTag, ConstraintType_List, caseSensitive));
+        (new DicomTagConstraint(fixedTag, ConstraintType_List, caseSensitive, mandatoryTag));
 
       std::vector<std::string> items;
       Toolbox::TokenizeString(items, dicomQuery, '\\');
@@ -251,12 +197,12 @@
              dicomQuery.find('?') != std::string::npos)
     {
       AddConstraint(new DicomTagConstraint
-                    (tag, ConstraintType_Wildcard, dicomQuery, caseSensitive));
+                    (tag, ConstraintType_Wildcard, dicomQuery, caseSensitive, mandatoryTag));
     }
     else
     {
       AddConstraint(new DicomTagConstraint
-                    (tag, ConstraintType_Equal, dicomQuery, caseSensitive));
+                    (tag, ConstraintType_Equal, dicomQuery, caseSensitive, mandatoryTag));
     }
   }
 }
--- a/OrthancServer/Search/DatabaseLookup.h	Mon Dec 17 10:26:01 2018 +0100
+++ b/OrthancServer/Search/DatabaseLookup.h	Mon Dec 17 17:05:28 2018 +0100
@@ -40,44 +40,12 @@
   class DatabaseLookup : public boost::noncopyable
   {
   private:
-    class TagInfo
-    {
-    private:
-      DicomTagType  type_;
-      ResourceType  level_;
-
-    public:
-      TagInfo() :
-        type_(DicomTagType_Generic),
-        level_(ResourceType_Instance)
-      {
-      }
-
-      TagInfo(DicomTagType type,
-              ResourceType level) :
-        type_(type),
-        level_(level)
-      {
-      }
-
-      DicomTagType GetType() const
-      {
-        return type_;
-      }
-
-      ResourceType GetLevel() const
-      {
-        return level_;
-      }
-    };
-
     std::vector<DicomTagConstraint*>  constraints_;
-    std::map<DicomTag, TagInfo>       tags_;
-
-    void LoadTags(ResourceType level);
 
   public:
-    DatabaseLookup();
+    DatabaseLookup()
+    {
+    }
 
     ~DatabaseLookup();
 
@@ -99,6 +67,7 @@
 
     void AddDicomConstraint(const DicomTag& tag,
                             const std::string& dicomQuery,
-                            bool caseSensitivePN);
+                            bool caseSensitivePN,
+                            bool mandatoryTag);
   };
 }
--- a/OrthancServer/Search/DicomTagConstraint.cpp	Mon Dec 17 10:26:01 2018 +0100
+++ b/OrthancServer/Search/DicomTagConstraint.cpp	Mon Dec 17 17:05:28 2018 +0100
@@ -97,13 +97,12 @@
   DicomTagConstraint::DicomTagConstraint(const DicomTag& tag,
                                          ConstraintType type,
                                          const std::string& value,
-                                         bool caseSensitive) :
-    hasTagInfo_(false),
-    tagType_(DicomTagType_Generic),  // Dummy initialization
-    level_(ResourceType_Patient),    // Dummy initialization
+                                         bool caseSensitive,
+                                         bool mandatory) :
     tag_(tag),
     constraintType_(type),
-    caseSensitive_(caseSensitive)
+    caseSensitive_(caseSensitive),
+    mandatory_(mandatory)
   {
     if (type == ConstraintType_Equal ||
         type == ConstraintType_SmallerOrEqual ||
@@ -128,13 +127,12 @@
 
   DicomTagConstraint::DicomTagConstraint(const DicomTag& tag,
                                          ConstraintType type,
-                                         bool caseSensitive) :
-    hasTagInfo_(false),
-    tagType_(DicomTagType_Generic),  // Dummy initialization
-    level_(ResourceType_Patient),    // Dummy initialization
+                                         bool caseSensitive,
+                                         bool mandatory) :
     tag_(tag),
     constraintType_(type),
-    caseSensitive_(caseSensitive)
+    caseSensitive_(caseSensitive),
+    mandatory_(mandatory)
   {
     if (type != ConstraintType_List)
     {
@@ -143,41 +141,6 @@
   }
 
 
-  void DicomTagConstraint::SetTagInfo(DicomTagType tagType,
-                                      ResourceType level)
-  {
-    hasTagInfo_ = true;
-    tagType_ = tagType;
-    level_ = level;
-  }
-
-
-  DicomTagType DicomTagConstraint::GetTagType() const
-  {
-    if (!hasTagInfo_)
-    {
-      throw OrthancException(ErrorCode_BadSequenceOfCalls);
-    }
-    else
-    {
-      return tagType_;
-    }
-  }
-
-
-  const ResourceType DicomTagConstraint::GetLevel() const
-  {
-    if (!hasTagInfo_)
-    {
-      throw OrthancException(ErrorCode_BadSequenceOfCalls);
-    }
-    else
-    {
-      return level_;
-    }
-  }
-
-
   void DicomTagConstraint::AddValue(const std::string& value)
   {
     if (constraintType_ != ConstraintType_List)
@@ -268,8 +231,18 @@
     const DicomValue* tmp = value.TestAndGetValue(tag_);
 
     if (tmp == NULL ||
-        tmp->IsNull() ||
-        tmp->IsBinary())
+        tmp->IsNull())
+    {
+      if (mandatory_)
+      {
+        return false;
+      }
+      else
+      {
+        return true;
+      }
+    }
+    else if (tmp->IsBinary())
     {
       return false;
     }
--- a/OrthancServer/Search/DicomTagConstraint.h	Mon Dec 17 10:26:01 2018 +0100
+++ b/OrthancServer/Search/DicomTagConstraint.h	Mon Dec 17 17:05:28 2018 +0100
@@ -46,13 +46,11 @@
     class NormalizedString;
     class RegularExpression;
 
-    bool                    hasTagInfo_;
-    DicomTagType            tagType_;
-    ResourceType            level_;
     DicomTag                tag_;
     ConstraintType          constraintType_;
     std::set<std::string>   values_;
     bool                    caseSensitive_;
+    bool                    mandatory_;
 
     boost::shared_ptr<RegularExpression>  regex_;
 
@@ -60,30 +58,21 @@
     DicomTagConstraint(const DicomTag& tag,
                        ConstraintType type,
                        const std::string& value,
-                       bool caseSensitive);
+                       bool caseSensitive,
+                       bool mandatory);
 
+    // For list search
     DicomTagConstraint(const DicomTag& tag,
                        ConstraintType type,
-                       bool caseSensitive);
-
-    bool HasTagInfo() const
-    {
-      return hasTagInfo_;
-    }
-
-    void SetTagInfo(DicomTagType tagType,
-                    ResourceType level);
-
-    DicomTagType GetTagType() const;
-
-    const ResourceType GetLevel() const;
+                       bool caseSensitive,
+                       bool mandatory);
 
     const DicomTag& GetTag() const
     {
       return tag_;
     }
 
-    ConstraintType GetConstraintType() const
+    ConstraintType GetType() const
     {
       return constraintType_;
     }
@@ -93,6 +82,11 @@
       return caseSensitive_;
     }
 
+    bool IsMandatory() const
+    {
+      return mandatory_;
+    }
+
     void AddValue(const std::string& value);
 
     const std::string& GetValue() const;
--- a/OrthancServer/ServerContext.cpp	Mon Dec 17 10:26:01 2018 +0100
+++ b/OrthancServer/ServerContext.cpp	Mon Dec 17 17:05:28 2018 +0100
@@ -775,6 +775,7 @@
 
   void ServerContext::Apply(ILookupVisitor& visitor,
                             const ::Orthanc::LookupResource& lookup,
+                            const DatabaseLookup& lookup2,
                             size_t since,
                             size_t limit)
   {
@@ -810,6 +811,34 @@
     std::vector<std::string> resources, instances;
     GetIndex().FindCandidates(resources, instances, lookup);
 
+#if 1
+    {
+      std::vector<std::string> resources2, instances2;
+
+      if (lookup.GetLevel() == ResourceType_Patient)
+      {
+        GetIndex().ApplyLookupPatients(resources2, instances2, lookup2, limit /* TODO */);
+      }
+      else
+      {
+        GetIndex().ApplyLookupResources(resources2, instances2, lookup2, lookup.GetLevel(), limit /* TODO */);
+      }
+
+      std::set<std::string> r;
+      for (size_t i = 0; i < resources2.size(); i++)
+      {
+        r.insert(resources[i]);
+      }
+
+      assert(r.size() == resources.size());
+      
+      for (size_t i = 0; i < resources.size(); i++)
+      {
+        assert(r.find(resources[i]) != r.end());
+      }
+    }
+#endif
+    
     LOG(INFO) << "Number of candidate resources after fast DB filtering on main DICOM tags: " << resources.size();
 
     assert(resources.size() == instances.size());
--- a/OrthancServer/ServerContext.h	Mon Dec 17 10:26:01 2018 +0100
+++ b/OrthancServer/ServerContext.h	Mon Dec 17 17:05:28 2018 +0100
@@ -364,6 +364,7 @@
 
     void Apply(ILookupVisitor& visitor,
                const ::Orthanc::LookupResource& lookup,
+               const DatabaseLookup& lookup2,
                size_t since,
                size_t limit);
 
--- a/OrthancServer/ServerIndex.cpp	Mon Dec 17 10:26:01 2018 +0100
+++ b/OrthancServer/ServerIndex.cpp	Mon Dec 17 17:05:28 2018 +0100
@@ -2452,4 +2452,29 @@
       LOG(ERROR) << "EXCEPTION [" << e.What() << "]";
     }
   }
+
+
+  void ServerIndex::ApplyLookupPatients(std::vector<std::string>& patientsId,
+                                        std::vector<std::string>& instancesId,
+                                        const DatabaseLookup& lookup,
+                                        size_t limit)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+    
+    db_.ApplyLookupPatients(patientsId, lookup, limit);
+    db_.FindOneChildInstance(instancesId, patientsId, ResourceType_Patient);
+  }
+
+  
+  void ServerIndex::ApplyLookupResources(std::vector<std::string>& resourcesId,
+                                         std::vector<std::string>& instancesId,
+                                         const DatabaseLookup& lookup,
+                                         ResourceType queryLevel,
+                                         size_t limit)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    db_.ApplyLookupResources(resourcesId, lookup, queryLevel, limit);
+    db_.FindOneChildInstance(instancesId, resourcesId, queryLevel);
+  }
 }
--- a/OrthancServer/ServerIndex.h	Mon Dec 17 10:26:01 2018 +0100
+++ b/OrthancServer/ServerIndex.h	Mon Dec 17 17:05:28 2018 +0100
@@ -292,5 +292,16 @@
                       ResourceType parentType);
 
     void ReconstructInstance(ParsedDicomFile& dicom);
+
+    void ApplyLookupPatients(std::vector<std::string>& patientsId,
+                             std::vector<std::string>& instancesId,
+                             const DatabaseLookup& lookup,
+                             size_t limit);
+
+    void ApplyLookupResources(std::vector<std::string>& resourcesId,
+                              std::vector<std::string>& instancesId,
+                              const DatabaseLookup& lookup,
+                              ResourceType queryLevel,
+                              size_t limit);
   };
 }
--- a/Plugins/Engine/OrthancPluginDatabase.cpp	Mon Dec 17 10:26:01 2018 +0100
+++ b/Plugins/Engine/OrthancPluginDatabase.cpp	Mon Dec 17 17:05:28 2018 +0100
@@ -1160,4 +1160,29 @@
       return currentDiskSize_ > threshold;
     }      
   }
+
+
+  void OrthancPluginDatabase::FindOneChildInstance(std::vector<std::string>& instancesId,
+                                                   const std::vector<std::string>& resourcesId,
+                                                   ResourceType level)
+  {
+    throw OrthancException(ErrorCode_NotImplemented);
+  }
+
+
+  void OrthancPluginDatabase::ApplyLookupPatients(std::vector<std::string>& patientsId,
+                                                  const DatabaseLookup& lookup,
+                                                  size_t limit)
+  {
+    throw OrthancException(ErrorCode_NotImplemented);
+  }
+  
+
+  void OrthancPluginDatabase::ApplyLookupResources(std::vector<std::string>& patientsId,
+                                                   const DatabaseLookup& lookup,
+                                                   ResourceType queryLevel,
+                                                   size_t limit)
+  {
+    throw OrthancException(ErrorCode_NotImplemented);
+  }
 }
--- a/Plugins/Engine/OrthancPluginDatabase.h	Mon Dec 17 10:26:01 2018 +0100
+++ b/Plugins/Engine/OrthancPluginDatabase.h	Mon Dec 17 17:05:28 2018 +0100
@@ -270,6 +270,19 @@
     void AnswerReceived(const _OrthancPluginDatabaseAnswer& answer);
 
     virtual bool IsDiskSizeAbove(uint64_t threshold);
+
+    virtual void FindOneChildInstance(std::vector<std::string>& instancesId,
+                                      const std::vector<std::string>& resourcesId,
+                                      ResourceType level);
+
+    virtual void ApplyLookupPatients(std::vector<std::string>& patientsId,
+                                     const DatabaseLookup& lookup,
+                                     size_t limit);
+
+    virtual void ApplyLookupResources(std::vector<std::string>& patientsId,
+                                      const DatabaseLookup& lookup,
+                                      ResourceType queryLevel,
+                                      size_t limit);
   };
 }
 
--- a/UnitTestsSources/DatabaseLookupTests.cpp	Mon Dec 17 10:26:01 2018 +0100
+++ b/UnitTestsSources/DatabaseLookupTests.cpp	Mon Dec 17 17:05:28 2018 +0100
@@ -44,27 +44,18 @@
 {
   {
     ASSERT_THROW(DicomTagConstraint tag(DICOM_TAG_PATIENT_NAME, ConstraintType_Equal, 
-                                        "HEL*LO", true), OrthancException);
+                                        "HEL*LO", true, true), OrthancException);
     ASSERT_THROW(DicomTagConstraint tag(DICOM_TAG_PATIENT_NAME, ConstraintType_Equal,
-                                        "HEL?LO", true), OrthancException);
+                                        "HEL?LO", true, true), OrthancException);
     ASSERT_THROW(DicomTagConstraint tag(DICOM_TAG_PATIENT_NAME, ConstraintType_Equal,
-                                        true), OrthancException);
+                                        true, true), OrthancException);
 
-    DicomTagConstraint tag(DICOM_TAG_PATIENT_NAME, ConstraintType_Equal, "HELLO", true);
+    DicomTagConstraint tag(DICOM_TAG_PATIENT_NAME, ConstraintType_Equal, "HELLO", true, true);
     ASSERT_TRUE(tag.IsMatch("HELLO"));
     ASSERT_FALSE(tag.IsMatch("hello"));
 
     ASSERT_TRUE(tag.IsCaseSensitive());
-    ASSERT_EQ(ConstraintType_Equal, tag.GetConstraintType());
-
-    ASSERT_FALSE(tag.HasTagInfo());
-    ASSERT_THROW(tag.GetTagType(), OrthancException);
-    ASSERT_THROW(tag.GetLevel(), OrthancException);
-
-    tag.SetTagInfo(DicomTagType_Identifier, ResourceType_Series);
-    ASSERT_TRUE(tag.HasTagInfo());
-    ASSERT_EQ(DicomTagType_Identifier, tag.GetTagType());
-    ASSERT_EQ(ResourceType_Series, tag.GetLevel());
+    ASSERT_EQ(ConstraintType_Equal, tag.GetType());
 
     DicomMap m;
     ASSERT_FALSE(tag.IsMatch(m));
@@ -77,7 +68,7 @@
   }
 
   {
-    DicomTagConstraint tag(DICOM_TAG_PATIENT_NAME, ConstraintType_Equal, "HELlo", false);
+    DicomTagConstraint tag(DICOM_TAG_PATIENT_NAME, ConstraintType_Equal, "HELlo", false, true);
     ASSERT_TRUE(tag.IsMatch("HELLO"));
     ASSERT_TRUE(tag.IsMatch("hello"));
 
@@ -85,7 +76,7 @@
   }
 
   {
-    DicomTagConstraint tag(DICOM_TAG_PATIENT_NAME, ConstraintType_Wildcard, "HE*L?O", true);
+    DicomTagConstraint tag(DICOM_TAG_PATIENT_NAME, ConstraintType_Wildcard, "HE*L?O", true, true);
     ASSERT_TRUE(tag.IsMatch("HELLO"));
     ASSERT_TRUE(tag.IsMatch("HELLLLLO"));
     ASSERT_TRUE(tag.IsMatch("HELxO"));
@@ -93,32 +84,34 @@
   }
 
   {
-    DicomTagConstraint tag(DICOM_TAG_PATIENT_NAME, ConstraintType_Wildcard, "HE*l?o", false);
+    DicomTagConstraint tag(DICOM_TAG_PATIENT_NAME, ConstraintType_Wildcard, "HE*l?o", false, true);
     ASSERT_TRUE(tag.IsMatch("HELLO"));
     ASSERT_TRUE(tag.IsMatch("HELLLLLO"));
     ASSERT_TRUE(tag.IsMatch("HELxO"));
     ASSERT_TRUE(tag.IsMatch("hello"));
 
     ASSERT_FALSE(tag.IsCaseSensitive());
-    ASSERT_EQ(ConstraintType_Wildcard, tag.GetConstraintType());
+    ASSERT_EQ(ConstraintType_Wildcard, tag.GetType());
   }
 
   {
-    DicomTagConstraint tag(DICOM_TAG_PATIENT_NAME, ConstraintType_SmallerOrEqual, "123", true);
+    DicomTagConstraint tag(DICOM_TAG_PATIENT_NAME, ConstraintType_SmallerOrEqual, "123", true, true);
     ASSERT_TRUE(tag.IsMatch("120"));
     ASSERT_TRUE(tag.IsMatch("123"));
     ASSERT_FALSE(tag.IsMatch("124"));
+    ASSERT_TRUE(tag.IsMandatory());
   }
 
   {
-    DicomTagConstraint tag(DICOM_TAG_PATIENT_NAME, ConstraintType_GreaterOrEqual, "123", true);
+    DicomTagConstraint tag(DICOM_TAG_PATIENT_NAME, ConstraintType_GreaterOrEqual, "123", true, false);
     ASSERT_FALSE(tag.IsMatch("122"));
     ASSERT_TRUE(tag.IsMatch("123"));
     ASSERT_TRUE(tag.IsMatch("124"));
+    ASSERT_FALSE(tag.IsMandatory());
   }
 
   {
-    DicomTagConstraint tag(DICOM_TAG_PATIENT_NAME, ConstraintType_List, true);
+    DicomTagConstraint tag(DICOM_TAG_PATIENT_NAME, ConstraintType_List, true, true);
     ASSERT_FALSE(tag.IsMatch("CT"));
     ASSERT_FALSE(tag.IsMatch("MR"));
 
@@ -137,7 +130,7 @@
   }
 
   {
-    DicomTagConstraint tag(DICOM_TAG_PATIENT_NAME, ConstraintType_List, false);
+    DicomTagConstraint tag(DICOM_TAG_PATIENT_NAME, ConstraintType_List, false, true);
 
     tag.AddValue("ct");
     tag.AddValue("mr");
@@ -155,20 +148,16 @@
 {
   {
     DatabaseLookup lookup;
-    lookup.AddDicomConstraint(DICOM_TAG_PATIENT_ID, "HELLO", true);
+    lookup.AddDicomConstraint(DICOM_TAG_PATIENT_ID, "HELLO", true, true);
     ASSERT_EQ(1u, lookup.GetConstraintsCount());
-    ASSERT_EQ(ConstraintType_Equal, lookup.GetConstraint(0).GetConstraintType());
+    ASSERT_EQ(ConstraintType_Equal, lookup.GetConstraint(0).GetType());
     ASSERT_EQ("HELLO", lookup.GetConstraint(0).GetValue());
     ASSERT_TRUE(lookup.GetConstraint(0).IsCaseSensitive());
-
-    ASSERT_TRUE(lookup.GetConstraint(0).HasTagInfo());
-    ASSERT_EQ(DicomTagType_Identifier, lookup.GetConstraint(0).GetTagType());
-    ASSERT_EQ(ResourceType_Patient, lookup.GetConstraint(0).GetLevel());
   }
 
   {
     DatabaseLookup lookup;
-    lookup.AddDicomConstraint(DICOM_TAG_PATIENT_ID, "HELLO", false);
+    lookup.AddDicomConstraint(DICOM_TAG_PATIENT_ID, "HELLO", false, true);
     ASSERT_EQ(1u, lookup.GetConstraintsCount());
 
     // This is *not* a PN VR => "false" above is *not* used
@@ -177,14 +166,14 @@
 
   {
     DatabaseLookup lookup;
-    lookup.AddDicomConstraint(DICOM_TAG_PATIENT_NAME, "HELLO", true);
+    lookup.AddDicomConstraint(DICOM_TAG_PATIENT_NAME, "HELLO", true, true);
     ASSERT_EQ(1u, lookup.GetConstraintsCount());
     ASSERT_TRUE(lookup.GetConstraint(0).IsCaseSensitive());
   }
 
   {
     DatabaseLookup lookup;
-    lookup.AddDicomConstraint(DICOM_TAG_PATIENT_NAME, "HELLO", false);
+    lookup.AddDicomConstraint(DICOM_TAG_PATIENT_NAME, "HELLO", false, true);
     ASSERT_EQ(1u, lookup.GetConstraintsCount());
 
     // This is a PN VR => "false" above is used
@@ -193,59 +182,54 @@
 
   {
     DatabaseLookup lookup;
-    lookup.AddDicomConstraint(DICOM_TAG_SERIES_DESCRIPTION, "2012-2016", false);
-
-    ASSERT_TRUE(lookup.GetConstraint(0).HasTagInfo());
-    ASSERT_EQ(DicomTagType_Main, lookup.GetConstraint(0).GetTagType());
-    ASSERT_EQ(ResourceType_Series, lookup.GetConstraint(0).GetLevel());
+    lookup.AddDicomConstraint(DICOM_TAG_SERIES_DESCRIPTION, "2012-2016", false, true);
 
     // This is not a data VR
-    ASSERT_EQ(ConstraintType_Equal, lookup.GetConstraint(0).GetConstraintType());
+    ASSERT_EQ(ConstraintType_Equal, lookup.GetConstraint(0).GetType());
   }
 
   {
     DatabaseLookup lookup;
-    lookup.AddDicomConstraint(DICOM_TAG_PATIENT_BIRTH_DATE, "2012-2016", false);
+    lookup.AddDicomConstraint(DICOM_TAG_PATIENT_BIRTH_DATE, "2012-2016", false, true);
 
     // This is a data VR => range is effective
     ASSERT_EQ(2u, lookup.GetConstraintsCount());
 
-    ASSERT_TRUE(lookup.GetConstraint(0).GetConstraintType() != lookup.GetConstraint(1).GetConstraintType());
+    ASSERT_TRUE(lookup.GetConstraint(0).GetType() != lookup.GetConstraint(1).GetType());
 
     for (size_t i = 0; i < 2; i++)
     {
-      ASSERT_TRUE(lookup.GetConstraint(i).GetConstraintType() == ConstraintType_SmallerOrEqual ||
-                  lookup.GetConstraint(i).GetConstraintType() == ConstraintType_GreaterOrEqual);
+      ASSERT_TRUE(lookup.GetConstraint(i).GetType() == ConstraintType_SmallerOrEqual ||
+                  lookup.GetConstraint(i).GetType() == ConstraintType_GreaterOrEqual);
     }
   }
 
   {
     DatabaseLookup lookup;
-    lookup.AddDicomConstraint(DICOM_TAG_PATIENT_BIRTH_DATE, "2012-", false);
+    lookup.AddDicomConstraint(DICOM_TAG_PATIENT_BIRTH_DATE, "2012-", false, true);
 
     ASSERT_EQ(1u, lookup.GetConstraintsCount());
-    ASSERT_EQ(ConstraintType_GreaterOrEqual, lookup.GetConstraint(0).GetConstraintType());
+    ASSERT_EQ(ConstraintType_GreaterOrEqual, lookup.GetConstraint(0).GetType());
     ASSERT_EQ("2012", lookup.GetConstraint(0).GetValue());
   }
 
   {
     DatabaseLookup lookup;
-    lookup.AddDicomConstraint(DICOM_TAG_PATIENT_BIRTH_DATE, "-2016", false);
+    lookup.AddDicomConstraint(DICOM_TAG_PATIENT_BIRTH_DATE, "-2016", false, true);
 
     ASSERT_EQ(1u, lookup.GetConstraintsCount());
     ASSERT_EQ(DICOM_TAG_PATIENT_BIRTH_DATE,  lookup.GetConstraint(0).GetTag());
-    ASSERT_EQ(ConstraintType_SmallerOrEqual, lookup.GetConstraint(0).GetConstraintType());
+    ASSERT_EQ(ConstraintType_SmallerOrEqual, lookup.GetConstraint(0).GetType());
     ASSERT_EQ("2016", lookup.GetConstraint(0).GetValue());
   }
 
   {
     DatabaseLookup lookup;
-    lookup.AddDicomConstraint(DICOM_TAG_MODALITIES_IN_STUDY, "CT\\MR", false);
+    lookup.AddDicomConstraint(DICOM_TAG_MODALITIES_IN_STUDY, "CT\\MR", false, true);
 
     ASSERT_EQ(1u, lookup.GetConstraintsCount());
     ASSERT_EQ(DICOM_TAG_MODALITY,  lookup.GetConstraint(0).GetTag());
-    ASSERT_EQ(ResourceType_Series, lookup.GetConstraint(0).GetLevel());
-    ASSERT_EQ(ConstraintType_List, lookup.GetConstraint(0).GetConstraintType());
+    ASSERT_EQ(ConstraintType_List, lookup.GetConstraint(0).GetType());
 
     const std::set<std::string>& values = lookup.GetConstraint(0).GetValues();
     ASSERT_EQ(2u, values.size());
@@ -256,12 +240,11 @@
 
   {
     DatabaseLookup lookup;
-    lookup.AddDicomConstraint(DICOM_TAG_STUDY_DESCRIPTION, "CT\\MR", false);
+    lookup.AddDicomConstraint(DICOM_TAG_STUDY_DESCRIPTION, "CT\\MR", false, true);
 
     ASSERT_EQ(1u, lookup.GetConstraintsCount());
     ASSERT_EQ(DICOM_TAG_STUDY_DESCRIPTION, lookup.GetConstraint(0).GetTag());
-    ASSERT_EQ(ResourceType_Study, lookup.GetConstraint(0).GetLevel());
-    ASSERT_EQ(ConstraintType_List, lookup.GetConstraint(0).GetConstraintType());
+    ASSERT_EQ(ConstraintType_List, lookup.GetConstraint(0).GetType());
 
     const std::set<std::string>& values = lookup.GetConstraint(0).GetValues();
     ASSERT_EQ(2u, values.size());
@@ -272,26 +255,25 @@
 
   {
     DatabaseLookup lookup;
-    lookup.AddDicomConstraint(DICOM_TAG_STUDY_DESCRIPTION, "HE*O", false);
+    lookup.AddDicomConstraint(DICOM_TAG_STUDY_DESCRIPTION, "HE*O", false, true);
 
     ASSERT_EQ(1u, lookup.GetConstraintsCount());
-    ASSERT_EQ(ConstraintType_Wildcard, lookup.GetConstraint(0).GetConstraintType());
+    ASSERT_EQ(ConstraintType_Wildcard, lookup.GetConstraint(0).GetType());
   }
 
   {
     DatabaseLookup lookup;
-    lookup.AddDicomConstraint(DICOM_TAG_STUDY_DESCRIPTION, "HE?O", false);
+    lookup.AddDicomConstraint(DICOM_TAG_STUDY_DESCRIPTION, "HE?O", false, true);
 
     ASSERT_EQ(1u, lookup.GetConstraintsCount());
-    ASSERT_EQ(ConstraintType_Wildcard, lookup.GetConstraint(0).GetConstraintType());
+    ASSERT_EQ(ConstraintType_Wildcard, lookup.GetConstraint(0).GetType());
   }
 
   {
     DatabaseLookup lookup;
-    lookup.AddDicomConstraint(DICOM_TAG_RELATED_FRAME_OF_REFERENCE_UID, "TEST", false);
-
-    ASSERT_TRUE(lookup.GetConstraint(0).HasTagInfo());
-    ASSERT_EQ(DicomTagType_Generic, lookup.GetConstraint(0).GetTagType());
-    ASSERT_EQ(ResourceType_Instance, lookup.GetConstraint(0).GetLevel());
+    lookup.AddDicomConstraint(DICOM_TAG_RELATED_FRAME_OF_REFERENCE_UID, "TEST", false, true);
+    lookup.AddDicomConstraint(DICOM_TAG_PATIENT_NAME, "TEST2", false, false);
+    ASSERT_TRUE(lookup.GetConstraint(0).IsMandatory());
+    ASSERT_FALSE(lookup.GetConstraint(1).IsMandatory());
   }
 }