# HG changeset patch # User Sebastien Jodogne # Date 1721390589 -7200 # Node ID e63538a6d9deeeb7cdf90af91af5e1be9852d024 # Parent 3fd4d5833c55738d4124079f6a9e48298e9c9839# Parent 95a3802ad13326c1083bf31ca31c8ec9e3f9d81a integration find-refactoring->find-refactoring-clean diff -r 3fd4d5833c55 -r e63538a6d9de OrthancServer/Plugins/Engine/OrthancPluginDatabaseV4.cpp --- a/OrthancServer/Plugins/Engine/OrthancPluginDatabaseV4.cpp Fri Jul 19 10:09:08 2024 +0200 +++ b/OrthancServer/Plugins/Engine/OrthancPluginDatabaseV4.cpp Fri Jul 19 14:03:09 2024 +0200 @@ -135,6 +135,136 @@ } + static void Convert(DatabasePluginMessages::DatabaseConstraint& target, + const DatabaseConstraint& source) + { + target.set_level(Convert(source.GetLevel())); + target.set_tag_group(source.GetTag().GetGroup()); + target.set_tag_element(source.GetTag().GetElement()); + target.set_is_identifier_tag(source.IsIdentifier()); + target.set_is_case_sensitive(source.IsCaseSensitive()); + target.set_is_mandatory(source.IsMandatory()); + + target.mutable_values()->Reserve(source.GetValuesCount()); + for (size_t j = 0; j < source.GetValuesCount(); j++) + { + target.add_values(source.GetValue(j)); + } + + switch (source.GetConstraintType()) + { + case ConstraintType_Equal: + target.set_type(DatabasePluginMessages::CONSTRAINT_EQUAL); + break; + + case ConstraintType_SmallerOrEqual: + target.set_type(DatabasePluginMessages::CONSTRAINT_SMALLER_OR_EQUAL); + break; + + case ConstraintType_GreaterOrEqual: + target.set_type(DatabasePluginMessages::CONSTRAINT_GREATER_OR_EQUAL); + break; + + case ConstraintType_Wildcard: + target.set_type(DatabasePluginMessages::CONSTRAINT_WILDCARD); + break; + + case ConstraintType_List: + target.set_type(DatabasePluginMessages::CONSTRAINT_LIST); + break; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + + static DatabasePluginMessages::LabelsConstraintType Convert(LabelsConstraint constraint) + { + switch (constraint) + { + case LabelsConstraint_All: + return DatabasePluginMessages::LABELS_CONSTRAINT_ALL; + + case LabelsConstraint_Any: + return DatabasePluginMessages::LABELS_CONSTRAINT_ANY; + + case LabelsConstraint_None: + return DatabasePluginMessages::LABELS_CONSTRAINT_NONE; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + + static void Convert(DatabasePluginMessages::Find_Request_ChildrenSpecification& target, + const FindRequest::ChildrenSpecification& source) + { + target.set_retrieve_identifiers(source.IsRetrieveIdentifiers()); + + for (std::set::const_iterator it = source.GetMetadata().begin(); it != source.GetMetadata().end(); ++it) + { + target.add_retrieve_metadata(*it); + } + + for (std::set::const_iterator it = source.GetMainDicomTags().begin(); it != source.GetMainDicomTags().end(); ++it) + { + DatabasePluginMessages::Find_Request_Tag* tag = target.add_retrieve_main_dicom_tags(); + tag->set_group(it->GetGroup()); + tag->set_element(it->GetElement()); + } + } + + + static void Convert(FindResponse::Resource& target, + ResourceType level, + const DatabasePluginMessages::Find_Response_ResourceContent& source) + { + for (int i = 0; i < source.main_dicom_tags().size(); i++) + { + target.AddStringDicomTag(level, source.main_dicom_tags(i).group(), + source.main_dicom_tags(i).element(), source.main_dicom_tags(i).value()); + } + + for (int i = 0; i < source.metadata().size(); i++) + { + target.AddMetadata(level, static_cast(source.metadata(i).key()), source.metadata(i).value()); + } + } + + + static void Convert(FindResponse::Resource& target, + ResourceType level, + const DatabasePluginMessages::Find_Response_ChildrenContent& source) + { + for (int i = 0; i < source.identifiers().size(); i++) + { + target.AddChildIdentifier(level, source.identifiers(i)); + } + + for (int i = 0; i < source.main_dicom_tags().size(); i++) + { + const DicomTag tag(source.main_dicom_tags(i).group(), source.main_dicom_tags(i).element()); + + for (int j = 0; j < source.main_dicom_tags(i).values().size(); j++) + { + target.AddChildrenMainDicomTagValue(level, tag, source.main_dicom_tags(i).values(j)); + } + } + + for (int i = 0; i < source.metadata().size(); i++) + { + MetadataType key = static_cast(source.metadata(i).key()); + + for (int j = 0; j < source.metadata(i).values().size(); j++) + { + target.AddChildrenMetadataValue(level, key, source.metadata(i).values(j)); + } + } + } + + static void Execute(DatabasePluginMessages::Response& response, const OrthancPluginDatabaseV4& database, const DatabasePluginMessages::Request& request) @@ -973,7 +1103,7 @@ return response.is_disk_size_above().result(); } - + virtual void ApplyLookupResources(std::list& resourcesId, std::list* instancesId, // Can be NULL if not needed const DatabaseConstraints& lookup, @@ -997,47 +1127,7 @@ for (size_t i = 0; i < lookup.GetSize(); i++) { - const DatabaseConstraint& source = lookup.GetConstraint(i); - - DatabasePluginMessages::DatabaseConstraint* target = request.mutable_lookup_resources()->add_lookup(); - target->set_level(Convert(source.GetLevel())); - target->set_tag_group(source.GetTag().GetGroup()); - target->set_tag_element(source.GetTag().GetElement()); - target->set_is_identifier_tag(source.IsIdentifier()); - target->set_is_case_sensitive(source.IsCaseSensitive()); - target->set_is_mandatory(source.IsMandatory()); - - target->mutable_values()->Reserve(source.GetValuesCount()); - for (size_t j = 0; j < source.GetValuesCount(); j++) - { - target->add_values(source.GetValue(j)); - } - - switch (source.GetConstraintType()) - { - case ConstraintType_Equal: - target->set_type(DatabasePluginMessages::CONSTRAINT_EQUAL); - break; - - case ConstraintType_SmallerOrEqual: - target->set_type(DatabasePluginMessages::CONSTRAINT_SMALLER_OR_EQUAL); - break; - - case ConstraintType_GreaterOrEqual: - target->set_type(DatabasePluginMessages::CONSTRAINT_GREATER_OR_EQUAL); - break; - - case ConstraintType_Wildcard: - target->set_type(DatabasePluginMessages::CONSTRAINT_WILDCARD); - break; - - case ConstraintType_List: - target->set_type(DatabasePluginMessages::CONSTRAINT_LIST); - break; - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } + Convert(*request.mutable_lookup_resources()->add_lookup(), lookup.GetConstraint(i)); } for (std::set::const_iterator it = labels.begin(); it != labels.end(); ++it) @@ -1045,23 +1135,7 @@ request.mutable_lookup_resources()->add_labels(*it); } - switch (labelsConstraint) - { - case LabelsConstraint_All: - request.mutable_lookup_resources()->set_labels_constraint(DatabasePluginMessages::LABELS_CONSTRAINT_ALL); - break; - - case LabelsConstraint_Any: - request.mutable_lookup_resources()->set_labels_constraint(DatabasePluginMessages::LABELS_CONSTRAINT_ANY); - break; - - case LabelsConstraint_None: - request.mutable_lookup_resources()->set_labels_constraint(DatabasePluginMessages::LABELS_CONSTRAINT_NONE); - break; - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } + request.mutable_lookup_resources()->set_labels_constraint(Convert(labelsConstraint)); DatabasePluginMessages::TransactionResponse response; ExecuteTransaction(response, DatabasePluginMessages::OPERATION_LOOKUP_RESOURCES, request); @@ -1285,8 +1359,170 @@ const FindRequest& request, const Capabilities& capabilities) ORTHANC_OVERRIDE { - // TODO-FIND - throw OrthancException(ErrorCode_NotImplemented); + if (capabilities.HasFindSupport()) + { + DatabasePluginMessages::TransactionRequest dbRequest; + dbRequest.mutable_find()->set_level(Convert(request.GetLevel())); + + if (request.GetOrthancIdentifiers().HasPatientId()) + { + dbRequest.mutable_find()->set_orthanc_id_patient(request.GetOrthancIdentifiers().GetPatientId()); + } + + if (request.GetOrthancIdentifiers().HasStudyId()) + { + dbRequest.mutable_find()->set_orthanc_id_study(request.GetOrthancIdentifiers().GetStudyId()); + } + + if (request.GetOrthancIdentifiers().HasSeriesId()) + { + dbRequest.mutable_find()->set_orthanc_id_series(request.GetOrthancIdentifiers().GetSeriesId()); + } + + if (request.GetOrthancIdentifiers().HasInstanceId()) + { + dbRequest.mutable_find()->set_orthanc_id_instance(request.GetOrthancIdentifiers().GetInstanceId()); + } + + for (size_t i = 0; i < request.GetDicomTagConstraints().GetSize(); i++) + { + Convert(*dbRequest.mutable_find()->add_dicom_tag_constraints(), request.GetDicomTagConstraints().GetConstraint(i)); + } + + if (request.HasLimits()) + { + dbRequest.mutable_find()->mutable_limits()->set_since(request.GetLimitsSince()); + dbRequest.mutable_find()->mutable_limits()->set_count(request.GetLimitsCount()); + } + + for (std::set::const_iterator it = request.GetLabels().begin(); it != request.GetLabels().end(); ++it) + { + dbRequest.mutable_find()->add_labels(*it); + } + + dbRequest.mutable_find()->set_labels_constraint(Convert(request.GetLabelsConstraint())); + + // TODO-FIND: ordering_ + // TODO-FIND: metadataConstraints__ + + dbRequest.mutable_find()->set_retrieve_main_dicom_tags(request.IsRetrieveMainDicomTags()); + dbRequest.mutable_find()->set_retrieve_metadata(request.IsRetrieveMetadata()); + dbRequest.mutable_find()->set_retrieve_labels(request.IsRetrieveLabels()); + dbRequest.mutable_find()->set_retrieve_attachments(request.IsRetrieveAttachments()); + dbRequest.mutable_find()->set_retrieve_parent_identifier(request.IsRetrieveParentIdentifier()); + dbRequest.mutable_find()->set_retrieve_at_least_one_instance(request.IsRetrieveOneInstanceIdentifier()); + + if (request.GetLevel() == ResourceType_Study || + request.GetLevel() == ResourceType_Series || + request.GetLevel() == ResourceType_Instance) + { + dbRequest.mutable_find()->mutable_parent_patient()->set_retrieve_main_dicom_tags(request.GetParentSpecification(ResourceType_Patient).IsRetrieveMainDicomTags()); + dbRequest.mutable_find()->mutable_parent_patient()->set_retrieve_metadata(request.GetParentSpecification(ResourceType_Patient).IsRetrieveMetadata()); + } + + if (request.GetLevel() == ResourceType_Series || + request.GetLevel() == ResourceType_Instance) + { + dbRequest.mutable_find()->mutable_parent_study()->set_retrieve_main_dicom_tags(request.GetParentSpecification(ResourceType_Study).IsRetrieveMainDicomTags()); + dbRequest.mutable_find()->mutable_parent_study()->set_retrieve_metadata(request.GetParentSpecification(ResourceType_Study).IsRetrieveMetadata()); + } + + if (request.GetLevel() == ResourceType_Instance) + { + dbRequest.mutable_find()->mutable_parent_series()->set_retrieve_main_dicom_tags(request.GetParentSpecification(ResourceType_Series).IsRetrieveMainDicomTags()); + dbRequest.mutable_find()->mutable_parent_series()->set_retrieve_metadata(request.GetParentSpecification(ResourceType_Series).IsRetrieveMetadata()); + } + + if (request.GetLevel() == ResourceType_Patient) + { + Convert(*dbRequest.mutable_find()->mutable_children_studies(), request.GetChildrenSpecification(ResourceType_Study)); + } + + if (request.GetLevel() == ResourceType_Patient || + request.GetLevel() == ResourceType_Study) + { + Convert(*dbRequest.mutable_find()->mutable_children_series(), request.GetChildrenSpecification(ResourceType_Series)); + } + + if (request.GetLevel() == ResourceType_Patient || + request.GetLevel() == ResourceType_Study || + request.GetLevel() == ResourceType_Series) + { + Convert(*dbRequest.mutable_find()->mutable_children_instances(), request.GetChildrenSpecification(ResourceType_Instance)); + } + + DatabasePluginMessages::TransactionResponse dbResponse; + ExecuteTransaction(dbResponse, DatabasePluginMessages::OPERATION_FIND, dbRequest); + + for (int i = 0; i < dbResponse.find().size(); i++) + { + const DatabasePluginMessages::Find_Response& source = dbResponse.find(i); + + std::unique_ptr target( + new FindResponse::Resource(request.GetLevel(), source.internal_id(), source.public_id())); + + if (request.IsRetrieveParentIdentifier()) + { + target->SetParentIdentifier(source.parent_public_id()); + } + + for (int i = 0; i < source.labels().size(); i++) + { + target->AddLabel(source.labels(i)); + } + + for (int i = 0; i < source.attachments().size(); i++) + { + target->AddAttachment(Convert(source.attachments(i))); + } + + Convert(*target, ResourceType_Patient, source.patient_content()); + + if (request.GetLevel() == ResourceType_Study || + request.GetLevel() == ResourceType_Series || + request.GetLevel() == ResourceType_Instance) + { + Convert(*target, ResourceType_Study, source.study_content()); + } + + if (request.GetLevel() == ResourceType_Series || + request.GetLevel() == ResourceType_Instance) + { + Convert(*target, ResourceType_Series, source.series_content()); + } + + if (request.GetLevel() == ResourceType_Instance) + { + Convert(*target, ResourceType_Instance, source.instance_content()); + } + + if (request.GetLevel() == ResourceType_Patient) + { + Convert(*target, ResourceType_Patient, source.children_studies_content()); + } + + if (request.GetLevel() == ResourceType_Patient || + request.GetLevel() == ResourceType_Study) + { + Convert(*target, ResourceType_Study, source.children_series_content()); + } + + if (request.GetLevel() == ResourceType_Patient || + request.GetLevel() == ResourceType_Study || + request.GetLevel() == ResourceType_Series) + { + Convert(*target, ResourceType_Series, source.children_instances_content()); + } + + response.Add(target.release()); + } + + throw OrthancException(ErrorCode_NotImplemented); + } + else + { + throw OrthancException(ErrorCode_NotImplemented); + } } @@ -1294,9 +1530,16 @@ const Capabilities& capabilities, const FindRequest& request) ORTHANC_OVERRIDE { - // TODO-FIND - Compatibility::GenericFind find(*this); - find.ExecuteFind(identifiers, capabilities, request); + if (capabilities.HasFindSupport()) + { + // The integrated version of "ExecuteFind()" should have been called + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + Compatibility::GenericFind find(*this); + find.ExecuteFind(identifiers, capabilities, request); + } } @@ -1305,9 +1548,16 @@ const FindRequest& request, const std::string& identifier) ORTHANC_OVERRIDE { - // TODO-FIND - Compatibility::GenericFind find(*this); - find.ExecuteExpand(response, capabilities, request, identifier); + if (capabilities.HasFindSupport()) + { + // The integrated version of "ExecuteFind()" should have been called + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + Compatibility::GenericFind find(*this); + find.ExecuteExpand(response, capabilities, request, identifier); + } } }; @@ -1397,6 +1647,9 @@ dbCapabilities_.SetAtomicIncrementGlobalProperty(systemInfo.supports_increment_global_property()); dbCapabilities_.SetUpdateAndGetStatistics(systemInfo.has_update_and_get_statistics()); dbCapabilities_.SetMeasureLatency(systemInfo.has_measure_latency()); + dbCapabilities_.SetHasFindSupport(systemInfo.supports_find()); + + printf(">>> %d\n", dbCapabilities_.HasFindSupport()); } open_ = true; @@ -1527,6 +1780,6 @@ bool OrthancPluginDatabaseV4::HasIntegratedFind() const { - return false; // TODO-FIND + return dbCapabilities_.HasFindSupport(); } } diff -r 3fd4d5833c55 -r e63538a6d9de OrthancServer/Plugins/Include/orthanc/OrthancCPlugin.h --- a/OrthancServer/Plugins/Include/orthanc/OrthancCPlugin.h Fri Jul 19 10:09:08 2024 +0200 +++ b/OrthancServer/Plugins/Include/orthanc/OrthancCPlugin.h Fri Jul 19 14:03:09 2024 +0200 @@ -121,7 +121,7 @@ #define ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER 1 #define ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER 12 -#define ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER 4 +#define ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER 5 #if !defined(ORTHANC_PLUGINS_VERSION_IS_ABOVE) diff -r 3fd4d5833c55 -r e63538a6d9de OrthancServer/Plugins/Include/orthanc/OrthancDatabasePlugin.proto --- a/OrthancServer/Plugins/Include/orthanc/OrthancDatabasePlugin.proto Fri Jul 19 10:09:08 2024 +0200 +++ b/OrthancServer/Plugins/Include/orthanc/OrthancDatabasePlugin.proto Fri Jul 19 14:03:09 2024 +0200 @@ -141,6 +141,7 @@ bool supports_increment_global_property = 5; bool has_update_and_get_statistics = 6; bool has_measure_latency = 7; + bool supports_find = 8; // New in Orthanc 1.12.5 } } @@ -288,11 +289,12 @@ OPERATION_GET_CHILDREN_METADATA = 42; OPERATION_GET_LAST_CHANGE_INDEX = 43; OPERATION_LOOKUP_RESOURCE_AND_PARENT = 44; - OPERATION_ADD_LABEL = 45; // New in Orthanc 1.12.0 - OPERATION_REMOVE_LABEL = 46; // New in Orthanc 1.12.0 - OPERATION_LIST_LABELS = 47; // New in Orthanc 1.12.0 - OPERATION_INCREMENT_GLOBAL_PROPERTY = 48; // New in Orthanc 1.12.3 - OPERATION_UPDATE_AND_GET_STATISTICS = 49; // New in Orthanc 1.12.3 + OPERATION_ADD_LABEL = 45; // New in Orthanc 1.12.0 + OPERATION_REMOVE_LABEL = 46; // New in Orthanc 1.12.0 + OPERATION_LIST_LABELS = 47; // New in Orthanc 1.12.0 + OPERATION_INCREMENT_GLOBAL_PROPERTY = 48; // New in Orthanc 1.12.3 + OPERATION_UPDATE_AND_GET_STATISTICS = 49; // New in Orthanc 1.12.3 + OPERATION_FIND = 50; // New in Orthanc 1.12.5 } message Rollback { @@ -824,6 +826,99 @@ } } +message Find { // New in Orthanc 1.12.5 + message Request { // This corresponds to "FindRequest" in C++ + message Tag { + uint32 group = 1; + uint32 element = 2; + } + message Limits { + uint64 since = 1; + uint64 count = 2; + } + message ParentSpecification { + bool retrieve_main_dicom_tags = 1; + bool retrieve_metadata = 2; + } + message ChildrenSpecification { + bool retrieve_identifiers = 1; + repeated int32 retrieve_metadata = 2; + repeated Tag retrieve_main_dicom_tags = 3; + } + + // Part 1 of the request: Constraints + ResourceType level = 1; + string orthanc_id_patient = 2; // optional - GetOrthancIdentifiers().GetPatientId(); + string orthanc_id_study = 3; // optional - GetOrthancIdentifiers().GetStudyId(); + string orthanc_id_series = 4; // optional - GetOrthancIdentifiers().GetSeriesId(); + string orthanc_id_instance = 5; // optional - GetOrthancIdentifiers().GetInstanceId(); + repeated DatabaseConstraint dicom_tag_constraints = 6; + Limits limits = 7; // optional + repeated string labels = 8; + LabelsConstraintType labels_constraint = 9; + + // TODO-FIND: ordering_ + // TODO-FIND: metadataConstraints_ + + // Part 2 of the request: What is to be retrieved + bool retrieve_main_dicom_tags = 100; + bool retrieve_metadata = 101; + bool retrieve_labels = 102; + bool retrieve_attachments = 103; + bool retrieve_parent_identifier = 104; + bool retrieve_at_least_one_instance = 105; + ParentSpecification parent_patient = 106; + ParentSpecification parent_study = 107; + ParentSpecification parent_series = 108; + ChildrenSpecification children_studies = 109; + ChildrenSpecification children_series = 110; + ChildrenSpecification children_instances = 111; + } + + message Response { // This corresponds to "FindResponse" in C++ + message Tag { + uint32 group = 1; + uint32 element = 2; + string value = 3; + } + message Metadata { + int32 key = 1; + string value = 2; + } + message MultipleTags { + uint32 group = 1; + uint32 element = 2; + repeated string values = 3; + } + message MultipleMetadata { + int32 key = 1; + repeated string values = 2; + } + message ResourceContent { + repeated Tag main_dicom_tags = 1; + repeated Metadata metadata = 2; + } + message ChildrenContent { + repeated string identifiers = 1; + repeated MultipleTags main_dicom_tags = 2; + repeated MultipleMetadata metadata = 3; + } + + int64 internal_id = 1; + string public_id = 2; + string parent_public_id = 3; // optional + repeated string labels = 4; + repeated FileInfo attachments = 5; + ResourceContent patient_content = 6; + ResourceContent study_content = 7; + ResourceContent series_content = 8; + ResourceContent instance_content = 9; + ChildrenContent children_studies_content = 10; + ChildrenContent children_series_content = 11; + ChildrenContent children_instances_content = 12; + } +} + message TransactionRequest { sfixed64 transaction = 1; TransactionOperation operation = 2; @@ -878,6 +973,7 @@ ListLabels.Request list_labels = 147; IncrementGlobalProperty.Request increment_global_property = 148; UpdateAndGetStatistics.Request update_and_get_statistics = 149; + Find.Request find = 150; } message TransactionResponse { @@ -931,6 +1027,7 @@ ListLabels.Response list_labels = 147; IncrementGlobalProperty.Response increment_global_property = 148; UpdateAndGetStatistics.Response update_and_get_statistics = 149; + repeated Find.Response find = 150; // One message per found resources } enum RequestType { diff -r 3fd4d5833c55 -r e63538a6d9de OrthancServer/Resources/Graveyard/FindRefactoringForSQLite.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/Resources/Graveyard/FindRefactoringForSQLite.cpp Fri Jul 19 14:03:09 2024 +0200 @@ -0,0 +1,167 @@ +#if 0 + // TODO-FIND: Remove this implementation, as it should be done by + // the compatibility mode implemented by "GenericFind" + + virtual void ExecuteFind(FindResponse& response, + const FindRequest& request, + const std::vector& normalized) ORTHANC_OVERRIDE + { +#if 0 + Compatibility::GenericFind find(*this); + find.Execute(response, request); +#else + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "DROP TABLE IF EXISTS FilteredResourcesIds"); + s.Run(); + } + + { + + LookupFormatter formatter; + + std::string sqlLookup; + LookupFormatter::Apply(sqlLookup, + formatter, + normalized, + request.GetLevel(), + request.GetLabels(), + request.GetLabelsConstraint(), + (request.HasLimits() ? request.GetLimitsCount() : 0)); // TODO: handles since and count + + { + // first create a temporary table that with the filtered and ordered results + sqlLookup = "CREATE TEMPORARY TABLE FilteredResourcesIds AS " + sqlLookup; + + SQLite::Statement statement(db_, SQLITE_FROM_HERE_DYNAMIC(sqlLookup), sqlLookup); + formatter.Bind(statement); + statement.Run(); + } + + { + // create the response item with the public ids only + SQLite::Statement statement(db_, SQLITE_FROM_HERE, "SELECT publicId FROM FilteredResourcesIds"); + formatter.Bind(statement); + + while (statement.Step()) + { + const std::string resourceId = statement.ColumnString(0); + response.Add(new FindResponse::Resource(request.GetLevel(), resourceId)); + } + } + + // request Each response content through INNER JOIN with the temporary table + if (request.IsRetrieveMainDicomTags()) + { + // TODO-FIND: handle the case where we request tags from multiple levels + SQLite::Statement statement(db_, SQLITE_FROM_HERE, + "SELECT publicId, tagGroup, tagElement, value FROM MainDicomTags AS tags " + " INNER JOIN FilteredResourcesIds ON tags.id = FilteredResourcesIds.internalId"); + formatter.Bind(statement); + + while (statement.Step()) + { + const std::string& resourceId = statement.ColumnString(0); + assert(response.HasResource(resourceId)); + response.GetResource(resourceId).AddStringDicomTag(statement.ColumnInt(1), + statement.ColumnInt(2), + statement.ColumnString(3)); + } + } + + if (request.IsRetrieveChildrenIdentifiers()) + { + SQLite::Statement statement(db_, SQLITE_FROM_HERE, + "SELECT filtered.publicId, childLevel.publicId AS childPublicId " + "FROM Resources as currentLevel " + " INNER JOIN FilteredResourcesIds filtered ON filtered.internalId = currentLevel.internalId " + " INNER JOIN Resources childLevel ON childLevel.parentId = currentLevel.internalId"); + formatter.Bind(statement); + + while (statement.Step()) + { + const std::string& resourceId = statement.ColumnString(0); + assert(response.HasResource(resourceId)); + response.GetResource(resourceId).AddChildIdentifier(GetChildResourceType(request.GetLevel()), statement.ColumnString(1)); + } + } + + if (request.IsRetrieveParentIdentifier()) + { + SQLite::Statement statement(db_, SQLITE_FROM_HERE, + "SELECT filtered.publicId, parentLevel.publicId AS parentPublicId " + "FROM Resources as currentLevel " + " INNER JOIN FilteredResourcesIds filtered ON filtered.internalId = currentLevel.internalId " + " INNER JOIN Resources parentLevel ON currentLevel.parentId = parentLevel.internalId"); + + while (statement.Step()) + { + const std::string& resourceId = statement.ColumnString(0); + const std::string& parentId = statement.ColumnString(1); + assert(response.HasResource(resourceId)); + response.GetResource(resourceId).SetParentIdentifier(parentId); + } + } + + if (request.IsRetrieveMetadata()) + { + SQLite::Statement statement(db_, SQLITE_FROM_HERE, + "SELECT filtered.publicId, metadata.type, metadata.value " + "FROM Metadata " + " INNER JOIN FilteredResourcesIds filtered ON filtered.internalId = Metadata.id"); + + while (statement.Step()) + { + const std::string& resourceId = statement.ColumnString(0); + assert(response.HasResource(resourceId)); + response.GetResource(resourceId).AddMetadata(static_cast(statement.ColumnInt(1)), + statement.ColumnString(2)); + } + } + + if (request.IsRetrieveLabels()) + { + SQLite::Statement statement(db_, SQLITE_FROM_HERE, + "SELECT filtered.publicId, label " + "FROM Labels " + " INNER JOIN FilteredResourcesIds filtered ON filtered.internalId = Labels.id"); + + while (statement.Step()) + { + const std::string& resourceId = statement.ColumnString(0); + assert(response.HasResource(resourceId)); + response.GetResource(resourceId).AddLabel(statement.ColumnString(1)); + } + } + + if (request.IsRetrieveAttachments()) + { + SQLite::Statement statement(db_, SQLITE_FROM_HERE, + "SELECT filtered.publicId, uuid, fileType, uncompressedSize, compressionType, compressedSize, " + " uncompressedMD5, compressedMD5 " + "FROM AttachedFiles " + " INNER JOIN FilteredResourcesIds filtered ON filtered.internalId = AttachedFiles.id"); + + while (statement.Step()) + { + const std::string& resourceId = statement.ColumnString(0); + FileInfo attachment = FileInfo(statement.ColumnString(1), + static_cast(statement.ColumnInt(2)), + statement.ColumnInt64(3), + statement.ColumnString(6), + static_cast(statement.ColumnInt(4)), + statement.ColumnInt64(5), + statement.ColumnString(7)); + + assert(response.HasResource(resourceId)); + response.GetResource(resourceId).AddAttachment(attachment); + }; + } + + // TODO-FIND: implement other responseContent: ResponseContent_ChildInstanceId, ResponseContent_ChildrenMetadata (later: ResponseContent_IsStable) + + } + +#endif + } +#endif + diff -r 3fd4d5833c55 -r e63538a6d9de OrthancServer/Sources/Database/Compatibility/GenericFind.cpp --- a/OrthancServer/Sources/Database/Compatibility/GenericFind.cpp Fri Jul 19 10:09:08 2024 +0200 +++ b/OrthancServer/Sources/Database/Compatibility/GenericFind.cpp Fri Jul 19 14:03:09 2024 +0200 @@ -209,7 +209,11 @@ GetChildrenIdentifiers(identifiers, transaction_, request.GetOrthancIdentifiers(), ResourceType_Series, request.GetLevel()); } else if (request.GetMetadataConstraintsCount() == 0 && - request.GetOrdering().empty()) + request.GetOrdering().empty() && + !request.GetOrthancIdentifiers().HasPatientId() && + !request.GetOrthancIdentifiers().HasStudyId() && + !request.GetOrthancIdentifiers().HasSeriesId() && + !request.GetOrthancIdentifiers().HasInstanceId()) { transaction_.ApplyLookupResources(identifiers, NULL /* TODO-FIND: Could the "instancesId" information be exploited? */, request.GetDicomTagConstraints(), request.GetLevel(), request.GetLabels(), diff -r 3fd4d5833c55 -r e63538a6d9de OrthancServer/Sources/Database/FindRequest.h --- a/OrthancServer/Sources/Database/FindRequest.h Fri Jul 19 10:09:08 2024 +0200 +++ b/OrthancServer/Sources/Database/FindRequest.h Fri Jul 19 14:03:09 2024 +0200 @@ -43,23 +43,6 @@ class FindRequest : public boost::noncopyable { public: - /** - - TO DISCUSS: - - (1) ResponseContent_ChildInstanceId = (1 << 6), // When you need to access all tags from a patient/study/series, you might need to open the DICOM file of a child instance - - if (requestedTags.size() > 0 && resourceType != ResourceType_Instance) // if we are requesting specific tags that might be outside of the MainDicomTags, we must get a childInstanceId too - { - responseContent = static_cast(responseContent | FindRequest::ResponseContent_ChildInstanceId); - } - - - (2) ResponseContent_IsStable = (1 << 8), // This is currently not saved in DB but it could be in the future. - - **/ - - enum KeyType // used for ordering and filters { KeyType_DicomTag, @@ -250,13 +233,15 @@ ResourceType level_; // The level of the response (the filtering on tags, labels and metadata also happens at this level) OrthancIdentifiers orthancIdentifiers_; // The response must belong to this Orthanc resources hierarchy DatabaseConstraints dicomTagConstraints_; // All tags filters (note: the order is not important) - std::deque /* TODO-FIND */ metadataConstraints_; // All metadata filters (note: the order is not important) bool hasLimits_; uint64_t limitsSince_; uint64_t limitsCount_; std::set labels_; LabelsConstraint labelsConstraint_; + + // TODO-FIND std::deque ordering_; // The ordering criteria (note: the order is important !) + std::deque /* TODO-FIND */ metadataConstraints_; // All metadata filters (note: the order is not important) bool retrieveMainDicomTags_; bool retrieveMetadata_; @@ -331,9 +316,11 @@ uint64_t GetLimitsCount() const; - void AddOrdering(const DicomTag& tag, OrderingDirection direction); + void AddOrdering(const DicomTag& tag, + OrderingDirection direction); - void AddOrdering(MetadataType metadataType, OrderingDirection direction); + void AddOrdering(MetadataType metadataType, + OrderingDirection direction); const std::deque& GetOrdering() const { diff -r 3fd4d5833c55 -r e63538a6d9de OrthancServer/Sources/Database/IDatabaseWrapper.h --- a/OrthancServer/Sources/Database/IDatabaseWrapper.h Fri Jul 19 10:09:08 2024 +0200 +++ b/OrthancServer/Sources/Database/IDatabaseWrapper.h Fri Jul 19 14:03:09 2024 +0200 @@ -54,6 +54,7 @@ bool hasAtomicIncrementGlobalProperty_; bool hasUpdateAndGetStatistics_; bool hasMeasureLatency_; + bool hasFindSupport_; public: Capabilities() : @@ -62,7 +63,8 @@ hasLabelsSupport_(false), hasAtomicIncrementGlobalProperty_(false), hasUpdateAndGetStatistics_(false), - hasMeasureLatency_(false) + hasMeasureLatency_(false), + hasFindSupport_(false) { } @@ -125,6 +127,16 @@ { return hasMeasureLatency_; } + + void SetHasFindSupport(bool value) + { + hasFindSupport_ = value; + } + + bool HasFindSupport() const + { + return hasFindSupport_; + } }; diff -r 3fd4d5833c55 -r e63538a6d9de OrthancServer/Sources/Database/SQLiteDatabaseWrapper.cpp --- a/OrthancServer/Sources/Database/SQLiteDatabaseWrapper.cpp Fri Jul 19 10:09:08 2024 +0200 +++ b/OrthancServer/Sources/Database/SQLiteDatabaseWrapper.cpp Fri Jul 19 14:03:09 2024 +0200 @@ -1139,175 +1139,6 @@ target.insert(s.ColumnString(0)); } } - - -#if 0 - // TODO-FIND: Remove this implementation, as it should be done by - // the compatibility mode implemented by "GenericFind" - - virtual void ExecuteFind(FindResponse& response, - const FindRequest& request, - const std::vector& normalized) ORTHANC_OVERRIDE - { -#if 0 - Compatibility::GenericFind find(*this); - find.Execute(response, request); -#else - { - SQLite::Statement s(db_, SQLITE_FROM_HERE, "DROP TABLE IF EXISTS FilteredResourcesIds"); - s.Run(); - } - - { - - LookupFormatter formatter; - - std::string sqlLookup; - LookupFormatter::Apply(sqlLookup, - formatter, - normalized, - request.GetLevel(), - request.GetLabels(), - request.GetLabelsConstraint(), - (request.HasLimits() ? request.GetLimitsCount() : 0)); // TODO: handles since and count - - { - // first create a temporary table that with the filtered and ordered results - sqlLookup = "CREATE TEMPORARY TABLE FilteredResourcesIds AS " + sqlLookup; - - SQLite::Statement statement(db_, SQLITE_FROM_HERE_DYNAMIC(sqlLookup), sqlLookup); - formatter.Bind(statement); - statement.Run(); - } - - { - // create the response item with the public ids only - SQLite::Statement statement(db_, SQLITE_FROM_HERE, "SELECT publicId FROM FilteredResourcesIds"); - formatter.Bind(statement); - - while (statement.Step()) - { - const std::string resourceId = statement.ColumnString(0); - response.Add(new FindResponse::Resource(request.GetLevel(), resourceId)); - } - } - - // request Each response content through INNER JOIN with the temporary table - if (request.IsRetrieveMainDicomTags()) - { - // TODO-FIND: handle the case where we request tags from multiple levels - SQLite::Statement statement(db_, SQLITE_FROM_HERE, - "SELECT publicId, tagGroup, tagElement, value FROM MainDicomTags AS tags " - " INNER JOIN FilteredResourcesIds ON tags.id = FilteredResourcesIds.internalId"); - formatter.Bind(statement); - - while (statement.Step()) - { - const std::string& resourceId = statement.ColumnString(0); - assert(response.HasResource(resourceId)); - response.GetResource(resourceId).AddStringDicomTag(statement.ColumnInt(1), - statement.ColumnInt(2), - statement.ColumnString(3)); - } - } - - if (request.IsRetrieveChildrenIdentifiers()) - { - SQLite::Statement statement(db_, SQLITE_FROM_HERE, - "SELECT filtered.publicId, childLevel.publicId AS childPublicId " - "FROM Resources as currentLevel " - " INNER JOIN FilteredResourcesIds filtered ON filtered.internalId = currentLevel.internalId " - " INNER JOIN Resources childLevel ON childLevel.parentId = currentLevel.internalId"); - formatter.Bind(statement); - - while (statement.Step()) - { - const std::string& resourceId = statement.ColumnString(0); - assert(response.HasResource(resourceId)); - response.GetResource(resourceId).AddChildIdentifier(GetChildResourceType(request.GetLevel()), statement.ColumnString(1)); - } - } - - if (request.IsRetrieveParentIdentifier()) - { - SQLite::Statement statement(db_, SQLITE_FROM_HERE, - "SELECT filtered.publicId, parentLevel.publicId AS parentPublicId " - "FROM Resources as currentLevel " - " INNER JOIN FilteredResourcesIds filtered ON filtered.internalId = currentLevel.internalId " - " INNER JOIN Resources parentLevel ON currentLevel.parentId = parentLevel.internalId"); - - while (statement.Step()) - { - const std::string& resourceId = statement.ColumnString(0); - const std::string& parentId = statement.ColumnString(1); - assert(response.HasResource(resourceId)); - response.GetResource(resourceId).SetParentIdentifier(parentId); - } - } - - if (request.IsRetrieveMetadata()) - { - SQLite::Statement statement(db_, SQLITE_FROM_HERE, - "SELECT filtered.publicId, metadata.type, metadata.value " - "FROM Metadata " - " INNER JOIN FilteredResourcesIds filtered ON filtered.internalId = Metadata.id"); - - while (statement.Step()) - { - const std::string& resourceId = statement.ColumnString(0); - assert(response.HasResource(resourceId)); - response.GetResource(resourceId).AddMetadata(static_cast(statement.ColumnInt(1)), - statement.ColumnString(2)); - } - } - - if (request.IsRetrieveLabels()) - { - SQLite::Statement statement(db_, SQLITE_FROM_HERE, - "SELECT filtered.publicId, label " - "FROM Labels " - " INNER JOIN FilteredResourcesIds filtered ON filtered.internalId = Labels.id"); - - while (statement.Step()) - { - const std::string& resourceId = statement.ColumnString(0); - assert(response.HasResource(resourceId)); - response.GetResource(resourceId).AddLabel(statement.ColumnString(1)); - } - } - - if (request.IsRetrieveAttachments()) - { - SQLite::Statement statement(db_, SQLITE_FROM_HERE, - "SELECT filtered.publicId, uuid, fileType, uncompressedSize, compressionType, compressedSize, " - " uncompressedMD5, compressedMD5 " - "FROM AttachedFiles " - " INNER JOIN FilteredResourcesIds filtered ON filtered.internalId = AttachedFiles.id"); - - while (statement.Step()) - { - const std::string& resourceId = statement.ColumnString(0); - FileInfo attachment = FileInfo(statement.ColumnString(1), - static_cast(statement.ColumnInt(2)), - statement.ColumnInt64(3), - statement.ColumnString(6), - static_cast(statement.ColumnInt(4)), - statement.ColumnInt64(5), - statement.ColumnString(7)); - - assert(response.HasResource(resourceId)); - response.GetResource(resourceId).AddAttachment(attachment); - }; - } - - // TODO-FIND: implement other responseContent: ResponseContent_ChildInstanceId, ResponseContent_ChildrenMetadata (later: ResponseContent_IsStable) - - } - -#endif - } -#endif - };