Mercurial > hg > orthanc-databases
changeset 599:cda99cc96843 find-refactoring tip
childCount in the Resources table instead of a separate table
author | Alain Mazy <am@orthanc.team> |
---|---|
date | Thu, 28 Nov 2024 12:42:36 +0100 |
parents | 8f6e7ae942f3 |
children | |
files | Framework/Plugins/IndexBackend.cpp PostgreSQL/CMakeLists.txt PostgreSQL/NEWS PostgreSQL/Plugins/SQL/Downgrades/Rev3bToRev2.sql PostgreSQL/Plugins/SQL/PrepareIndex.sql PostgreSQL/Plugins/SQL/Upgrades/Rev2ToRev3b.sql |
diffstat | 6 files changed, 250 insertions(+), 68 deletions(-) [+] |
line wrap: on
line diff
--- a/Framework/Plugins/IndexBackend.cpp Wed Nov 27 16:05:49 2024 +0100 +++ b/Framework/Plugins/IndexBackend.cpp Thu Nov 28 12:42:36 2024 +0100 @@ -3587,9 +3587,30 @@ } else if (childrenSpec->retrieve_count()) // no need to count if we have retrieved the list of identifiers { - if (HasChildCountTable()) + if (HasChildCountTable()) // TODO: rename in HasChildCountColumn ? { - // we get the count value either from the childCount table if it has been computed or from the Resources table + // // we get the count value either from the childCount table if it has been computed or from the Resources table + // sql += "UNION ALL SELECT " + // " " TOSTRING(QUERY_CHILDREN_COUNT) " AS c0_queryId, " + // " Lookup.internalId AS c1_internalId, " + // " " + formatter.FormatNull("BIGINT") + " AS c2_rowNumber, " + // " " + formatter.FormatNull("TEXT") + " AS c3_string1, " + // " " + formatter.FormatNull("TEXT") + " AS c4_string2, " + // " " + formatter.FormatNull("TEXT") + " AS c5_string3, " + // " " + formatter.FormatNull("INT") + " AS c6_int1, " + // " " + formatter.FormatNull("INT") + " AS c7_int2, " + // " " + formatter.FormatNull("INT") + " AS c8_int3, " + // " COALESCE(" + // " (ChildCount.childCount)," + // " (SELECT COUNT(childLevel.internalId)" + // " FROM Resources AS childLevel" + // " WHERE Lookup.internalId = childLevel.parentId" + // " )) AS c9_big_int1, " + // " " + formatter.FormatNull("BIGINT") + " AS c10_big_int2 " + // "FROM Lookup " + // "LEFT JOIN ChildCount ON Lookup.internalId = ChildCount.parentId "; + + // we get the count value either from the childCount column if it has been computed or from the Resources table sql += "UNION ALL SELECT " " " TOSTRING(QUERY_CHILDREN_COUNT) " AS c0_queryId, " " Lookup.internalId AS c1_internalId, " @@ -3601,14 +3622,14 @@ " " + formatter.FormatNull("INT") + " AS c7_int2, " " " + formatter.FormatNull("INT") + " AS c8_int3, " " COALESCE(" - " (ChildCount.childCount)," + " (Resources.childCount)," " (SELECT COUNT(childLevel.internalId)" " FROM Resources AS childLevel" " WHERE Lookup.internalId = childLevel.parentId" " )) AS c9_big_int1, " " " + formatter.FormatNull("BIGINT") + " AS c10_big_int2 " "FROM Lookup " - "LEFT JOIN ChildCount ON Lookup.internalId = ChildCount.parentId "; + "LEFT JOIN Resources ON Lookup.internalId = Resources.internalId "; } else { @@ -3687,7 +3708,31 @@ { if (HasChildCountTable()) { - // we get the count value either from the childCount table if it has been computed or from the Resources table + // // we get the count value either from the childCount table if it has been computed or from the Resources table + // sql += "UNION ALL SELECT " + // " " TOSTRING(QUERY_GRAND_CHILDREN_COUNT) " AS c0_queryId, " + // " Lookup.internalId AS c1_internalId, " + // " " + formatter.FormatNull("BIGINT") + " AS c2_rowNumber, " + // " " + formatter.FormatNull("TEXT") + " AS c3_string1, " + // " " + formatter.FormatNull("TEXT") + " AS c4_string2, " + // " " + formatter.FormatNull("TEXT") + " AS c5_string3, " + // " " + formatter.FormatNull("INT") + " AS c6_int1, " + // " " + formatter.FormatNull("INT") + " AS c7_int2, " + // " " + formatter.FormatNull("INT") + " AS c8_int3, " + // " COALESCE(" + // " (SELECT SUM(ChildCount.childCount)" + // " FROM ChildCount" + // " INNER JOIN Resources AS childLevel ON childLevel.parentId = Lookup.internalId" + // " WHERE ChildCount.parentId = childLevel.internalId)," + // " (SELECT COUNT(grandChildLevel.internalId)" + // " FROM Resources AS childLevel" + // " INNER JOIN Resources AS grandChildLevel ON childLevel.internalId = grandChildLevel.parentId" + // " WHERE Lookup.internalId = childLevel.parentId" + // " )) AS c9_big_int1, " + // " " + formatter.FormatNull("BIGINT") + " AS c10_big_int2 " + // "FROM Lookup "; + + // we get the count value either from the childCount column if it has been computed or from the Resources table sql += "UNION ALL SELECT " " " TOSTRING(QUERY_GRAND_CHILDREN_COUNT) " AS c0_queryId, " " Lookup.internalId AS c1_internalId, " @@ -3699,10 +3744,9 @@ " " + formatter.FormatNull("INT") + " AS c7_int2, " " " + formatter.FormatNull("INT") + " AS c8_int3, " " COALESCE(" - " (SELECT SUM(ChildCount.childCount)" - " FROM ChildCount" - " INNER JOIN Resources AS childLevel ON childLevel.parentId = Lookup.internalId" - " WHERE ChildCount.parentId = childLevel.internalId)," + " (SELECT SUM(childLevel.childCount)" + " FROM Resources AS childLevel" + " WHERE childLevel.parentId = Lookup.internalId)," " (SELECT COUNT(grandChildLevel.internalId)" " FROM Resources AS childLevel" " INNER JOIN Resources AS grandChildLevel ON childLevel.internalId = grandChildLevel.parentId" @@ -3799,7 +3843,33 @@ { if (HasChildCountTable()) { - // we get the count value either from the childCount table if it has been computed or from the Resources table + // // we get the count value either from the childCount table if it has been computed or from the Resources table + // sql += "UNION ALL SELECT " + // " " TOSTRING(QUERY_GRAND_GRAND_CHILDREN_COUNT) " AS c0_queryId, " + // " Lookup.internalId AS c1_internalId, " + // " " + formatter.FormatNull("BIGINT") + " AS c2_rowNumber, " + // " " + formatter.FormatNull("TEXT") + " AS c3_string1, " + // " " + formatter.FormatNull("TEXT") + " AS c4_string2, " + // " " + formatter.FormatNull("TEXT") + " AS c5_string3, " + // " " + formatter.FormatNull("INT") + " AS c6_int1, " + // " " + formatter.FormatNull("INT") + " AS c7_int2, " + // " " + formatter.FormatNull("INT") + " AS c8_int3, " + // " COALESCE(" + // " (SELECT SUM(ChildCount.childCount)" + // " FROM ChildCount" + // " INNER JOIN Resources AS childLevel ON childLevel.parentId = Lookup.internalId" + // " INNER JOIN Resources AS grandChildLevel ON grandChildLevel.parentId = childLevel.internalId" + // " WHERE ChildCount.parentId = grandChildLevel.internalId)," + // " (SELECT COUNT(grandGrandChildLevel.internalId)" + // " FROM Resources AS childLevel" + // " INNER JOIN Resources AS grandChildLevel ON childLevel.internalId = grandChildLevel.parentId" + // " INNER JOIN Resources AS grandGrandChildLevel ON grandChildLevel.internalId = grandGrandChildLevel.parentId" + // " WHERE Lookup.internalId = childLevel.parentId" + // " )) AS c9_big_int1, " + // " " + formatter.FormatNull("BIGINT") + " AS c10_big_int2 " + // "FROM Lookup "; + + // we get the count value either from the childCount column if it has been computed or from the Resources table sql += "UNION ALL SELECT " " " TOSTRING(QUERY_GRAND_GRAND_CHILDREN_COUNT) " AS c0_queryId, " " Lookup.internalId AS c1_internalId, " @@ -3811,11 +3881,10 @@ " " + formatter.FormatNull("INT") + " AS c7_int2, " " " + formatter.FormatNull("INT") + " AS c8_int3, " " COALESCE(" - " (SELECT SUM(ChildCount.childCount)" - " FROM ChildCount" + " (SELECT SUM(grandChildLevel.childCount)" + " FROM Resources AS grandChildLevel" " INNER JOIN Resources AS childLevel ON childLevel.parentId = Lookup.internalId" - " INNER JOIN Resources AS grandChildLevel ON grandChildLevel.parentId = childLevel.internalId" - " WHERE ChildCount.parentId = grandChildLevel.internalId)," + " WHERE grandChildLevel.parentId = childLevel.internalId)," " (SELECT COUNT(grandGrandChildLevel.internalId)" " FROM Resources AS childLevel" " INNER JOIN Resources AS grandChildLevel ON childLevel.internalId = grandChildLevel.parentId"
--- a/PostgreSQL/CMakeLists.txt Wed Nov 27 16:05:49 2024 +0100 +++ b/PostgreSQL/CMakeLists.txt Thu Nov 28 12:42:36 2024 +0100 @@ -92,7 +92,7 @@ POSTGRESQL_PREPARE_INDEX ${CMAKE_SOURCE_DIR}/Plugins/SQL/PrepareIndex.sql POSTGRESQL_UPGRADE_UNKNOWN_TO_REV1 ${CMAKE_SOURCE_DIR}/Plugins/SQL/Upgrades/UnknownToRev1.sql POSTGRESQL_UPGRADE_REV1_TO_REV2 ${CMAKE_SOURCE_DIR}/Plugins/SQL/Upgrades/Rev1ToRev2.sql - POSTGRESQL_UPGRADE_REV2_TO_REV3 ${CMAKE_SOURCE_DIR}/Plugins/SQL/Upgrades/Rev2ToRev3.sql + POSTGRESQL_UPGRADE_REV2_TO_REV3 ${CMAKE_SOURCE_DIR}/Plugins/SQL/Upgrades/Rev2ToRev3b.sql )
--- a/PostgreSQL/NEWS Wed Nov 27 16:05:49 2024 +0100 +++ b/PostgreSQL/NEWS Thu Nov 28 12:42:36 2024 +0100 @@ -16,9 +16,9 @@ creation of this index. If needed, you can create it manually before installing the new plugin by executing these SQL commands: - https://orthanc.uclouvain.be/hg/orthanc-databases/file/tip/PostgreSQL/Plugins/SQL/Upgrades/Partial/Rev2_1_ReplaceChildrenIndex.sql + https://orthanc.uclouvain.be/hg/orthanc-databases/file/tip/PostgreSQL/Plugins/SQL/Upgrades/Partial/Rev2_To_Rev3b.sql -* WARNING: A new table is being created when upgrading to this version. A new thread +* WARNING: A new column has been added in the resources table when upgrading to this version. A new thread is populating this new table and might consume DB bandwitdh and CPU.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/PostgreSQL/Plugins/SQL/Downgrades/Rev3bToRev2.sql Thu Nov 28 12:42:36 2024 +0100 @@ -0,0 +1,84 @@ +-- This file contains an SQL procedure to downgrade from schema Rev3 to Rev2 (version = 6, revision = 1). + -- It actually deletes the ChildCount table and triggers + -- It actually does not uninstall ChildrenIndex2 because it is anyway more efficient than + -- ChildrenIndex and is not incompatible with previous revisions. + +-- remove the childCount column in resources +DO $body$ +BEGIN + IF EXISTS (SELECT * FROM information_schema.columns WHERE table_schema='public' AND table_name='resources' AND column_name='childcount') THEN + ALTER TABLE Resources DROP COLUMN childcount; + ELSE + raise notice 'the resources.childcount column does not exists'; + END IF; + +END $body$; + + +------------------- re-install old CreateInstance function ------------------- +CREATE OR REPLACE FUNCTION CreateInstance( + IN patient_public_id TEXT, + IN study_public_id TEXT, + IN series_public_id TEXT, + IN instance_public_id TEXT, + OUT is_new_patient BIGINT, + OUT is_new_study BIGINT, + OUT is_new_series BIGINT, + OUT is_new_instance BIGINT, + OUT patient_internal_id BIGINT, + OUT study_internal_id BIGINT, + OUT series_internal_id BIGINT, + OUT instance_internal_id BIGINT) AS $body$ + +BEGIN + is_new_patient := 1; + is_new_study := 1; + is_new_series := 1; + is_new_instance := 1; + + BEGIN + INSERT INTO "resources" VALUES (DEFAULT, 0, patient_public_id, NULL) RETURNING internalid INTO patient_internal_id; + EXCEPTION + WHEN unique_violation THEN + is_new_patient := 0; + SELECT internalid INTO patient_internal_id FROM "resources" WHERE publicId = patient_public_id FOR UPDATE; -- also locks the resource and its parent to prevent from deletion while we complete this transaction + END; + + BEGIN + INSERT INTO "resources" VALUES (DEFAULT, 1, study_public_id, patient_internal_id) RETURNING internalid INTO study_internal_id; + EXCEPTION + WHEN unique_violation THEN + is_new_study := 0; + SELECT internalid INTO study_internal_id FROM "resources" WHERE publicId = study_public_id FOR UPDATE; -- also locks the resource and its parent to prevent from deletion while we complete this transaction END; + END; + + BEGIN + INSERT INTO "resources" VALUES (DEFAULT, 2, series_public_id, study_internal_id) RETURNING internalid INTO series_internal_id; + EXCEPTION + WHEN unique_violation THEN + is_new_series := 0; + SELECT internalid INTO series_internal_id FROM "resources" WHERE publicId = series_public_id FOR UPDATE; -- also locks the resource and its parent to prevent from deletion while we complete this transaction END; + END; + + BEGIN + INSERT INTO "resources" VALUES (DEFAULT, 3, instance_public_id, series_internal_id) RETURNING internalid INTO instance_internal_id; + EXCEPTION + WHEN unique_violation THEN + is_new_instance := 0; + SELECT internalid INTO instance_internal_id FROM "resources" WHERE publicId = instance_public_id FOR UPDATE; -- also locks the resource and its parent to prevent from deletion while we complete this transaction + END; + + IF is_new_instance > 0 THEN + -- Move the patient to the end of the recycling order. + PERFORM PatientAddedOrUpdated(patient_internal_id, 1); + END IF; +END; +$body$ LANGUAGE plpgsql; + + + + +-- 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, 11); +INSERT INTO GlobalProperties VALUES (4, 2); -- GlobalProperty_DatabasePatchLevel
--- a/PostgreSQL/Plugins/SQL/PrepareIndex.sql Wed Nov 27 16:05:49 2024 +0100 +++ b/PostgreSQL/Plugins/SQL/PrepareIndex.sql Thu Nov 28 12:42:36 2024 +0100 @@ -15,6 +15,7 @@ resourceType INTEGER NOT NULL, publicId VARCHAR(64) NOT NULL, parentId BIGINT REFERENCES Resources(internalId) ON DELETE CASCADE, + childCount INTEGER, CONSTRAINT UniquePublicId UNIQUE (publicId) ); @@ -529,7 +530,7 @@ is_new_instance := 1; BEGIN - INSERT INTO "resources" VALUES (DEFAULT, 0, patient_public_id, NULL) RETURNING internalid INTO patient_internal_id; + INSERT INTO "resources" VALUES (DEFAULT, 0, patient_public_id, NULL, 0) RETURNING internalid INTO patient_internal_id; EXCEPTION WHEN unique_violation THEN is_new_patient := 0; @@ -537,7 +538,7 @@ END; BEGIN - INSERT INTO "resources" VALUES (DEFAULT, 1, study_public_id, patient_internal_id) RETURNING internalid INTO study_internal_id; + INSERT INTO "resources" VALUES (DEFAULT, 1, study_public_id, patient_internal_id, 0) RETURNING internalid INTO study_internal_id; EXCEPTION WHEN unique_violation THEN is_new_study := 0; @@ -545,7 +546,7 @@ END; BEGIN - INSERT INTO "resources" VALUES (DEFAULT, 2, series_public_id, study_internal_id) RETURNING internalid INTO series_internal_id; + INSERT INTO "resources" VALUES (DEFAULT, 2, series_public_id, study_internal_id, 0) RETURNING internalid INTO series_internal_id; EXCEPTION WHEN unique_violation THEN is_new_series := 0; @@ -553,7 +554,7 @@ END; BEGIN - INSERT INTO "resources" VALUES (DEFAULT, 3, instance_public_id, series_internal_id) RETURNING internalid INTO instance_internal_id; + INSERT INTO "resources" VALUES (DEFAULT, 3, instance_public_id, series_internal_id, 0) RETURNING internalid INTO instance_internal_id; EXCEPTION WHEN unique_violation THEN is_new_instance := 0; @@ -590,89 +591,75 @@ $body$ LANGUAGE plpgsql; --- new in Rev3 +-- -- new in Rev3b -CREATE TABLE IF NOT EXISTS ChildCount ( - parentId BIGINT PRIMARY KEY REFERENCES Resources(internalId) ON DELETE CASCADE, - childCount INTEGER NOT NULL DEFAULT 0, - CONSTRAINT UniqueParentID UNIQUE (parentId) -); - - --- Computes the ChildCount for a number of resources for which it has not been computed yet. +-- Computes the childCount for a number of resources for which it has not been computed yet. -- This is actually used only after an update to Rev3. A thread will call this function -- at regular interval to update all missing values and stop once all values have been processed. CREATE OR REPLACE FUNCTION ComputeMissingChildCount( IN batch_size BIGINT, OUT updated_rows_count BIGINT ) RETURNS BIGINT AS $body$ - BEGIN - - INSERT INTO ChildCount (parentID, childCount) - SELECT r.internalId AS parentId, COUNT(childLevel.internalId) AS childCount - FROM Resources AS r - LEFT JOIN Resources AS childLevel ON r.internalId = childLevel.parentId - WHERE r.internalId IN ( - SELECT internalId FROM Resources AS r - WHERE resourceType < 3 AND NOT EXISTS(SELECT 1 FROM ChildCount WHERE ChildCount.parentId = r.internalId) - LIMIT batch_size) - GROUP BY r.internalId; + UPDATE Resources AS r + SET childCount = (SELECT COUNT(childLevel.internalId) + FROM Resources AS childLevel + WHERE childLevel.parentId = r.internalId) + WHERE internalId IN ( + SELECT internalId FROM Resources + WHERE resourceType < 3 AND childCount IS NULL + LIMIT batch_size); -- Get the number of rows affected GET DIAGNOSTICS updated_rows_count = ROW_COUNT; - END; $body$ LANGUAGE plpgsql; +DROP TRIGGER IF EXISTS IncrementChildCount on Resources; +DROP TRIGGER IF EXISTS DecrementChildCount on Resources; + CREATE OR REPLACE FUNCTION UpdateChildCount() RETURNS TRIGGER AS $body$ -DECLARE - parent_id BIGINT; BEGIN IF TG_OP = 'INSERT' THEN IF new.parentId IS NOT NULL THEN -- try to increment the childcount from the parent - UPDATE ChildCount + -- note that we already have the lock on this row because the parent is locked in CreateInstance + UPDATE Resources SET childCount = childCount + 1 - WHERE parentId = new.parentId - RETURNING parentId INTO parent_id; + WHERE internalId = new.parentId AND childCount IS NOT NULL; -- this should only happen for old studies whose childCount has not yet been computed + -- note: this child has already been added so it will be counted IF NOT FOUND THEN - INSERT INTO childcount (parentId, childCount) - SELECT parentID, COUNT(*) - FROM Resources - WHERE parentId = parent_id - GROUP BY parentId; + UPDATE Resources + SET childCount = (SELECT COUNT(*) + FROM Resources + WHERE internalId = new.parentId) + WHERE internalId = new.parentId; END IF; END IF; - -- this is a future parent, start counting the children - IF new.resourcetype < 3 THEN - insert into ChildCount (parentId, childCount) - values (new.internalId, 0); - END IF; - ELSIF TG_OP = 'DELETE' THEN IF old.parentId IS NOT NULL THEN -- Decrement the child count for the parent - UPDATE ChildCount + -- note that we already have the lock on this row because the parent is locked in DeleteResource + UPDATE Resources SET childCount = childCount - 1 - WHERE parentId = old.parentId - RETURNING parentId INTO parent_id; + WHERE internalId = old.parentId AND childCount IS NOT NULL; -- this should only happen for old studies whose childCount has not yet been computed + -- note: this child has already been removed so it will not be counted IF NOT FOUND THEN - INSERT INTO childcount (parentId, childCount) - SELECT parentID, COUNT(*) - FROM Resources - WHERE parentId = parent_id - GROUP BY parentId; + UPDATE Resources + SET childCount = (SELECT COUNT(*) + FROM Resources + WHERE internalId = new.parentId) + WHERE internalId = new.parentId; END IF; END IF; @@ -681,13 +668,11 @@ END; $body$ LANGUAGE plpgsql; -DROP TRIGGER IF EXISTS IncrementChildCount on Resources; CREATE TRIGGER IncrementChildCount AFTER INSERT ON Resources FOR EACH ROW EXECUTE PROCEDURE UpdateChildCount(); -DROP TRIGGER IF EXISTS DecrementChildCount on Resources; CREATE TRIGGER DecrementChildCount AFTER DELETE ON Resources FOR EACH ROW
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/PostgreSQL/Plugins/SQL/Upgrades/Rev2ToRev3b.sql Thu Nov 28 12:42:36 2024 +0100 @@ -0,0 +1,44 @@ +-- This file contains part of the changes required to upgrade from Revision 2 to Revision 3 (DB version 6 and revision 2) +-- It actually contains only the changes that: + -- can not be executed with an idempotent statement in SQL + -- or would polute the PrepareIndex.sql + -- do facilite an up-time upgrade +-- This file is executed only if the current schema is in revision 2 and it is executed +-- before PrepareIndex.sql that is idempotent. + + +-- create a new ChildrenIndex2 that is replacing ChildrenIndex. +-- We create it in this partial update so it can be created while the system is up ! +DO $$ +DECLARE + pg_version text; +BEGIN + SELECT version() INTO pg_version; + + IF substring(pg_version from 'PostgreSQL (\d+)\.')::int >= 11 THEN + -- PostgreSQL 11 or later + EXECUTE 'CREATE INDEX IF NOT EXISTS ChildrenIndex2 ON Resources USING btree (parentId ASC NULLS LAST) INCLUDE (publicId, internalId)'; + ELSE + EXECUTE 'CREATE INDEX IF NOT EXISTS ChildrenIndex2 ON Resources USING btree (parentId ASC NULLS LAST, publicId, internalId)'; + END IF; +END $$; + +DROP INDEX IF EXISTS ChildrenIndex; -- replaced by ChildrenIndex2 but no need to uninstall ChildrenIndex2 when downgrading + +-- add the childCount columns in Resources if not yet done + +DO $body$ +BEGIN + IF NOT EXISTS (SELECT * FROM information_schema.columns WHERE table_schema='public' AND table_name='resources' AND column_name='childcount') THEN + ALTER TABLE Resources ADD COLUMN childcount INTEGER; + ELSE + raise notice 'the resources.childcount column already exists'; + END IF; + +END $body$; + + + +-- other changes performed in PrepareIndex.sql: + -- add ChildCount tables and triggers +