changeset 407:ca6dc9bb8b79

integration db-protobuf->mainline
author Sebastien Jodogne <s.jodogne@gmail.com>
date Sat, 15 Apr 2023 13:58:16 +0200
parents 1938ba8fba35 (current diff) de6de66d70b2 (diff)
children e25f4500b531
files
diffstat 28 files changed, 499 insertions(+), 68 deletions(-) [+]
line wrap: on
line diff
--- a/Framework/Plugins/DatabaseBackendAdapterV2.cpp	Sat Apr 15 13:49:53 2023 +0200
+++ b/Framework/Plugins/DatabaseBackendAdapterV2.cpp	Sat Apr 15 13:58:16 2023 +0200
@@ -90,7 +90,8 @@
 
       if (manager_.get() == NULL)
       {
-        manager_.reset(IndexBackend::CreateSingleDatabaseManager(*backend_));
+        std::list<IdentifierTag> identifierTags;
+        manager_.reset(IndexBackend::CreateSingleDatabaseManager(*backend_, false, identifierTags));
       }
       else
       {
@@ -1419,7 +1420,10 @@
         lookup.push_back(Orthanc::DatabaseConstraint(constraints[i]));
       }
         
-      adapter->GetBackend().LookupResources(*output, accessor.GetManager(), lookup, queryLevel, limit, (requestSomeInstance != 0));
+      std::set<std::string> noLabel;
+      adapter->GetBackend().LookupResources(*output, accessor.GetManager(), lookup, queryLevel, noLabel,
+                                            Orthanc::LabelsConstraint_All, limit, (requestSomeInstance != 0));
+      
       return OrthancPluginErrorCode_Success;
     }
     ORTHANC_PLUGINS_DATABASE_CATCH;
--- a/Framework/Plugins/DatabaseBackendAdapterV3.cpp	Sat Apr 15 13:49:53 2023 +0200
+++ b/Framework/Plugins/DatabaseBackendAdapterV3.cpp	Sat Apr 15 13:58:16 2023 +0200
@@ -800,7 +800,8 @@
 
     try
     {
-      pool->OpenConnections();
+      std::list<IdentifierTag> identifierTags;
+      pool->OpenConnections(false, identifierTags);
       return OrthancPluginErrorCode_Success;
     }
     ORTHANC_PLUGINS_DATABASE_CATCH(pool->GetContext());
@@ -1646,7 +1647,9 @@
         lookup.push_back(Orthanc::DatabaseConstraint(constraints[i]));
       }
         
-      t->GetBackend().LookupResources(t->GetOutput(), t->GetManager(), lookup, queryLevel, limit, (requestSomeInstanceId != 0));
+      std::set<std::string> noLabel;
+      t->GetBackend().LookupResources(t->GetOutput(), t->GetManager(), lookup, queryLevel, noLabel,
+                                      Orthanc::LabelsConstraint_All, limit, (requestSomeInstanceId != 0));
       return OrthancPluginErrorCode_Success;
     }
     ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext());
--- a/Framework/Plugins/DatabaseBackendAdapterV4.cpp	Sat Apr 15 13:49:53 2023 +0200
+++ b/Framework/Plugins/DatabaseBackendAdapterV4.cpp	Sat Apr 15 13:58:16 2023 +0200
@@ -90,6 +90,28 @@
   }
 
 
+  static Orthanc::ResourceType Convert2(Orthanc::DatabasePluginMessages::ResourceType resourceType)
+  {
+    switch (resourceType)
+    {
+      case Orthanc::DatabasePluginMessages::RESOURCE_PATIENT:
+        return Orthanc::ResourceType_Patient;
+
+      case Orthanc::DatabasePluginMessages::RESOURCE_STUDY:
+        return Orthanc::ResourceType_Study;
+
+      case Orthanc::DatabasePluginMessages::RESOURCE_SERIES:
+        return Orthanc::ResourceType_Series;
+
+      case Orthanc::DatabasePluginMessages::RESOURCE_INSTANCE:
+        return Orthanc::ResourceType_Instance;
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
   class Output : public IDatabaseBackendOutput
   {
   private:
@@ -408,12 +430,30 @@
         response.mutable_get_system_information()->set_database_version(accessor.GetBackend().GetDatabaseVersion(accessor.GetManager()));
         response.mutable_get_system_information()->set_supports_flush_to_disk(false);
         response.mutable_get_system_information()->set_supports_revisions(accessor.GetBackend().HasRevisionsSupport());
+        response.mutable_get_system_information()->set_supports_labels(accessor.GetBackend().HasLabelsSupport());
         break;
       }
 
       case Orthanc::DatabasePluginMessages::OPERATION_OPEN:
       {
-        pool.OpenConnections();
+        std::list<IdentifierTag> identifierTags;
+
+        if (request.open().identifier_tags().empty())
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError,
+                                          "No identifier tag was provided by the Orthanc core");
+        }
+
+        for (int i = 0; i < request.open().identifier_tags().size(); i++)
+        {
+          const Orthanc::DatabasePluginMessages::Open_Request_IdentifierTag& tag = request.open().identifier_tags(i);
+          identifierTags.push_back(IdentifierTag(Convert2(tag.level()),
+                                                 Orthanc::DicomTag(tag.group(), tag.element()),
+                                                 tag.name()));
+        }
+          
+        pool.OpenConnections(true, identifierTags);
+        
         break;
       }
 
@@ -567,9 +607,35 @@
 
     assert(values.size() == countValues);
 
+    std::set<std::string> labels;
+
+    for (int i = 0; i < request.labels().size(); i++)
+    {
+      labels.insert(request.labels(i));
+    }
+
+    Orthanc::LabelsConstraint labelsConstraint;
+    switch (request.labels_constraint())
+    {
+      case Orthanc::DatabasePluginMessages::LABELS_CONSTRAINT_ALL:
+        labelsConstraint = Orthanc::LabelsConstraint_All;
+        break;
+            
+      case Orthanc::DatabasePluginMessages::LABELS_CONSTRAINT_ANY:
+        labelsConstraint = Orthanc::LabelsConstraint_Any;
+        break;
+            
+      case Orthanc::DatabasePluginMessages::LABELS_CONSTRAINT_NONE:
+        labelsConstraint = Orthanc::LabelsConstraint_None;
+        break;
+            
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
     Output output(response);
     backend.LookupResources(output, manager, lookup, Convert(request.query_level()),
-                            request.limit(), request.retrieve_instances_ids());
+                            labels, labelsConstraint, request.limit(), request.retrieve_instances_ids());
   }
 
   
@@ -1154,6 +1220,40 @@
         break;
       }
       
+      case Orthanc::DatabasePluginMessages::OPERATION_ADD_LABEL:
+      {
+        backend.AddLabel(manager, request.add_label().id(), request.add_label().label());
+        break;
+      }
+      
+      case Orthanc::DatabasePluginMessages::OPERATION_REMOVE_LABEL:
+      {
+        backend.RemoveLabel(manager, request.remove_label().id(), request.remove_label().label());
+        break;
+      }
+      
+      case Orthanc::DatabasePluginMessages::OPERATION_LIST_LABELS:
+      {
+        std::list<std::string>  labels;
+
+        if (request.list_labels().single_resource())
+        {
+          backend.ListLabels(labels, manager, request.list_labels().id());
+        }
+        else
+        {
+          backend.ListAllLabels(labels, manager);
+        }
+
+        response.mutable_list_available_attachments()->mutable_attachments()->Reserve(labels.size());
+        for (std::list<std::string>::const_iterator it = labels.begin(); it != labels.end(); ++it)
+        {
+          response.mutable_list_labels()->add_labels(*it);
+        }
+        
+        break;
+      }
+      
       default:
         LOG(ERROR) << "Not implemented transaction operation from protobuf: " << request.operation();
         throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
--- a/Framework/Plugins/IDatabaseBackend.h	Sat Apr 15 13:49:53 2023 +0200
+++ b/Framework/Plugins/IDatabaseBackend.h	Sat Apr 15 13:58:16 2023 +0200
@@ -23,9 +23,11 @@
 
 #pragma once
 
-#include "IDatabaseBackendOutput.h"
+#include "../../Resources/Orthanc/Databases/ISqlLookupFormatter.h"
+#include "../Common/DatabaseManager.h"
 #include "../Common/DatabasesEnumerations.h"
-#include "../Common/DatabaseManager.h"
+#include "IDatabaseBackendOutput.h"
+#include "IdentifierTag.h"
 
 #include <list>
 
@@ -42,8 +44,13 @@
 
     virtual IDatabaseFactory* CreateDatabaseFactory() = 0;
 
-    // This function is invoked once, even if multiple connections are open
-    virtual void ConfigureDatabase(DatabaseManager& database) = 0;
+    /**
+     * This function is invoked once, even if multiple connections are
+     * open. It is notably used to update the schema of the database.
+     **/
+    virtual void ConfigureDatabase(DatabaseManager& database,
+                                   bool hasIdentifierTags,
+                                   const std::list<IdentifierTag>& identifierTags) = 0;
 
     virtual void SetOutputFactory(IDatabaseBackendOutput::IFactory* factory) = 0;
                         
@@ -271,6 +278,8 @@
                                  DatabaseManager& manager,
                                  const std::vector<Orthanc::DatabaseConstraint>& lookup,
                                  OrthancPluginResourceType queryLevel,
+                                 const std::set<std::string>& labels,         // New in Orthanc 1.12.0
+                                 Orthanc::LabelsConstraint labelsConstraint,  // New in Orthanc 1.12.0
                                  uint32_t limit,
                                  bool requestSomeInstance) = 0;
 #endif
@@ -307,23 +316,37 @@
     virtual void TagMostRecentPatient(DatabaseManager& manager,
                                       int64_t patientId) = 0;
 
-#if defined(ORTHANC_PLUGINS_VERSION_IS_ABOVE)      // Macro introduced in 1.3.1
-#  if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 4)
     // NB: "parentPublicId" must be cleared if the resource has no parent
     virtual bool LookupResourceAndParent(int64_t& id,
                                          OrthancPluginResourceType& type,
                                          std::string& parentPublicId,
                                          DatabaseManager& manager,
                                          const char* publicId) = 0;
-#  endif
-#endif
 
-#if defined(ORTHANC_PLUGINS_VERSION_IS_ABOVE)      // Macro introduced in 1.3.1
-#  if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 4)
     virtual void GetAllMetadata(std::map<int32_t, std::string>& result,
                                 DatabaseManager& manager,
                                 int64_t id) = 0;
-#  endif
-#endif
+
+    // New in Orthanc 1.12.0
+    virtual bool HasLabelsSupport() const = 0;
+
+    // New in Orthanc 1.12.0
+    virtual void AddLabel(DatabaseManager& manager,
+                          int64_t resource,
+                          const std::string& label) = 0;
+
+    // New in Orthanc 1.12.0
+    virtual void RemoveLabel(DatabaseManager& manager,
+                             int64_t resource,
+                             const std::string& label) = 0;
+
+    // New in Orthanc 1.12.0
+    virtual void ListLabels(std::list<std::string>& target,
+                            DatabaseManager& manager,
+                            int64_t resource) = 0;
+
+    // New in Orthanc 1.12.0
+    virtual void ListAllLabels(std::list<std::string>& target,
+                               DatabaseManager& manager) = 0;
   };
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Plugins/IdentifierTag.h	Sat Apr 15 13:58:16 2023 +0200
@@ -0,0 +1,62 @@
+/**
+ * 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) 2021-2023 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero 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
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+
+#pragma once
+
+#include <DicomFormat/DicomTag.h>
+
+namespace OrthancDatabases
+{
+  class IdentifierTag
+  {
+  private:
+    Orthanc::ResourceType  level_;
+    Orthanc::DicomTag      tag_;
+    std::string            name_;
+
+  public:
+    IdentifierTag(Orthanc::ResourceType level,
+                  const Orthanc::DicomTag& tag,
+                  const std::string& name) :
+      level_(level),
+      tag_(tag),
+      name_(name)
+    {
+    }
+
+    Orthanc::ResourceType GetLevel() const
+    {
+      return level_;
+    }
+
+    const Orthanc::DicomTag& GetTag() const
+    {
+      return tag_;
+    }
+
+    const std::string& GetName() const
+    {
+      return name_;
+    }
+  };
+}
--- a/Framework/Plugins/IndexBackend.cpp	Sat Apr 15 13:49:53 2023 +0200
+++ b/Framework/Plugins/IndexBackend.cpp	Sat Apr 15 13:58:16 2023 +0200
@@ -22,7 +22,6 @@
 
 #include "IndexBackend.h"
 
-#include "../../Resources/Orthanc/Databases/ISqlLookupFormatter.h"
 #include "../Common/BinaryStringValue.h"
 #include "../Common/Integer64Value.h"
 #include "../Common/Utf8StringValue.h"
@@ -2065,15 +2064,16 @@
                                      DatabaseManager& manager,
                                      const std::vector<Orthanc::DatabaseConstraint>& lookup,
                                      OrthancPluginResourceType queryLevel,
+                                     const std::set<std::string>& labels,
+                                     Orthanc::LabelsConstraint labelsConstraint,
                                      uint32_t limit,
                                      bool requestSomeInstance)
   {
     LookupFormatter formatter(manager.GetDialect());
 
-    std::set<std::string> noLabels;
     std::string sql;
     Orthanc::ISqlLookupFormatter::Apply(sql, formatter, lookup, Orthanc::Plugins::Convert(queryLevel),
-                                        noLabels, Orthanc::LabelsConstraint_All, limit);
+                                        labels, labelsConstraint, limit);
 
     if (requestSomeInstance)
     {
@@ -2610,6 +2610,96 @@
 #endif
 
 
+  void IndexBackend::AddLabel(DatabaseManager& manager,
+                              int64_t resource,
+                              const std::string& label)
+  {
+    std::unique_ptr<DatabaseManager::CachedStatement> statement;
+
+    switch (manager.GetDialect())
+    {
+      case Dialect_PostgreSQL:
+        statement.reset(new DatabaseManager::CachedStatement(
+                          STATEMENT_FROM_HERE, manager,
+                          "INSERT INTO Labels VALUES(${id}, ${label}) ON CONFLICT DO NOTHING"));
+        break;
+
+      case Dialect_SQLite:
+        statement.reset(new DatabaseManager::CachedStatement(
+                          STATEMENT_FROM_HERE, manager,
+                          "INSERT OR IGNORE INTO Labels VALUES(${id}, ${label})"));
+        break;
+
+      case Dialect_MySQL:
+        statement.reset(new DatabaseManager::CachedStatement(
+                          STATEMENT_FROM_HERE, manager,
+                          "INSERT IGNORE INTO Labels VALUES(${id}, ${label})"));
+        break;
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+    }
+    
+    statement->SetParameterType("id", ValueType_Integer64);
+    statement->SetParameterType("label", ValueType_Utf8String);
+
+    Dictionary args;
+    args.SetIntegerValue("id", resource);
+    args.SetUtf8Value("label", label);
+
+    statement->Execute(args);
+  }
+
+
+  void IndexBackend::RemoveLabel(DatabaseManager& manager,
+                                 int64_t resource,
+                                 const std::string& label)
+  {
+    DatabaseManager::CachedStatement statement(
+      STATEMENT_FROM_HERE, manager,
+      "DELETE FROM Labels WHERE id=${id} AND label=${label}");
+
+    statement.SetParameterType("id", ValueType_Integer64);
+    statement.SetParameterType("label", ValueType_Utf8String);
+
+    Dictionary args;
+    args.SetIntegerValue("id", resource);
+    args.SetUtf8Value("label", label);
+
+    statement.Execute(args);
+  }
+
+
+  void IndexBackend::ListLabels(std::list<std::string>& target,
+                                DatabaseManager& manager,
+                                int64_t resource)
+  {
+    DatabaseManager::CachedStatement statement(
+      STATEMENT_FROM_HERE, manager,
+      "SELECT label FROM Labels WHERE id=${id}");
+      
+    statement.SetReadOnly(true);
+    statement.SetParameterType("id", ValueType_Integer64);
+
+    Dictionary args;
+    args.SetIntegerValue("id", resource);
+
+    ReadListOfStrings(target, statement, args);
+  }
+  
+
+  void IndexBackend::ListAllLabels(std::list<std::string>& target,
+                                   DatabaseManager& manager)
+  {
+    DatabaseManager::CachedStatement statement(
+      STATEMENT_FROM_HERE, manager,
+      "SELECT DISTINCT label FROM Labels");
+      
+    Dictionary args;
+    ReadListOfStrings(target, statement, args);
+  }
+
+  
   void IndexBackend::Register(IndexBackend* backend,
                               size_t countConnections,
                               unsigned int maxDatabaseRetries)
@@ -2663,7 +2753,7 @@
       }
       catch (boost::bad_lexical_cast&)
       {
-        LOG(ERROR) << "Corrupted PostgreSQL database";
+        LOG(ERROR) << "Corrupted database";
         throw Orthanc::OrthancException(Orthanc::ErrorCode_Database);
       }      
     }
@@ -2702,10 +2792,12 @@
   }
 
 
-  DatabaseManager* IndexBackend::CreateSingleDatabaseManager(IDatabaseBackend& backend)
+  DatabaseManager* IndexBackend::CreateSingleDatabaseManager(IDatabaseBackend& backend,
+                                                             bool hasIdentifierTags,
+                                                             const std::list<IdentifierTag>& identifierTags)
   {
     std::unique_ptr<DatabaseManager> manager(new DatabaseManager(backend.CreateDatabaseFactory()));
-    backend.ConfigureDatabase(*manager);
+    backend.ConfigureDatabase(*manager, hasIdentifierTags, identifierTags);
     return manager.release();
   }
 }
--- a/Framework/Plugins/IndexBackend.h	Sat Apr 15 13:49:53 2023 +0200
+++ b/Framework/Plugins/IndexBackend.h	Sat Apr 15 13:58:16 2023 +0200
@@ -302,6 +302,8 @@
                                  DatabaseManager& manager,
                                  const std::vector<Orthanc::DatabaseConstraint>& lookup,
                                  OrthancPluginResourceType queryLevel,
+                                 const std::set<std::string>& labels,
+                                 Orthanc::LabelsConstraint labelsConstraint,
                                  uint32_t limit,
                                  bool requestSomeInstance) ORTHANC_OVERRIDE;
 #endif
@@ -327,25 +329,17 @@
     virtual void TagMostRecentPatient(DatabaseManager& manager,
                                       int64_t patient) ORTHANC_OVERRIDE;
 
-#if defined(ORTHANC_PLUGINS_VERSION_IS_ABOVE)      // Macro introduced in 1.3.1
-#  if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 4)
     // New primitive since Orthanc 1.5.4
     virtual bool LookupResourceAndParent(int64_t& id,
                                          OrthancPluginResourceType& type,
                                          std::string& parentPublicId,
                                          DatabaseManager& manager,
                                          const char* publicId) ORTHANC_OVERRIDE;
-#  endif
-#endif
 
-#if defined(ORTHANC_PLUGINS_VERSION_IS_ABOVE)      // Macro introduced in 1.3.1
-#  if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 4)
     // New primitive since Orthanc 1.5.4
     virtual void GetAllMetadata(std::map<int32_t, std::string>& result,
                                 DatabaseManager& manager,
                                 int64_t id) ORTHANC_OVERRIDE;
-#  endif
-#endif
 
     virtual bool HasCreateInstance() const ORTHANC_OVERRIDE
     {
@@ -387,6 +381,21 @@
                                   int32_t property,
                                   int value);
 
+    virtual void AddLabel(DatabaseManager& manager,
+                          int64_t resource,
+                          const std::string& label) ORTHANC_OVERRIDE;
+
+    virtual void RemoveLabel(DatabaseManager& manager,
+                             int64_t resource,
+                             const std::string& label) ORTHANC_OVERRIDE;
+
+    virtual void ListLabels(std::list<std::string>& target,
+                            DatabaseManager& manager,
+                            int64_t resource) ORTHANC_OVERRIDE;
+
+    virtual void ListAllLabels(std::list<std::string>& target,
+                               DatabaseManager& manager) ORTHANC_OVERRIDE;
+    
     /**
      * "maxDatabaseRetries" is to handle
      * "OrthancPluginErrorCode_DatabaseCannotSerialize" if there is a
@@ -399,6 +408,8 @@
 
     static void Finalize();
 
-    static DatabaseManager* CreateSingleDatabaseManager(IDatabaseBackend& backend);
+    static DatabaseManager* CreateSingleDatabaseManager(IDatabaseBackend& backend,
+                                                        bool hasIdentifierTags,
+                                                        const std::list<IdentifierTag>& identifierTags);
   };
 }
--- a/Framework/Plugins/IndexConnectionsPool.cpp	Sat Apr 15 13:49:53 2023 +0200
+++ b/Framework/Plugins/IndexConnectionsPool.cpp	Sat Apr 15 13:58:16 2023 +0200
@@ -75,7 +75,8 @@
   }
 
 
-  void IndexConnectionsPool::OpenConnections()
+  void IndexConnectionsPool::OpenConnections(bool hasIdentifierTags,
+                                             const std::list<IdentifierTag>& identifierTags)
   {
     boost::unique_lock<boost::shared_mutex>  lock(connectionsMutex_);
 
@@ -87,7 +88,7 @@
         std::unique_ptr<DatabaseManager> manager(new DatabaseManager(backend_->CreateDatabaseFactory()));
         manager->GetDatabase();  // Make sure to open the database connection
           
-        backend_->ConfigureDatabase(*manager);
+        backend_->ConfigureDatabase(*manager, hasIdentifierTags, identifierTags);
         connections_.push_back(manager.release());
       }
 
--- a/Framework/Plugins/IndexConnectionsPool.h	Sat Apr 15 13:49:53 2023 +0200
+++ b/Framework/Plugins/IndexConnectionsPool.h	Sat Apr 15 13:58:16 2023 +0200
@@ -22,6 +22,7 @@
 
 #pragma once
 
+#include "IdentifierTag.h"
 #include "IndexBackend.h"
 
 #include <MultiThreading/SharedMessageQueue.h>
@@ -53,7 +54,8 @@
       return context_;
     }
 
-    void OpenConnections();
+    void OpenConnections(bool hasIdentifierTags,
+                         const std::list<IdentifierTag>& identifierTags);
 
     void CloseConnections();
 
--- a/Framework/Plugins/IndexUnitTests.h	Sat Apr 15 13:49:53 2023 +0200
+++ b/Framework/Plugins/IndexUnitTests.h	Sat Apr 15 13:58:16 2023 +0200
@@ -246,7 +246,8 @@
 
   db.SetOutputFactory(new DatabaseBackendAdapterV2::Factory(&context, NULL));
 
-  std::unique_ptr<DatabaseManager> manager(IndexBackend::CreateSingleDatabaseManager(db));
+  std::list<IdentifierTag> identifierTags;
+  std::unique_ptr<DatabaseManager> manager(IndexBackend::CreateSingleDatabaseManager(db, false, identifierTags));
   
   std::unique_ptr<IDatabaseBackendOutput> output(db.CreateOutput());
 
--- a/MySQL/NEWS	Sat Apr 15 13:49:53 2023 +0200
+++ b/MySQL/NEWS	Sat Apr 15 13:58:16 2023 +0200
@@ -4,7 +4,7 @@
 * Compatibility with Orthanc SDK 1.12.0 (communications between the
   Orthanc core and the database plugin using Google Protocol Buffers)
 * Upgraded dependencies for static builds (notably on Windows and LSB):
-  - openssl 3.0.1
+  - openssl 3.1.0
 
 
 Release 4.3 (2021-07-22)
--- a/MySQL/Plugins/MySQLIndex.cpp	Sat Apr 15 13:49:53 2023 +0200
+++ b/MySQL/Plugins/MySQLIndex.cpp	Sat Apr 15 13:58:16 2023 +0200
@@ -63,7 +63,9 @@
   }
   
 
-  void MySQLIndex::ConfigureDatabase(DatabaseManager& manager)
+  void MySQLIndex::ConfigureDatabase(DatabaseManager& manager,
+                                     bool hasIdentifierTags,
+                                     const std::list<IdentifierTag>& identifierTags)
   {
     uint32_t expectedVersion = 6;
 
@@ -295,7 +297,27 @@
         t.Commit();
       }
 
-      if (revision != 6)
+      if (revision == 6)
+      {
+        // Added new table "Labels" since release 5.0 to deal with
+        // labels that were introduced in Orthanc 1.12.0
+        DatabaseManager::Transaction t(manager, TransactionType_ReadWrite);
+
+        t.GetDatabaseTransaction().ExecuteMultiLines(
+          "CREATE TABLE Labels(id BIGINT NOT NULL,"
+          "label VARCHAR(64) NOT NULL,"
+          "PRIMARY KEY(id, label),"
+          "CONSTRAINT Labels1 FOREIGN KEY (id) REFERENCES Resources(internalId) ON DELETE CASCADE);"
+          "CREATE INDEX LabelsIndex1 ON Labels(id);"
+          "CREATE INDEX LabelsIndex2 ON Labels(label);");
+
+        revision = 7;
+        SetGlobalIntegerProperty(manager, MISSING_SERVER_IDENTIFIER, Orthanc::GlobalProperty_DatabasePatchLevel, revision);
+
+        t.Commit();
+      }
+
+      if (revision != 7)
       {
         LOG(ERROR) << "MySQL plugin is incompatible with database schema revision: " << revision;
         throw Orthanc::OrthancException(Orthanc::ErrorCode_Database);        
--- a/MySQL/Plugins/MySQLIndex.h	Sat Apr 15 13:49:53 2023 +0200
+++ b/MySQL/Plugins/MySQLIndex.h	Sat Apr 15 13:58:16 2023 +0200
@@ -44,7 +44,9 @@
 
     virtual IDatabaseFactory* CreateDatabaseFactory() ORTHANC_OVERRIDE;
 
-    virtual void ConfigureDatabase(DatabaseManager& database) ORTHANC_OVERRIDE;
+    virtual void ConfigureDatabase(DatabaseManager& database,
+                                   bool hasIdentifierTags,
+                                   const std::list<IdentifierTag>& identifierTags) ORTHANC_OVERRIDE;
  
     virtual bool HasRevisionsSupport() const ORTHANC_OVERRIDE
     {
@@ -76,5 +78,11 @@
                                 const char* hashInstance)
       ORTHANC_OVERRIDE;
 #endif
+
+    // New primitive since Orthanc 1.12.0
+    virtual bool HasLabelsSupport() const ORTHANC_OVERRIDE
+    {
+      return true;
+    }
   };
 }
--- a/MySQL/UnitTests/UnitTestsMain.cpp	Sat Apr 15 13:49:53 2023 +0200
+++ b/MySQL/UnitTests/UnitTestsMain.cpp	Sat Apr 15 13:58:16 2023 +0200
@@ -51,19 +51,21 @@
   OrthancDatabases::MySQLIndex db1(NULL, noLock);
   db1.SetClearAll(true);
 
-  std::unique_ptr<OrthancDatabases::DatabaseManager> manager1(OrthancDatabases::IndexBackend::CreateSingleDatabaseManager(db1));
+  std::list<OrthancDatabases::IdentifierTag> identifierTags;
+  
+  std::unique_ptr<OrthancDatabases::DatabaseManager> manager1(OrthancDatabases::IndexBackend::CreateSingleDatabaseManager(db1, false, identifierTags));
 
   {
     OrthancDatabases::MySQLIndex db2(NULL, lock);
-    std::unique_ptr<OrthancDatabases::DatabaseManager> manager2(OrthancDatabases::IndexBackend::CreateSingleDatabaseManager(db2));
+    std::unique_ptr<OrthancDatabases::DatabaseManager> manager2(OrthancDatabases::IndexBackend::CreateSingleDatabaseManager(db2, false, identifierTags));
 
     OrthancDatabases::MySQLIndex db3(NULL, lock);
-    ASSERT_THROW(OrthancDatabases::IndexBackend::CreateSingleDatabaseManager(db3), Orthanc::OrthancException);
+    ASSERT_THROW(OrthancDatabases::IndexBackend::CreateSingleDatabaseManager(db3, false, identifierTags), Orthanc::OrthancException);
 
   }
 
   OrthancDatabases::MySQLIndex db4(NULL, lock);
-  std::unique_ptr<OrthancDatabases::DatabaseManager> manager4(OrthancDatabases::IndexBackend::CreateSingleDatabaseManager(db4));
+  std::unique_ptr<OrthancDatabases::DatabaseManager> manager4(OrthancDatabases::IndexBackend::CreateSingleDatabaseManager(db4, false, identifierTags));
 }
 
 
--- a/Odbc/CMakeLists.txt	Sat Apr 15 13:49:53 2023 +0200
+++ b/Odbc/CMakeLists.txt	Sat Apr 15 13:58:16 2023 +0200
@@ -24,8 +24,8 @@
 set(ORTHANC_PLUGIN_VERSION "mainline")
 
 set(ORTHANC_OPTIMAL_VERSION_MAJOR    1)
-set(ORTHANC_OPTIMAL_VERSION_MINOR    9)
-set(ORTHANC_OPTIMAL_VERSION_REVISION 2)
+set(ORTHANC_OPTIMAL_VERSION_MINOR    12)
+set(ORTHANC_OPTIMAL_VERSION_REVISION 0)
 
 if (ORTHANC_PLUGIN_VERSION STREQUAL "mainline")
   set(ORTHANC_FRAMEWORK_VERSION "mainline")
@@ -79,6 +79,23 @@
   ODBC_PREPARE_STORAGE  ${CMAKE_SOURCE_DIR}/Plugins/PrepareStorage.sql
   )
 
+if (EXISTS ${ORTHANC_SDK_ROOT}/orthanc/OrthancDatabasePlugin.proto)
+  add_custom_command(
+    COMMAND
+    ${PROTOC_EXECUTABLE} ${ORTHANC_SDK_ROOT}/orthanc/OrthancDatabasePlugin.proto --cpp_out=${AUTOGENERATED_DIR} -I${ORTHANC_SDK_ROOT}/orthanc/
+    DEPENDS
+    ProtobufCompiler
+    ${ORTHANC_SDK_ROOT}/orthanc/OrthancDatabasePlugin.proto
+    OUTPUT
+    ${AUTOGENERATED_DIR}/OrthancDatabasePlugin.pb.cc
+    ${AUTOGENERATED_DIR}/OrthancDatabasePlugin.pb.h
+    )
+
+  list(APPEND AUTOGENERATED_SOURCES
+    ${AUTOGENERATED_DIR}/OrthancDatabasePlugin.pb.cc
+    )
+endif()
+
 add_custom_target(
   AutogeneratedTarget
   DEPENDS 
--- a/Odbc/NEWS	Sat Apr 15 13:49:53 2023 +0200
+++ b/Odbc/NEWS	Sat Apr 15 13:58:16 2023 +0200
@@ -1,6 +1,9 @@
 Pending changes in the mainline
 ===============================
 
+* Compatibility with Orthanc SDK 1.12.0 (communications between the
+  Orthanc core and the database plugin using Google Protocol Buffers)
+
 
 Release 1.1 (2021-12-06)
 ========================
--- a/Odbc/Plugins/IndexPlugin.cpp	Sat Apr 15 13:49:53 2023 +0200
+++ b/Odbc/Plugins/IndexPlugin.cpp	Sat Apr 15 13:58:16 2023 +0200
@@ -42,6 +42,9 @@
 #endif
 
 
+#include <google/protobuf/any.h>
+
+
 static const char* const KEY_ODBC = "Odbc";
 
 
@@ -54,6 +57,8 @@
   
   ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* context)
   {
+    GOOGLE_PROTOBUF_VERIFY_VERSION;
+
     if (!OrthancDatabases::InitializePlugin(context, "ODBC", true))
     {
       return -1;
@@ -130,6 +135,7 @@
   {
     LOG(WARNING) << "ODBC index is finalizing";
     OrthancDatabases::IndexBackend::Finalize();
+    google::protobuf::ShutdownProtobufLibrary();
   }
 
 
--- a/Odbc/Plugins/OdbcIndex.cpp	Sat Apr 15 13:49:53 2023 +0200
+++ b/Odbc/Plugins/OdbcIndex.cpp	Sat Apr 15 13:58:16 2023 +0200
@@ -162,7 +162,9 @@
   }
   
   
-  void OdbcIndex::ConfigureDatabase(DatabaseManager& manager)
+  void OdbcIndex::ConfigureDatabase(DatabaseManager& manager,
+                                    bool hasIdentifierTags,
+                                    const std::list<IdentifierTag>& identifierTags)
   {
     uint32_t expectedVersion = 6;
     
--- a/Odbc/Plugins/OdbcIndex.h	Sat Apr 15 13:49:53 2023 +0200
+++ b/Odbc/Plugins/OdbcIndex.h	Sat Apr 15 13:58:16 2023 +0200
@@ -56,7 +56,9 @@
 
     virtual IDatabaseFactory* CreateDatabaseFactory() ORTHANC_OVERRIDE;    
     
-    virtual void ConfigureDatabase(DatabaseManager& manager) ORTHANC_OVERRIDE;
+    virtual void ConfigureDatabase(DatabaseManager& manager,
+                                   bool hasIdentifierTags,
+                                   const std::list<IdentifierTag>& identifierTags) ORTHANC_OVERRIDE;
     
     virtual bool HasRevisionsSupport() const ORTHANC_OVERRIDE
     {
@@ -83,5 +85,11 @@
                                   DatabaseManager& manager,
                                   int64_t id,
                                   int32_t attachment) ORTHANC_OVERRIDE;
+    
+    // New primitive since Orthanc 1.12.0
+    virtual bool HasLabelsSupport() const ORTHANC_OVERRIDE
+    {
+      return false;
+    }
   };
 }
--- a/PostgreSQL/NEWS	Sat Apr 15 13:49:53 2023 +0200
+++ b/PostgreSQL/NEWS	Sat Apr 15 13:58:16 2023 +0200
@@ -4,7 +4,7 @@
 * Compatibility with Orthanc SDK 1.12.0 (communications between the
   Orthanc core and the database plugin using Google Protocol Buffers)
 * Upgraded dependencies for static builds (notably on Windows and LSB):
-  - openssl 3.0.1
+  - openssl 3.1.0
 
 
 Release 4.0 (2021-04-22)
--- a/PostgreSQL/Plugins/PostgreSQLIndex.cpp	Sat Apr 15 13:49:53 2023 +0200
+++ b/PostgreSQL/Plugins/PostgreSQLIndex.cpp	Sat Apr 15 13:58:16 2023 +0200
@@ -61,7 +61,9 @@
   }
 
   
-  void PostgreSQLIndex::ConfigureDatabase(DatabaseManager& manager)
+  void PostgreSQLIndex::ConfigureDatabase(DatabaseManager& manager,
+                                          bool hasIdentifierTags,
+                                          const std::list<IdentifierTag>& identifierTags)
   {
     uint32_t expectedVersion = 6;
 
@@ -297,6 +299,24 @@
 
         t.Commit();
       }
+ 
+
+      {
+        // New in release 5.0 to deal with labels
+        DatabaseManager::Transaction t(manager, TransactionType_ReadWrite);
+
+        if (!t.GetDatabaseTransaction().DoesTableExist("Labels"))
+        {
+          t.GetDatabaseTransaction().ExecuteMultiLines(
+            "CREATE TABLE Labels("
+            "id BIGINT REFERENCES Resources(internalId) ON DELETE CASCADE,"
+            "label TEXT, PRIMARY KEY(id, label));"
+            "CREATE INDEX LabelsIndex1 ON LABELS(id);"
+            "CREATE INDEX LabelsIndex2 ON LABELS(label);");
+        }
+
+        t.Commit();
+      }
     }
   }
 
--- a/PostgreSQL/Plugins/PostgreSQLIndex.h	Sat Apr 15 13:49:53 2023 +0200
+++ b/PostgreSQL/Plugins/PostgreSQLIndex.h	Sat Apr 15 13:58:16 2023 +0200
@@ -44,7 +44,9 @@
 
     virtual IDatabaseFactory* CreateDatabaseFactory() ORTHANC_OVERRIDE;
 
-    virtual void ConfigureDatabase(DatabaseManager& manager) ORTHANC_OVERRIDE;
+    virtual void ConfigureDatabase(DatabaseManager& manager,
+                                   bool hasIdentifierTags,
+                                   const std::list<IdentifierTag>& identifierTags) ORTHANC_OVERRIDE;
 
     virtual bool HasRevisionsSupport() const ORTHANC_OVERRIDE
     {
@@ -82,5 +84,11 @@
 
     virtual void TagMostRecentPatient(DatabaseManager& manager,
                                       int64_t patient) ORTHANC_OVERRIDE;
+
+    // New primitive since Orthanc 1.12.0
+    virtual bool HasLabelsSupport() const ORTHANC_OVERRIDE
+    {
+      return true;
+    }
   };
 }
--- a/PostgreSQL/UnitTests/PostgreSQLTests.cpp	Sat Apr 15 13:49:53 2023 +0200
+++ b/PostgreSQL/UnitTests/PostgreSQLTests.cpp	Sat Apr 15 13:58:16 2023 +0200
@@ -542,7 +542,8 @@
   OrthancDatabases::PostgreSQLIndex db(NULL, globalParameters_);
   db.SetClearAll(true);
 
-  std::unique_ptr<OrthancDatabases::DatabaseManager> manager(OrthancDatabases::IndexBackend::CreateSingleDatabaseManager(db));
+  std::list<OrthancDatabases::IdentifierTag> tags;
+  std::unique_ptr<OrthancDatabases::DatabaseManager> manager(OrthancDatabases::IndexBackend::CreateSingleDatabaseManager(db, false, tags));
 
   std::string s;
   ASSERT_TRUE(db.LookupGlobalProperty(s, *manager, MISSING_SERVER_IDENTIFIER, Orthanc::GlobalProperty_DatabaseInternal1));
--- a/PostgreSQL/UnitTests/UnitTestsMain.cpp	Sat Apr 15 13:49:53 2023 +0200
+++ b/PostgreSQL/UnitTests/UnitTestsMain.cpp	Sat Apr 15 13:58:16 2023 +0200
@@ -95,18 +95,19 @@
   OrthancDatabases::PostgreSQLIndex db1(NULL, noLock);
   db1.SetClearAll(true);
 
-  std::unique_ptr<OrthancDatabases::DatabaseManager> manager1(OrthancDatabases::IndexBackend::CreateSingleDatabaseManager(db1));
+  std::list<OrthancDatabases::IdentifierTag> identifierTags;
+  std::unique_ptr<OrthancDatabases::DatabaseManager> manager1(OrthancDatabases::IndexBackend::CreateSingleDatabaseManager(db1, false, identifierTags));
 
   {
     OrthancDatabases::PostgreSQLIndex db2(NULL, lock);
-    std::unique_ptr<OrthancDatabases::DatabaseManager> manager2(OrthancDatabases::IndexBackend::CreateSingleDatabaseManager(db2));
+    std::unique_ptr<OrthancDatabases::DatabaseManager> manager2(OrthancDatabases::IndexBackend::CreateSingleDatabaseManager(db2, false, identifierTags));
 
     OrthancDatabases::PostgreSQLIndex db3(NULL, lock);
-    ASSERT_THROW(OrthancDatabases::IndexBackend::CreateSingleDatabaseManager(db3), Orthanc::OrthancException);
+    ASSERT_THROW(OrthancDatabases::IndexBackend::CreateSingleDatabaseManager(db3, false, identifierTags), Orthanc::OrthancException);
   }
 
   OrthancDatabases::PostgreSQLIndex db4(NULL, lock);
-    std::unique_ptr<OrthancDatabases::DatabaseManager> manager4(OrthancDatabases::IndexBackend::CreateSingleDatabaseManager(db4));
+    std::unique_ptr<OrthancDatabases::DatabaseManager> manager4(OrthancDatabases::IndexBackend::CreateSingleDatabaseManager(db4, false, identifierTags));
 }
 
 
--- a/Resources/SyncOrthancFolder.py	Sat Apr 15 13:49:53 2023 +0200
+++ b/Resources/SyncOrthancFolder.py	Sat Apr 15 13:58:16 2023 +0200
@@ -8,7 +8,12 @@
 import multiprocessing
 import os
 import stat
-import urllib2
+import sys
+
+if sys.version_info[0] < 3:
+    from urllib2 import urlopen
+else:
+    from urllib.request import urlopen
 
 TARGET = os.path.join(os.path.dirname(__file__), 'Orthanc')
 PLUGIN_SDK_VERSION_OLD = [ '0.9.5', '1.4.0', '1.5.2', '1.5.4' ]
@@ -55,7 +60,7 @@
     branch = x[0]
     source = x[1]
     target = os.path.join(TARGET, x[2])
-    print target
+    print(target)
 
     try:
         os.makedirs(os.path.dirname(target))
@@ -65,8 +70,8 @@
     url = '%s/%s/%s' % (REPOSITORY, branch, source)
 
     try:
-        with open(target, 'w') as f:
-            f.write(urllib2.urlopen(url).read())
+        with open(target, 'wb') as f:
+            f.write(urlopen(url).read())
     except Exception as e:
         raise Exception('Cannot download: %s' % url)    
 
--- a/SQLite/Plugins/SQLiteIndex.cpp	Sat Apr 15 13:49:53 2023 +0200
+++ b/SQLite/Plugins/SQLiteIndex.cpp	Sat Apr 15 13:58:16 2023 +0200
@@ -85,7 +85,9 @@
   }
 
 
-  void SQLiteIndex::ConfigureDatabase(DatabaseManager& manager)
+  void SQLiteIndex::ConfigureDatabase(DatabaseManager& manager,
+                                      bool hasIdentifierTags,
+                                      const std::list<IdentifierTag>& identifierTags)
   {
     uint32_t expectedVersion = 6;
 
@@ -166,6 +168,23 @@
 
       t.Commit();
     }    
+
+    {
+      DatabaseManager::Transaction t(manager, TransactionType_ReadWrite);
+
+      if (!t.GetDatabaseTransaction().DoesTableExist("Labels"))
+      {
+        t.GetDatabaseTransaction().ExecuteMultiLines(
+          "CREATE TABLE Labels("
+          "  id INTEGER REFERENCES Resources(internalId) ON DELETE CASCADE,"
+          "  label TEXT NOT NULL,"
+          "  PRIMARY KEY(id, label));"
+          "CREATE INDEX LabelsIndex1 ON Labels(id);"
+          "CREATE INDEX LabelsIndex2 ON Labels(label);");
+      }
+
+      t.Commit();
+    }    
   }
 
 
--- a/SQLite/Plugins/SQLiteIndex.h	Sat Apr 15 13:49:53 2023 +0200
+++ b/SQLite/Plugins/SQLiteIndex.h	Sat Apr 15 13:58:16 2023 +0200
@@ -45,7 +45,9 @@
 
     virtual IDatabaseFactory* CreateDatabaseFactory() ORTHANC_OVERRIDE;
 
-    virtual void ConfigureDatabase(DatabaseManager& manager) ORTHANC_OVERRIDE;
+    virtual void ConfigureDatabase(DatabaseManager& manager,
+                                   bool hasIdentifierTags,
+                                   const std::list<IdentifierTag>& identifierTags) ORTHANC_OVERRIDE;
     
     virtual bool HasRevisionsSupport() const ORTHANC_OVERRIDE
     {
@@ -58,5 +60,11 @@
 
     // New primitive since Orthanc 1.5.2
     virtual int64_t GetLastChangeIndex(DatabaseManager& manager) ORTHANC_OVERRIDE;
+
+    // New primitive since Orthanc 1.12.0
+    virtual bool HasLabelsSupport() const ORTHANC_OVERRIDE
+    {
+      return true;
+    }
   };
 }
--- a/SQLite/UnitTests/UnitTestsMain.cpp	Sat Apr 15 13:49:53 2023 +0200
+++ b/SQLite/UnitTests/UnitTestsMain.cpp	Sat Apr 15 13:58:16 2023 +0200
@@ -35,28 +35,30 @@
 
 TEST(SQLiteIndex, Lock)
 {
+  std::list<OrthancDatabases::IdentifierTag> identifierTags;
+  
   {
     // No locking if using memory backend
     OrthancDatabases::SQLiteIndex db1(NULL);
-    std::unique_ptr<OrthancDatabases::DatabaseManager> manager1(OrthancDatabases::IndexBackend::CreateSingleDatabaseManager(db1));
+    std::unique_ptr<OrthancDatabases::DatabaseManager> manager1(OrthancDatabases::IndexBackend::CreateSingleDatabaseManager(db1, false, identifierTags));
 
     OrthancDatabases::SQLiteIndex db2(NULL);
-    std::unique_ptr<OrthancDatabases::DatabaseManager> manager2(OrthancDatabases::IndexBackend::CreateSingleDatabaseManager(db2));
+    std::unique_ptr<OrthancDatabases::DatabaseManager> manager2(OrthancDatabases::IndexBackend::CreateSingleDatabaseManager(db2, false, identifierTags));
   }
 
   Orthanc::SystemToolbox::RemoveFile("index.db");
 
   {
     OrthancDatabases::SQLiteIndex db1(NULL, "index.db");
-    std::unique_ptr<OrthancDatabases::DatabaseManager> manager1(OrthancDatabases::IndexBackend::CreateSingleDatabaseManager(db1));
+    std::unique_ptr<OrthancDatabases::DatabaseManager> manager1(OrthancDatabases::IndexBackend::CreateSingleDatabaseManager(db1, false, identifierTags));
 
     OrthancDatabases::SQLiteIndex db2(NULL, "index.db");
-    ASSERT_THROW(OrthancDatabases::IndexBackend::CreateSingleDatabaseManager(db2), Orthanc::OrthancException);
+    ASSERT_THROW(OrthancDatabases::IndexBackend::CreateSingleDatabaseManager(db2, false, identifierTags), Orthanc::OrthancException);
   }
 
   {
     OrthancDatabases::SQLiteIndex db3(NULL, "index.db");
-    std::unique_ptr<OrthancDatabases::DatabaseManager> manager3(OrthancDatabases::IndexBackend::CreateSingleDatabaseManager(db3));
+    std::unique_ptr<OrthancDatabases::DatabaseManager> manager3(OrthancDatabases::IndexBackend::CreateSingleDatabaseManager(db3, false, identifierTags));
   }
 }