changeset 843:68b52a369685 pg-next-1099

changed rev to 1099 and added upgrade/downgrade
author Alain Mazy <am@orthanc.team>
date Mon, 14 Sep 2026 14:54:00 +0200
parents 99598fd743f5
children e76a0b1763dc
files PostgreSQL/CMakeLists.txt PostgreSQL/Plugins/PostgreSQLIndex.cpp PostgreSQL/Plugins/SQL/Downgrades/Rev1099ToRev10.sql PostgreSQL/Plugins/SQL/PrepareIndex.sql PostgreSQL/Plugins/SQL/Upgrades/Rev10ToRev1099.sql
diffstat 5 files changed, 132 insertions(+), 3 deletions(-) [+]
line wrap: on
line diff
--- a/PostgreSQL/CMakeLists.txt	Mon Sep 14 14:37:40 2026 +0200
+++ b/PostgreSQL/CMakeLists.txt	Mon Sep 14 14:54:00 2026 +0200
@@ -96,7 +96,8 @@
   POSTGRESQL_UPGRADE_REV3_TO_REV4    ${CMAKE_SOURCE_DIR}/Plugins/SQL/Upgrades/Rev3ToRev4.sql
   POSTGRESQL_UPGRADE_REV4_TO_REV5    ${CMAKE_SOURCE_DIR}/Plugins/SQL/Upgrades/Rev4ToRev5.sql
   POSTGRESQL_UPGRADE_REV5_TO_REV6    ${CMAKE_SOURCE_DIR}/Plugins/SQL/Upgrades/Rev5ToRev6.sql
-  POSTGRESQL_UPGRADE_REV6_TO_REV10  ${CMAKE_SOURCE_DIR}/Plugins/SQL/Upgrades/Rev6ToRev10.sql
+  POSTGRESQL_UPGRADE_REV6_TO_REV10   ${CMAKE_SOURCE_DIR}/Plugins/SQL/Upgrades/Rev6ToRev10.sql
+  POSTGRESQL_UPGRADE_REV10_TO_REV1099  ${CMAKE_SOURCE_DIR}/Plugins/SQL/Upgrades/Rev10ToRev1099.sql
   )
 
 
--- a/PostgreSQL/Plugins/PostgreSQLIndex.cpp	Mon Sep 14 14:37:40 2026 +0200
+++ b/PostgreSQL/Plugins/PostgreSQLIndex.cpp	Mon Sep 14 14:54:00 2026 +0200
@@ -49,7 +49,7 @@
   static const GlobalProperty GlobalProperty_HasComputeStatisticsReadOnly = GlobalProperty_DatabaseInternal4;
 }
 
-#define CURRENT_DB_REVISION 10
+#define CURRENT_DB_REVISION 1099
 
 namespace OrthancDatabases
 {
@@ -283,6 +283,19 @@
             currentRevision = 10;
           }
 
+          if (currentRevision == 10)
+          {
+            LOG(WARNING) << "Upgrading DB schema from revision 10 to revision 1099";
+
+            std::string query;
+
+            Orthanc::EmbeddedResources::GetFileResource
+              (query, Orthanc::EmbeddedResources::POSTGRESQL_UPGRADE_REV10_TO_REV1099);
+            t.GetDatabaseTransaction().ExecuteMultiLines(query);
+            hasAppliedAnUpgrade = true;
+            currentRevision = 1099;
+          }
+
           if (hasAppliedAnUpgrade)
           {
             LOG(WARNING) << "Upgrading DB schema by applying PrepareIndex.sql";
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/PostgreSQL/Plugins/SQL/Downgrades/Rev1099ToRev10.sql	Mon Sep 14 14:54:00 2026 +0200
@@ -0,0 +1,112 @@
+-- restore the old DeleteResource function
+
+CREATE OR REPLACE FUNCTION DeleteResource(
+    IN id BIGINT,
+    OUT remaining_ancestor_resource_type INTEGER,
+    OUT remaining_anncestor_public_id TEXT) AS $body$
+DECLARE
+    deleted_resource_row RECORD;
+    deleted_parent_row RECORD;
+    deleted_grand_parent_row RECORD;
+    deleted_grand_grand_parent_row RECORD;
+    locked_parent_row RECORD;
+    locked_resource_row RECORD;
+BEGIN
+    SET client_min_messages = warning;   -- suppress NOTICE:  relation "deletedresources" already exists, skipping
+    -- note: temporary tables are created at connection level -> they are likely to exist.
+    -- These tables are used by the triggers
+    CREATE TEMPORARY TABLE IF NOT EXISTS DeletedResources(
+        resourceType INTEGER NOT NULL,
+        publicId VARCHAR(64) NOT NULL
+        );
+    RESET client_min_messages;
+    -- clear the temporary table in case it has been created earlier in the connection
+    DELETE FROM DeletedResources;
+    -- create/clear the DeletedFiles temporary table
+    PERFORM CreateDeletedFilesTemporaryTable();
+    -- Before deleting an object, we need to lock its parent until the end of the transaction to avoid that
+    -- 2 threads deletes the last 2 instances of a series at the same time -> none of them would realize
+    -- that they are deleting the last instance and the parent resources would not be deleted.
+    -- Locking only the immediate parent is sufficient to prevent from this.
+    SELECT * INTO locked_parent_row FROM resources WHERE internalid = (SELECT parentid FROM resources WHERE internalid = id) FOR UPDATE;
+    -- Before deleting the resource itself, we lock it to retrieve the resourceType and to make sure not 2 connections try to
+    -- delete it at the same time
+    SELECT * INTO locked_resource_row FROM resources WHERE internalid = id FOR UPDATE;
+    -- before delete the resource itself, we must delete its grand-grand-children, the grand-children and its children no to violate 
+    -- the parentId referencing an existing primary key constrain.  This is actually implementing the ON DELETE CASCADE that was on the parentId in previous revisions.
+    
+    -- If this resource has grand-grand-children, delete them
+    if locked_resource_row.resourceType < 1 THEN
+        WITH grand_grand_children_to_delete AS (SELECT grandGrandChildLevel.internalId, grandGrandChildLevel.resourceType, grandGrandChildLevel.publicId
+                                                FROM Resources childLevel
+                                                INNER JOIN Resources grandChildLevel ON childLevel.internalId = grandChildLevel.parentId
+                                                INNER JOIN Resources grandGrandChildLevel ON grandChildLevel.internalId = grandGrandChildLevel.parentId
+                                                WHERE childLevel.parentId = id),
+        
+        deleted_grand_grand_children_rows AS (DELETE FROM Resources WHERE internalId IN (SELECT internalId FROM grand_grand_children_to_delete)
+                                              RETURNING resourceType, publicId)
+        INSERT INTO DeletedResources SELECT resourceType, publicId FROM deleted_grand_grand_children_rows; 
+    END IF;
+    -- If this resource has grand-children, delete them
+    if locked_resource_row.resourceType < 2 THEN
+        WITH grand_children_to_delete AS (SELECT grandChildLevel.internalId, grandChildLevel.resourceType, grandChildLevel.publicId
+                                          FROM Resources childLevel
+                                          INNER JOIN Resources grandChildLevel ON childLevel.internalId = grandChildLevel.parentId
+                                          WHERE childLevel.parentId = id),
+        
+        deleted_grand_children_rows AS (DELETE FROM Resources WHERE internalId IN (SELECT internalId FROM grand_children_to_delete)
+                                        RETURNING resourceType, publicId)
+        INSERT INTO DeletedResources SELECT resourceType, publicId FROM deleted_grand_children_rows; 
+    END IF;
+    -- If this resource has children, delete them
+    if locked_resource_row.resourceType < 3 THEN
+        WITH deleted_children AS (DELETE FROM Resources 
+                                  WHERE parentId = id
+                                  RETURNING resourceType, publicId)
+        INSERT INTO DeletedResources SELECT resourceType, publicId FROM deleted_children; 
+    END IF;
+    -- delete the resource itself
+    DELETE FROM Resources WHERE internalId=id RETURNING * INTO deleted_resource_row;
+
+    -- keep track of the deleted resources for C++ code
+    INSERT INTO DeletedResources VALUES (deleted_resource_row.resourceType, deleted_resource_row.publicId);
+
+    -- If this resource still has siblings, keep track of the remaining parent
+    -- (a parent that must not be deleted but whose LastUpdate must be updated)
+    SELECT resourceType, publicId INTO remaining_ancestor_resource_type, remaining_anncestor_public_id
+        FROM Resources 
+        WHERE internalId = deleted_resource_row.parentId
+            AND EXISTS (SELECT 1 FROM Resources WHERE parentId = deleted_resource_row.parentId);
+	IF deleted_resource_row.resourceType > 0 THEN
+        -- If this resource is the latest child, delete the parent
+        DELETE FROM Resources WHERE internalId = deleted_resource_row.parentId
+                                    AND NOT EXISTS (SELECT 1 FROM Resources WHERE parentId = deleted_resource_row.parentId)
+                                    RETURNING * INTO deleted_parent_row;
+        IF FOUND THEN
+            INSERT INTO DeletedResources VALUES (deleted_parent_row.resourceType, deleted_parent_row.publicId);
+            IF deleted_parent_row.resourceType > 0 THEN
+                -- If this resource is the latest child, delete the parent
+                DELETE FROM Resources WHERE internalId = deleted_parent_row.parentId
+                                    AND NOT EXISTS (SELECT 1 FROM Resources WHERE parentId = deleted_parent_row.parentId)
+                                    RETURNING * INTO deleted_grand_parent_row;
+                IF FOUND THEN
+                    INSERT INTO DeletedResources VALUES (deleted_grand_parent_row.resourceType, deleted_grand_parent_row.publicId);
+                    IF deleted_grand_parent_row.resourceType > 0 THEN
+                        -- If this resource is the latest child, delete the parent
+                        DELETE FROM Resources WHERE internalId = deleted_grand_parent_row.parentId
+                                            AND NOT EXISTS (SELECT 1 FROM Resources WHERE parentId = deleted_grand_parent_row.parentId)
+                                            RETURNING * INTO deleted_grand_parent_row;
+                        IF FOUND THEN
+                            INSERT INTO DeletedResources VALUES (deleted_grand_parent_row.resourceType, deleted_grand_parent_row.publicId);
+                        END IF;
+                    END IF;
+                END IF;
+            END IF;
+        END IF;
+    END IF;
+END;
+
+-- set the global properties that actually documents the DB version, revision and some of the capabilities
+-- modify only the ones that have changed
+DELETE FROM GlobalProperties WHERE property IN (4);
+INSERT INTO GlobalProperties VALUES (4, 10); -- GlobalProperty_DatabasePatchLevel
--- a/PostgreSQL/Plugins/SQL/PrepareIndex.sql	Mon Sep 14 14:37:40 2026 +0200
+++ b/PostgreSQL/Plugins/SQL/PrepareIndex.sql	Mon Sep 14 14:54:00 2026 +0200
@@ -859,7 +859,7 @@
 -- set the global properties that actually documents the DB version, revision and some of the capabilities
 DELETE FROM GlobalProperties WHERE property IN (1, 4, 6, 10, 11, 12, 13, 14);
 INSERT INTO GlobalProperties VALUES (1, 6); -- GlobalProperty_DatabaseSchemaVersion
-INSERT INTO GlobalProperties VALUES (4, 10); -- GlobalProperty_DatabasePatchLevel
+INSERT INTO GlobalProperties VALUES (4, 1099); -- GlobalProperty_DatabasePatchLevel
 INSERT INTO GlobalProperties VALUES (6, 1); -- GlobalProperty_GetTotalSizeIsFast
 INSERT INTO GlobalProperties VALUES (10, 1); -- GlobalProperty_HasTrigramIndex
 INSERT INTO GlobalProperties VALUES (11, 3); -- GlobalProperty_HasCreateInstance  -- this is actually the 3rd version of HasCreateInstance
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/PostgreSQL/Plugins/SQL/Upgrades/Rev10ToRev1099.sql	Mon Sep 14 14:54:00 2026 +0200
@@ -0,0 +1,3 @@
+-- Update from Rev 10 to Rev 1099
+-- the DeleteResource function is updated in PrepareIndex.sql
+