# HG changeset patch # User Alain Mazy # Date 1725440040 -7200 # Node ID f75596b224e06421f965a93f96d5b139e7c7bf12 # Parent afd421225eb476ca6a19fe406c1ee538661b4fc8# Parent fc591f166d530540854be65b998513e203864b90 merged find-refactoring -> find-refactoring-clean (integration tests ok) diff -r afd421225eb4 -r f75596b224e0 NEWS --- a/NEWS Fri Aug 30 18:03:37 2024 +0200 +++ b/NEWS Wed Sep 04 10:54:00 2024 +0200 @@ -2,7 +2,8 @@ =============================== * TODO-FIND: complete the list of updated routes: - /studies?expand and sibbling routes now also return "Metadata" (if the DB implements 'extended-api-v1') + - /studies?expand and sibbling routes now also return "Metadata" (if the DB implements 'extended-api-v1') + - /studies?since=x&limit=0 and sibbling routes: limit=0 now means "no limit" instead of "no results" REST API -------- diff -r afd421225eb4 -r f75596b224e0 OrthancServer/Sources/Database/FindResponse.cpp --- a/OrthancServer/Sources/Database/FindResponse.cpp Fri Aug 30 18:03:37 2024 +0200 +++ b/OrthancServer/Sources/Database/FindResponse.cpp Wed Sep 04 10:54:00 2024 +0200 @@ -160,14 +160,8 @@ void FindResponse::ChildrenInformation::AddIdentifier(const std::string& identifier) { - if (identifiers_.find(identifier) == identifiers_.end()) - { - identifiers_.insert(identifier); - } - else - { - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } + // The same identifier can be added through AddChildIdentifier and through AddOneInstanceIdentifier + identifiers_.insert(identifier); } diff -r afd421225eb4 -r f75596b224e0 OrthancServer/Sources/Database/SQLiteDatabaseWrapper.cpp --- a/OrthancServer/Sources/Database/SQLiteDatabaseWrapper.cpp Fri Aug 30 18:03:37 2024 +0200 +++ b/OrthancServer/Sources/Database/SQLiteDatabaseWrapper.cpp Wed Sep 04 10:54:00 2024 +0200 @@ -386,32 +386,34 @@ const FindRequest& request, const Capabilities& capabilities) ORTHANC_OVERRIDE { - LookupFormatter formatter; - + const ResourceType requestLevel = request.GetLevel(); std::string sql; - LookupFormatter::Apply(sql, formatter, request); - sql = "CREATE TEMPORARY TABLE Lookup AS " + sql; - { + // clean previous lookup table SQLite::Statement s(db_, SQLITE_FROM_HERE, "DROP TABLE IF EXISTS Lookup"); s.Run(); } { + // extract the resource id of interest by executing the lookup + LookupFormatter formatter; + LookupFormatter::Apply(sql, formatter, request); + + sql = "CREATE TEMPORARY TABLE Lookup AS " + sql; + SQLite::Statement statement(db_, sql); formatter.Bind(statement); statement.Run(); - } - { SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT publicId, internalId FROM Lookup"); while (s.Step()) { - response.Add(new FindResponse::Resource(request.GetLevel(), s.ColumnInt64(1), s.ColumnString(0))); + response.Add(new FindResponse::Resource(requestLevel, s.ColumnInt64(1), s.ColumnString(0))); } } + // need MainDicomTags from resource ? if (request.IsRetrieveMainDicomTags()) { sql = "SELECT id, tagGroup, tagElement, value " @@ -422,13 +424,92 @@ while (s.Step()) { FindResponse::Resource& res = response.GetResourceByInternalId(s.ColumnInt64(0)); - res.AddStringDicomTag(request.GetLevel(), + res.AddStringDicomTag(requestLevel, + static_cast(s.ColumnInt(1)), + static_cast(s.ColumnInt(2)), + s.ColumnString(3)); + } + } + + // need MainDicomTags from parent ? + if (requestLevel > ResourceType_Patient && request.GetParentSpecification(static_cast(requestLevel - 1)).IsRetrieveMainDicomTags()) + { + sql = "SELECT currentLevel.internalId, tagGroup, tagElement, value " + "FROM MainDicomTags " + "INNER JOIN Resources currentLevel ON Lookup.internalId = currentLevel.internalId " + "INNER JOIN Lookup ON MainDicomTags.id = currentLevel.parentId"; + + SQLite::Statement s(db_, SQLITE_FROM_HERE, sql); + while (s.Step()) + { + FindResponse::Resource& res = response.GetResourceByInternalId(s.ColumnInt64(0)); + res.AddStringDicomTag(static_cast(requestLevel - 1), static_cast(s.ColumnInt(1)), static_cast(s.ColumnInt(2)), s.ColumnString(3)); } } + // need MainDicomTags from grandparent ? + if (requestLevel > ResourceType_Study && request.GetParentSpecification(static_cast(requestLevel - 2)).IsRetrieveMainDicomTags()) + { + sql = "SELECT currentLevel.internalId, tagGroup, tagElement, value " + "FROM MainDicomTags " + "INNER JOIN Resources currentLevel ON Lookup.internalId = currentLevel.internalId " + "INNER JOIN Resources parentLevel ON currentLevel.parentId = parentLevel.internalId " + "INNER JOIN Lookup ON MainDicomTags.id = parentLevel.parentId"; + + SQLite::Statement s(db_, SQLITE_FROM_HERE, sql); + while (s.Step()) + { + FindResponse::Resource& res = response.GetResourceByInternalId(s.ColumnInt64(0)); + res.AddStringDicomTag(static_cast(requestLevel - 2), + static_cast(s.ColumnInt(1)), + static_cast(s.ColumnInt(2)), + s.ColumnString(3)); + } + } + + // need MainDicomTags from children ? + if (requestLevel <= ResourceType_Series && request.GetChildrenSpecification(static_cast(requestLevel + 1)).GetMainDicomTags().size() > 0) + { + sql = "SELECT Lookup.internalId, tagGroup, tagElement, value " + "FROM MainDicomTags " + "INNER JOIN Resources childLevel ON childLevel.parentId = Lookup.internalId " + "INNER JOIN Lookup ON MainDicomTags.id = childLevel.internalId "; + + SQLite::Statement s(db_, SQLITE_FROM_HERE, sql); + while (s.Step()) + { + FindResponse::Resource& res = response.GetResourceByInternalId(s.ColumnInt64(0)); + res.AddChildrenMainDicomTagValue(static_cast(requestLevel + 1), + DicomTag(static_cast(s.ColumnInt(1)), + static_cast(s.ColumnInt(2))), + s.ColumnString(3)); + } + } + + // need MainDicomTags from grandchildren ? + if (requestLevel <= ResourceType_Study && request.GetChildrenSpecification(static_cast(requestLevel + 2)).GetMainDicomTags().size() > 0) + { + sql = "SELECT Lookup.internalId, tagGroup, tagElement, value " + "FROM MainDicomTags " + "INNER JOIN Resources childLevel ON childLevel.parentId = Lookup.internalId " + "INNER JOIN Resources grandChildLevel ON childLevel.parentId = Lookup.internalId " + "INNER JOIN Lookup ON MainDicomTags.id = grandChildLevel.internalId "; + + SQLite::Statement s(db_, SQLITE_FROM_HERE, sql); + while (s.Step()) + { + FindResponse::Resource& res = response.GetResourceByInternalId(s.ColumnInt64(0)); + res.AddChildrenMainDicomTagValue(static_cast(requestLevel + 2), + DicomTag(static_cast(s.ColumnInt(1)), + static_cast(s.ColumnInt(2))), + s.ColumnString(3)); + } + } + + // need parent identifier ? if (request.IsRetrieveParentIdentifier()) { sql = "SELECT currentLevel.internalId, parentLevel.publicId " @@ -444,6 +525,7 @@ } } + // need resource metadata ? if (request.IsRetrieveMetadata()) { sql = "SELECT id, type, value " @@ -454,15 +536,16 @@ while (s.Step()) { FindResponse::Resource& res = response.GetResourceByInternalId(s.ColumnInt64(0)); - res.AddMetadata(request.GetLevel(), + res.AddMetadata(requestLevel, static_cast(s.ColumnInt(1)), s.ColumnString(2)); } } + // need resource labels ? if (request.IsRetrieveLabels()) { - sql = "SELECT id, label " + sql = "SELECT Lookup.internalId, label " "FROM Labels " "INNER JOIN Lookup ON Labels.id = Lookup.internalId"; @@ -474,9 +557,98 @@ } } - if (request.GetLevel() <= ResourceType_Series && request.GetChildrenSpecification(static_cast(request.GetLevel() + 1)).IsRetrieveIdentifiers()) + // need one instance identifier ? TODO: it might be actually more interesting to retrieve directly the attachment ids .... + if (request.IsRetrieveOneInstanceIdentifier()) { - sql = "SELECT currentLevel.internalId, childLevel.publicId " + if (requestLevel == ResourceType_Series) + { + sql = "SELECT Lookup.internalId, childLevel.publicId " + "FROM Resources AS childLevel " + "INNER JOIN Lookup ON childLevel.parentId = Lookup.internalId "; + + SQLite::Statement s(db_, SQLITE_FROM_HERE, sql); + while (s.Step()) + { + FindResponse::Resource& res = response.GetResourceByInternalId(s.ColumnInt64(0)); + res.AddChildIdentifier(ResourceType_Instance, s.ColumnString(1)); + } + } + else if (requestLevel == ResourceType_Study) + { + sql = "SELECT Lookup.internalId, grandChildLevel.publicId " + "FROM Resources AS grandChildLevel " + "INNER JOIN Resources childLevel ON grandChildLevel.parentId = childLevel.internalId " + "INNER JOIN Lookup ON childLevel.parentId = Lookup.internalId "; + + SQLite::Statement s(db_, SQLITE_FROM_HERE, sql); + while (s.Step()) + { + FindResponse::Resource& res = response.GetResourceByInternalId(s.ColumnInt64(0)); + res.AddChildIdentifier(ResourceType_Instance, s.ColumnString(1)); + } + } + else if (requestLevel == ResourceType_Patient) + { + sql = "SELECT Lookup.internalId, grandGrandChildLevel.publicId " + "FROM Resources AS grandGrandChildLevel " + "INNER JOIN Resources grandChildLevel ON grandGrandChildLevel.parentId = grandChildLevel.internalId " + "INNER JOIN Resources childLevel ON grandChildLevel.parentId = childLevel.internalId " + "INNER JOIN Lookup ON childLevel.parentId = Lookup.internalId "; + + SQLite::Statement s(db_, SQLITE_FROM_HERE, sql); + while (s.Step()) + { + FindResponse::Resource& res = response.GetResourceByInternalId(s.ColumnInt64(0)); + res.AddChildIdentifier(ResourceType_Instance, s.ColumnString(1)); + } + } + else + { + throw OrthancException(ErrorCode_InternalError); + } + } + + // need children metadata ? + if (requestLevel <= ResourceType_Series && request.GetChildrenSpecification(static_cast(requestLevel + 1)).GetMetadata().size() > 0) + { + sql = "SELECT Lookup.internalId, type, value " + "FROM Metadata " + "INNER JOIN Lookup ON Lookup.internalId = childLevel.parentId " + "INNER JOIN Resources childLevel ON childLevel.internalId = Metadata.id"; + + SQLite::Statement s(db_, SQLITE_FROM_HERE, sql); + while (s.Step()) + { + FindResponse::Resource& res = response.GetResourceByInternalId(s.ColumnInt64(0)); + res.AddChildrenMetadataValue(static_cast(requestLevel + 1), + static_cast(s.ColumnInt(1)), + s.ColumnString(2)); + } + } + + // need grandchildren metadata ? + if (requestLevel <= ResourceType_Study && request.GetChildrenSpecification(static_cast(requestLevel + 2)).GetMetadata().size() > 0) + { + sql = "SELECT Lookup.internalId, type, value " + "FROM Metadata " + "INNER JOIN Lookup ON Lookup.internalId = childLevel.parentId " + "INNER JOIN Resources childLevel ON childLevel.internalId = grandChildLevel.parentId " + "INNER JOIN Resources grandChildLevel ON grandChildLevel.internalId = Metadata.id"; + + SQLite::Statement s(db_, SQLITE_FROM_HERE, sql); + while (s.Step()) + { + FindResponse::Resource& res = response.GetResourceByInternalId(s.ColumnInt64(0)); + res.AddChildrenMetadataValue(static_cast(requestLevel + 2), + static_cast(s.ColumnInt(1)), + s.ColumnString(2)); + } + } + + // need children identifiers ? + if (requestLevel <= ResourceType_Series && request.GetChildrenSpecification(static_cast(requestLevel + 1)).IsRetrieveIdentifiers()) + { + sql = "SELECT Lookup.internalId, childLevel.publicId " "FROM Resources AS currentLevel " "INNER JOIN Lookup ON currentLevel.internalId = Lookup.internalId " "INNER JOIN Resources childLevel ON currentLevel.internalId = childLevel.parentId "; @@ -485,13 +657,14 @@ while (s.Step()) { FindResponse::Resource& res = response.GetResourceByInternalId(s.ColumnInt64(0)); - res.AddChildIdentifier(static_cast(request.GetLevel() + 1), s.ColumnString(1)); + res.AddChildIdentifier(static_cast(requestLevel + 1), s.ColumnString(1)); } } - if (request.GetLevel() <= ResourceType_Study && request.GetChildrenSpecification(static_cast(request.GetLevel() + 2)).IsRetrieveIdentifiers()) + // need grandchildren identifiers ? + if (requestLevel <= ResourceType_Study && request.GetChildrenSpecification(static_cast(requestLevel + 2)).IsRetrieveIdentifiers()) { - sql = "SELECT currentLevel.internalId, grandChildLevel.publicId " + sql = "SELECT Lookup.internalId, grandChildLevel.publicId " "FROM Resources AS currentLevel " "INNER JOIN Lookup ON currentLevel.internalId = Lookup.internalId " "INNER JOIN Resources childLevel ON currentLevel.internalId = childLevel.parentId " @@ -501,9 +674,29 @@ while (s.Step()) { FindResponse::Resource& res = response.GetResourceByInternalId(s.ColumnInt64(0)); - res.AddChildIdentifier(static_cast(request.GetLevel() + 2), s.ColumnString(1)); + res.AddChildIdentifier(static_cast(requestLevel + 2), s.ColumnString(1)); } } + + // need resource attachments ? + if (request.IsRetrieveAttachments()) + { + sql = "SELECT Lookup.internalId, fileType, uuid, uncompressedSize, compressedSize, compressionType, uncompressedMD5, compressedMD5 " + "FROM AttachedFiles " + "INNER JOIN Lookup ON AttachedFiles.id = Lookup.internalId"; + + SQLite::Statement s(db_, SQLITE_FROM_HERE, sql); + while (s.Step()) + { + FindResponse::Resource& res = response.GetResourceByInternalId(s.ColumnInt64(0)); + FileInfo file(s.ColumnString(2), static_cast(s.ColumnInt(1)), + s.ColumnInt64(3), s.ColumnString(6), + static_cast(s.ColumnInt(5)), + s.ColumnInt64(4), s.ColumnString(7)); + res.AddAttachment(file); + } + + } } diff -r afd421225eb4 -r f75596b224e0 OrthancServer/Sources/ResourceFinder.cpp --- a/OrthancServer/Sources/ResourceFinder.cpp Fri Aug 30 18:03:37 2024 +0200 +++ b/OrthancServer/Sources/ResourceFinder.cpp Wed Sep 04 10:54:00 2024 +0200 @@ -758,7 +758,14 @@ { requestedComputedTags_.insert(tag); hasRequestedTags_ = true; - request_.GetChildrenSpecification(ResourceType_Series).AddMainDicomTag(DICOM_TAG_MODALITY); + if (request_.GetLevel() < ResourceType_Series) + { + request_.GetChildrenSpecification(ResourceType_Series).AddMainDicomTag(DICOM_TAG_MODALITY); + } + else if (request_.GetLevel() == ResourceType_Instance) // this happens in QIDO-RS when searching for instances without specifying a StudyInstanceUID -> all Study level tags must be included in the response + { + request_.GetParentSpecification(ResourceType_Series).SetRetrieveMainDicomTags(true); + } } else if (tag == DICOM_TAG_INSTANCE_AVAILABILITY) { diff -r afd421225eb4 -r f75596b224e0 OrthancServer/Sources/Search/ISqlLookupFormatter.cpp --- a/OrthancServer/Sources/Search/ISqlLookupFormatter.cpp Fri Aug 30 18:03:37 2024 +0200 +++ b/OrthancServer/Sources/Search/ISqlLookupFormatter.cpp Wed Sep 04 10:54:00 2024 +0200 @@ -625,40 +625,42 @@ const bool escapeBrackets = formatter.IsEscapeBrackets(); ResourceType queryLevel = request.GetLevel(); const std::string& strQueryLevel = FormatLevel(queryLevel); - + + ResourceType lowerLevel, upperLevel; + GetLookupLevels(lowerLevel, upperLevel, queryLevel, request.GetDicomTagConstraints()); + + assert(upperLevel <= queryLevel && + queryLevel <= lowerLevel); + + + sql = ("SELECT " + + strQueryLevel + ".publicId, " + + strQueryLevel + ".internalId" + + " FROM Resources AS " + strQueryLevel); + + std::string joins, comparisons; - if (request.GetOrthancIdentifiers().IsDefined() && request.GetOrthancIdentifiers().DetectLevel() == queryLevel) - { - // single resource matching, there should not be other constraints - assert(request.GetDicomTagConstraints().GetSize() == 0); - assert(request.GetLabels().size() == 0); - assert(request.HasLimits() == false); - - comparisons = " AND " + strQueryLevel + ".publicId = " + formatter.GenerateParameter(request.GetOrthancIdentifiers().GetLevel(queryLevel)); - } - else if (request.GetOrthancIdentifiers().IsDefined() && request.GetOrthancIdentifiers().DetectLevel() == queryLevel - 1) + if (request.GetOrthancIdentifiers().IsDefined() && request.GetOrthancIdentifiers().DetectLevel() <= queryLevel) { // single child resource matching, there should not be other constraints (at least for now) assert(request.GetDicomTagConstraints().GetSize() == 0); assert(request.GetLabels().size() == 0); assert(request.HasLimits() == false); - ResourceType parentLevel = static_cast(queryLevel - 1); - const std::string& strParentLevel = FormatLevel(parentLevel); + ResourceType topParentLevel = request.GetOrthancIdentifiers().DetectLevel(); + const std::string& strTopParentLevel = FormatLevel(topParentLevel); + + comparisons = " AND " + strTopParentLevel + ".publicId = " + formatter.GenerateParameter(request.GetOrthancIdentifiers().GetLevel(topParentLevel)); - comparisons = " AND " + strParentLevel + ".publicId = " + formatter.GenerateParameter(request.GetOrthancIdentifiers().GetLevel(parentLevel)); - joins += (" INNER JOIN Resources " + - strParentLevel + " ON " + - strQueryLevel + ".parentId=" + - strParentLevel + ".internalId"); + for (int level = queryLevel; level > topParentLevel; level--) + { + sql += (" INNER JOIN Resources " + + FormatLevel(static_cast(level - 1)) + " ON " + + FormatLevel(static_cast(level - 1)) + ".internalId=" + + FormatLevel(static_cast(level)) + ".parentId"); + } } - // TODO-FIND other levels (there's probably a way to make it generic as it was done before !!!): - // /studies/../instances - // /patients/../instances - // /series/../study - // /instances/../study - // ... else { size_t count = 0; @@ -686,26 +688,21 @@ } } - sql = ("SELECT " + - strQueryLevel + ".publicId, " + - strQueryLevel + ".internalId" + - " FROM Resources AS " + strQueryLevel); - - // for (int level = queryLevel - 1; level >= upperLevel; level--) - // { - // sql += (" INNER JOIN Resources " + - // FormatLevel(static_cast(level)) + " ON " + - // FormatLevel(static_cast(level)) + ".internalId=" + - // FormatLevel(static_cast(level + 1)) + ".parentId"); - // } + for (int level = queryLevel - 1; level >= upperLevel; level--) + { + sql += (" INNER JOIN Resources " + + FormatLevel(static_cast(level)) + " ON " + + FormatLevel(static_cast(level)) + ".internalId=" + + FormatLevel(static_cast(level + 1)) + ".parentId"); + } - // for (int level = queryLevel + 1; level <= lowerLevel; level++) - // { - // sql += (" INNER JOIN Resources " + - // FormatLevel(static_cast(level)) + " ON " + - // FormatLevel(static_cast(level - 1)) + ".internalId=" + - // FormatLevel(static_cast(level)) + ".parentId"); - // } + for (int level = queryLevel + 1; level <= lowerLevel; level++) + { + sql += (" INNER JOIN Resources " + + FormatLevel(static_cast(level)) + " ON " + + FormatLevel(static_cast(level - 1)) + ".internalId=" + + FormatLevel(static_cast(level)) + ".parentId"); + } std::list where; where.push_back(strQueryLevel + ".resourceType = " + @@ -755,8 +752,14 @@ if (request.HasLimits()) { - sql += " LIMIT " + boost::lexical_cast(request.GetLimitsCount()); - sql += " OFFSET " + boost::lexical_cast(request.GetLimitsSince()); + if (request.GetLimitsCount() > 0) + { + sql += " LIMIT " + boost::lexical_cast(request.GetLimitsCount()); + } + if (request.GetLimitsSince() > 0) + { + sql += " OFFSET " + boost::lexical_cast(request.GetLimitsSince()); + } } } diff -r afd421225eb4 -r f75596b224e0 OrthancServer/Sources/ServerContext.cpp diff -r afd421225eb4 -r f75596b224e0 OrthancServer/Sources/ServerContext.h