changeset 1615:c40fe92a68e7

Primitives to upgrade the database version in plugins
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 16 Sep 2015 15:18:59 +0200
parents 1c9e99d2bfd2
children 644c32c07306
files NEWS OrthancServer/DatabaseWrapper.cpp OrthancServer/DatabaseWrapper.h OrthancServer/IDatabaseWrapper.h OrthancServer/main.cpp Plugins/Engine/OrthancPluginDatabase.cpp Plugins/Engine/OrthancPluginDatabase.h Plugins/Engine/OrthancPlugins.cpp Plugins/Include/orthanc/OrthancCDatabasePlugin.h Plugins/Include/orthanc/OrthancCPlugin.h Plugins/Include/orthanc/OrthancCppDatabasePlugin.h
diffstat 11 files changed, 423 insertions(+), 68 deletions(-) [+]
line wrap: on
line diff
--- a/NEWS	Fri Sep 11 18:46:13 2015 +0200
+++ b/NEWS	Wed Sep 16 15:18:59 2015 +0200
@@ -30,6 +30,7 @@
 * New function "OrthancPluginRegisterRestCallbackNoLock()" for high-performance plugins
 * Plugins have access to explicit error codes 
 * Improvements to the sample "ServeFolders" plugin
+* Primitives to upgrade the database version in plugins
 
 Maintenance
 -----------
--- a/OrthancServer/DatabaseWrapper.cpp	Fri Sep 11 18:46:13 2015 +0200
+++ b/OrthancServer/DatabaseWrapper.cpp	Wed Sep 16 15:18:59 2015 +0200
@@ -765,16 +765,6 @@
     }
   }
 
-  static void UpgradeDatabase(SQLite::Connection& db,
-                              EmbeddedResources::FileResourceId script)
-  {
-    std::string upgrade;
-    EmbeddedResources::GetFileResource(upgrade, script);
-    db.BeginTransaction();
-    db.Execute(upgrade);
-    db.CommitTransaction();    
-  }
-
 
   DatabaseWrapper::DatabaseWrapper(const std::string& path) : listener_(NULL)
   {
@@ -807,49 +797,26 @@
     }
 
     // Check the version of the database
-    std::string version;
-    if (!LookupGlobalProperty(version, GlobalProperty_DatabaseSchemaVersion))
+    std::string tmp;
+    if (!LookupGlobalProperty(tmp, GlobalProperty_DatabaseSchemaVersion))
     {
-      version = "Unknown";
+      tmp = "Unknown";
     }
 
     bool ok = false;
     try
     {
-      LOG(INFO) << "Version of the Orthanc database: " << version;
-      unsigned int v = boost::lexical_cast<unsigned int>(version);
-
-      // This version of Orthanc is only compatible with versions 3, 4 and 5 of the DB schema
-      ok = (v == 3 || v == 4 || v == 5);
-
-      if (v == 3)
-      {
-        LOG(WARNING) << "Upgrading database version from 3 to 4";
-        UpgradeDatabase(db_, EmbeddedResources::UPGRADE_DATABASE_3_TO_4);
-        v = 4;
-      }
-
-      if (v == 4)
-      {
-        LOG(WARNING) << "Upgrading database version from 4 to 5";
-        UpgradeDatabase(db_, EmbeddedResources::UPGRADE_DATABASE_4_TO_5);
-        v = 5;
-      }
-
-      // Sanity check
-      if (ORTHANC_DATABASE_VERSION != v)
-      {
-        throw OrthancException(ErrorCode_InternalError);
-      }
+      LOG(INFO) << "Version of the Orthanc database: " << tmp;
+      version_ = boost::lexical_cast<unsigned int>(tmp);
+      ok = true;
     }
     catch (boost::bad_lexical_cast&)
     {
-      ok = false;
     }
 
     if (!ok)
     {
-      LOG(ERROR) << "Incompatible version of the Orthanc database: " << version;
+      LOG(ERROR) << "Incompatible version of the Orthanc database: " << tmp;
       throw OrthancException(ErrorCode_IncompatibleDatabaseVersion);
     }
 
@@ -857,6 +824,50 @@
     db_.Register(signalRemainingAncestor_);
   }
 
+
+  static void ExecuteUpgradeScript(SQLite::Connection& db,
+                                   EmbeddedResources::FileResourceId script)
+  {
+    std::string upgrade;
+    EmbeddedResources::GetFileResource(upgrade, script);
+    db.BeginTransaction();
+    db.Execute(upgrade);
+    db.CommitTransaction();    
+  }
+
+
+  void DatabaseWrapper::Upgrade(unsigned int targetVersion,
+                                IStorageArea& storageArea)
+  {
+    if (targetVersion != 5)
+    {
+      throw OrthancException(ErrorCode_IncompatibleDatabaseVersion);
+    }
+
+    // This version of Orthanc is only compatible with versions 3, 4 and 5 of the DB schema
+    if (version_ != 3 &&
+        version_ != 4 &&
+        version_ != 5)
+    {
+      throw OrthancException(ErrorCode_IncompatibleDatabaseVersion);
+    }
+
+    if (version_ == 3)
+    {
+      LOG(WARNING) << "Upgrading database version from 3 to 4";
+      ExecuteUpgradeScript(db_, EmbeddedResources::UPGRADE_DATABASE_3_TO_4);
+      version_ = 4;
+    }
+
+    if (version_ == 4)
+    {
+      LOG(WARNING) << "Upgrading database version from 4 to 5";
+      ExecuteUpgradeScript(db_, EmbeddedResources::UPGRADE_DATABASE_4_TO_5);
+      version_ = 5;
+    }
+  }
+
+
   void DatabaseWrapper::SetListener(IDatabaseListener& listener)
   {
     listener_ = &listener;
--- a/OrthancServer/DatabaseWrapper.h	Fri Sep 11 18:46:13 2015 +0200
+++ b/OrthancServer/DatabaseWrapper.h	Wed Sep 16 15:18:59 2015 +0200
@@ -55,6 +55,7 @@
     IDatabaseListener* listener_;
     SQLite::Connection db_;
     Internals::SignalRemainingAncestor* signalRemainingAncestor_;
+    unsigned int version_;
 
     void Open();
 
@@ -222,6 +223,13 @@
     virtual void GetAllMetadata(std::map<MetadataType, std::string>& target,
                                 int64_t id);
 
+    virtual unsigned int GetDatabaseVersion()
+    {
+      return version_;
+    }
+
+    virtual void Upgrade(unsigned int targetVersion,
+                         IStorageArea& storageArea);
 
 
 
--- a/OrthancServer/IDatabaseWrapper.h	Fri Sep 11 18:46:13 2015 +0200
+++ b/OrthancServer/IDatabaseWrapper.h	Wed Sep 16 15:18:59 2015 +0200
@@ -34,6 +34,7 @@
 
 #include "../Core/DicomFormat/DicomMap.h"
 #include "../Core/SQLite/ITransaction.h"
+#include "../Core/FileStorage/IStorageArea.h"
 #include "../Core/FileStorage/FileInfo.h"
 #include "IDatabaseListener.h"
 #include "ExportedResource.h"
@@ -181,5 +182,10 @@
     virtual SQLite::ITransaction* StartTransaction() = 0;
 
     virtual void SetListener(IDatabaseListener& listener) = 0;
+
+    virtual unsigned int GetDatabaseVersion() = 0;
+
+    virtual void Upgrade(unsigned int targetVersion,
+                         IStorageArea& storageArea) = 0;
   };
 }
--- a/OrthancServer/main.cpp	Fri Sep 11 18:46:13 2015 +0200
+++ b/OrthancServer/main.cpp	Wed Sep 16 15:18:59 2015 +0200
@@ -315,21 +315,23 @@
     << std::endl
     << "If no configuration file is given on the command line, a set of default " << std::endl
     << "parameters is used. Please refer to the Orthanc homepage for the full " << std::endl
-    << "instructions about how to use Orthanc " << std::endl
-    << "<https://code.google.com/p/orthanc/wiki/OrthancCookbook>." << std::endl
+    << "instructions about how to use Orthanc <http://www.orthanc-server.com/>." << std::endl
     << std::endl
     << "Command-line options:" << std::endl
     << "  --help\t\tdisplay this help and exit" << std::endl
     << "  --logdir=[dir]\tdirectory where to store the log files" << std::endl
     << "\t\t\t(if not used, the logs are dumped to stderr)" << std::endl
     << "  --config=[file]\tcreate a sample configuration file and exit" << std::endl
+    << "  --verbose\t\tbe verbose in logs" << std::endl
     << "  --trace\t\thighest verbosity in logs (for debug)" << std::endl
-    << "  --verbose\t\tbe verbose in logs" << std::endl
+    << "  --upgrade\t\tallow Orthanc to upgrade the version of the" << std::endl
+    << "\t\t\tdatabase (beware that the database will become" << std::endl
+    << "\t\t\tincompatible with former versions of Orthanc)" << std::endl
     << "  --version\t\toutput version information and exit" << std::endl
     << std::endl
     << "Exit status:" << std::endl
-    << " 0  if OK," << std::endl
-    << " -1  if error (have a look at the logs)." << std::endl
+    << "   0 if success," << std::endl
+    << "  -1 if error (have a look at the logs)." << std::endl
     << std::endl;
 }
 
@@ -519,10 +521,51 @@
 }
 
 
+static bool UpgradeDatabase(IDatabaseWrapper& database,
+                            IStorageArea& storageArea,
+                            bool allowDatabaseUpgrade)
+{
+  // Upgrade the database, if needed
+  unsigned int currentVersion = database.GetDatabaseVersion();
+  if (currentVersion == ORTHANC_DATABASE_VERSION)
+  {
+    return true;
+  }
+
+  if (!allowDatabaseUpgrade)
+  {
+    LOG(ERROR) << "The database must be upgraded from version "
+               << currentVersion << " to " << ORTHANC_DATABASE_VERSION 
+               << ": Please run Orthanc with the \"--upgrade\" command-line option";
+    return false;
+  }
+
+  LOG(WARNING) << "Upgrading the database from version "
+               << currentVersion << " to " << ORTHANC_DATABASE_VERSION;
+  database.Upgrade(ORTHANC_DATABASE_VERSION, storageArea);
+    
+  // Sanity check
+  currentVersion = database.GetDatabaseVersion();
+  if (ORTHANC_DATABASE_VERSION != currentVersion)
+  {
+    LOG(ERROR) << "The database was not properly updated, it is still at version " << currentVersion;
+    throw OrthancException(ErrorCode_InternalError);
+  }
+
+  return true;
+}
+
+
 static bool ConfigureServerContext(IDatabaseWrapper& database,
                                    IStorageArea& storageArea,
-                                   OrthancPlugins *plugins)
+                                   OrthancPlugins *plugins,
+                                   bool allowDatabaseUpgrade)
 {
+  if (!UpgradeDatabase(database, storageArea, allowDatabaseUpgrade))
+  {
+    return false;
+  }
+
   ServerContext context(database, storageArea);
 
   HttpClient::SetDefaultTimeout(Configuration::GetGlobalIntegerParameter("HttpTimeout", 0));
@@ -569,7 +612,8 @@
 
 
 static bool ConfigurePlugins(int argc, 
-                             char* argv[])
+                             char* argv[],
+                             bool allowDatabaseUpgrade)
 {
   std::auto_ptr<IDatabaseWrapper>  databasePtr;
   std::auto_ptr<IStorageArea>  storage;
@@ -604,14 +648,14 @@
   assert(database != NULL);
   assert(storage.get() != NULL);
 
-  return ConfigureServerContext(*database, *storage, &plugins);
+  return ConfigureServerContext(*database, *storage, &plugins, allowDatabaseUpgrade);
 
 #elif ORTHANC_PLUGINS_ENABLED == 0
   // The plugins are disabled
   databasePtr.reset(Configuration::CreateDatabaseWrapper());
   storage.reset(Configuration::CreateStorageArea());
 
-  return ConfigureServerContext(*databasePtr, *storage, NULL);
+  return ConfigureServerContext(*databasePtr, *storage, NULL, allowDatabaseUpgrade);
 
 #else
 #  error The macro ORTHANC_PLUGINS_ENABLED must be set to 0 or 1
@@ -619,9 +663,11 @@
 }
 
 
-static bool StartOrthanc(int argc, char* argv[])
+static bool StartOrthanc(int argc, 
+                         char* argv[],
+                         bool allowDatabaseUpgrade)
 {
-  return ConfigurePlugins(argc, argv);
+  return ConfigurePlugins(argc, argv, allowDatabaseUpgrade);
 }
 
 
@@ -629,6 +675,8 @@
 {
   Logging::Initialize();
 
+  bool allowDatabaseUpgrade = false;
+
   for (int i = 1; i < argc; i++)
   {
     if (std::string(argv[i]) == "--help")
@@ -668,6 +716,11 @@
       }
     }
 
+    if (std::string(argv[i]) == "--upgrade")
+    {
+      allowDatabaseUpgrade = true;
+    }
+
     if (boost::starts_with(argv[i], "--config="))
     {
       std::string configurationSample;
@@ -706,7 +759,7 @@
     {
       OrthancInitialize(configurationFile);
 
-      bool restart = StartOrthanc(argc, argv);
+      bool restart = StartOrthanc(argc, argv, allowDatabaseUpgrade);
       if (restart)
       {
         OrthancFinalize();
--- a/Plugins/Engine/OrthancPluginDatabase.cpp	Fri Sep 11 18:46:13 2015 +0200
+++ b/Plugins/Engine/OrthancPluginDatabase.cpp	Wed Sep 16 15:18:59 2015 +0200
@@ -982,6 +982,43 @@
   }
 
 
+  unsigned int OrthancPluginDatabase::GetDatabaseVersion()
+  {
+    if (extensions_.getDatabaseVersion != NULL)
+    {
+      uint32_t version;
+      if (extensions_.getDatabaseVersion(&version, payload_) != 0)
+      {
+        throw OrthancException(ErrorCode_Plugin);
+      }
+
+      return version;
+    }
+    else
+    {
+      // Before adding the "GetDatabaseVersion()" extension in plugins
+      // (OrthancPostgreSQL <= 1.2), the only supported DB schema was
+      // version 5.
+      return 5;
+    }
+  }
+
+
+  void OrthancPluginDatabase::Upgrade(unsigned int targetVersion,
+                                      IStorageArea& storageArea)
+  {
+    if (extensions_.upgradeDatabase != NULL)
+    {
+      if (extensions_.upgradeDatabase(
+            payload_, targetVersion, 
+            reinterpret_cast<OrthancPluginStorageArea*>(&storageArea)) != 0)
+      {
+        throw OrthancException(ErrorCode_Plugin);
+      }
+    }
+  }
+
+
   void OrthancPluginDatabase::AnswerReceived(const _OrthancPluginDatabaseAnswer& answer)
   {
     if (answer.type == _OrthancPluginDatabaseAnswerType_None)
--- a/Plugins/Engine/OrthancPluginDatabase.h	Fri Sep 11 18:46:13 2015 +0200
+++ b/Plugins/Engine/OrthancPluginDatabase.h	Wed Sep 16 15:18:59 2015 +0200
@@ -222,6 +222,11 @@
       listener_ = &listener;
     }
 
+    virtual unsigned int GetDatabaseVersion();
+
+    virtual void Upgrade(unsigned int targetVersion,
+                         IStorageArea& storageArea);
+
     void AnswerReceived(const _OrthancPluginDatabaseAnswer& answer);
   };
 }
--- a/Plugins/Engine/OrthancPlugins.cpp	Fri Sep 11 18:46:13 2015 +0200
+++ b/Plugins/Engine/OrthancPlugins.cpp	Wed Sep 16 15:18:59 2015 +0200
@@ -165,6 +165,37 @@
   }
 
 
+  static OrthancPluginContentType Convert(FileContentType type)
+  {
+    switch (type)
+    {
+      case FileContentType_Dicom:
+        return OrthancPluginContentType_Dicom;
+
+      case FileContentType_DicomAsJson:
+        return OrthancPluginContentType_DicomAsJson;
+
+      default:
+        return OrthancPluginContentType_Unknown;
+    }
+  }
+
+
+  static FileContentType Convert(OrthancPluginContentType type)
+  {
+    switch (type)
+    {
+      case OrthancPluginContentType_Dicom:
+        return FileContentType_Dicom;
+
+      case OrthancPluginContentType_DicomAsJson:
+        return FileContentType_DicomAsJson;
+
+      default:
+        return FileContentType_Unknown;
+    }
+  }
+
 
   struct OrthancPlugins::PImpl
   {
@@ -1618,6 +1649,35 @@
         DrawText(parameters);
         return true;
 
+      case _OrthancPluginService_StorageAreaCreate:
+      {
+        const _OrthancPluginStorageAreaCreate& p =
+          *reinterpret_cast<const _OrthancPluginStorageAreaCreate*>(parameters);
+        IStorageArea& storage = *reinterpret_cast<IStorageArea*>(p.storageArea);
+        storage.Create(p.uuid, p.content, p.size, Convert(p.type));
+        return true;
+      }
+
+      case _OrthancPluginService_StorageAreaRead:
+      {
+        const _OrthancPluginStorageAreaRead& p =
+          *reinterpret_cast<const _OrthancPluginStorageAreaRead*>(parameters);
+        IStorageArea& storage = *reinterpret_cast<IStorageArea*>(p.storageArea);
+        std::string content;
+        storage.Read(content, p.uuid, Convert(p.type));
+        CopyToMemoryBuffer(*p.target, content);
+        return true;
+      }
+
+      case _OrthancPluginService_StorageAreaRemove:
+      {
+        const _OrthancPluginStorageAreaRemove& p =
+          *reinterpret_cast<const _OrthancPluginStorageAreaRemove*>(parameters);
+        IStorageArea& storage = *reinterpret_cast<IStorageArea*>(p.storageArea);
+        storage.Remove(p.uuid, Convert(p.type));
+        return true;
+      }
+
       default:
       {
         // This service is unknown to the Orthanc plugin engine
@@ -1654,21 +1714,6 @@
         }
       }
 
-      OrthancPluginContentType Convert(FileContentType type) const
-      {
-        switch (type)
-        {
-          case FileContentType_Dicom:
-            return OrthancPluginContentType_Dicom;
-
-          case FileContentType_DicomAsJson:
-            return OrthancPluginContentType_DicomAsJson;
-
-          default:
-            return OrthancPluginContentType_Unknown;
-        }
-      }
-
     public:
       PluginStorageArea(const _OrthancPluginRegisterStorageArea& params) : params_(params)
       {
--- a/Plugins/Include/orthanc/OrthancCDatabasePlugin.h	Fri Sep 11 18:46:13 2015 +0200
+++ b/Plugins/Include/orthanc/OrthancCDatabasePlugin.h	Wed Sep 16 15:18:59 2015 +0200
@@ -643,6 +643,18 @@
       OrthancPluginResourceType resourceType,
       uint64_t since,
       uint64_t limit);
+
+    int32_t  (*getDatabaseVersion) (
+      /* outputs */
+      uint32_t* version,
+      /* inputs */
+      void* payload);
+
+    int32_t  (*upgradeDatabase) (
+      /* inputs */
+      void* payload,
+      uint32_t targetVersion,
+      OrthancPluginStorageArea* storageArea);
   } OrthancPluginDatabaseExtensions;
 
 /*<! @endcond */
--- a/Plugins/Include/orthanc/OrthancCPlugin.h	Fri Sep 11 18:46:13 2015 +0200
+++ b/Plugins/Include/orthanc/OrthancCPlugin.h	Wed Sep 16 15:18:59 2015 +0200
@@ -429,6 +429,9 @@
     _OrthancPluginService_RegisterDatabaseBackend = 5000,
     _OrthancPluginService_DatabaseAnswer = 5001,
     _OrthancPluginService_RegisterDatabaseBackendV2 = 5002,
+    _OrthancPluginService_StorageAreaCreate = 5003,
+    _OrthancPluginService_StorageAreaRead = 5004,
+    _OrthancPluginService_StorageAreaRemove = 5005,
 
     /* Primitives for handling images */
     _OrthancPluginService_GetImagePixelFormat = 6000,
@@ -630,7 +633,7 @@
 
 
   /**
-   * @brief Opaque structure that represents a uncompressed image in memory.
+   * @brief Opaque structure that represents an image that is uncompressed in memory.
    * @ingroup Images
    **/
   typedef struct _OrthancPluginImage_t OrthancPluginImage;
@@ -638,6 +641,14 @@
 
 
   /**
+   * @brief Opaque structure that represents the storage area that is actually used by Orthanc.
+   * @ingroup Images
+   **/
+  typedef struct _OrthancPluginStorageArea_t OrthancPluginStorageArea;
+
+
+
+  /**
    * @brief Signature of a callback function that answers to a REST request.
    * @ingroup Callbacks
    **/
@@ -2402,6 +2413,7 @@
    * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
    * @return The version.
    * @ingroup Callbacks
+   * @deprecated Please instead use IDatabaseBackend::UpgradeDatabase()
    **/
   ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetExpectedDatabaseVersion(
     OrthancPluginContext*  context)
@@ -3484,6 +3496,127 @@
 
 
 
+  typedef struct
+  {
+    OrthancPluginStorageArea*   storageArea;
+    const char*                 uuid;
+    const void*                 content;
+    uint64_t                    size;
+    OrthancPluginContentType    type;
+  } _OrthancPluginStorageAreaCreate;
+
+
+  /**
+   * @brief Create a file inside the storage area.
+   *
+   * This function creates a new file inside the storage area that is
+   * currently used by Orthanc.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param storageArea The storage area.
+   * @param uuid The identifier of the file to be created.
+   * @param content The content to store in the newly created file.
+   * @param size The size of the content.
+   * @param type The type of the file content.
+   * @return 0 if success, other value if error.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginStorageAreaCreate(
+    OrthancPluginContext*       context,
+    OrthancPluginStorageArea*   storageArea,
+    const char*                 uuid,
+    const void*                 content,
+    uint64_t                    size,
+    OrthancPluginContentType    type)
+  {
+    _OrthancPluginStorageAreaCreate params;
+    params.storageArea = storageArea;
+    params.uuid = uuid;
+    params.content = content;
+    params.size = size;
+    params.type = type;
+
+    return context->InvokeService(context, _OrthancPluginService_StorageAreaCreate, &params);
+  }
+
+
+  typedef struct
+  {
+    OrthancPluginMemoryBuffer*  target;
+    OrthancPluginStorageArea*   storageArea;
+    const char*                 uuid;
+    OrthancPluginContentType    type;
+  } _OrthancPluginStorageAreaRead;
+
+
+  /**
+   * @brief Read a file from the storage area.
+   *
+   * This function reads the content of a given file from the storage
+   * area that is currently used by Orthanc.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param storageArea The storage area.
+   * @param uuid The identifier of the file to be read.
+   * @param type The type of the file content.
+   * @return 0 if success, other value if error.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginStorageAreaRead(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    OrthancPluginStorageArea*   storageArea,
+    const char*                 uuid,
+    OrthancPluginContentType    type)
+  {
+    _OrthancPluginStorageAreaRead params;
+    params.target = target;
+    params.storageArea = storageArea;
+    params.uuid = uuid;
+    params.type = type;
+
+    return context->InvokeService(context, _OrthancPluginService_StorageAreaRead, &params);
+  }
+
+
+  typedef struct
+  {
+    OrthancPluginStorageArea*   storageArea;
+    const char*                 uuid;
+    OrthancPluginContentType    type;
+  } _OrthancPluginStorageAreaRemove;
+
+
+  /**
+   * @brief Remove a file from the storage area.
+   *
+   * This function removes a given file from the storage area that is
+   * currently used by Orthanc.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param storageArea The storage area.
+   * @param uuid The identifier of the file to be removed.
+   * @param type The type of the file content.
+   * @return 0 if success, other value if error.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginStorageAreaRemove(
+    OrthancPluginContext*       context,
+    OrthancPluginStorageArea*   storageArea,
+    const char*                 uuid,
+    OrthancPluginContentType    type)
+  {
+    _OrthancPluginStorageAreaRemove params;
+    params.storageArea = storageArea;
+    params.uuid = uuid;
+    params.type = type;
+
+    return context->InvokeService(context, _OrthancPluginService_StorageAreaRemove, &params);
+  }
+
+
+
 #ifdef  __cplusplus
 }
 #endif
--- a/Plugins/Include/orthanc/OrthancCppDatabasePlugin.h	Fri Sep 11 18:46:13 2015 +0200
+++ b/Plugins/Include/orthanc/OrthancCppDatabasePlugin.h	Wed Sep 16 15:18:59 2015 +0200
@@ -430,6 +430,11 @@
     virtual void RollbackTransaction() = 0;
 
     virtual void CommitTransaction() = 0;
+
+    virtual uint32_t GetDatabaseVersion() = 0;
+
+    virtual void UpgradeDatabase(uint32_t  targetVersion,
+                                 OrthancPluginStorageArea* storageArea) = 0;
   };
 
 
@@ -1518,6 +1523,43 @@
       }
     }
 
+
+    static int32_t GetDatabaseVersion(uint32_t* version,
+                                      void* payload)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      
+      try
+      {
+        *version = backend->GetDatabaseVersion();
+        return 0;
+      }
+      catch (std::runtime_error& e)
+      {
+        LogError(backend, e);
+        return -1;
+      }
+    }
+
+
+    static int32_t UpgradeDatabase(void* payload,
+                                   uint32_t  targetVersion,
+                                   OrthancPluginStorageArea* storageArea)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      
+      try
+      {
+        backend->UpgradeDatabase(targetVersion, storageArea);
+        return 0;
+      }
+      catch (std::runtime_error& e)
+      {
+        LogError(backend, e);
+        return -1;
+      }
+    }
+
     
   public:
     /**
@@ -1584,6 +1626,8 @@
       params.close = Close;
 
       extensions.getAllPublicIdsWithLimit = GetAllPublicIdsWithLimit;
+      extensions.getDatabaseVersion = GetDatabaseVersion;
+      extensions.upgradeDatabase = UpgradeDatabase;
 
       OrthancPluginDatabaseContext* database = OrthancPluginRegisterDatabaseBackendV2(context, &params, &extensions, &backend);
       if (!context)