changeset 557:d8ee2f676a3c find-refactoring

wip: started implementing Find in PostgreSQL
author Alain Mazy <am@orthanc.team>
date Fri, 13 Sep 2024 11:56:25 +0200
parents 7057d9db8d9a
children d186007b0f1e
files Framework/Plugins/ISqlLookupFormatter.cpp Framework/Plugins/IndexBackend.cpp Framework/Plugins/IndexBackend.h PostgreSQL/Plugins/PostgreSQLIndex.cpp PostgreSQL/Plugins/PostgreSQLIndex.h
diffstat 5 files changed, 217 insertions(+), 25 deletions(-) [+]
line wrap: on
line diff
--- a/Framework/Plugins/ISqlLookupFormatter.cpp	Wed Sep 11 16:32:21 2024 +0200
+++ b/Framework/Plugins/ISqlLookupFormatter.cpp	Fri Sep 13 11:56:25 2024 +0200
@@ -664,10 +664,12 @@
     assert(upperLevel <= queryLevel &&
            queryLevel <= lowerLevel);
 
+    std::string ordering = "NULL::BIGINT AS rowNumber"; // "row_number() over (order by publicId) as rn";  // default ordering for now
 
     sql = ("SELECT " +
            strQueryLevel + ".publicId, " +
-           strQueryLevel + ".internalId" +
+           strQueryLevel + ".internalId, " +
+           ordering +
            " FROM Resources AS " + strQueryLevel);
 
 
--- a/Framework/Plugins/IndexBackend.cpp	Wed Sep 11 16:32:21 2024 +0200
+++ b/Framework/Plugins/IndexBackend.cpp	Fri Sep 13 11:56:25 2024 +0200
@@ -3056,4 +3056,184 @@
     backend.ConfigureDatabase(*manager, hasIdentifierTags, identifierTags);
     return manager.release();
   }
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 5)
+  bool IndexBackend::HasFindSupport() const
+  {
+    // TODO-FIND  move to child plugins ?
+    return true;
+  }
+#endif
+
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 5)
+
+#define C0_QUERY_ID 0
+#define C1_INTERNAL_ID 1
+#define C2_ROW_NUMBER 2
+#define C3_STRING_1 3
+#define C4_STRING_2 4
+#define C5_STRING_3 5
+#define C6_INT_1 6
+#define C7_INT_2 7
+#define C8_BIG_INT_1 8
+#define C9_BIG_INT_2 9
+
+#define QUERY_LOOKUP 1
+#define QUERY_MAIN_DICOM_TAGS 2
+#define QUERY_ATTACHMENTS 3
+#define STRINGIFY(x) #x
+#define TOSTRING(x) STRINGIFY(x)
+
+  void IndexBackend::ExecuteFind(Orthanc::DatabasePluginMessages::TransactionResponse& response,
+                                    DatabaseManager& manager,
+                                    const Orthanc::DatabasePluginMessages::Find_Request& request)
+  {
+    // TODO-FIND move to child plugins ?
+
+
+    // If we want the Find to use a read-only transaction, we can not create temporary tables with
+    // the lookup results.  So we must use a CTE (Common Table Expression).  
+    // However, a CTE can only be used in a single query -> we must unionize all the following 
+    // queries to retrieve values from various tables.
+    // However, to use UNION, all tables must have the same columns (numbers and types).  That's
+    // why we have generic column names.
+    // So, at the end we'll have only one very big query !
+
+    std::string sql;
+
+    // extract the resource id of interest by executing the lookup in a CTE
+    LookupFormatter formatter(manager.GetDialect());
+    std::string lookupSql;
+    ISqlLookupFormatter::Apply(lookupSql, formatter, request);
+
+    // base query, retrieve the ordered internalId and publicId of the selected resources
+    sql = "WITH Lookup AS (" + lookupSql + ") "
+          "SELECT "
+          "  " TOSTRING(QUERY_LOOKUP) " AS c0_queryId, "
+          "  Lookup.internalId AS c1_internalId, "
+          "  Lookup.rowNumber AS c2_rowNumber, "
+          "  Lookup.publicId AS c3_string1, "
+          "  NULL::TEXT AS c4_string2, "
+          "  NULL::TEXT AS c5_string3, "
+          "  NULL::INT AS c6_int1, "
+          "  NULL::INT AS c7_int2, "
+          "  NULL::BIGINT AS c8_big_int1, "
+          "  NULL::BIGINT AS c9_big_int2 "
+          "  FROM Lookup ";
+
+    // need MainDicomTags from resource ?
+    if (request.retrieve_main_dicom_tags())
+    {
+      sql += 
+          "UNION SELECT "
+          "  " TOSTRING(QUERY_MAIN_DICOM_TAGS) " AS c0_queryId, "
+          "  Lookup.internalId AS c1_internalId, "
+          "  NULL::BIGINT AS c2_rowNumber, "
+          "  value AS c3_string1, "
+          "  NULL::TEXT AS c4_string2, "
+          "  NULL::TEXT AS c5_string3, "
+          "  tagGroup AS c6_int1, "
+          "  tagElement AS c7_int2, "
+          "  NULL::BIGINT AS c8_big_int1, "
+          "  NULL::BIGINT AS c9_big_int2 "
+          "FROM MainDicomTags "
+          "INNER JOIN Lookup ON MainDicomTags.id = Lookup.internalId ";
+    }
+
+    // need resource attachments ?
+    if (request.retrieve_attachments())
+    {
+      sql += 
+          "UNION SELECT "
+          "  " TOSTRING(QUERY_ATTACHMENTS) " AS c0_queryId, "
+          "  Lookup.internalId AS c1_internalId, "
+          "  NULL::BIGINT AS c2_rowNumber, "
+          "  uuid AS c3_string1, "
+          "  uncompressedHash AS c4_string2, "
+          "  compressedHash AS c5_string3, "
+          "  fileType AS c6_int1, "
+          "  compressionType AS c7_int2, "
+          "  compressedSize AS c8_big_int1, "
+          "  uncompressedSize AS c9_big_int2 "
+          "FROM AttachedFiles "
+          "INNER JOIN Lookup ON AttachedFiles.id = Lookup.internalId ";
+    }
+
+
+    // TODO-FIND: other requests
+
+    sql += "ORDER BY c0_queryId, c2_rowNumber";  // this is really important to make sure that the Lookup query is the first one to provide results since we use it to create the responses element !
+
+    DatabaseManager::StandaloneStatement statement(manager, sql);  // TODO-FIND: cache dynamic statement ?  Probably worth it since it can be very complex queries !
+    formatter.PrepareStatement(statement);
+    statement.Execute();
+    
+
+    std::map<int64_t, Orthanc::DatabasePluginMessages::Find_Response*> responses;
+
+    while (!statement.IsDone())
+    {
+      int32_t queryId = statement.ReadInteger32(C0_QUERY_ID);
+      int64_t internalId = statement.ReadInteger64(C1_INTERNAL_ID);
+      
+      assert(queryId == QUERY_LOOKUP || responses.find(internalId) != responses.end()); // the QUERY_LOOKUP must be read first and must create the response before any other query tries to populate the fields
+
+      switch (queryId)
+      {
+        case QUERY_LOOKUP:
+          responses[internalId] = response.add_find();  // the protobuf message is the owner of the response
+          responses[internalId]->set_public_id(statement.ReadString(C3_STRING_1));
+          responses[internalId]->set_internal_id(internalId);
+          break;
+
+        case QUERY_MAIN_DICOM_TAGS:
+        {
+          Orthanc::DatabasePluginMessages::Find_Response_ResourceContent* content = NULL;  // the protobuf response will be the owner
+          
+          switch (request.level())
+          {
+            case Orthanc::DatabasePluginMessages::ResourceType::RESOURCE_PATIENT:
+              content = responses[internalId]->mutable_patient_content();
+              break;
+            case Orthanc::DatabasePluginMessages::ResourceType::RESOURCE_STUDY:
+              content = responses[internalId]->mutable_study_content();
+              break;
+            case Orthanc::DatabasePluginMessages::ResourceType::RESOURCE_SERIES:
+              content = responses[internalId]->mutable_series_content();
+              break;
+            case Orthanc::DatabasePluginMessages::ResourceType::RESOURCE_INSTANCE:
+              content = responses[internalId]->mutable_instance_content();
+              break;
+            default:
+              throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+          }
+
+          Orthanc::DatabasePluginMessages::Find_Response_Tag* tag = content->add_main_dicom_tags();
+          tag->set_value(statement.ReadString(C3_STRING_1));
+          tag->set_group(statement.ReadInteger32(C6_INT_1));
+          tag->set_element(statement.ReadInteger32(C7_INT_2));
+          }; break;
+
+        case QUERY_ATTACHMENTS:
+        {
+          Orthanc::DatabasePluginMessages::FileInfo* attachment = responses[internalId]->add_attachments();  // the protobuf response is the owner
+
+          attachment->set_uuid(statement.ReadString(C3_STRING_1));
+          attachment->set_uncompressed_hash(statement.ReadString(C4_STRING_2));
+          attachment->set_compressed_hash(statement.ReadString(C5_STRING_3));
+          attachment->set_content_type(statement.ReadInteger32(C6_INT_1));
+          attachment->set_compression_type(statement.ReadInteger32(C7_INT_2));
+          attachment->set_compressed_size(statement.ReadInteger64(C8_BIG_INT_1));
+          attachment->set_uncompressed_size(statement.ReadInteger64(C9_BIG_INT_2));
+        }; break;
+
+        default:
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+      }
+      statement.Next();
+    }    
+  }
+#endif
+
 }
--- a/Framework/Plugins/IndexBackend.h	Wed Sep 11 16:32:21 2024 +0200
+++ b/Framework/Plugins/IndexBackend.h	Fri Sep 13 11:56:25 2024 +0200
@@ -435,6 +435,16 @@
       return true;
     }
 
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 5)
+    virtual bool HasFindSupport() const ORTHANC_OVERRIDE;
+#endif
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 5)
+    virtual void ExecuteFind(Orthanc::DatabasePluginMessages::TransactionResponse& response,
+                             DatabaseManager& manager,
+                             const Orthanc::DatabasePluginMessages::Find_Request& request) ORTHANC_OVERRIDE;
+#endif
+
 
     /**
      * "maxDatabaseRetries" is to handle
--- a/PostgreSQL/Plugins/PostgreSQLIndex.cpp	Wed Sep 11 16:32:21 2024 +0200
+++ b/PostgreSQL/Plugins/PostgreSQLIndex.cpp	Fri Sep 13 11:56:25 2024 +0200
@@ -684,22 +684,22 @@
   }
 
 
-#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 5)
-  bool PostgreSQLIndex::HasFindSupport() const
-  {
-    // TODO-FIND
-    return false;
-  }
-#endif
+// #if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 5)
+//   bool PostgreSQLIndex::HasFindSupport() const
+//   {
+//     // TODO-FIND
+//     return false;
+//   }
+// #endif
 
 
-#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 5)
-  void PostgreSQLIndex::ExecuteFind(Orthanc::DatabasePluginMessages::TransactionResponse& response,
-                                    DatabaseManager& manager,
-                                    const Orthanc::DatabasePluginMessages::Find_Request& request)
-  {
-    // TODO-FIND
-    throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
-  }
-#endif
+// #if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 5)
+//   void PostgreSQLIndex::ExecuteFind(Orthanc::DatabasePluginMessages::TransactionResponse& response,
+//                                     DatabaseManager& manager,
+//                                     const Orthanc::DatabasePluginMessages::Find_Request& request)
+//   {
+//     // TODO-FIND
+//     throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+//   }
+// #endif
 }
--- a/PostgreSQL/Plugins/PostgreSQLIndex.h	Wed Sep 11 16:32:21 2024 +0200
+++ b/PostgreSQL/Plugins/PostgreSQLIndex.h	Fri Sep 13 11:56:25 2024 +0200
@@ -135,14 +135,14 @@
                                         int64_t& compressedSize,
                                         int64_t& uncompressedSize) ORTHANC_OVERRIDE;
 
-#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 5)
-    virtual bool HasFindSupport() const ORTHANC_OVERRIDE;
-#endif
+// #if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 5)
+//     virtual bool HasFindSupport() const ORTHANC_OVERRIDE;
+// #endif
 
-#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 5)
-    virtual void ExecuteFind(Orthanc::DatabasePluginMessages::TransactionResponse& response,
-                             DatabaseManager& manager,
-                             const Orthanc::DatabasePluginMessages::Find_Request& request) ORTHANC_OVERRIDE;
-#endif
+// #if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 5)
+//     virtual void ExecuteFind(Orthanc::DatabasePluginMessages::TransactionResponse& response,
+//                              DatabaseManager& manager,
+//                              const Orthanc::DatabasePluginMessages::Find_Request& request) ORTHANC_OVERRIDE;
+// #endif
   };
 }