changeset 398:8dedfd982b83 db-protobuf

implemented lookup for labels in postgresql
author Sebastien Jodogne <s.jodogne@gmail.com>
date Thu, 06 Apr 2023 19:09:51 +0200
parents c4f0f8087564
children 19bd3ee1f0b3
files Framework/Plugins/IndexBackend.cpp Resources/Orthanc/Databases/ISqlLookupFormatter.cpp Resources/Orthanc/Databases/ISqlLookupFormatter.h Resources/SyncOrthancFolder.py
diffstat 4 files changed, 82 insertions(+), 14 deletions(-) [+]
line wrap: on
line diff
--- a/Framework/Plugins/IndexBackend.cpp	Thu Apr 06 19:07:19 2023 +0200
+++ b/Framework/Plugins/IndexBackend.cpp	Thu Apr 06 19:09:51 2023 +0200
@@ -2070,17 +2070,12 @@
                                      uint32_t limit,
                                      bool requestSomeInstance)
   {
-    if (!withLabels.empty() ||
-        !withoutLabels.empty())
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
-    }
-
     LookupFormatter formatter(manager.GetDialect());
 
     std::string sql;
-    Orthanc::ISqlLookupFormatter::Apply(sql, formatter, lookup,
-                                        Orthanc::Plugins::Convert(queryLevel), limit);
+    Orthanc::ISqlLookupFormatter::Apply(
+      sql, formatter, lookup, Orthanc::Plugins::Convert(queryLevel),
+      withLabels, withoutLabels, limit);
 
     if (requestSomeInstance)
     {
--- a/Resources/Orthanc/Databases/ISqlLookupFormatter.cpp	Thu Apr 06 19:07:19 2023 +0200
+++ b/Resources/Orthanc/Databases/ISqlLookupFormatter.cpp	Thu Apr 06 19:09:51 2023 +0200
@@ -39,6 +39,7 @@
 #include "DatabaseConstraint.h"
 
 #include <boost/lexical_cast.hpp>
+#include <list>
 
 
 namespace Orthanc
@@ -268,12 +269,46 @@
                " AND " + tag + ".tagElement = " +
                boost::lexical_cast<std::string>(constraint.GetTag().GetElement()));
   }
+
+
+  static std::string Join(const std::list<std::string>& values,
+                          const std::string& prefix,
+                          const std::string& separator)
+  {
+    if (values.empty())
+    {
+      return "";
+    }
+    else
+    {
+      std::string s = prefix;
+
+      bool first = true;
+      for (std::list<std::string>::const_iterator it = values.begin(); it != values.end(); ++it)
+      {
+        if (first)
+        {
+          first = false;
+        }
+        else
+        {
+          s += separator;
+        }
+
+        s += *it;
+      }
+
+      return s;
+    }
+  }
   
 
   void ISqlLookupFormatter::Apply(std::string& sql,
                                   ISqlLookupFormatter& formatter,
                                   const std::vector<DatabaseConstraint>& lookup,
                                   ResourceType queryLevel,
+                                  const std::set<std::string>& withLabels,
+                                  const std::set<std::string>& withoutLabels,
                                   size_t limit)
   {
     assert(ResourceType_Patient < ResourceType_Study &&
@@ -346,9 +381,44 @@
               FormatLevel(static_cast<ResourceType>(level - 1)) + ".internalId=" +
               FormatLevel(static_cast<ResourceType>(level)) + ".parentId");
     }
-      
-    sql += (joins + " WHERE " + FormatLevel(queryLevel) + ".resourceType = " +
-            formatter.FormatResourceType(queryLevel) + comparisons);
+
+    std::list<std::string> where;
+
+    if (!withLabels.empty())
+    {
+      std::list<std::string> labels;
+      for (std::set<std::string>::const_iterator it = withLabels.begin(); it != withLabels.end(); ++it)
+      {
+        labels.push_back(formatter.GenerateParameter(*it));
+      }
+
+      where.push_back(boost::lexical_cast<std::string>(withLabels.size()) +
+                      " = (SELECT COUNT(1) FROM Labels WHERE internalId = " + FormatLevel(queryLevel) +
+                      ".internalId AND label IN (" + Join(labels, "", ", ") + "))");
+    }
+    
+    if (!withoutLabels.empty())
+    {
+      /**
+       * "In SQL Server, NOT EXISTS and NOT IN predicates are the best
+       * way to search for missing values, as long as both columns in
+       * question are NOT NULL."
+       * https://explainextended.com/2009/09/15/not-in-vs-not-exists-vs-left-join-is-null-sql-server/
+       **/
+      std::list<std::string> labels;
+      for (std::set<std::string>::const_iterator it = withoutLabels.begin(); it != withoutLabels.end(); ++it)
+      {
+        labels.push_back(formatter.GenerateParameter(*it));
+      }
+
+      where.push_back("NOT EXISTS (SELECT 1 FROM Labels WHERE internalId = " + FormatLevel(queryLevel) +
+                      ".internalId AND label IN (" + Join(labels, "", ", ") + "))");
+    }
+    
+    where.push_back(FormatLevel(queryLevel) + ".resourceType = " +
+                    formatter.FormatResourceType(queryLevel) + comparisons);
+
+    sql += joins + Join(where, " WHERE ", " AND ");
 
     if (limit != 0)
     {
--- a/Resources/Orthanc/Databases/ISqlLookupFormatter.h	Thu Apr 06 19:07:19 2023 +0200
+++ b/Resources/Orthanc/Databases/ISqlLookupFormatter.h	Thu Apr 06 19:09:51 2023 +0200
@@ -60,6 +60,8 @@
                       ISqlLookupFormatter& formatter,
                       const std::vector<DatabaseConstraint>& lookup,
                       ResourceType queryLevel,
+                      const std::set<std::string>& withLabels,     // New in Orthanc 1.12.0
+                      const std::set<std::string>& withoutLabels,  // New in Orthanc 1.12.0
                       size_t limit);
   };
 }
--- a/Resources/SyncOrthancFolder.py	Thu Apr 06 19:07:19 2023 +0200
+++ b/Resources/SyncOrthancFolder.py	Thu Apr 06 19:09:51 2023 +0200
@@ -39,9 +39,10 @@
     ('default', 'OrthancServer/Plugins/Samples/Common/VersionScriptPlugins.map', 'Plugins'),
     ('default', 'OrthancServer/Sources/Search/DatabaseConstraint.cpp', 'Databases'),
     ('default', 'OrthancServer/Sources/Search/DatabaseConstraint.h', 'Databases'),
-    
-    ('default', 'OrthancServer/Sources/Search/ISqlLookupFormatter.cpp', 'Databases'),
-    ('default', 'OrthancServer/Sources/Search/ISqlLookupFormatter.h', 'Databases'),
+
+    # TODO - Replace "db-protobuf" by "default" once Orthanc 1.12.0 is released
+    ('db-protobuf', 'OrthancServer/Sources/Search/ISqlLookupFormatter.cpp', 'Databases'),
+    ('db-protobuf', 'OrthancServer/Sources/Search/ISqlLookupFormatter.h', 'Databases'),
 ]
 
 SDK = [