changeset 5828:7030fa489669 find-refactoring tip

tools/find: QueryMetadata
author Alain Mazy <am@orthanc.team>
date Mon, 07 Oct 2024 15:19:26 +0200
parents 976872a99d39
children
files NEWS OrthancServer/CMakeLists.txt OrthancServer/Plugins/Engine/OrthancPluginDatabase.cpp OrthancServer/Plugins/Engine/OrthancPluginDatabaseV3.cpp OrthancServer/Plugins/Engine/OrthancPluginDatabaseV4.cpp OrthancServer/Sources/Database/Compatibility/DatabaseLookup.cpp OrthancServer/Sources/Database/Compatibility/DatabaseLookup.h OrthancServer/Sources/Database/Compatibility/ILookupResources.cpp OrthancServer/Sources/Database/Compatibility/ILookupResources.h OrthancServer/Sources/Database/FindRequest.cpp OrthancServer/Sources/Database/FindRequest.h OrthancServer/Sources/Database/IDatabaseWrapper.h OrthancServer/Sources/Database/MainDicomTagsRegistry.cpp OrthancServer/Sources/Database/MainDicomTagsRegistry.h OrthancServer/Sources/Database/SQLiteDatabaseWrapper.cpp OrthancServer/Sources/Database/StatelessDatabaseOperations.cpp OrthancServer/Sources/Database/StatelessDatabaseOperations.h OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp OrthancServer/Sources/ResourceFinder.cpp OrthancServer/Sources/ResourceFinder.h OrthancServer/Sources/Search/DatabaseConstraint.cpp OrthancServer/Sources/Search/DatabaseConstraint.h OrthancServer/Sources/Search/DatabaseConstraints.cpp OrthancServer/Sources/Search/DatabaseConstraints.h OrthancServer/Sources/Search/DatabaseDicomTagConstraint.cpp OrthancServer/Sources/Search/DatabaseDicomTagConstraint.h OrthancServer/Sources/Search/DatabaseDicomTagConstraints.cpp OrthancServer/Sources/Search/DatabaseDicomTagConstraints.h OrthancServer/Sources/Search/DatabaseMetadataConstraint.cpp OrthancServer/Sources/Search/DatabaseMetadataConstraint.h OrthancServer/Sources/Search/DicomTagConstraint.cpp OrthancServer/Sources/Search/DicomTagConstraint.h OrthancServer/Sources/Search/IDatabaseConstraint.h OrthancServer/Sources/Search/ISqlLookupFormatter.cpp OrthancServer/Sources/Search/ISqlLookupFormatter.h OrthancServer/UnitTestsSources/ServerIndexTests.cpp
diffstat 36 files changed, 829 insertions(+), 492 deletions(-) [+]
line wrap: on
line diff
--- a/NEWS	Mon Oct 07 10:54:40 2024 +0200
+++ b/NEWS	Mon Oct 07 15:19:26 2024 +0200
@@ -38,6 +38,7 @@
   - 'OrderBy' to order by DICOM Tag or metadata value
   - 'ParentPatient', 'ParentStudy', 'ParentSeries' to retrieve only descendants of an
     Orthanc resource.
+  - 'QueryMetadata' to filter results based on metadata values.
 
 
 Maintenance
--- a/OrthancServer/CMakeLists.txt	Mon Oct 07 10:54:40 2024 +0200
+++ b/OrthancServer/CMakeLists.txt	Mon Oct 07 15:19:26 2024 +0200
@@ -125,8 +125,9 @@
   ${CMAKE_SOURCE_DIR}/Sources/OrthancWebDav.cpp
   ${CMAKE_SOURCE_DIR}/Sources/QueryRetrieveHandler.cpp
   ${CMAKE_SOURCE_DIR}/Sources/ResourceFinder.cpp
-  ${CMAKE_SOURCE_DIR}/Sources/Search/DatabaseConstraint.cpp
-  ${CMAKE_SOURCE_DIR}/Sources/Search/DatabaseConstraints.cpp
+  ${CMAKE_SOURCE_DIR}/Sources/Search/DatabaseDicomTagConstraint.cpp
+  ${CMAKE_SOURCE_DIR}/Sources/Search/DatabaseDicomTagConstraints.cpp
+  ${CMAKE_SOURCE_DIR}/Sources/Search/DatabaseMetadataConstraint.cpp
   ${CMAKE_SOURCE_DIR}/Sources/Search/DatabaseLookup.cpp
   ${CMAKE_SOURCE_DIR}/Sources/Search/DicomTagConstraint.cpp
   ${CMAKE_SOURCE_DIR}/Sources/Search/HierarchicalMatcher.cpp
--- a/OrthancServer/Plugins/Engine/OrthancPluginDatabase.cpp	Mon Oct 07 10:54:40 2024 +0200
+++ b/OrthancServer/Plugins/Engine/OrthancPluginDatabase.cpp	Mon Oct 07 15:19:26 2024 +0200
@@ -564,7 +564,7 @@
     
     virtual void ApplyLookupResources(std::list<std::string>& resourcesId,
                                       std::list<std::string>* instancesId,
-                                      const DatabaseConstraints& lookup,
+                                      const DatabaseDicomTagConstraints& lookup,
                                       ResourceType queryLevel,
                                       const std::set<std::string>& labels,
                                       LabelsConstraint labelsConstraint,
--- a/OrthancServer/Plugins/Engine/OrthancPluginDatabaseV3.cpp	Mon Oct 07 10:54:40 2024 +0200
+++ b/OrthancServer/Plugins/Engine/OrthancPluginDatabaseV3.cpp	Mon Oct 07 15:19:26 2024 +0200
@@ -798,7 +798,7 @@
     
     virtual void ApplyLookupResources(std::list<std::string>& resourcesId,
                                       std::list<std::string>* instancesId, // Can be NULL if not needed
-                                      const DatabaseConstraints& lookup,
+                                      const DatabaseDicomTagConstraints& lookup,
                                       ResourceType queryLevel,
                                       const std::set<std::string>& labels,
                                       LabelsConstraint labelsConstraint,
--- a/OrthancServer/Plugins/Engine/OrthancPluginDatabaseV4.cpp	Mon Oct 07 10:54:40 2024 +0200
+++ b/OrthancServer/Plugins/Engine/OrthancPluginDatabaseV4.cpp	Mon Oct 07 15:19:26 2024 +0200
@@ -136,7 +136,7 @@
 
 
   static void Convert(DatabasePluginMessages::DatabaseConstraint& target,
-                      const DatabaseConstraint& source)
+                      const DatabaseDicomTagConstraint& source)
   {
     target.set_level(Convert(source.GetLevel()));
     target.set_tag_group(source.GetTag().GetGroup());
@@ -1137,7 +1137,7 @@
 
     virtual void ApplyLookupResources(std::list<std::string>& resourcesId,
                                       std::list<std::string>* instancesId, // Can be NULL if not needed
-                                      const DatabaseConstraints& lookup,
+                                      const DatabaseDicomTagConstraints& lookup,
                                       ResourceType queryLevel,
                                       const std::set<std::string>& labels,
                                       LabelsConstraint labelsConstraint,
--- a/OrthancServer/Sources/Database/Compatibility/DatabaseLookup.cpp	Mon Oct 07 10:54:40 2024 +0200
+++ b/OrthancServer/Sources/Database/Compatibility/DatabaseLookup.cpp	Mon Oct 07 15:19:26 2024 +0200
@@ -74,7 +74,7 @@
           }
         }
         
-        void Add(const DatabaseConstraint& constraint)
+        void Add(const DatabaseDicomTagConstraint& constraint)
         {
           constraints_.push_back(new DicomTagConstraint(constraint));
         }          
@@ -84,7 +84,7 @@
     
     static void ApplyIdentifierConstraint(SetOfResources& candidates,
                                           ILookupResources& compatibility,
-                                          const DatabaseConstraint& constraint,
+                                          const DatabaseDicomTagConstraint& constraint,
                                           ResourceType level)
     {
       std::list<int64_t> matches;
@@ -134,8 +134,8 @@
     
     static void ApplyIdentifierRange(SetOfResources& candidates,
                                      ILookupResources& compatibility,
-                                     const DatabaseConstraint& smaller,
-                                     const DatabaseConstraint& greater,
+                                     const DatabaseDicomTagConstraint& smaller,
+                                     const DatabaseDicomTagConstraint& greater,
                                      ResourceType level)
     {
       assert(smaller.GetConstraintType() == ConstraintType_SmallerOrEqual &&
@@ -153,10 +153,10 @@
     static void ApplyLevel(SetOfResources& candidates,
                            IDatabaseWrapper::ITransaction& transaction,
                            ILookupResources& compatibility,
-                           const DatabaseConstraints& lookup,
+                           const DatabaseDicomTagConstraints& lookup,
                            ResourceType level)
     {
-      typedef std::set<const DatabaseConstraint*>  SetOfConstraints;
+      typedef std::set<const DatabaseDicomTagConstraint*>  SetOfConstraints;
       typedef std::map<DicomTag, SetOfConstraints> Identifiers;
 
       // (1) Select which constraints apply to this level, and split
@@ -168,7 +168,7 @@
       
       for (size_t i = 0; i < lookup.GetSize(); i++)
       {
-        const DatabaseConstraint& constraint = lookup.GetConstraint(i);
+        const DatabaseDicomTagConstraint& constraint = lookup.GetConstraint(i);
 
         if (constraint.GetLevel() == level)
         {
@@ -191,8 +191,8 @@
       {
         // Check whether some range constraint over identifiers is
         // present at this level
-        const DatabaseConstraint* smaller = NULL;
-        const DatabaseConstraint* greater = NULL;
+        const DatabaseDicomTagConstraint* smaller = NULL;
+        const DatabaseDicomTagConstraint* greater = NULL;
         
         for (SetOfConstraints::const_iterator it2 = it->second.begin();
              it2 != it->second.end(); ++it2)
@@ -308,7 +308,7 @@
 
     void DatabaseLookup::ApplyLookupResources(std::list<std::string>& resourcesId,
                                               std::list<std::string>* instancesId,
-                                              const DatabaseConstraints& lookup,
+                                              const DatabaseDicomTagConstraints& lookup,
                                               ResourceType queryLevel,
                                               size_t limit)
     {
--- a/OrthancServer/Sources/Database/Compatibility/DatabaseLookup.h	Mon Oct 07 10:54:40 2024 +0200
+++ b/OrthancServer/Sources/Database/Compatibility/DatabaseLookup.h	Mon Oct 07 15:19:26 2024 +0200
@@ -46,7 +46,7 @@
 
       void ApplyLookupResources(std::list<std::string>& resourcesId,
                                 std::list<std::string>* instancesId,
-                                const DatabaseConstraints& lookup,
+                                const DatabaseDicomTagConstraints& lookup,
                                 ResourceType queryLevel,
                                 size_t limit);
     };
--- a/OrthancServer/Sources/Database/Compatibility/ILookupResources.cpp	Mon Oct 07 10:54:40 2024 +0200
+++ b/OrthancServer/Sources/Database/Compatibility/ILookupResources.cpp	Mon Oct 07 15:19:26 2024 +0200
@@ -35,7 +35,7 @@
       ILookupResources& compatibility,
       std::list<std::string>& resourcesId,
       std::list<std::string>* instancesId,
-      const DatabaseConstraints& lookup,
+      const DatabaseDicomTagConstraints& lookup,
       ResourceType queryLevel,
       size_t limit)
     {
--- a/OrthancServer/Sources/Database/Compatibility/ILookupResources.h	Mon Oct 07 10:54:40 2024 +0200
+++ b/OrthancServer/Sources/Database/Compatibility/ILookupResources.h	Mon Oct 07 15:19:26 2024 +0200
@@ -60,7 +60,7 @@
                         ILookupResources& compatibility,
                         std::list<std::string>& resourcesId,
                         std::list<std::string>* instancesId,
-                        const DatabaseConstraints& lookup,
+                        const DatabaseDicomTagConstraints& lookup,
                         ResourceType queryLevel,
                         size_t limit);
     };
--- a/OrthancServer/Sources/Database/FindRequest.cpp	Mon Oct 07 10:54:40 2024 +0200
+++ b/OrthancServer/Sources/Database/FindRequest.cpp	Mon Oct 07 15:19:26 2024 +0200
@@ -100,8 +100,13 @@
 
   FindRequest::~FindRequest()
   {
+    for (std::deque<Ordering*>::iterator it = ordering_.begin(); it != ordering_.end(); ++it)
+    {
+      assert(*it != NULL);
+      delete *it;
+    }
 
-    for (std::deque<Ordering*>::iterator it = ordering_.begin(); it != ordering_.end(); ++it)
+    for (std::deque<DatabaseMetadataConstraint*>::iterator it = metadataConstraints_.begin(); it != metadataConstraints_.end(); ++it)
     {
       assert(*it != NULL);
       delete *it;
@@ -233,6 +238,12 @@
   }
 
 
+  void FindRequest::AddMetadataConstraint(DatabaseMetadataConstraint* constraint)
+  {
+    metadataConstraints_.push_back(constraint);
+  }
+
+
   void FindRequest::SetRetrieveParentIdentifier(bool retrieve)
   {
     if (level_ == ResourceType_Patient)
--- a/OrthancServer/Sources/Database/FindRequest.h	Mon Oct 07 10:54:40 2024 +0200
+++ b/OrthancServer/Sources/Database/FindRequest.h	Mon Oct 07 15:19:26 2024 +0200
@@ -24,7 +24,8 @@
 #pragma once
 
 #include "../../../OrthancFramework/Sources/DicomFormat/DicomTag.h"
-#include "../Search/DatabaseConstraints.h"
+#include "../Search/DatabaseDicomTagConstraints.h"
+#include "../Search/DatabaseMetadataConstraint.h"
 #include "../Search/DicomTagConstraint.h"
 #include "../Search/ISqlLookupFormatter.h"
 #include "../ServerEnumerations.h"
@@ -232,16 +233,15 @@
     // filter & ordering fields
     ResourceType                         level_;                // The level of the response (the filtering on tags, labels and metadata also happens at this level)
     OrthancIdentifiers                   orthancIdentifiers_;   // The response must belong to this Orthanc resources hierarchy
-    DatabaseConstraints                  dicomTagConstraints_;  // All tags filters (note: the order is not important)
+    DatabaseDicomTagConstraints          dicomTagConstraints_;  // All tags filters (note: the order is not important)
     bool                                 hasLimits_;
     uint64_t                             limitsSince_;
     uint64_t                             limitsCount_;
     std::set<std::string>                labels_;
     LabelsConstraint                     labelsConstraint_;
 
-    // TODO-FIND
     std::deque<Ordering*>                ordering_;             // The ordering criteria (note: the order is important !)
-    std::deque<void*>   /* TODO-FIND */       metadataConstraints_;  // All metadata filters (note: the order is not important)
+    std::deque<DatabaseMetadataConstraint*>  metadataConstraints_;  // All metadata filters (note: the order is not important)
 
     bool                                 retrieveMainDicomTags_;
     bool                                 retrieveMetadata_;
@@ -284,12 +284,12 @@
       return orthancIdentifiers_;
     }
 
-    DatabaseConstraints& GetDicomTagConstraints()
+    DatabaseDicomTagConstraints& GetDicomTagConstraints()
     {
       return dicomTagConstraints_;
     }
 
-    const DatabaseConstraints& GetDicomTagConstraints() const
+    const DatabaseDicomTagConstraints& GetDicomTagConstraints() const
     {
       return dicomTagConstraints_;
     }
@@ -327,6 +327,13 @@
       return ordering_;
     }
 
+    void AddMetadataConstraint(DatabaseMetadataConstraint* constraint);
+
+    const std::deque<DatabaseMetadataConstraint*>& GetMetadataConstraint() const
+    {
+      return metadataConstraints_;
+    }
+
     void SetLabels(const std::set<std::string>& labels)
     {
       labels_ = labels;
--- a/OrthancServer/Sources/Database/IDatabaseWrapper.h	Mon Oct 07 10:54:40 2024 +0200
+++ b/OrthancServer/Sources/Database/IDatabaseWrapper.h	Mon Oct 07 15:19:26 2024 +0200
@@ -39,7 +39,7 @@
 
 namespace Orthanc
 {
-  class DatabaseConstraints;
+  class DatabaseDicomTagConstraints;
   class ResourcesContent;
 
   class IDatabaseWrapper : public boost::noncopyable
@@ -308,7 +308,7 @@
     
       virtual void ApplyLookupResources(std::list<std::string>& resourcesId,
                                         std::list<std::string>* instancesId, // Can be NULL if not needed
-                                        const DatabaseConstraints& lookup,
+                                        const DatabaseDicomTagConstraints& lookup,
                                         ResourceType queryLevel,
                                         const std::set<std::string>& labels,
                                         LabelsConstraint labelsConstraint,
--- a/OrthancServer/Sources/Database/MainDicomTagsRegistry.cpp	Mon Oct 07 10:54:40 2024 +0200
+++ b/OrthancServer/Sources/Database/MainDicomTagsRegistry.cpp	Mon Oct 07 15:19:26 2024 +0200
@@ -98,7 +98,7 @@
   }
 
 
-  bool MainDicomTagsRegistry::NormalizeLookup(DatabaseConstraints& target,
+  bool MainDicomTagsRegistry::NormalizeLookup(DatabaseDicomTagConstraints& target,
                                               const DatabaseLookup& source,
                                               ResourceType queryLevel) const
   {
--- a/OrthancServer/Sources/Database/MainDicomTagsRegistry.h	Mon Oct 07 10:54:40 2024 +0200
+++ b/OrthancServer/Sources/Database/MainDicomTagsRegistry.h	Mon Oct 07 15:19:26 2024 +0200
@@ -24,7 +24,7 @@
 #pragma once
 
 #include "../Search/DatabaseLookup.h"
-#include "../Search/DatabaseConstraints.h"
+#include "../Search/DatabaseDicomTagConstraints.h"
 
 #include <boost/noncopyable.hpp>
 
@@ -82,7 +82,7 @@
      * constraints are less strict than the original DatabaseLookup,
      * so more resources will match them.
      **/
-    bool NormalizeLookup(DatabaseConstraints& target,
+    bool NormalizeLookup(DatabaseDicomTagConstraints& target,
                          const DatabaseLookup& source,
                          ResourceType queryLevel) const;
   };
--- a/OrthancServer/Sources/Database/SQLiteDatabaseWrapper.cpp	Mon Oct 07 10:54:40 2024 +0200
+++ b/OrthancServer/Sources/Database/SQLiteDatabaseWrapper.cpp	Mon Oct 07 15:19:26 2024 +0200
@@ -426,7 +426,7 @@
 
     virtual void ApplyLookupResources(std::list<std::string>& resourcesId,
                                       std::list<std::string>* instancesId,
-                                      const DatabaseConstraints& lookup,
+                                      const DatabaseDicomTagConstraints& lookup,
                                       ResourceType queryLevel,
                                       const std::set<std::string>& labels,
                                       LabelsConstraint labelsConstraint,
--- a/OrthancServer/Sources/Database/StatelessDatabaseOperations.cpp	Mon Oct 07 10:54:40 2024 +0200
+++ b/OrthancServer/Sources/Database/StatelessDatabaseOperations.cpp	Mon Oct 07 15:19:26 2024 +0200
@@ -1593,7 +1593,7 @@
 
     DicomTagConstraint c(tag, ConstraintType_Equal, value, true, true);
 
-    DatabaseConstraints query;
+    DatabaseDicomTagConstraints query;
     bool isIdentical;  // unused
     query.AddConstraint(c.ConvertToDatabaseConstraint(isIdentical, level, DicomTagType_Identifier));
 
@@ -1602,12 +1602,12 @@
     {
     private:
       std::vector<std::string>&   result_;
-      const DatabaseConstraints&  query_;
+      const DatabaseDicomTagConstraints&  query_;
       ResourceType                level_;
       
     public:
       Operations(std::vector<std::string>& result,
-                 const DatabaseConstraints& query,
+                 const DatabaseDicomTagConstraints& query,
                  ResourceType level) :
         result_(result),
         query_(query),
@@ -1893,7 +1893,7 @@
                                                          LabelsConstraint labelsConstraint,
                                                          uint32_t limit)
   {
-    class Operations : public ReadOnlyOperationsT6<bool, const DatabaseConstraints&, ResourceType,
+    class Operations : public ReadOnlyOperationsT6<bool, const DatabaseDicomTagConstraints&, ResourceType,
                                                    const std::set<std::string>&, LabelsConstraint, size_t>
     {
     private:
@@ -1939,7 +1939,7 @@
       ServerToolbox::CheckValidLabel(*it);
     }
 
-    DatabaseConstraints normalized;
+    DatabaseDicomTagConstraints normalized;
 
     assert(mainDicomTagsRegistry_.get() != NULL);
     mainDicomTagsRegistry_->NormalizeLookup(normalized, lookup, queryLevel);
--- a/OrthancServer/Sources/Database/StatelessDatabaseOperations.h	Mon Oct 07 10:54:40 2024 +0200
+++ b/OrthancServer/Sources/Database/StatelessDatabaseOperations.h	Mon Oct 07 15:19:26 2024 +0200
@@ -221,7 +221,7 @@
 
       void ApplyLookupResources(std::list<std::string>& resourcesId,
                                 std::list<std::string>* instancesId, // Can be NULL if not needed
-                                const DatabaseConstraints& lookup,
+                                const DatabaseDicomTagConstraints& lookup,
                                 ResourceType queryLevel,
                                 const std::set<std::string>& labels,  // New in Orthanc 1.12.0
                                 LabelsConstraint labelsConstraint,    // New in Orthanc 1.12.0
--- a/OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp	Mon Oct 07 10:54:40 2024 +0200
+++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp	Mon Oct 07 15:19:26 2024 +0200
@@ -41,6 +41,7 @@
 
 #include "../OrthancConfiguration.h"
 #include "../Search/DatabaseLookup.h"
+#include "../Search/DatabaseMetadataConstraint.h"
 #include "../ServerContext.h"
 #include "../ServerToolbox.h"
 #include "../SliceOrdering.h"
@@ -3255,6 +3256,7 @@
     static const char* const KEY_PARENT_PATIENT = "ParentPatient";        // New in Orthanc 1.12.5
     static const char* const KEY_PARENT_STUDY = "ParentStudy";            // New in Orthanc 1.12.5
     static const char* const KEY_PARENT_SERIES = "ParentSeries";          // New in Orthanc 1.12.5
+    static const char* const KEY_QUERY_METADATA = "QueryMetadata";        // New in Orthanc 1.12.5
 
     if (call.IsDocumentation())
     {
@@ -3296,6 +3298,8 @@
                          "Limit the reported resources to descendants of this study (new in Orthanc 1.12.5)", true)
         .SetRequestField(KEY_PARENT_SERIES, RestApiCallDocumentation::Type_String,
                          "Limit the reported resources to descendants of this series (new in Orthanc 1.12.5)", true)
+        .SetRequestField(KEY_QUERY_METADATA, RestApiCallDocumentation::Type_JsonObject,
+                         "Associative array containing the filter on the values of the metadata (new in Orthanc 1.12.5)", true)
         .AddAnswerType(MimeType_Json, "JSON array containing either the Orthanc identifiers, or detailed information "
                        "about the reported resources (if `Expand` argument is `true`)");
       return;
@@ -3364,6 +3368,12 @@
       throw OrthancException(ErrorCode_BadRequest, 
                              "Field \"" + std::string(KEY_ORDER_BY) + "\" must be an array");
     }
+    else if (request.isMember(KEY_QUERY_METADATA) &&
+             request[KEY_QUERY_METADATA].type() != Json::objectValue)
+    {
+      throw OrthancException(ErrorCode_BadRequest, 
+                             "Field \"" + std::string(KEY_QUERY_METADATA) + "\" must be an JSON object");
+    }
     else if (request.isMember(KEY_PARENT_PATIENT) &&
              request[KEY_PARENT_PATIENT].type() != Json::stringValue)
     {
@@ -3436,30 +3446,66 @@
           caseSensitive = request[KEY_CASE_SENSITIVE].asBool();
         }
 
-        DatabaseLookup query;
-
-        Json::Value::Members members = request[KEY_QUERY].getMemberNames();
-        for (size_t i = 0; i < members.size(); i++)
-        {
-          if (request[KEY_QUERY][members[i]].type() != Json::stringValue)
+        { // DICOM Tag query
+          DatabaseLookup dicomTagLookup;
+
+          Json::Value::Members members = request[KEY_QUERY].getMemberNames();
+          for (size_t i = 0; i < members.size(); i++)
           {
-            throw OrthancException(ErrorCode_BadRequest,
-                                   "Tag \"" + members[i] + "\" must be associated with a string");
+            if (request[KEY_QUERY][members[i]].type() != Json::stringValue)
+            {
+              throw OrthancException(ErrorCode_BadRequest,
+                                    "Tag \"" + members[i] + "\" must be associated with a string");
+            }
+
+            const std::string value = request[KEY_QUERY][members[i]].asString();
+
+            if (!value.empty())
+            {
+              // An empty string corresponds to an universal constraint,
+              // so we ignore it. This mimics the behavior of class
+              // "OrthancFindRequestHandler"
+              dicomTagLookup.AddRestConstraint(FromDcmtkBridge::ParseTag(members[i]),
+                                      value, caseSensitive, true);
+            }
           }
 
-          const std::string value = request[KEY_QUERY][members[i]].asString();
-
-          if (!value.empty())
+          finder.SetDatabaseLookup(dicomTagLookup);
+        }
+
+        { // Metadata query
+          Json::Value::Members members = request[KEY_QUERY_METADATA].getMemberNames();
+          for (size_t i = 0; i < members.size(); i++)
           {
-            // An empty string corresponds to an universal constraint,
-            // so we ignore it. This mimics the behavior of class
-            // "OrthancFindRequestHandler"
-            query.AddRestConstraint(FromDcmtkBridge::ParseTag(members[i]),
-                                    value, caseSensitive, true);
+            if (request[KEY_QUERY_METADATA][members[i]].type() != Json::stringValue)
+            {
+              throw OrthancException(ErrorCode_BadRequest,
+                                    "Tag \"" + members[i] + "\" must be associated with a string");
+            }
+            MetadataType metadata = StringToMetadata(members[i]);
+
+            const std::string value = request[KEY_QUERY_METADATA][members[i]].asString();
+
+            if (!value.empty())
+            {
+              if (value.find('\\') != std::string::npos)
+              {
+                std::vector<std::string> items;
+                Toolbox::TokenizeString(items, value, '\\');
+                
+                finder.AddMetadataConstraint(new DatabaseMetadataConstraint(metadata, ConstraintType_List, items, caseSensitive));
+              }
+              else if (value.find('*') != std::string::npos || value.find('?') != std::string::npos)
+              {
+                finder.AddMetadataConstraint(new DatabaseMetadataConstraint(metadata, ConstraintType_Wildcard, value, caseSensitive));
+              }
+              else
+              {
+                finder.AddMetadataConstraint(new DatabaseMetadataConstraint(metadata, ConstraintType_Equal, value, caseSensitive));
+              }
+            }
           }
         }
-
-        finder.SetDatabaseLookup(query);
       }
 
       if (request.isMember(KEY_REQUESTED_TAGS))
--- a/OrthancServer/Sources/ResourceFinder.cpp	Mon Oct 07 10:54:40 2024 +0200
+++ b/OrthancServer/Sources/ResourceFinder.cpp	Mon Oct 07 15:19:26 2024 +0200
@@ -601,7 +601,7 @@
 
     for (size_t i = 0; i < request_.GetDicomTagConstraints().GetSize(); i++)
     {
-      const DatabaseConstraint& constraint = request_.GetDicomTagConstraints().GetConstraint(i);
+      const DatabaseDicomTagConstraint& constraint = request_.GetDicomTagConstraints().GetConstraint(i);
       if (constraint.GetLevel() == request_.GetLevel())
       {
         request_.SetRetrieveMainDicomTags(true);
--- a/OrthancServer/Sources/ResourceFinder.h	Mon Oct 07 10:54:40 2024 +0200
+++ b/OrthancServer/Sources/ResourceFinder.h	Mon Oct 07 15:19:26 2024 +0200
@@ -145,6 +145,10 @@
       request_.AddOrdering(metadataType, direction);
     }
 
+    void AddMetadataConstraint(DatabaseMetadataConstraint* constraint)
+    {
+      request_.AddMetadataConstraint(constraint);
+    }
 
     void SetLabels(const std::set<std::string>& labels)
     {
--- a/OrthancServer/Sources/Search/DatabaseConstraint.cpp	Mon Oct 07 10:54:40 2024 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,112 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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.
- * 
- * 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 "../../../OrthancFramework/Sources/OrthancException.h"
-
-#if ORTHANC_ENABLE_PLUGINS == 1
-#  include "../../Plugins/Engine/PluginsEnumerations.h"
-#endif
-
-#include <boost/lexical_cast.hpp>
-#include <cassert>
-
-
-namespace Orthanc
-{
-  DatabaseConstraint::DatabaseConstraint(ResourceType level,
-                                         const DicomTag& tag,
-                                         bool isIdentifier,
-                                         ConstraintType type,
-                                         const std::vector<std::string>& values,
-                                         bool caseSensitive,
-                                         bool mandatory) :
-    level_(level),
-    tag_(tag),
-    isIdentifier_(isIdentifier),
-    constraintType_(type),
-    values_(values),
-    caseSensitive_(caseSensitive),
-    mandatory_(mandatory)
-  {
-    if (type != ConstraintType_List &&
-        values_.size() != 1)
-    {
-      throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-  }      
-
-    
-  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];
-    }
-  }
-
-
-#if ORTHANC_ENABLE_PLUGINS == 1
-  void DatabaseConstraint::EncodeForPlugins(OrthancPluginDatabaseConstraint& constraint,
-                                            std::vector<const char*>& tmpValues) const
-  {
-    memset(&constraint, 0, sizeof(constraint));
-    
-    tmpValues.resize(values_.size());
-
-    for (size_t i = 0; i < values_.size(); i++)
-    {
-      tmpValues[i] = values_[i].c_str();
-    }
-
-    constraint.level = Plugins::Convert(level_);
-    constraint.tagGroup = tag_.GetGroup();
-    constraint.tagElement = tag_.GetElement();
-    constraint.isIdentifierTag = isIdentifier_;
-    constraint.isCaseSensitive = caseSensitive_;
-    constraint.isMandatory = mandatory_;
-    constraint.type = Plugins::Convert(constraintType_);
-    constraint.valuesCount = values_.size();
-    constraint.values = (tmpValues.empty() ? NULL : &tmpValues[0]);
-  }
-#endif    
-}
--- a/OrthancServer/Sources/Search/DatabaseConstraint.h	Mon Oct 07 10:54:40 2024 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,101 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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.
- * 
- * 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 "../../../OrthancFramework/Sources/DicomFormat/DicomMap.h"
-#include "../ServerEnumerations.h"
-
-#if ORTHANC_ENABLE_PLUGINS == 1
-#  include "../../Plugins/Include/orthanc/OrthancCDatabasePlugin.h"
-#endif
-
-namespace Orthanc
-{
-  class DatabaseConstraint : public boost::noncopyable
-  {
-  private:
-    ResourceType              level_;
-    DicomTag                  tag_;
-    bool                      isIdentifier_;
-    ConstraintType            constraintType_;
-    std::vector<std::string>  values_;
-    bool                      caseSensitive_;
-    bool                      mandatory_;
-
-  public:
-    DatabaseConstraint(ResourceType level,
-                       const DicomTag& tag,
-                       bool isIdentifier,
-                       ConstraintType type,
-                       const std::vector<std::string>& values,
-                       bool caseSensitive,
-                       bool mandatory);
-    
-    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_;
-    }
-
-    bool IsMatch(const DicomMap& dicom) const;
-
-#if ORTHANC_ENABLE_PLUGINS == 1
-    void EncodeForPlugins(OrthancPluginDatabaseConstraint& constraint,
-                          std::vector<const char*>& tmpValues) const;
-#endif    
-  };
-}
--- a/OrthancServer/Sources/Search/DatabaseConstraints.cpp	Mon Oct 07 10:54:40 2024 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,132 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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.
- * 
- * 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 "DatabaseConstraints.h"
-
-#include "../../../OrthancFramework/Sources/OrthancException.h"
-
-#include <boost/lexical_cast.hpp>
-#include <cassert>
-
-
-namespace Orthanc
-{
-  void DatabaseConstraints::Clear()
-  {
-    for (size_t i = 0; i < constraints_.size(); i++)
-    {
-      assert(constraints_[i] != NULL);
-      delete constraints_[i];
-    }
-
-    constraints_.clear();
-  }
-
-
-  void DatabaseConstraints::AddConstraint(DatabaseConstraint* constraint)
-  {
-    if (constraint == NULL)
-    {
-      throw OrthancException(ErrorCode_NullPointer);
-    }
-    else
-    {
-      constraints_.push_back(constraint);
-    }
-  }
-
-
-  const DatabaseConstraint& DatabaseConstraints::GetConstraint(size_t index) const
-  {
-    if (index >= constraints_.size())
-    {
-      throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-    else
-    {
-      assert(constraints_[index] != NULL);
-      return *constraints_[index];
-    }
-  }
-
-
-  std::string DatabaseConstraints::Format() const
-  {
-    std::string s;
-
-    for (size_t i = 0; i < constraints_.size(); i++)
-    {
-      assert(constraints_[i] != NULL);
-      const DatabaseConstraint& constraint = *constraints_[i];
-      s += "Constraint " + boost::lexical_cast<std::string>(i) + " at " + EnumerationToString(constraint.GetLevel()) +
-        ": " + constraint.GetTag().Format();
-
-      switch (constraint.GetConstraintType())
-      {
-        case ConstraintType_Equal:
-          s += " == " + constraint.GetSingleValue();
-          break;
-
-        case ConstraintType_SmallerOrEqual:
-          s += " <= " + constraint.GetSingleValue();
-          break;
-
-        case ConstraintType_GreaterOrEqual:
-          s += " >= " + constraint.GetSingleValue();
-          break;
-
-        case ConstraintType_Wildcard:
-          s += " ~~ " + constraint.GetSingleValue();
-          break;
-
-        case ConstraintType_List:
-        {
-          s += " in [ ";
-          bool first = true;
-          for (size_t j = 0; j < constraint.GetValuesCount(); j++)
-          {
-            if (first)
-            {
-              first = false;
-            }
-            else
-            {
-              s += ", ";
-            }
-            s += constraint.GetValue(j);
-          }
-          s += "]";
-          break;
-        }
-
-        default:
-          throw OrthancException(ErrorCode_InternalError);
-      }
-
-      s += "\n";
-    }
-
-    return s;
-  }
-}
--- a/OrthancServer/Sources/Search/DatabaseConstraints.h	Mon Oct 07 10:54:40 2024 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,61 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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.
- * 
- * 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 "DatabaseConstraint.h"
-
-#include <deque>
-
-namespace Orthanc
-{
-  class DatabaseConstraints : public boost::noncopyable
-  {
-  private:
-    std::deque<DatabaseConstraint*>  constraints_;
-
-  public:
-    ~DatabaseConstraints()
-    {
-      Clear();
-    }
-
-    void Clear();
-
-    void AddConstraint(DatabaseConstraint* constraint);  // Takes ownership
-
-    bool IsEmpty() const
-    {
-      return constraints_.empty();
-    }
-
-    size_t GetSize() const
-    {
-      return constraints_.size();
-    }
-
-    const DatabaseConstraint& GetConstraint(size_t index) const;
-
-    std::string Format() const;
-  };
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Sources/Search/DatabaseDicomTagConstraint.cpp	Mon Oct 07 15:19:26 2024 +0200
@@ -0,0 +1,112 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2023 Osimis S.A., Belgium
+ * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
+ * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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.
+ * 
+ * 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 "DatabaseDicomTagConstraint.h"
+
+#include "../../../OrthancFramework/Sources/OrthancException.h"
+
+#if ORTHANC_ENABLE_PLUGINS == 1
+#  include "../../Plugins/Engine/PluginsEnumerations.h"
+#endif
+
+#include <boost/lexical_cast.hpp>
+#include <cassert>
+
+
+namespace Orthanc
+{
+  DatabaseDicomTagConstraint::DatabaseDicomTagConstraint(ResourceType level,
+                                                         const DicomTag& tag,
+                                                         bool isIdentifier,
+                                                         ConstraintType type,
+                                                         const std::vector<std::string>& values,
+                                                         bool caseSensitive,
+                                                         bool mandatory) :
+    level_(level),
+    tag_(tag),
+    isIdentifier_(isIdentifier),
+    constraintType_(type),
+    values_(values),
+    caseSensitive_(caseSensitive),
+    mandatory_(mandatory)
+  {
+    if (type != ConstraintType_List &&
+        values_.size() != 1)
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }      
+
+    
+  const std::string& DatabaseDicomTagConstraint::GetValue(size_t index) const
+  {
+    if (index >= values_.size())
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+    else
+    {
+      return values_[index];
+    }
+  }
+
+
+  const std::string& DatabaseDicomTagConstraint::GetSingleValue() const
+  {
+    if (values_.size() != 1)
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      return values_[0];
+    }
+  }
+
+
+#if ORTHANC_ENABLE_PLUGINS == 1
+  void DatabaseDicomTagConstraint::EncodeForPlugins(OrthancPluginDatabaseConstraint& constraint,
+                                                    std::vector<const char*>& tmpValues) const
+  {
+    memset(&constraint, 0, sizeof(constraint));
+    
+    tmpValues.resize(values_.size());
+
+    for (size_t i = 0; i < values_.size(); i++)
+    {
+      tmpValues[i] = values_[i].c_str();
+    }
+
+    constraint.level = Plugins::Convert(level_);
+    constraint.tagGroup = tag_.GetGroup();
+    constraint.tagElement = tag_.GetElement();
+    constraint.isIdentifierTag = isIdentifier_;
+    constraint.isCaseSensitive = caseSensitive_;
+    constraint.isMandatory = mandatory_;
+    constraint.type = Plugins::Convert(constraintType_);
+    constraint.valuesCount = values_.size();
+    constraint.values = (tmpValues.empty() ? NULL : &tmpValues[0]);
+  }
+#endif    
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Sources/Search/DatabaseDicomTagConstraint.h	Mon Oct 07 15:19:26 2024 +0200
@@ -0,0 +1,101 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2023 Osimis S.A., Belgium
+ * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
+ * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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.
+ * 
+ * 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 "../../../OrthancFramework/Sources/DicomFormat/DicomMap.h"
+#include "../ServerEnumerations.h"
+#include "IDatabaseConstraint.h"
+
+#if ORTHANC_ENABLE_PLUGINS == 1
+#  include "../../Plugins/Include/orthanc/OrthancCDatabasePlugin.h"
+#endif
+
+namespace Orthanc
+{
+  class DatabaseDicomTagConstraint : public IDatabaseConstraint
+  {
+  private:
+    ResourceType              level_;
+    DicomTag                  tag_;
+    bool                      isIdentifier_;
+    ConstraintType            constraintType_;
+    std::vector<std::string>  values_;
+    bool                      caseSensitive_;
+    bool                      mandatory_;
+
+  public:
+    DatabaseDicomTagConstraint(ResourceType level,
+                               const DicomTag& tag,
+                               bool isIdentifier,
+                               ConstraintType type,
+                               const std::vector<std::string>& values,
+                               bool caseSensitive,
+                               bool mandatory);
+    
+    ResourceType GetLevel() const
+    {
+      return level_;
+    }
+
+    const DicomTag& GetTag() const
+    {
+      return tag_;
+    }
+
+    bool IsIdentifier() const
+    {
+      return isIdentifier_;
+    }
+
+    virtual ConstraintType GetConstraintType() const ORTHANC_OVERRIDE
+    {
+      return constraintType_;
+    }
+
+    virtual size_t GetValuesCount() const ORTHANC_OVERRIDE
+    {
+      return values_.size();
+    }
+
+    virtual const std::string& GetValue(size_t index) const ORTHANC_OVERRIDE;
+
+    virtual const std::string& GetSingleValue() const ORTHANC_OVERRIDE;
+
+    virtual bool IsCaseSensitive() const ORTHANC_OVERRIDE
+    {
+      return caseSensitive_;
+    }
+
+    virtual bool IsMandatory() const ORTHANC_OVERRIDE
+    {
+      return mandatory_;
+    }
+
+
+#if ORTHANC_ENABLE_PLUGINS == 1
+    void EncodeForPlugins(OrthancPluginDatabaseConstraint& constraint,
+                          std::vector<const char*>& tmpValues) const;
+#endif    
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Sources/Search/DatabaseDicomTagConstraints.cpp	Mon Oct 07 15:19:26 2024 +0200
@@ -0,0 +1,132 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2023 Osimis S.A., Belgium
+ * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
+ * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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.
+ * 
+ * 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 "DatabaseDicomTagConstraints.h"
+
+#include "../../../OrthancFramework/Sources/OrthancException.h"
+
+#include <boost/lexical_cast.hpp>
+#include <cassert>
+
+
+namespace Orthanc
+{
+  void DatabaseDicomTagConstraints::Clear()
+  {
+    for (size_t i = 0; i < constraints_.size(); i++)
+    {
+      assert(constraints_[i] != NULL);
+      delete constraints_[i];
+    }
+
+    constraints_.clear();
+  }
+
+
+  void DatabaseDicomTagConstraints::AddConstraint(DatabaseDicomTagConstraint* constraint)
+  {
+    if (constraint == NULL)
+    {
+      throw OrthancException(ErrorCode_NullPointer);
+    }
+    else
+    {
+      constraints_.push_back(constraint);
+    }
+  }
+
+
+  const DatabaseDicomTagConstraint& DatabaseDicomTagConstraints::GetConstraint(size_t index) const
+  {
+    if (index >= constraints_.size())
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+    else
+    {
+      assert(constraints_[index] != NULL);
+      return *constraints_[index];
+    }
+  }
+
+
+  std::string DatabaseDicomTagConstraints::Format() const
+  {
+    std::string s;
+
+    for (size_t i = 0; i < constraints_.size(); i++)
+    {
+      assert(constraints_[i] != NULL);
+      const DatabaseDicomTagConstraint& constraint = *constraints_[i];
+      s += "Constraint " + boost::lexical_cast<std::string>(i) + " at " + EnumerationToString(constraint.GetLevel()) +
+        ": " + constraint.GetTag().Format();
+
+      switch (constraint.GetConstraintType())
+      {
+        case ConstraintType_Equal:
+          s += " == " + constraint.GetSingleValue();
+          break;
+
+        case ConstraintType_SmallerOrEqual:
+          s += " <= " + constraint.GetSingleValue();
+          break;
+
+        case ConstraintType_GreaterOrEqual:
+          s += " >= " + constraint.GetSingleValue();
+          break;
+
+        case ConstraintType_Wildcard:
+          s += " ~~ " + constraint.GetSingleValue();
+          break;
+
+        case ConstraintType_List:
+        {
+          s += " in [ ";
+          bool first = true;
+          for (size_t j = 0; j < constraint.GetValuesCount(); j++)
+          {
+            if (first)
+            {
+              first = false;
+            }
+            else
+            {
+              s += ", ";
+            }
+            s += constraint.GetValue(j);
+          }
+          s += "]";
+          break;
+        }
+
+        default:
+          throw OrthancException(ErrorCode_InternalError);
+      }
+
+      s += "\n";
+    }
+
+    return s;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Sources/Search/DatabaseDicomTagConstraints.h	Mon Oct 07 15:19:26 2024 +0200
@@ -0,0 +1,61 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2023 Osimis S.A., Belgium
+ * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
+ * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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.
+ * 
+ * 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 "DatabaseDicomTagConstraint.h"
+
+#include <deque>
+
+namespace Orthanc
+{
+  class DatabaseDicomTagConstraints : public boost::noncopyable
+  {
+  private:
+    std::deque<DatabaseDicomTagConstraint*>  constraints_;
+
+  public:
+    ~DatabaseDicomTagConstraints()
+    {
+      Clear();
+    }
+
+    void Clear();
+
+    void AddConstraint(DatabaseDicomTagConstraint* constraint);  // Takes ownership
+
+    bool IsEmpty() const
+    {
+      return constraints_.empty();
+    }
+
+    size_t GetSize() const
+    {
+      return constraints_.size();
+    }
+
+    const DatabaseDicomTagConstraint& GetConstraint(size_t index) const;
+
+    std::string Format() const;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Sources/Search/DatabaseMetadataConstraint.cpp	Mon Oct 07 15:19:26 2024 +0200
@@ -0,0 +1,93 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2023 Osimis S.A., Belgium
+ * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
+ * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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.
+ * 
+ * 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 "DatabaseMetadataConstraint.h"
+
+#include "../../../OrthancFramework/Sources/OrthancException.h"
+
+#include <boost/lexical_cast.hpp>
+#include <cassert>
+
+
+namespace Orthanc
+{
+  DatabaseMetadataConstraint::DatabaseMetadataConstraint(MetadataType metadata,
+                                                         ConstraintType type,
+                                                         const std::vector<std::string>& values,
+                                                         bool caseSensitive) :
+    metadata_(metadata),
+    constraintType_(type),
+    values_(values),
+    caseSensitive_(caseSensitive)
+  {
+    if (type != ConstraintType_List &&
+        values_.size() != 1)
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }      
+
+
+  DatabaseMetadataConstraint::DatabaseMetadataConstraint(MetadataType metadata,
+                                                         ConstraintType type,
+                                                         const std::string& value,
+                                                         bool caseSensitive) :
+    metadata_(metadata),
+    constraintType_(type),
+    caseSensitive_(caseSensitive)
+  {
+    if (type == ConstraintType_List)
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    values_.push_back(value);
+  }      
+
+  const std::string& DatabaseMetadataConstraint::GetValue(size_t index) const
+  {
+    if (index >= values_.size())
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+    else
+    {
+      return values_[index];
+    }
+  }
+
+
+  const std::string& DatabaseMetadataConstraint::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/Sources/Search/DatabaseMetadataConstraint.h	Mon Oct 07 15:19:26 2024 +0200
@@ -0,0 +1,81 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2023 Osimis S.A., Belgium
+ * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
+ * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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.
+ * 
+ * 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 "../../../OrthancFramework/Sources/DicomFormat/DicomMap.h"
+#include "../ServerEnumerations.h"
+#include "IDatabaseConstraint.h"
+
+namespace Orthanc
+{
+  class DatabaseMetadataConstraint : public IDatabaseConstraint
+  {
+  private:
+    MetadataType              metadata_;
+    ConstraintType            constraintType_;
+    std::vector<std::string>  values_;
+    bool                      caseSensitive_;
+
+  public:
+    DatabaseMetadataConstraint(MetadataType metadata,
+                               ConstraintType type,
+                               const std::string& value,
+                               bool caseSensitive);
+
+    DatabaseMetadataConstraint(MetadataType metadata,
+                               ConstraintType type,
+                               const std::vector<std::string>& values,
+                               bool caseSensitive);
+
+    const MetadataType& GetMetadata() const
+    {
+      return metadata_;
+    }
+
+    virtual ConstraintType GetConstraintType() const ORTHANC_OVERRIDE
+    {
+      return constraintType_;
+    }
+
+    virtual size_t GetValuesCount() const ORTHANC_OVERRIDE
+    {
+      return values_.size();
+    }
+
+    virtual const std::string& GetValue(size_t index) const ORTHANC_OVERRIDE;
+
+    virtual const std::string& GetSingleValue() const ORTHANC_OVERRIDE;
+
+    virtual bool IsCaseSensitive() const ORTHANC_OVERRIDE
+    {
+      return caseSensitive_;
+    }
+
+    virtual bool IsMandatory() const ORTHANC_OVERRIDE
+    {
+      return true;
+    }
+
+  };
+}
--- a/OrthancServer/Sources/Search/DicomTagConstraint.cpp	Mon Oct 07 10:54:40 2024 +0200
+++ b/OrthancServer/Sources/Search/DicomTagConstraint.cpp	Mon Oct 07 15:19:26 2024 +0200
@@ -30,7 +30,7 @@
 
 #include "../../../OrthancFramework/Sources/OrthancException.h"
 #include "../../../OrthancFramework/Sources/Toolbox.h"
-#include "DatabaseConstraint.h"
+#include "DatabaseDicomTagConstraint.h"
 
 #include <boost/regex.hpp>
 
@@ -154,7 +154,7 @@
   }
     
 
-  DicomTagConstraint::DicomTagConstraint(const DatabaseConstraint& constraint) :
+  DicomTagConstraint::DicomTagConstraint(const DatabaseDicomTagConstraint& constraint) :
     tag_(constraint.GetTag()),
     constraintType_(constraint.GetConstraintType()),
     caseSensitive_(constraint.IsCaseSensitive()),
@@ -369,9 +369,9 @@
   }
 
 
-  DatabaseConstraint* DicomTagConstraint::ConvertToDatabaseConstraint(bool& isIdentical,
-                                                                      ResourceType level,
-                                                                      DicomTagType tagType) const
+  DatabaseDicomTagConstraint* DicomTagConstraint::ConvertToDatabaseConstraint(bool& isIdentical,
+                                                                              ResourceType level,
+                                                                              DicomTagType tagType) const
   {
     bool isIdentifier, caseSensitive;
     
@@ -415,7 +415,7 @@
       }
     }
 
-    return new DatabaseConstraint(level, tag_, isIdentifier, constraintType_,
-                                  values, caseSensitive, mandatory_);
+    return new DatabaseDicomTagConstraint(level, tag_, isIdentifier, constraintType_,
+                                          values, caseSensitive, mandatory_);
   }  
 }
--- a/OrthancServer/Sources/Search/DicomTagConstraint.h	Mon Oct 07 10:54:40 2024 +0200
+++ b/OrthancServer/Sources/Search/DicomTagConstraint.h	Mon Oct 07 15:19:26 2024 +0200
@@ -25,7 +25,7 @@
 
 #include "../ServerEnumerations.h"
 #include "../../../OrthancFramework/Sources/DicomFormat/DicomMap.h"
-#include "DatabaseConstraint.h"
+#include "DatabaseDicomTagConstraint.h"
 
 #include <boost/shared_ptr.hpp>
 
@@ -62,7 +62,7 @@
 
     explicit DicomTagConstraint(const DicomTagConstraint& other);
     
-    explicit DicomTagConstraint(const DatabaseConstraint& constraint);
+    explicit DicomTagConstraint(const DatabaseDicomTagConstraint& constraint);
 
     const DicomTag& GetTag() const
     {
@@ -109,8 +109,8 @@
 
     std::string Format() const;
 
-    DatabaseConstraint* ConvertToDatabaseConstraint(bool& isIdentical /* out */,
-                                                    ResourceType level,
-                                                    DicomTagType tagType) const;
+    DatabaseDicomTagConstraint* ConvertToDatabaseConstraint(bool& isIdentical /* out */,
+                                                            ResourceType level,
+                                                            DicomTagType tagType) const;
   };
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Sources/Search/IDatabaseConstraint.h	Mon Oct 07 15:19:26 2024 +0200
@@ -0,0 +1,50 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2023 Osimis S.A., Belgium
+ * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
+ * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, 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.
+ * 
+ * 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 "../ServerEnumerations.h"
+#include <boost/noncopyable.hpp>
+
+namespace Orthanc
+{
+  class IDatabaseConstraint : public boost::noncopyable
+  {
+  public:
+    virtual ~IDatabaseConstraint()
+    {
+    }
+    
+    virtual ConstraintType GetConstraintType() const = 0;
+
+    virtual size_t GetValuesCount() const = 0;
+
+    virtual const std::string& GetValue(size_t index) const = 0;
+
+    virtual const std::string& GetSingleValue() const = 0;
+
+    virtual bool IsCaseSensitive() const  = 0;
+
+    virtual bool IsMandatory() const  = 0;
+  };
+}
--- a/OrthancServer/Sources/Search/ISqlLookupFormatter.cpp	Mon Oct 07 10:54:40 2024 +0200
+++ b/OrthancServer/Sources/Search/ISqlLookupFormatter.cpp	Mon Oct 07 15:19:26 2024 +0200
@@ -27,7 +27,7 @@
 #include "../../../OrthancFramework/Sources/OrthancException.h"
 #include "../../../OrthancFramework/Sources/Toolbox.h"
 #include "../Database/FindRequest.h"
-#include "DatabaseConstraint.h"
+#include "DatabaseDicomTagConstraint.h"
 #include "../Database/MainDicomTagsRegistry.h"
 
 #include <cassert>
@@ -82,7 +82,7 @@
 
   static bool FormatComparison(std::string& target,
                                ISqlLookupFormatter& formatter,
-                               const DatabaseConstraint& constraint,
+                               const IDatabaseConstraint& constraint,
                                size_t index,
                                bool escapeBrackets)
   {
@@ -254,7 +254,7 @@
 
 
   static void FormatJoin(std::string& target,
-                         const DatabaseConstraint& constraint,
+                         const DatabaseDicomTagConstraint& constraint,
                          size_t index)
   {
     std::string tag = "t" + boost::lexical_cast<std::string>(index);
@@ -284,6 +284,29 @@
                boost::lexical_cast<std::string>(constraint.GetTag().GetElement()));
   }
 
+  static void FormatJoin(std::string& target,
+                         const DatabaseMetadataConstraint& constraint,
+                         ResourceType level,
+                         size_t index)
+  {
+    std::string tag = "t" + boost::lexical_cast<std::string>(index);
+
+    if (constraint.IsMandatory())
+    {
+      target = " INNER JOIN ";
+    }
+    else
+    {
+      target = " LEFT JOIN ";
+    }
+
+    target += "Metadata ";
+
+    target += tag + " ON " + tag + ".id = " + FormatLevel(level) +
+               ".internalId AND " + tag + ".type = " +
+               boost::lexical_cast<std::string>(constraint.GetMetadata());
+  }
+
 
   static void FormatJoinForOrdering(std::string& target,
                                     const DicomTag& tag,
@@ -388,7 +411,7 @@
 
   static bool FormatComparison2(std::string& target,
                                 ISqlLookupFormatter& formatter,
-                                const DatabaseConstraint& constraint,
+                                const DatabaseDicomTagConstraint& constraint,
                                 bool escapeBrackets)
   {
     std::string comparison;
@@ -558,7 +581,7 @@
   void ISqlLookupFormatter::GetLookupLevels(ResourceType& lowerLevel,
                                             ResourceType& upperLevel,
                                             const ResourceType& queryLevel,
-                                            const DatabaseConstraints& lookup)
+                                            const DatabaseDicomTagConstraints& lookup)
   {
     assert(ResourceType_Patient < ResourceType_Study &&
            ResourceType_Study < ResourceType_Series &&
@@ -586,12 +609,13 @@
 
   void ISqlLookupFormatter::Apply(std::string& sql,
                                   ISqlLookupFormatter& formatter,
-                                  const DatabaseConstraints& lookup,
+                                  const DatabaseDicomTagConstraints& lookup,
                                   ResourceType queryLevel,
                                   const std::set<std::string>& labels,
                                   LabelsConstraint labelsConstraint,
                                   size_t limit)
   {
+    // get the limit levels of the DICOM Tags lookup
     ResourceType lowerLevel, upperLevel;
     GetLookupLevels(lowerLevel, upperLevel, queryLevel, lookup);
 
@@ -606,7 +630,7 @@
     
     for (size_t i = 0; i < lookup.GetSize(); i++)
     {
-      const DatabaseConstraint& constraint = lookup.GetConstraint(i);
+      const DatabaseDicomTagConstraint& constraint = lookup.GetConstraint(i);
 
       std::string comparison;
       
@@ -798,10 +822,10 @@
 
     size_t count = 0;
     
-    const DatabaseConstraints& dicomTagsConstraints = request.GetDicomTagConstraints();
+    const DatabaseDicomTagConstraints& dicomTagsConstraints = request.GetDicomTagConstraints();
     for (size_t i = 0; i < dicomTagsConstraints.GetSize(); i++)
     {
-      const DatabaseConstraint& constraint = dicomTagsConstraints.GetConstraint(i);
+      const DatabaseDicomTagConstraint& constraint = dicomTagsConstraints.GetConstraint(i);
 
       std::string comparison;
       
@@ -820,6 +844,25 @@
       }
     }
 
+    for (std::deque<DatabaseMetadataConstraint*>::const_iterator it = request.GetMetadataConstraint().begin(); it != request.GetMetadataConstraint().end(); ++it)
+    {
+      std::string comparison;
+      
+      if (FormatComparison(comparison, formatter, *(*it), count, escapeBrackets))
+      {
+        std::string join;
+        FormatJoin(join, *(*it), request.GetLevel(), count);
+        joins += join;
+
+        if (!comparison.empty())
+        {
+          comparisons += " AND " + comparison;
+        }
+        
+        count ++;
+      }
+    }
+
     for (int level = queryLevel - 1; level >= upperLevel; level--)
     {
       sql += (" INNER JOIN Resources " +
@@ -891,7 +934,7 @@
 
   void ISqlLookupFormatter::ApplySingleLevel(std::string& sql,
                                              ISqlLookupFormatter& formatter,
-                                             const DatabaseConstraints& lookup,
+                                             const DatabaseDicomTagConstraints& lookup,
                                              ResourceType queryLevel,
                                              const std::set<std::string>& labels,
                                              LabelsConstraint labelsConstraint,
@@ -910,7 +953,7 @@
 
     for (size_t i = 0; i < lookup.GetSize(); i++)
     {
-      const DatabaseConstraint& constraint = lookup.GetConstraint(i);
+      const DatabaseDicomTagConstraint& constraint = lookup.GetConstraint(i);
 
       std::string comparison;
       
--- a/OrthancServer/Sources/Search/ISqlLookupFormatter.h	Mon Oct 07 10:54:40 2024 +0200
+++ b/OrthancServer/Sources/Search/ISqlLookupFormatter.h	Mon Oct 07 15:19:26 2024 +0200
@@ -30,7 +30,7 @@
 
 namespace Orthanc
 {
-  class DatabaseConstraints;
+  class DatabaseDicomTagConstraints;
   class FindRequest;
 
   enum LabelsConstraint
@@ -65,11 +65,11 @@
     static void GetLookupLevels(ResourceType& lowerLevel,
                                 ResourceType& upperLevel,
                                 const ResourceType& queryLevel,
-                                const DatabaseConstraints& lookup);
+                                const DatabaseDicomTagConstraints& lookup);
 
     static void Apply(std::string& sql,
                       ISqlLookupFormatter& formatter,
-                      const DatabaseConstraints& lookup,
+                      const DatabaseDicomTagConstraints& lookup,
                       ResourceType queryLevel,
                       const std::set<std::string>& labels,  // New in Orthanc 1.12.0
                       LabelsConstraint labelsConstraint,    // New in Orthanc 1.12.0
@@ -77,7 +77,7 @@
 
     static void ApplySingleLevel(std::string& sql,
                                  ISqlLookupFormatter& formatter,
-                                 const DatabaseConstraints& lookup,
+                                 const DatabaseDicomTagConstraints& lookup,
                                  ResourceType queryLevel,
                                  const std::set<std::string>& labels,  // New in Orthanc 1.12.0
                                  LabelsConstraint labelsConstraint,    // New in Orthanc 1.12.0
--- a/OrthancServer/UnitTestsSources/ServerIndexTests.cpp	Mon Oct 07 10:54:40 2024 +0200
+++ b/OrthancServer/UnitTestsSources/ServerIndexTests.cpp	Mon Oct 07 15:19:26 2024 +0200
@@ -166,7 +166,7 @@
       
       DicomTagConstraint c(tag, type, value, true, true);
       
-      DatabaseConstraints lookup;
+      DatabaseDicomTagConstraints lookup;
       bool isEquivalent;  // unused
       lookup.AddConstraint(c.ConvertToDatabaseConstraint(isEquivalent, level, DicomTagType_Identifier));
 
@@ -187,7 +187,7 @@
       DicomTagConstraint c1(tag, type1, value1, true, true);
       DicomTagConstraint c2(tag, type2, value2, true, true);
 
-      DatabaseConstraints lookup;
+      DatabaseDicomTagConstraints lookup;
       bool isEquivalent;  // unused
       lookup.AddConstraint(c1.ConvertToDatabaseConstraint(isEquivalent, level, DicomTagType_Identifier));
       lookup.AddConstraint(c2.ConvertToDatabaseConstraint(isEquivalent, level, DicomTagType_Identifier));