# HG changeset patch # User Alain Mazy # Date 1725634597 -7200 # Node ID 4ecf50a4521ce87ab767fd2da5fc7857abe1cb85 # Parent 03a4a1bc852ab7b8eb3190a6bcc7212197854b7a sync ISqlLookupFormatter from Orthanc + fix bug 224: LIMIT shall not be used with MSSQL diff -r 03a4a1bc852a -r 4ecf50a4521c Framework/Plugins/DatabaseBackendAdapterV2.cpp --- a/Framework/Plugins/DatabaseBackendAdapterV2.cpp Fri Sep 06 15:44:40 2024 +0200 +++ b/Framework/Plugins/DatabaseBackendAdapterV2.cpp Fri Sep 06 16:56:37 2024 +0200 @@ -1413,12 +1413,11 @@ { DatabaseBackendAdapterV2::Adapter::DatabaseAccessor accessor(*adapter); - std::vector lookup; - lookup.reserve(constraintsCount); + Orthanc::DatabaseConstraints lookup; for (uint32_t i = 0; i < constraintsCount; i++) { - lookup.push_back(Orthanc::DatabaseConstraint(constraints[i])); + lookup.AddConstraint(new Orthanc::DatabaseConstraint(constraints[i])); } std::set noLabel; diff -r 03a4a1bc852a -r 4ecf50a4521c Framework/Plugins/DatabaseBackendAdapterV3.cpp --- a/Framework/Plugins/DatabaseBackendAdapterV3.cpp Fri Sep 06 15:44:40 2024 +0200 +++ b/Framework/Plugins/DatabaseBackendAdapterV3.cpp Fri Sep 06 16:56:37 2024 +0200 @@ -1640,12 +1640,11 @@ { t->GetOutput().Clear(); - std::vector lookup; - lookup.reserve(constraintsCount); + Orthanc::DatabaseConstraints lookup; for (uint32_t i = 0; i < constraintsCount; i++) { - lookup.push_back(Orthanc::DatabaseConstraint(constraints[i])); + lookup.AddConstraint(new Orthanc::DatabaseConstraint(constraints[i])); } std::set noLabel; diff -r 03a4a1bc852a -r 4ecf50a4521c Framework/Plugins/DatabaseBackendAdapterV4.cpp --- a/Framework/Plugins/DatabaseBackendAdapterV4.cpp Fri Sep 06 15:44:40 2024 +0200 +++ b/Framework/Plugins/DatabaseBackendAdapterV4.cpp Fri Sep 06 16:56:37 2024 +0200 @@ -119,6 +119,7 @@ Orthanc::DatabasePluginMessages::DeleteAttachment::Response* deleteAttachment_; Orthanc::DatabasePluginMessages::DeleteResource::Response* deleteResource_; Orthanc::DatabasePluginMessages::GetChanges::Response* getChanges_; + Orthanc::DatabasePluginMessages::GetChangesExtended::Response* getChangesExtended_; Orthanc::DatabasePluginMessages::GetExportedResources::Response* getExportedResources_; Orthanc::DatabasePluginMessages::GetLastChange::Response* getLastChange_; Orthanc::DatabasePluginMessages::GetLastExportedResource::Response* getLastExportedResource_; @@ -131,6 +132,7 @@ deleteAttachment_ = NULL; deleteResource_ = NULL; getChanges_ = NULL; + getChangesExtended_ = NULL; getExportedResources_ = NULL; getLastChange_ = NULL; getLastExportedResource_ = NULL; @@ -157,7 +159,13 @@ Clear(); getChanges_ = &getChanges; } - + + Output(Orthanc::DatabasePluginMessages::GetChangesExtended::Response& getChangesExtended) + { + Clear(); + getChangesExtended_ = &getChangesExtended; + } + Output(Orthanc::DatabasePluginMessages::GetExportedResources::Response& getExportedResources) { Clear(); @@ -310,6 +318,10 @@ { change = getChanges_->add_changes(); } + else if (getChangesExtended_ != NULL) + { + change = getChangesExtended_->add_changes(); + } else if (getLastChange_ != NULL) { if (getLastChange_->found()) @@ -549,8 +561,7 @@ IndexBackend& backend, DatabaseManager& manager) { - std::vector lookup; - lookup.reserve(request.lookup().size()); + Orthanc::DatabaseConstraints lookup; size_t countValues = 0; @@ -624,7 +635,7 @@ } } - lookup.push_back(Orthanc::DatabaseConstraint(c)); + lookup.AddConstraint(new Orthanc::DatabaseConstraint(c)); } assert(values.size() == countValues); @@ -791,7 +802,7 @@ #if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 5) case Orthanc::DatabasePluginMessages::OPERATION_GET_CHANGES_EXTENDED: { - Output output(*response.mutable_get_changes()); + Output output(*response.mutable_get_changes_extended()); bool done; backend.GetChangesExtended(output, done, manager, request.get_changes_extended().since(), request.get_changes_extended().to(), static_cast(request.get_changes_extended().change_type()), request.get_changes_extended().limit()); diff -r 03a4a1bc852a -r 4ecf50a4521c Framework/Plugins/IDatabaseBackend.h --- a/Framework/Plugins/IDatabaseBackend.h Fri Sep 06 15:44:40 2024 +0200 +++ b/Framework/Plugins/IDatabaseBackend.h Fri Sep 06 16:56:37 2024 +0200 @@ -293,7 +293,7 @@ #if ORTHANC_PLUGINS_HAS_DATABASE_CONSTRAINT == 1 virtual void LookupResources(IDatabaseBackendOutput& output, DatabaseManager& manager, - const std::vector& lookup, + Orthanc::DatabaseConstraints& lookup, OrthancPluginResourceType queryLevel, const std::set& labels, // New in Orthanc 1.12.0 Orthanc::LabelsConstraint labelsConstraint, // New in Orthanc 1.12.0 diff -r 03a4a1bc852a -r 4ecf50a4521c Framework/Plugins/IndexBackend.cpp --- a/Framework/Plugins/IndexBackend.cpp Fri Sep 06 15:44:40 2024 +0200 +++ b/Framework/Plugins/IndexBackend.cpp Fri Sep 06 16:56:37 2024 +0200 @@ -2178,6 +2178,43 @@ } } + virtual std::string FormatLimits(uint64_t since, uint64_t count) + { + std::string sql; + + switch (dialect_) + { + case Dialect_MSSQL: + { + if (since > 0) + { + sql += " OFFSET " + boost::lexical_cast(since) + " ROWS "; + } + if (count > 0) + { + sql += " FETCH NEXT " + boost::lexical_cast(count) + " ROWS ONLY "; + } + }; break; + case Dialect_SQLite: + case Dialect_PostgreSQL: + case Dialect_MySQL: + { + if (count > 0) + { + sql += " LIMIT " + boost::lexical_cast(count); + } + if (since > 0) + { + sql += " OFFSET " + boost::lexical_cast(since); + } + }; break; + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + + return sql; + } + virtual bool IsEscapeBrackets() const { // This was initially done at a bad location by the following changeset: @@ -2207,7 +2244,7 @@ // New primitive since Orthanc 1.5.2 void IndexBackend::LookupResources(IDatabaseBackendOutput& output, DatabaseManager& manager, - const std::vector& lookup, + Orthanc::DatabaseConstraints& lookup, OrthancPluginResourceType queryLevel_, const std::set& labels, Orthanc::LabelsConstraint labelsConstraint, diff -r 03a4a1bc852a -r 4ecf50a4521c Framework/Plugins/IndexBackend.h --- a/Framework/Plugins/IndexBackend.h Fri Sep 06 15:44:40 2024 +0200 +++ b/Framework/Plugins/IndexBackend.h Fri Sep 06 16:56:37 2024 +0200 @@ -312,7 +312,7 @@ // New primitive since Orthanc 1.5.2 virtual void LookupResources(IDatabaseBackendOutput& output, DatabaseManager& manager, - const std::vector& lookup, + Orthanc::DatabaseConstraints& lookup, OrthancPluginResourceType queryLevel, const std::set& labels, Orthanc::LabelsConstraint labelsConstraint, diff -r 03a4a1bc852a -r 4ecf50a4521c Odbc/NEWS --- a/Odbc/NEWS Fri Sep 06 15:44:40 2024 +0200 +++ b/Odbc/NEWS Fri Sep 06 16:56:37 2024 +0200 @@ -9,6 +9,8 @@ * Fix check of Orthanc runtime version * Added support for ExtendedChanges: - changes?type=...&to=... +* Fix bug 224, error when using LIMIT with MSSQLServer + https://orthanc.uclouvain.be/bugs/show_bug.cgi?id=224 Release 1.2 (2024-03-06) diff -r 03a4a1bc852a -r 4ecf50a4521c Resources/Orthanc/CMake/Compiler.cmake --- a/Resources/Orthanc/CMake/Compiler.cmake Fri Sep 06 15:44:40 2024 +0200 +++ b/Resources/Orthanc/CMake/Compiler.cmake Fri Sep 06 16:56:37 2024 +0200 @@ -232,6 +232,10 @@ endif() elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Darwin") + + # fix this error that appears with recent compilers on MacOS: boost/mpl/aux_/integral_wrapper.hpp:73:31: error: integer value -1 is outside the valid range of values [0, 3] for this enumeration type [-Wenum-constexpr-conversion] + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-enum-constexpr-conversion") + add_definitions( -D_XOPEN_SOURCE=1 ) diff -r 03a4a1bc852a -r 4ecf50a4521c Resources/Orthanc/Databases/DatabaseConstraint.cpp --- a/Resources/Orthanc/Databases/DatabaseConstraint.cpp Fri Sep 06 15:44:40 2024 +0200 +++ b/Resources/Orthanc/Databases/DatabaseConstraint.cpp Fri Sep 06 16:56:37 2024 +0200 @@ -37,6 +37,7 @@ # include #endif +#include #include @@ -245,4 +246,103 @@ constraint.values = (tmpValues.empty() ? NULL : &tmpValues[0]); } #endif + + + void DatabaseConstraints::Clear() + { + for (size_t i = 0; i < constraints_.size(); i++) + { + assert(constraints_[i] != NULL); + delete constraints_[i]; + } + + constraints_.clear(); + } + + + void DatabaseConstraints::AddConstraint(DatabaseConstraint* constraint) + { + if (constraint == NULL) + { + throw OrthancException(ErrorCode_NullPointer); + } + else + { + constraints_.push_back(constraint); + } + } + + + const DatabaseConstraint& DatabaseConstraints::GetConstraint(size_t index) const + { + if (index >= constraints_.size()) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + else + { + assert(constraints_[index] != NULL); + return *constraints_[index]; + } + } + + + std::string DatabaseConstraints::Format() const + { + std::string s; + + for (size_t i = 0; i < constraints_.size(); i++) + { + assert(constraints_[i] != NULL); + const DatabaseConstraint& constraint = *constraints_[i]; + s += "Constraint " + boost::lexical_cast(i) + " at " + EnumerationToString(constraint.GetLevel()) + + ": " + constraint.GetTag().Format(); + + switch (constraint.GetConstraintType()) + { + case ConstraintType_Equal: + s += " == " + constraint.GetSingleValue(); + break; + + case ConstraintType_SmallerOrEqual: + s += " <= " + constraint.GetSingleValue(); + break; + + case ConstraintType_GreaterOrEqual: + s += " >= " + constraint.GetSingleValue(); + break; + + case ConstraintType_Wildcard: + s += " ~~ " + constraint.GetSingleValue(); + break; + + case ConstraintType_List: + { + s += " in [ "; + bool first = true; + for (size_t j = 0; j < constraint.GetValuesCount(); j++) + { + if (first) + { + first = false; + } + else + { + s += ", "; + } + s += constraint.GetValue(j); + } + s += "]"; + break; + } + + default: + throw OrthancException(ErrorCode_InternalError); + } + + s += "\n"; + } + + return s; + } } diff -r 03a4a1bc852a -r 4ecf50a4521c Resources/Orthanc/Databases/DatabaseConstraint.h --- a/Resources/Orthanc/Databases/DatabaseConstraint.h Fri Sep 06 15:44:40 2024 +0200 +++ b/Resources/Orthanc/Databases/DatabaseConstraint.h Fri Sep 06 16:56:37 2024 +0200 @@ -46,6 +46,8 @@ # endif #endif +#include + namespace Orthanc { enum ConstraintType @@ -78,7 +80,7 @@ // This class is also used by the "orthanc-databases" project - class DatabaseConstraint + class DatabaseConstraint : public boost::noncopyable { private: ResourceType level_; @@ -148,4 +150,35 @@ std::vector& tmpValues) const; #endif }; + + + class DatabaseConstraints : public boost::noncopyable + { + private: + std::deque constraints_; + + public: + ~DatabaseConstraints() + { + Clear(); + } + + void Clear(); + + void AddConstraint(DatabaseConstraint* constraint); // Takes ownership + + bool IsEmpty() const + { + return constraints_.empty(); + } + + size_t GetSize() const + { + return constraints_.size(); + } + + const DatabaseConstraint& GetConstraint(size_t index) const; + + std::string Format() const; + }; } diff -r 03a4a1bc852a -r 4ecf50a4521c Resources/Orthanc/Databases/ISqlLookupFormatter.cpp --- a/Resources/Orthanc/Databases/ISqlLookupFormatter.cpp Fri Sep 06 15:44:40 2024 +0200 +++ b/Resources/Orthanc/Databases/ISqlLookupFormatter.cpp Fri Sep 06 16:56:37 2024 +0200 @@ -34,6 +34,7 @@ #if ORTHANC_BUILDING_SERVER_LIBRARY == 1 # include "../../../OrthancFramework/Sources/OrthancException.h" # include "../../../OrthancFramework/Sources/Toolbox.h" +# include "../Database/FindRequest.h" #else # include # include @@ -475,7 +476,10 @@ } - void ISqlLookupFormatter::GetLookupLevels(ResourceType& lowerLevel, ResourceType& upperLevel, const ResourceType& queryLevel, const std::vector& lookup) + void ISqlLookupFormatter::GetLookupLevels(ResourceType& lowerLevel, + ResourceType& upperLevel, + const ResourceType& queryLevel, + const DatabaseConstraints& lookup) { assert(ResourceType_Patient < ResourceType_Study && ResourceType_Study < ResourceType_Series && @@ -484,9 +488,9 @@ lowerLevel = queryLevel; upperLevel = queryLevel; - for (size_t i = 0; i < lookup.size(); i++) + for (size_t i = 0; i < lookup.GetSize(); i++) { - ResourceType level = lookup[i].GetLevel(); + ResourceType level = lookup.GetConstraint(i).GetLevel(); if (level < upperLevel) { @@ -503,7 +507,7 @@ void ISqlLookupFormatter::Apply(std::string& sql, ISqlLookupFormatter& formatter, - const std::vector& lookup, + const DatabaseConstraints& lookup, ResourceType queryLevel, const std::set& labels, LabelsConstraint labelsConstraint, @@ -521,14 +525,16 @@ size_t count = 0; - for (size_t i = 0; i < lookup.size(); i++) + for (size_t i = 0; i < lookup.GetSize(); i++) { + const DatabaseConstraint& constraint = lookup.GetConstraint(i); + std::string comparison; - if (FormatComparison(comparison, formatter, lookup[i], count, escapeBrackets)) + if (FormatComparison(comparison, formatter, constraint, count, escapeBrackets)) { std::string join; - FormatJoin(join, lookup[i], count); + FormatJoin(join, constraint, count); joins += join; if (!comparison.empty()) @@ -611,10 +617,151 @@ } } +#if ORTHANC_BUILDING_SERVER_LIBRARY == 1 + void ISqlLookupFormatter::Apply(std::string& sql, + ISqlLookupFormatter& formatter, + const FindRequest& request) + { + 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 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 topParentLevel = request.GetOrthancIdentifiers().DetectLevel(); + const std::string& strTopParentLevel = FormatLevel(topParentLevel); + + comparisons = " AND " + strTopParentLevel + ".publicId = " + formatter.GenerateParameter(request.GetOrthancIdentifiers().GetLevel(topParentLevel)); + + 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"); + } + } + else + { + size_t count = 0; + + const DatabaseConstraints& dicomTagsConstraints = request.GetDicomTagConstraints(); + for (size_t i = 0; i < dicomTagsConstraints.GetSize(); i++) + { + const DatabaseConstraint& constraint = dicomTagsConstraints.GetConstraint(i); + + std::string comparison; + + if (FormatComparison(comparison, formatter, constraint, count, escapeBrackets)) + { + std::string join; + FormatJoin(join, constraint, count); + joins += join; + + if (!comparison.empty()) + { + comparisons += " AND " + comparison; + } + + count ++; + } + } + } + + 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"); + } + + std::list where; + where.push_back(strQueryLevel + ".resourceType = " + + formatter.FormatResourceType(queryLevel) + comparisons); + + + if (!request.GetLabels().empty()) + { + /** + * "In SQL Server, NOT EXISTS and NOT IN predicates are the best + * way to search for missing values, as long as both columns in + * question are NOT NULL." + * https://explainextended.com/2009/09/15/not-in-vs-not-exists-vs-left-join-is-null-sql-server/ + **/ + + const std::set& labels = request.GetLabels(); + std::list formattedLabels; + for (std::set::const_iterator it = labels.begin(); it != labels.end(); ++it) + { + formattedLabels.push_back(formatter.GenerateParameter(*it)); + } + + std::string condition; + switch (request.GetLabelsConstraint()) + { + case LabelsConstraint_Any: + condition = "> 0"; + break; + + case LabelsConstraint_All: + condition = "= " + boost::lexical_cast(labels.size()); + break; + + case LabelsConstraint_None: + condition = "= 0"; + break; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + where.push_back("(SELECT COUNT(1) FROM Labels AS selectedLabels WHERE selectedLabels.id = " + strQueryLevel + + ".internalId AND selectedLabels.label IN (" + Join(formattedLabels, "", ", ") + ")) " + condition); + } + + sql += joins + Join(where, " WHERE ", " AND "); + + if (request.HasLimits()) + { + sql += formatter.FormatLimits(request.GetLimitsSince(), request.GetLimitsCount()); + } + + } +#endif + void ISqlLookupFormatter::ApplySingleLevel(std::string& sql, ISqlLookupFormatter& formatter, - const std::vector& lookup, + const DatabaseConstraints& lookup, ResourceType queryLevel, const std::set& labels, LabelsConstraint labelsConstraint, @@ -631,15 +778,17 @@ std::vector mainDicomTagsComparisons, dicomIdentifiersComparisons; - for (size_t i = 0; i < lookup.size(); i++) + for (size_t i = 0; i < lookup.GetSize(); i++) { + const DatabaseConstraint& constraint = lookup.GetConstraint(i); + std::string comparison; - if (FormatComparison2(comparison, formatter, lookup[i], escapeBrackets)) + if (FormatComparison2(comparison, formatter, constraint, escapeBrackets)) { if (!comparison.empty()) { - if (lookup[i].IsIdentifier()) + if (constraint.IsIdentifier()) { dicomIdentifiersComparisons.push_back(comparison); } diff -r 03a4a1bc852a -r 4ecf50a4521c Resources/Orthanc/Databases/ISqlLookupFormatter.h --- a/Resources/Orthanc/Databases/ISqlLookupFormatter.h Fri Sep 06 15:44:40 2024 +0200 +++ b/Resources/Orthanc/Databases/ISqlLookupFormatter.h Fri Sep 06 16:56:37 2024 +0200 @@ -34,8 +34,9 @@ namespace Orthanc { - class DatabaseConstraint; - + class DatabaseConstraints; + class FindRequest; + enum LabelsConstraint { LabelsConstraint_All, @@ -57,6 +58,8 @@ virtual std::string FormatWildcardEscape() = 0; + virtual std::string FormatLimits(uint64_t since, uint64_t count) = 0; + /** * Whether to escape '[' and ']', which is only needed for * MSSQL. New in Orthanc 1.10.0, from the following changeset: @@ -64,11 +67,14 @@ **/ virtual bool IsEscapeBrackets() const = 0; - static void GetLookupLevels(ResourceType& lowerLevel, ResourceType& upperLevel, const ResourceType& queryLevel, const std::vector& lookup); + static void GetLookupLevels(ResourceType& lowerLevel, + ResourceType& upperLevel, + const ResourceType& queryLevel, + const DatabaseConstraints& lookup); static void Apply(std::string& sql, ISqlLookupFormatter& formatter, - const std::vector& lookup, + const DatabaseConstraints& lookup, ResourceType queryLevel, const std::set& labels, // New in Orthanc 1.12.0 LabelsConstraint labelsConstraint, // New in Orthanc 1.12.0 @@ -76,10 +82,16 @@ static void ApplySingleLevel(std::string& sql, ISqlLookupFormatter& formatter, - const std::vector& lookup, + const DatabaseConstraints& lookup, ResourceType queryLevel, const std::set& labels, // New in Orthanc 1.12.0 LabelsConstraint labelsConstraint, // New in Orthanc 1.12.0 size_t limit); + +#if ORTHANC_BUILDING_SERVER_LIBRARY == 1 + static void Apply(std::string& sql, + ISqlLookupFormatter& formatter, + const FindRequest& request); +#endif }; }