# HG changeset patch # User Sebastien Jodogne # Date 1546606312 -3600 # Node ID d40c5fecd16052b6f8564513a018109999a81308 # Parent e6c13ddd26d95bbcbcfba9f73c158bddf70f7460 new extension implemented for PostgreSQL: CreateInstance diff -r e6c13ddd26d9 -r d40c5fecd160 Framework/Plugins/OrthancCppDatabasePlugin.h --- a/Framework/Plugins/OrthancCppDatabasePlugin.h Thu Jan 03 14:04:46 2019 +0100 +++ b/Framework/Plugins/OrthancCppDatabasePlugin.h Fri Jan 04 13:51:52 2019 +0100 @@ -491,12 +491,25 @@ virtual void ClearMainDicomTags(int64_t internalId) = 0; + virtual bool HasCreateInstance() const = 0; + #if ORTHANC_PLUGINS_HAS_DATABASE_CONSTRAINT == 1 virtual void LookupResources(const std::vector& lookup, OrthancPluginResourceType queryLevel, uint32_t limit, bool requestSomeInstance) = 0; #endif + +#if ORTHANC_PLUGINS_HAS_DATABASE_CONSTRAINT == 1 + virtual void CreateInstance(OrthancPluginCreateInstanceResult& result, + const char* hashPatient, + const char* hashStudy, + const char* hashSeries, + const char* hashInstance) + { + throw std::runtime_error("Not implemented"); + } +#endif }; @@ -1503,6 +1516,26 @@ } #endif + +#if ORTHANC_PLUGINS_HAS_DATABASE_CONSTRAINT == 1 + static OrthancPluginErrorCode CreateInstance(OrthancPluginCreateInstanceResult* output, + void* payload, + const char* hashPatient, + const char* hashStudy, + const char* hashSeries, + const char* hashInstance) + { + IDatabaseBackend* backend = reinterpret_cast(payload); + + try + { + backend->CreateInstance(*output, hashPatient, hashStudy, hashSeries, hashInstance); + return OrthancPluginErrorCode_Success; + } + ORTHANC_PLUGINS_DATABASE_CATCH + } +#endif + public: /** @@ -1585,6 +1618,12 @@ #if ORTHANC_PLUGINS_HAS_DATABASE_CONSTRAINT == 1 extensions.lookupResources = LookupResources; // New in Orthanc 1.5.2 (fast lookup) + + if (backend.HasCreateInstance()) + { + extensions.createInstance = CreateInstance; // New in Orthanc 1.5.2 (fast create) + } + performanceWarning = false; #endif diff -r e6c13ddd26d9 -r d40c5fecd160 PostgreSQL/CMakeLists.txt --- a/PostgreSQL/CMakeLists.txt Thu Jan 03 14:04:46 2019 +0100 +++ b/PostgreSQL/CMakeLists.txt Fri Jan 04 13:51:52 2019 +0100 @@ -52,7 +52,8 @@ EmbedResources( - POSTGRESQL_PREPARE_INDEX ${CMAKE_SOURCE_DIR}/Plugins/PrepareIndex.sql + POSTGRESQL_PREPARE_INDEX ${CMAKE_SOURCE_DIR}/Plugins/PrepareIndex.sql + POSTGRESQL_CREATE_INSTANCE ${CMAKE_SOURCE_DIR}/Plugins/CreateInstance.sql ) add_library(OrthancPostgreSQLIndex SHARED diff -r e6c13ddd26d9 -r d40c5fecd160 PostgreSQL/Plugins/CreateInstance.sql --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/PostgreSQL/Plugins/CreateInstance.sql Fri Jan 04 13:51:52 2019 +0100 @@ -0,0 +1,66 @@ +CREATE FUNCTION CreateInstance( + IN patient TEXT, + IN study TEXT, + IN series TEXT, + IN instance TEXT, + OUT isNewPatient BIGINT, + OUT isNewStudy BIGINT, + OUT isNewSeries BIGINT, + OUT isNewInstance BIGINT, + OUT patientKey BIGINT, + OUT studyKey BIGINT, + OUT seriesKey BIGINT, + OUT instanceKey BIGINT) AS $$ +BEGIN + SELECT internalId FROM Resources INTO instanceKey WHERE publicId = instance AND resourceType = 3; + + IF NOT (instanceKey IS NULL) THEN + -- This instance already exists, stop here + isNewInstance := 0; + ELSE + SELECT internalId FROM Resources INTO patientKey WHERE publicId = patient AND resourceType = 0; + SELECT internalId FROM Resources INTO studyKey WHERE publicId = study AND resourceType = 1; + SELECT internalId FROM Resources INTO seriesKey WHERE publicId = series AND resourceType = 2; + + IF patientKey IS NULL THEN + -- Must create a new patient + ASSERT studyKey IS NULL; + ASSERT seriesKey IS NULL; + ASSERT instanceKey IS NULL; + INSERT INTO Resources VALUES (DEFAULT, 0, patient, NULL) RETURNING internalId INTO patientKey; + isNewPatient := 1; + ELSE + isNewPatient := 0; + END IF; + + ASSERT NOT patientKey IS NULL; + + IF studyKey IS NULL THEN + -- Must create a new study + ASSERT seriesKey IS NULL; + ASSERT instanceKey IS NULL; + INSERT INTO Resources VALUES (DEFAULT, 1, study, patientKey) RETURNING internalId INTO studyKey; + isNewStudy := 1; + ELSE + isNewStudy := 0; + END IF; + + ASSERT NOT studyKey IS NULL; + + IF seriesKey IS NULL THEN + -- Must create a new series + ASSERT instanceKey IS NULL; + INSERT INTO Resources VALUES (DEFAULT, 2, series, studyKey) RETURNING internalId INTO seriesKey; + isNewSeries := 1; + ELSE + isNewSeries := 0; + END IF; + + ASSERT NOT seriesKey IS NULL; + ASSERT instanceKey IS NULL; + + INSERT INTO Resources VALUES (DEFAULT, 3, instance, seriesKey) RETURNING internalId INTO instanceKey; + isNewInstance := 1; + END IF; +END; +$$ LANGUAGE plpgsql; diff -r e6c13ddd26d9 -r d40c5fecd160 PostgreSQL/Plugins/PostgreSQLIndex.cpp --- a/PostgreSQL/Plugins/PostgreSQLIndex.cpp Thu Jan 03 14:04:46 2019 +0100 +++ b/PostgreSQL/Plugins/PostgreSQLIndex.cpp Fri Jan 04 13:51:52 2019 +0100 @@ -35,6 +35,7 @@ { // Some aliases for internal properties static const GlobalProperty GlobalProperty_HasTrigramIndex = GlobalProperty_DatabaseInternal0; + static const GlobalProperty GlobalProperty_HasCreateInstance = GlobalProperty_DatabaseInternal1; } @@ -126,7 +127,8 @@ PostgreSQLTransaction t(*db); int hasTrigram = 0; - if (!LookupGlobalIntegerProperty(hasTrigram, *db, t, Orthanc::GlobalProperty_HasTrigramIndex) || + if (!LookupGlobalIntegerProperty(hasTrigram, *db, t, + Orthanc::GlobalProperty_HasTrigramIndex) || hasTrigram != 1) { /** @@ -164,6 +166,27 @@ } } + { + PostgreSQLTransaction t(*db); + + int hasCreateInstance = 0; + if (!LookupGlobalIntegerProperty(hasCreateInstance, *db, t, + Orthanc::GlobalProperty_HasCreateInstance) || + hasCreateInstance != 1) + { + LOG(INFO) << "Installing the CreateInstance extension"; + + std::string query; + Orthanc::EmbeddedResources::GetFileResource + (query, Orthanc::EmbeddedResources::POSTGRESQL_CREATE_INSTANCE); + db->Execute(query); + + SetGlobalIntegerProperty(*db, t, Orthanc::GlobalProperty_HasCreateInstance, 1); + + t.Commit(); + } + } + return db.release(); } @@ -195,4 +218,53 @@ return ReadInteger64(statement, 0); } + + + void PostgreSQLIndex::CreateInstance(OrthancPluginCreateInstanceResult& result, + const char* hashPatient, + const char* hashStudy, + const char* hashSeries, + const char* hashInstance) + { + DatabaseManager::CachedStatement statement( + STATEMENT_FROM_HERE, GetManager(), + "SELECT * FROM CreateInstance(${patient}, ${study}, ${series}, ${instance})"); + + statement.SetParameterType("patient", ValueType_Utf8String); + statement.SetParameterType("study", ValueType_Utf8String); + statement.SetParameterType("series", ValueType_Utf8String); + statement.SetParameterType("instance", ValueType_Utf8String); + + Dictionary args; + args.SetUtf8Value("patient", hashPatient); + args.SetUtf8Value("study", hashStudy); + args.SetUtf8Value("series", hashSeries); + args.SetUtf8Value("instance", hashInstance); + + statement.Execute(args); + + if (statement.IsDone() || + statement.GetResultFieldsCount() != 8) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_Database); + } + + for (size_t i = 0; i < 8; i++) + { + statement.SetResultFieldType(i, ValueType_Integer64); + } + + result.isNewInstance = (ReadInteger64(statement, 3) == 1); + result.instanceId = ReadInteger64(statement, 7); + + if (result.isNewInstance) + { + result.isNewPatient = (ReadInteger64(statement, 0) == 1); + result.isNewStudy = (ReadInteger64(statement, 1) == 1); + result.isNewSeries = (ReadInteger64(statement, 2) == 1); + result.patientId = ReadInteger64(statement, 4); + result.studyId = ReadInteger64(statement, 5); + result.seriesId = ReadInteger64(statement, 6); + } + } } diff -r e6c13ddd26d9 -r d40c5fecd160 PostgreSQL/Plugins/PostgreSQLIndex.h --- a/PostgreSQL/Plugins/PostgreSQLIndex.h Thu Jan 03 14:04:46 2019 +0100 +++ b/PostgreSQL/Plugins/PostgreSQLIndex.h Fri Jan 04 13:51:52 2019 +0100 @@ -72,5 +72,18 @@ virtual int64_t CreateResource(const char* publicId, OrthancPluginResourceType type); + + virtual bool HasCreateInstance() const + { + return true; + } + +#if ORTHANC_PLUGINS_HAS_DATABASE_CONSTRAINT == 1 + virtual void CreateInstance(OrthancPluginCreateInstanceResult& result, + const char* hashPatient, + const char* hashStudy, + const char* hashSeries, + const char* hashInstance); +#endif }; } diff -r e6c13ddd26d9 -r d40c5fecd160 PostgreSQL/UnitTests/PostgreSQLTests.cpp --- a/PostgreSQL/UnitTests/PostgreSQLTests.cpp Thu Jan 03 14:04:46 2019 +0100 +++ b/PostgreSQL/UnitTests/PostgreSQLTests.cpp Fri Jan 04 13:51:52 2019 +0100 @@ -34,6 +34,7 @@ # undef S_IXOTH #endif +#include "../Plugins/PostgreSQLIndex.h" #include "../Plugins/PostgreSQLStorageArea.h" #include "../../Framework/PostgreSQL/PostgreSQLTransaction.h" #include "../../Framework/PostgreSQL/PostgreSQLResult.h" @@ -437,3 +438,76 @@ ASSERT_TRUE(db->DoesTableExist("test2")); } + +TEST(PostgreSQLIndex, CreateInstance) +{ + OrthancDatabases::PostgreSQLIndex db(globalParameters_); + db.SetClearAll(true); + db.Open(); + + std::string s; + ASSERT_TRUE(db.LookupGlobalProperty(s, Orthanc::GlobalProperty_DatabaseInternal1)); + ASSERT_EQ("1", s); + + OrthancPluginCreateInstanceResult r1, r2; + + memset(&r1, 0, sizeof(r1)); + db.CreateInstance(r1, "a", "b", "c", "d"); + ASSERT_TRUE(r1.isNewInstance); + ASSERT_TRUE(r1.isNewSeries); + ASSERT_TRUE(r1.isNewStudy); + ASSERT_TRUE(r1.isNewPatient); + + memset(&r2, 0, sizeof(r2)); + db.CreateInstance(r2, "a", "b", "c", "d"); + ASSERT_FALSE(r2.isNewInstance); + ASSERT_EQ(r1.instanceId, r2.instanceId); + + // Breaking the hierarchy + memset(&r2, 0, sizeof(r2)); + ASSERT_THROW(db.CreateInstance(r2, "a", "e", "c", "f"), Orthanc::OrthancException); + + memset(&r2, 0, sizeof(r2)); + db.CreateInstance(r2, "a", "b", "c", "e"); + ASSERT_TRUE(r2.isNewInstance); + ASSERT_FALSE(r2.isNewSeries); + ASSERT_FALSE(r2.isNewStudy); + ASSERT_FALSE(r2.isNewPatient); + ASSERT_EQ(r1.patientId, r2.patientId); + ASSERT_EQ(r1.studyId, r2.studyId); + ASSERT_EQ(r1.seriesId, r2.seriesId); + ASSERT_NE(r1.instanceId, r2.instanceId); + + memset(&r2, 0, sizeof(r2)); + db.CreateInstance(r2, "a", "b", "f", "g"); + ASSERT_TRUE(r2.isNewInstance); + ASSERT_TRUE(r2.isNewSeries); + ASSERT_FALSE(r2.isNewStudy); + ASSERT_FALSE(r2.isNewPatient); + ASSERT_EQ(r1.patientId, r2.patientId); + ASSERT_EQ(r1.studyId, r2.studyId); + ASSERT_NE(r1.seriesId, r2.seriesId); + ASSERT_NE(r1.instanceId, r2.instanceId); + + memset(&r2, 0, sizeof(r2)); + db.CreateInstance(r2, "a", "h", "i", "j"); + ASSERT_TRUE(r2.isNewInstance); + ASSERT_TRUE(r2.isNewSeries); + ASSERT_TRUE(r2.isNewStudy); + ASSERT_FALSE(r2.isNewPatient); + ASSERT_EQ(r1.patientId, r2.patientId); + ASSERT_NE(r1.studyId, r2.studyId); + ASSERT_NE(r1.seriesId, r2.seriesId); + ASSERT_NE(r1.instanceId, r2.instanceId); + + memset(&r2, 0, sizeof(r2)); + db.CreateInstance(r2, "k", "l", "m", "n"); + ASSERT_TRUE(r2.isNewInstance); + ASSERT_TRUE(r2.isNewSeries); + ASSERT_TRUE(r2.isNewStudy); + ASSERT_TRUE(r2.isNewPatient); + ASSERT_NE(r1.patientId, r2.patientId); + ASSERT_NE(r1.studyId, r2.studyId); + ASSERT_NE(r1.seriesId, r2.seriesId); + ASSERT_NE(r1.instanceId, r2.instanceId); +}