changeset 3036:8fd203510d8b db-changes

moving LookupIdentifierQuery to the graveyard
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 19 Dec 2018 16:27:07 +0100
parents 54e422fe31ce
children d23c84b1c0f5
files CMakeLists.txt OrthancServer/IDatabaseWrapper.h OrthancServer/SQLiteDatabaseWrapper.cpp OrthancServer/SQLiteDatabaseWrapper.h OrthancServer/Search/IFindConstraint.cpp OrthancServer/Search/IFindConstraint.h OrthancServer/Search/ListConstraint.cpp OrthancServer/Search/ListConstraint.h OrthancServer/Search/LookupIdentifierQuery.cpp OrthancServer/Search/LookupIdentifierQuery.h OrthancServer/Search/RangeConstraint.cpp OrthancServer/Search/RangeConstraint.h OrthancServer/Search/ValueConstraint.cpp OrthancServer/Search/ValueConstraint.h OrthancServer/Search/WildcardConstraint.cpp OrthancServer/Search/WildcardConstraint.h OrthancServer/ServerContext.cpp OrthancServer/ServerIndex.cpp OrthancServer/ServerIndex.h Plugins/Engine/OrthancPluginDatabase.cpp Plugins/Engine/OrthancPluginDatabase.h Resources/Graveyard/DatabaseOptimizations/LookupIdentifierQuery.cpp Resources/Graveyard/DatabaseOptimizations/LookupIdentifierQuery.h UnitTestsSources/ServerIndexTests.cpp
diffstat 24 files changed, 520 insertions(+), 550 deletions(-) [+]
line wrap: on
line diff
--- a/CMakeLists.txt	Wed Dec 19 14:20:11 2018 +0100
+++ b/CMakeLists.txt	Wed Dec 19 16:27:07 2018 +0100
@@ -77,7 +77,6 @@
   OrthancServer/Search/HierarchicalMatcher.cpp
   OrthancServer/Search/IFindConstraint.cpp
   OrthancServer/Search/ListConstraint.cpp
-  OrthancServer/Search/LookupIdentifierQuery.cpp
   OrthancServer/Search/RangeConstraint.cpp
   OrthancServer/Search/SetOfResources.cpp
   OrthancServer/Search/ValueConstraint.cpp
--- a/OrthancServer/IDatabaseWrapper.h	Wed Dec 19 14:20:11 2018 +0100
+++ b/OrthancServer/IDatabaseWrapper.h	Wed Dec 19 16:27:07 2018 +0100
@@ -228,7 +228,7 @@
     virtual bool IsDiskSizeAbove(uint64_t threshold) = 0;
 
     virtual void ApplyLookupResources(std::vector<std::string>& resourcesId,
-                                      std::vector<std::string>& instancesId,
+                                      std::vector<std::string>* instancesId,   // Can be NULL if not needed
                                       const std::vector<DatabaseConstraint>& lookup,
                                       ResourceType queryLevel,
                                       size_t limit) = 0;
--- a/OrthancServer/SQLiteDatabaseWrapper.cpp	Wed Dec 19 14:20:11 2018 +0100
+++ b/OrthancServer/SQLiteDatabaseWrapper.cpp	Wed Dec 19 16:27:07 2018 +0100
@@ -1498,11 +1498,17 @@
 
 
   void SQLiteDatabaseWrapper::ApplyLookupResources(std::vector<std::string>& resourcesId,
-                                                   std::vector<std::string>& instancesId,
+                                                   std::vector<std::string>* instancesId,
                                                    const std::vector<DatabaseConstraint>& lookup,
                                                    ResourceType queryLevel,
                                                    size_t limit)
   {
+    for (size_t i = 0; i < lookup.size(); i++)
+    {
+      std::cout << i << ": " << lookup[i].GetTag() << " - " << EnumerationToString(lookup[i].GetLevel());
+      std::cout << std::endl;
+    }
+    
     assert(ResourceType_Patient < ResourceType_Study &&
            ResourceType_Study < ResourceType_Series &&
            ResourceType_Series < ResourceType_Instance);
@@ -1584,16 +1590,33 @@
         sql += " LIMIT " + boost::lexical_cast<std::string>(limit);
       }
 
+      printf("[%s]\n", sql.c_str());
+
       SQLite::Statement s(db_, sql);
 
       for (size_t i = 0; i < parameters.size(); i++)
       {
+        printf("   %lu = '%s'\n", i, parameters[i].c_str());
         s.BindString(i, parameters[i]);
       }
 
       s.Run();
     }
 
-    AnswerLookup(resourcesId, instancesId, db_, queryLevel);
+    if (instancesId != NULL)
+    {
+      AnswerLookup(resourcesId, *instancesId, db_, queryLevel);
+    }
+    else
+    {
+      resourcesId.clear();
+    
+      SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT publicId FROM Lookup");
+        
+      while (s.Step())
+      {
+        resourcesId.push_back(s.ColumnString(0));
+      }
+    }
   }
 }
--- a/OrthancServer/SQLiteDatabaseWrapper.h	Wed Dec 19 14:20:11 2018 +0100
+++ b/OrthancServer/SQLiteDatabaseWrapper.h	Wed Dec 19 16:27:07 2018 +0100
@@ -278,7 +278,7 @@
     virtual bool IsDiskSizeAbove(uint64_t threshold);
 
     virtual void ApplyLookupResources(std::vector<std::string>& resourcesId,
-                                      std::vector<std::string>& instancesId,
+                                      std::vector<std::string>* instancesId,
                                       const std::vector<DatabaseConstraint>& lookup,
                                       ResourceType queryLevel,
                                       size_t limit);
--- a/OrthancServer/Search/IFindConstraint.cpp	Wed Dec 19 14:20:11 2018 +0100
+++ b/OrthancServer/Search/IFindConstraint.cpp	Wed Dec 19 16:27:07 2018 +0100
@@ -41,6 +41,7 @@
 
 #include "../../Core/DicomParsing/FromDcmtkBridge.h"
 #include "../../Core/OrthancException.h"
+#include "../../Core/Toolbox.h"
 
 namespace Orthanc
 {
--- a/OrthancServer/Search/IFindConstraint.h	Wed Dec 19 14:20:11 2018 +0100
+++ b/OrthancServer/Search/IFindConstraint.h	Wed Dec 19 16:27:07 2018 +0100
@@ -33,7 +33,9 @@
 
 #pragma once
 
-#include "LookupIdentifierQuery.h"
+#include "../../Core/DicomFormat/DicomTag.h"
+
+#include <boost/noncopyable.hpp>
 
 namespace Orthanc
 {
@@ -46,9 +48,6 @@
 
     virtual IFindConstraint* Clone() const = 0;
 
-    virtual void Setup(LookupIdentifierQuery& lookup,
-                       const DicomTag& tag) const = 0;
-
     virtual bool Match(const std::string& value) const = 0;
 
     virtual std::string Format() const = 0;
--- a/OrthancServer/Search/ListConstraint.cpp	Wed Dec 19 14:20:11 2018 +0100
+++ b/OrthancServer/Search/ListConstraint.cpp	Wed Dec 19 16:27:07 2018 +0100
@@ -34,6 +34,7 @@
 #include "../PrecompiledHeadersServer.h"
 #include "ListConstraint.h"
 
+#include "../../Core/Toolbox.h"
 
 namespace Orthanc
 {
@@ -50,19 +51,6 @@
   }
 
 
-  void ListConstraint::Setup(LookupIdentifierQuery& lookup, 
-                             const DicomTag& tag) const
-  {
-    LookupIdentifierQuery::Disjunction& target = lookup.AddDisjunction();
-
-    for (std::set<std::string>::const_iterator
-           it = allowedValues_.begin(); it != allowedValues_.end(); ++it)
-    {
-      target.Add(tag, IdentifierConstraintType_Equal, *it);
-    }
-  }
-
-
   bool ListConstraint::Match(const std::string& value) const
   {
     std::string s;
--- a/OrthancServer/Search/ListConstraint.h	Wed Dec 19 14:20:11 2018 +0100
+++ b/OrthancServer/Search/ListConstraint.h	Wed Dec 19 16:27:07 2018 +0100
@@ -64,9 +64,6 @@
       return new ListConstraint(*this);
     }
 
-    virtual void Setup(LookupIdentifierQuery& lookup,
-                       const DicomTag& tag) const;
-
     virtual bool Match(const std::string& value) const;
 
     virtual std::string Format() const;
--- a/OrthancServer/Search/LookupIdentifierQuery.cpp	Wed Dec 19 14:20:11 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,215 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2018 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "../PrecompiledHeadersServer.h"
-#include "LookupIdentifierQuery.h"
-
-#include "../../Core/DicomParsing/FromDcmtkBridge.h"
-#include "../../Core/OrthancException.h"
-#include "../ServerToolbox.h"
-#include "SetOfResources.h"
-
-#include <cassert>
-
-
-
-namespace Orthanc
-{
-  LookupIdentifierQuery::SingleConstraint::
-  SingleConstraint(const DicomTag& tag,
-                   IdentifierConstraintType type,
-                   const std::string& value) : 
-    tag_(tag),
-    type_(type),
-    value_(ServerToolbox::NormalizeIdentifier(value))
-  {
-  }
-
-
-  LookupIdentifierQuery::RangeConstraint::
-  RangeConstraint(const DicomTag& tag,
-                  const std::string& start,
-                  const std::string& end) : 
-    tag_(tag),
-    start_(ServerToolbox::NormalizeIdentifier(start)),
-    end_(ServerToolbox::NormalizeIdentifier(end))
-  {
-  }
-
-
-  LookupIdentifierQuery::Disjunction::~Disjunction()
-  {
-    for (size_t i = 0; i < singleConstraints_.size(); i++)
-    {
-      delete singleConstraints_[i];
-    }
-
-    for (size_t i = 0; i < rangeConstraints_.size(); i++)
-    {
-      delete rangeConstraints_[i];
-    }
-  }
-
-
-  void LookupIdentifierQuery::Disjunction::Add(const DicomTag& tag,
-                                               IdentifierConstraintType type,
-                                               const std::string& value)
-  {
-    singleConstraints_.push_back(new SingleConstraint(tag, type, value));
-  }
-
-
-  void LookupIdentifierQuery::Disjunction::AddRange(const DicomTag& tag,
-                                                    const std::string& start,
-                                                    const std::string& end)
-  {
-    rangeConstraints_.push_back(new RangeConstraint(tag, start, end));
-  }
-
-
-  LookupIdentifierQuery::~LookupIdentifierQuery()
-  {
-    for (Disjunctions::iterator it = disjunctions_.begin();
-         it != disjunctions_.end(); ++it)
-    {
-      delete *it;
-    }
-  }
-
-
-  bool LookupIdentifierQuery::IsIdentifier(const DicomTag& tag)
-  {
-    return ServerToolbox::IsIdentifier(tag, level_);
-  }
-
-
-  void LookupIdentifierQuery::AddConstraint(DicomTag tag,
-                                            IdentifierConstraintType type,
-                                            const std::string& value)
-  {
-    assert(IsIdentifier(tag));
-    disjunctions_.push_back(new Disjunction);
-    disjunctions_.back()->Add(tag, type, value);
-  }
-
-
-  void LookupIdentifierQuery::AddRange(DicomTag tag,
-                                       const std::string& start,
-                                       const std::string& end)
-  {
-    assert(IsIdentifier(tag));
-    disjunctions_.push_back(new Disjunction);
-    disjunctions_.back()->AddRange(tag, start, end);
-  }
-
-
-  LookupIdentifierQuery::Disjunction& LookupIdentifierQuery::AddDisjunction()
-  {
-    disjunctions_.push_back(new Disjunction);
-    return *disjunctions_.back();
-  }
-
-
-  void LookupIdentifierQuery::Apply(std::list<std::string>& result,
-                                    IDatabaseWrapper& database)
-  {
-    SetOfResources resources(database, level_);
-    Apply(resources, database);
-
-    resources.Flatten(result);
-  }
-
-
-  void LookupIdentifierQuery::Apply(SetOfResources& result,
-                                    IDatabaseWrapper& database)
-  {
-    for (size_t i = 0; i < disjunctions_.size(); i++)
-    {
-      std::list<int64_t> a;
-
-      for (size_t j = 0; j < disjunctions_[i]->GetSingleConstraintsCount(); j++)
-      {
-        const SingleConstraint& constraint = disjunctions_[i]->GetSingleConstraint(j);
-        std::list<int64_t> b;
-        database.LookupIdentifier(b, level_, constraint.GetTag(), 
-                                  constraint.GetType(), constraint.GetValue());
-
-        a.splice(a.end(), b);
-      }
-
-      for (size_t j = 0; j < disjunctions_[i]->GetRangeConstraintsCount(); j++)
-      {
-        const RangeConstraint& constraint = disjunctions_[i]->GetRangeConstraint(j);
-        std::list<int64_t> b;
-        database.LookupIdentifierRange(b, level_, constraint.GetTag(), 
-                                       constraint.GetStart(), constraint.GetEnd());
-
-        a.splice(a.end(), b);
-      }
-
-      result.Intersect(a);
-    }
-  }
-
-
-  void LookupIdentifierQuery::Print(std::ostream& s) const
-  {
-    s << "Constraint: " << std::endl;
-    for (Disjunctions::const_iterator
-           it = disjunctions_.begin(); it != disjunctions_.end(); ++it)
-    {
-      if (it == disjunctions_.begin())
-        s << "   ";
-      else
-        s << "OR ";
-
-      for (size_t j = 0; j < (*it)->GetSingleConstraintsCount(); j++)
-      {
-        const SingleConstraint& c = (*it)->GetSingleConstraint(j);
-        s << FromDcmtkBridge::GetTagName(c.GetTag(), "");
-
-        switch (c.GetType())
-        {
-          case IdentifierConstraintType_Equal: s << " == "; break;
-          case IdentifierConstraintType_SmallerOrEqual: s << " <= "; break;
-          case IdentifierConstraintType_GreaterOrEqual: s << " >= "; break;
-          case IdentifierConstraintType_Wildcard: s << " ~= "; break;
-          default:
-            s << " ? ";
-        }
-
-        s << c.GetValue() << std::endl;
-      }
-    }
-  }
-}
--- a/OrthancServer/Search/LookupIdentifierQuery.h	Wed Dec 19 14:20:11 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,207 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2018 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "../IDatabaseWrapper.h"
-
-#include "SetOfResources.h"
-
-#include <vector>
-#include <boost/noncopyable.hpp>
-
-namespace Orthanc
-{
-  /**
-   * Primitive for wildcard matching, as defined in DICOM:
-   * http://dicom.nema.org/dicom/2013/output/chtml/part04/sect_C.2.html#sect_C.2.2.2.4
-   * 
-   * "Any occurrence of an "*" or a "?", then "*" shall match any
-   * sequence of characters (including a zero length value) and "?"
-   * shall match any single character. This matching is case
-   * sensitive, except for Attributes with an PN Value
-   * Representation (e.g., Patient Name (0010,0010))."
-   * 
-   * Pay attention to the fact that "*" (resp. "?") generally
-   * corresponds to "%" (resp. "_") in primitive LIKE of SQL. The
-   * values "%", "_", "\" should in the user request should
-   * respectively be escaped as "\%", "\_" and "\\".
-   *
-   * This matching must be case sensitive: The special case of PN VR
-   * is taken into consideration by normalizing the query string in
-   * method "NormalizeIdentifier()".
-   **/
-
-  class LookupIdentifierQuery : public boost::noncopyable
-  {
-    // This class encodes a conjunction ("AND") of disjunctions. Each
-    // disjunction represents an "OR" of several constraints.
-
-  public:
-    class SingleConstraint
-    {
-    private:
-      DicomTag                  tag_;
-      IdentifierConstraintType  type_;
-      std::string               value_;
-
-    public:
-      SingleConstraint(const DicomTag& tag,
-                       IdentifierConstraintType type,
-                       const std::string& value);
-
-      const DicomTag& GetTag() const
-      {
-        return tag_;
-      }
-
-      IdentifierConstraintType GetType() const
-      {
-        return type_;
-      }
-      
-      const std::string& GetValue() const
-      {
-        return value_;
-      }
-    };
-
-
-    class RangeConstraint
-    {
-    private:
-      DicomTag     tag_;
-      std::string  start_;
-      std::string  end_;
-
-    public:
-      RangeConstraint(const DicomTag& tag,
-                      const std::string& start,
-                      const std::string& end);
-
-      const DicomTag& GetTag() const
-      {
-        return tag_;
-      }
-
-      const std::string& GetStart() const
-      {
-        return start_;
-      }
-
-      const std::string& GetEnd() const
-      {
-        return end_;
-      }
-    };
-
-
-    class Disjunction : public boost::noncopyable
-    {
-    private:
-      std::vector<SingleConstraint*>  singleConstraints_;
-      std::vector<RangeConstraint*>   rangeConstraints_;
-
-    public:
-      ~Disjunction();
-
-      void Add(const DicomTag& tag,
-               IdentifierConstraintType type,
-               const std::string& value);
-
-      void AddRange(const DicomTag& tag,
-                    const std::string& start,
-                    const std::string& end);
-
-      size_t GetSingleConstraintsCount() const
-      {
-        return singleConstraints_.size();
-      }
-
-      const SingleConstraint&  GetSingleConstraint(size_t i) const
-      {
-        return *singleConstraints_[i];
-      }
-
-      size_t GetRangeConstraintsCount() const
-      {
-        return rangeConstraints_.size();
-      }
-
-      const RangeConstraint&  GetRangeConstraint(size_t i) const
-      {
-        return *rangeConstraints_[i];
-      }
-    };
-
-
-  private:
-    typedef std::vector<Disjunction*>  Disjunctions;
-
-    ResourceType  level_;
-    Disjunctions  disjunctions_;
-
-  public:
-    LookupIdentifierQuery(ResourceType level) : level_(level)
-    {
-    }
-
-    ~LookupIdentifierQuery();
-
-    bool IsIdentifier(const DicomTag& tag);
-
-    void AddConstraint(DicomTag tag,
-                       IdentifierConstraintType type,
-                       const std::string& value);
-
-    void AddRange(DicomTag tag,
-                  const std::string& start,
-                  const std::string& end);
-
-    Disjunction& AddDisjunction();
-
-    ResourceType GetLevel() const
-    {
-      return level_;
-    }
-
-    // The database must be locked
-    void Apply(std::list<std::string>& result,
-               IDatabaseWrapper& database);
-
-    void Apply(SetOfResources& result,
-               IDatabaseWrapper& database);
-
-    void Print(std::ostream& s) const;
-  };
-}
--- a/OrthancServer/Search/RangeConstraint.cpp	Wed Dec 19 14:20:11 2018 +0100
+++ b/OrthancServer/Search/RangeConstraint.cpp	Wed Dec 19 16:27:07 2018 +0100
@@ -56,29 +56,6 @@
   }
 
 
-  void RangeConstraint::Setup(LookupIdentifierQuery& lookup,
-                              const DicomTag& tag) const
-  {
-    if (!lower_.empty() &&
-        !upper_.empty())
-    {
-      lookup.AddRange(tag, lower_, upper_);
-    }
-    else
-    {
-      if (!lower_.empty())
-      {
-        lookup.AddConstraint(tag, IdentifierConstraintType_GreaterOrEqual, lower_);
-      }
-
-      if (!upper_.empty())
-      {
-        lookup.AddConstraint(tag, IdentifierConstraintType_SmallerOrEqual, upper_);
-      }
-    }
-  }
-
-
   bool RangeConstraint::Match(const std::string& value) const
   {
     std::string v;
--- a/OrthancServer/Search/RangeConstraint.h	Wed Dec 19 14:20:11 2018 +0100
+++ b/OrthancServer/Search/RangeConstraint.h	Wed Dec 19 16:27:07 2018 +0100
@@ -61,9 +61,6 @@
       return new RangeConstraint(*this);
     }
 
-    virtual void Setup(LookupIdentifierQuery& lookup,
-                       const DicomTag& tag) const;
-
     virtual bool Match(const std::string& value) const;
 
     virtual std::string Format() const
--- a/OrthancServer/Search/ValueConstraint.cpp	Wed Dec 19 14:20:11 2018 +0100
+++ b/OrthancServer/Search/ValueConstraint.cpp	Wed Dec 19 16:27:07 2018 +0100
@@ -55,12 +55,6 @@
   }
 
 
-  void ValueConstraint::Setup(LookupIdentifierQuery& lookup,
-                              const DicomTag& tag) const
-  {
-    lookup.AddConstraint(tag, IdentifierConstraintType_Equal, value_);
-  }
-
   bool ValueConstraint::Match(const std::string& value) const
   {
     if (isCaseSensitive_)
--- a/OrthancServer/Search/ValueConstraint.h	Wed Dec 19 14:20:11 2018 +0100
+++ b/OrthancServer/Search/ValueConstraint.h	Wed Dec 19 16:27:07 2018 +0100
@@ -58,9 +58,6 @@
       return new ValueConstraint(*this);
     }
 
-    virtual void Setup(LookupIdentifierQuery& lookup,
-                       const DicomTag& tag) const;
-
     virtual bool Match(const std::string& value) const;
 
     virtual std::string Format() const
--- a/OrthancServer/Search/WildcardConstraint.cpp	Wed Dec 19 14:20:11 2018 +0100
+++ b/OrthancServer/Search/WildcardConstraint.cpp	Wed Dec 19 16:27:07 2018 +0100
@@ -34,6 +34,8 @@
 #include "../PrecompiledHeadersServer.h"
 #include "WildcardConstraint.h"
 
+#include "../../Core/Toolbox.h"
+
 #include <boost/regex.hpp>
 
 namespace Orthanc
@@ -87,12 +89,6 @@
     }
   }
 
-  void WildcardConstraint::Setup(LookupIdentifierQuery& lookup,
-                                 const DicomTag& tag) const
-  {
-    lookup.AddConstraint(tag, IdentifierConstraintType_Wildcard, pimpl_->wildcard_);
-  }
-
   std::string WildcardConstraint::Format() const
   {
     return pimpl_->wildcard_;
--- a/OrthancServer/Search/WildcardConstraint.h	Wed Dec 19 14:20:11 2018 +0100
+++ b/OrthancServer/Search/WildcardConstraint.h	Wed Dec 19 16:27:07 2018 +0100
@@ -56,9 +56,6 @@
       return new WildcardConstraint(*this);
     }
 
-    virtual void Setup(LookupIdentifierQuery& lookup,
-                       const DicomTag& tag) const;
-
     virtual bool Match(const std::string& value) const;
 
     virtual std::string Format() const;
--- a/OrthancServer/ServerContext.cpp	Wed Dec 19 14:20:11 2018 +0100
+++ b/OrthancServer/ServerContext.cpp	Wed Dec 19 16:27:07 2018 +0100
@@ -819,7 +819,7 @@
     std::vector<std::string> resources, instances;
 
     const size_t lookupLimit = (databaseLimit == 0 ? 0 : databaseLimit + 1);      
-    GetIndex().ApplyLookupResources(resources, instances, lookup, queryLevel, lookupLimit);
+    GetIndex().ApplyLookupResources(resources, &instances, lookup, queryLevel, lookupLimit);
 
     bool complete = (databaseLimit == 0 ||
                      resources.size() > databaseLimit);
--- a/OrthancServer/ServerIndex.cpp	Wed Dec 19 14:20:11 2018 +0100
+++ b/OrthancServer/ServerIndex.cpp	Wed Dec 19 16:27:07 2018 +0100
@@ -2141,11 +2141,9 @@
     std::vector<DatabaseConstraint> query;
     query.push_back(DatabaseConstraint(c, level, DicomTagType_Identifier));
 
-    std::vector<std::string> instancesId;
-
     {
       boost::mutex::scoped_lock lock(mutex_);
-      db_.ApplyLookupResources(result, instancesId, query, level, 0);
+      db_.ApplyLookupResources(result, NULL, query, level, 0);
     }
   }
 
@@ -2565,7 +2563,7 @@
 
 
   void ServerIndex::ApplyLookupResources(std::vector<std::string>& resourcesId,
-                                         std::vector<std::string>& instancesId,
+                                         std::vector<std::string>* instancesId,
                                          const DatabaseLookup& lookup,
                                          ResourceType queryLevel,
                                          size_t limit)
--- a/OrthancServer/ServerIndex.h	Wed Dec 19 14:20:11 2018 +0100
+++ b/OrthancServer/ServerIndex.h	Wed Dec 19 16:27:07 2018 +0100
@@ -44,7 +44,6 @@
 
 namespace Orthanc
 {
-  class LookupResource;
   class ServerContext;
   class DicomInstanceToStore;
   class ParsedDicomFile;
@@ -296,7 +295,7 @@
     void ReconstructInstance(ParsedDicomFile& dicom);
 
     void ApplyLookupResources(std::vector<std::string>& resourcesId,
-                              std::vector<std::string>& instancesId,
+                              std::vector<std::string>* instancesId,  // Can be NULL if not needed
                               const DatabaseLookup& lookup,
                               ResourceType queryLevel,
                               size_t limit);
--- a/Plugins/Engine/OrthancPluginDatabase.cpp	Wed Dec 19 14:20:11 2018 +0100
+++ b/Plugins/Engine/OrthancPluginDatabase.cpp	Wed Dec 19 16:27:07 2018 +0100
@@ -1163,7 +1163,7 @@
 
 
   void OrthancPluginDatabase::ApplyLookupResources(std::vector<std::string>& patientsId,
-                                                   std::vector<std::string>& instancesId,
+                                                   std::vector<std::string>* instancesId,
                                                    const std::vector<DatabaseConstraint>& lookup,
                                                    ResourceType queryLevel,
                                                    size_t limit)
--- a/Plugins/Engine/OrthancPluginDatabase.h	Wed Dec 19 14:20:11 2018 +0100
+++ b/Plugins/Engine/OrthancPluginDatabase.h	Wed Dec 19 16:27:07 2018 +0100
@@ -272,7 +272,7 @@
     virtual bool IsDiskSizeAbove(uint64_t threshold);
 
     virtual void ApplyLookupResources(std::vector<std::string>& patientsId,
-                                      std::vector<std::string>& instancesId,
+                                      std::vector<std::string>* instancesId,
                                       const std::vector<DatabaseConstraint>& lookup,
                                       ResourceType queryLevel,
                                       size_t limit);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Graveyard/DatabaseOptimizations/LookupIdentifierQuery.cpp	Wed Dec 19 16:27:07 2018 +0100
@@ -0,0 +1,215 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "../PrecompiledHeadersServer.h"
+#include "LookupIdentifierQuery.h"
+
+#include "../../Core/DicomParsing/FromDcmtkBridge.h"
+#include "../../Core/OrthancException.h"
+#include "../ServerToolbox.h"
+#include "SetOfResources.h"
+
+#include <cassert>
+
+
+
+namespace Orthanc
+{
+  LookupIdentifierQuery::SingleConstraint::
+  SingleConstraint(const DicomTag& tag,
+                   IdentifierConstraintType type,
+                   const std::string& value) : 
+    tag_(tag),
+    type_(type),
+    value_(ServerToolbox::NormalizeIdentifier(value))
+  {
+  }
+
+
+  LookupIdentifierQuery::RangeConstraint::
+  RangeConstraint(const DicomTag& tag,
+                  const std::string& start,
+                  const std::string& end) : 
+    tag_(tag),
+    start_(ServerToolbox::NormalizeIdentifier(start)),
+    end_(ServerToolbox::NormalizeIdentifier(end))
+  {
+  }
+
+
+  LookupIdentifierQuery::Disjunction::~Disjunction()
+  {
+    for (size_t i = 0; i < singleConstraints_.size(); i++)
+    {
+      delete singleConstraints_[i];
+    }
+
+    for (size_t i = 0; i < rangeConstraints_.size(); i++)
+    {
+      delete rangeConstraints_[i];
+    }
+  }
+
+
+  void LookupIdentifierQuery::Disjunction::Add(const DicomTag& tag,
+                                               IdentifierConstraintType type,
+                                               const std::string& value)
+  {
+    singleConstraints_.push_back(new SingleConstraint(tag, type, value));
+  }
+
+
+  void LookupIdentifierQuery::Disjunction::AddRange(const DicomTag& tag,
+                                                    const std::string& start,
+                                                    const std::string& end)
+  {
+    rangeConstraints_.push_back(new RangeConstraint(tag, start, end));
+  }
+
+
+  LookupIdentifierQuery::~LookupIdentifierQuery()
+  {
+    for (Disjunctions::iterator it = disjunctions_.begin();
+         it != disjunctions_.end(); ++it)
+    {
+      delete *it;
+    }
+  }
+
+
+  bool LookupIdentifierQuery::IsIdentifier(const DicomTag& tag)
+  {
+    return ServerToolbox::IsIdentifier(tag, level_);
+  }
+
+
+  void LookupIdentifierQuery::AddConstraint(DicomTag tag,
+                                            IdentifierConstraintType type,
+                                            const std::string& value)
+  {
+    assert(IsIdentifier(tag));
+    disjunctions_.push_back(new Disjunction);
+    disjunctions_.back()->Add(tag, type, value);
+  }
+
+
+  void LookupIdentifierQuery::AddRange(DicomTag tag,
+                                       const std::string& start,
+                                       const std::string& end)
+  {
+    assert(IsIdentifier(tag));
+    disjunctions_.push_back(new Disjunction);
+    disjunctions_.back()->AddRange(tag, start, end);
+  }
+
+
+  LookupIdentifierQuery::Disjunction& LookupIdentifierQuery::AddDisjunction()
+  {
+    disjunctions_.push_back(new Disjunction);
+    return *disjunctions_.back();
+  }
+
+
+  void LookupIdentifierQuery::Apply(std::list<std::string>& result,
+                                    IDatabaseWrapper& database)
+  {
+    SetOfResources resources(database, level_);
+    Apply(resources, database);
+
+    resources.Flatten(result);
+  }
+
+
+  void LookupIdentifierQuery::Apply(SetOfResources& result,
+                                    IDatabaseWrapper& database)
+  {
+    for (size_t i = 0; i < disjunctions_.size(); i++)
+    {
+      std::list<int64_t> a;
+
+      for (size_t j = 0; j < disjunctions_[i]->GetSingleConstraintsCount(); j++)
+      {
+        const SingleConstraint& constraint = disjunctions_[i]->GetSingleConstraint(j);
+        std::list<int64_t> b;
+        database.LookupIdentifier(b, level_, constraint.GetTag(), 
+                                  constraint.GetType(), constraint.GetValue());
+
+        a.splice(a.end(), b);
+      }
+
+      for (size_t j = 0; j < disjunctions_[i]->GetRangeConstraintsCount(); j++)
+      {
+        const RangeConstraint& constraint = disjunctions_[i]->GetRangeConstraint(j);
+        std::list<int64_t> b;
+        database.LookupIdentifierRange(b, level_, constraint.GetTag(), 
+                                       constraint.GetStart(), constraint.GetEnd());
+
+        a.splice(a.end(), b);
+      }
+
+      result.Intersect(a);
+    }
+  }
+
+
+  void LookupIdentifierQuery::Print(std::ostream& s) const
+  {
+    s << "Constraint: " << std::endl;
+    for (Disjunctions::const_iterator
+           it = disjunctions_.begin(); it != disjunctions_.end(); ++it)
+    {
+      if (it == disjunctions_.begin())
+        s << "   ";
+      else
+        s << "OR ";
+
+      for (size_t j = 0; j < (*it)->GetSingleConstraintsCount(); j++)
+      {
+        const SingleConstraint& c = (*it)->GetSingleConstraint(j);
+        s << FromDcmtkBridge::GetTagName(c.GetTag(), "");
+
+        switch (c.GetType())
+        {
+          case IdentifierConstraintType_Equal: s << " == "; break;
+          case IdentifierConstraintType_SmallerOrEqual: s << " <= "; break;
+          case IdentifierConstraintType_GreaterOrEqual: s << " >= "; break;
+          case IdentifierConstraintType_Wildcard: s << " ~= "; break;
+          default:
+            s << " ? ";
+        }
+
+        s << c.GetValue() << std::endl;
+      }
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Graveyard/DatabaseOptimizations/LookupIdentifierQuery.h	Wed Dec 19 16:27:07 2018 +0100
@@ -0,0 +1,207 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../IDatabaseWrapper.h"
+
+#include "SetOfResources.h"
+
+#include <vector>
+#include <boost/noncopyable.hpp>
+
+namespace Orthanc
+{
+  /**
+   * Primitive for wildcard matching, as defined in DICOM:
+   * http://dicom.nema.org/dicom/2013/output/chtml/part04/sect_C.2.html#sect_C.2.2.2.4
+   * 
+   * "Any occurrence of an "*" or a "?", then "*" shall match any
+   * sequence of characters (including a zero length value) and "?"
+   * shall match any single character. This matching is case
+   * sensitive, except for Attributes with an PN Value
+   * Representation (e.g., Patient Name (0010,0010))."
+   * 
+   * Pay attention to the fact that "*" (resp. "?") generally
+   * corresponds to "%" (resp. "_") in primitive LIKE of SQL. The
+   * values "%", "_", "\" should in the user request should
+   * respectively be escaped as "\%", "\_" and "\\".
+   *
+   * This matching must be case sensitive: The special case of PN VR
+   * is taken into consideration by normalizing the query string in
+   * method "NormalizeIdentifier()".
+   **/
+
+  class LookupIdentifierQuery : public boost::noncopyable
+  {
+    // This class encodes a conjunction ("AND") of disjunctions. Each
+    // disjunction represents an "OR" of several constraints.
+
+  public:
+    class SingleConstraint
+    {
+    private:
+      DicomTag                  tag_;
+      IdentifierConstraintType  type_;
+      std::string               value_;
+
+    public:
+      SingleConstraint(const DicomTag& tag,
+                       IdentifierConstraintType type,
+                       const std::string& value);
+
+      const DicomTag& GetTag() const
+      {
+        return tag_;
+      }
+
+      IdentifierConstraintType GetType() const
+      {
+        return type_;
+      }
+      
+      const std::string& GetValue() const
+      {
+        return value_;
+      }
+    };
+
+
+    class RangeConstraint
+    {
+    private:
+      DicomTag     tag_;
+      std::string  start_;
+      std::string  end_;
+
+    public:
+      RangeConstraint(const DicomTag& tag,
+                      const std::string& start,
+                      const std::string& end);
+
+      const DicomTag& GetTag() const
+      {
+        return tag_;
+      }
+
+      const std::string& GetStart() const
+      {
+        return start_;
+      }
+
+      const std::string& GetEnd() const
+      {
+        return end_;
+      }
+    };
+
+
+    class Disjunction : public boost::noncopyable
+    {
+    private:
+      std::vector<SingleConstraint*>  singleConstraints_;
+      std::vector<RangeConstraint*>   rangeConstraints_;
+
+    public:
+      ~Disjunction();
+
+      void Add(const DicomTag& tag,
+               IdentifierConstraintType type,
+               const std::string& value);
+
+      void AddRange(const DicomTag& tag,
+                    const std::string& start,
+                    const std::string& end);
+
+      size_t GetSingleConstraintsCount() const
+      {
+        return singleConstraints_.size();
+      }
+
+      const SingleConstraint&  GetSingleConstraint(size_t i) const
+      {
+        return *singleConstraints_[i];
+      }
+
+      size_t GetRangeConstraintsCount() const
+      {
+        return rangeConstraints_.size();
+      }
+
+      const RangeConstraint&  GetRangeConstraint(size_t i) const
+      {
+        return *rangeConstraints_[i];
+      }
+    };
+
+
+  private:
+    typedef std::vector<Disjunction*>  Disjunctions;
+
+    ResourceType  level_;
+    Disjunctions  disjunctions_;
+
+  public:
+    LookupIdentifierQuery(ResourceType level) : level_(level)
+    {
+    }
+
+    ~LookupIdentifierQuery();
+
+    bool IsIdentifier(const DicomTag& tag);
+
+    void AddConstraint(DicomTag tag,
+                       IdentifierConstraintType type,
+                       const std::string& value);
+
+    void AddRange(DicomTag tag,
+                  const std::string& start,
+                  const std::string& end);
+
+    Disjunction& AddDisjunction();
+
+    ResourceType GetLevel() const
+    {
+      return level_;
+    }
+
+    // The database must be locked
+    void Apply(std::list<std::string>& result,
+               IDatabaseWrapper& database);
+
+    void Apply(SetOfResources& result,
+               IDatabaseWrapper& database);
+
+    void Print(std::ostream& s) const;
+  };
+}
--- a/UnitTestsSources/ServerIndexTests.cpp	Wed Dec 19 14:20:11 2018 +0100
+++ b/UnitTestsSources/ServerIndexTests.cpp	Wed Dec 19 16:27:07 2018 +0100
@@ -38,9 +38,7 @@
 #include "../Core/FileStorage/MemoryStorageArea.h"
 #include "../Core/Logging.h"
 #include "../OrthancServer/SQLiteDatabaseWrapper.h"
-#include "../OrthancServer/Search/LookupIdentifierQuery.h"
 #include "../OrthancServer/ServerContext.h"
-#include "../OrthancServer/ServerIndex.h"
 #include "../OrthancServer/ServerToolbox.h"
 
 #include <ctype.h>
@@ -251,16 +249,42 @@
     }
 
 
-    void DoLookup(std::list<std::string>& result,
-                  ResourceType level,
-                  const DicomTag& tag,
-                  const std::string& value)
+    void DoLookupIdentifier(std::vector<std::string>& result,
+                            ResourceType level,
+                            const DicomTag& tag,
+                            ConstraintType type,
+                            const std::string& value)
     {
-      LookupIdentifierQuery query(level);
-      query.AddConstraint(tag, IdentifierConstraintType_Equal, value);
-      query.Apply(result, *index_);
+      assert(ServerToolbox::IsIdentifier(tag, level));
+      
+      DicomTagConstraint c(tag, type, value, true, true);
+      
+      std::vector<DatabaseConstraint> lookup;
+      lookup.push_back(DatabaseConstraint(c, level, DicomTagType_Identifier));
+      
+      index_->ApplyLookupResources(result, NULL, lookup, level, 0 /* no limit */);
     }
+    
 
+    void DoLookupIdentifier2(std::vector<std::string>& result,
+                             ResourceType level,
+                             const DicomTag& tag,
+                             ConstraintType type1,
+                             const std::string& value1,
+                             ConstraintType type2,
+                             const std::string& value2)
+    {
+      assert(ServerToolbox::IsIdentifier(tag, level));
+      
+      DicomTagConstraint c1(tag, type1, value1, true, true);
+      DicomTagConstraint c2(tag, type2, value2, true, true);
+      
+      std::vector<DatabaseConstraint> lookup;
+      lookup.push_back(DatabaseConstraint(c1, level, DicomTagType_Identifier));
+      lookup.push_back(DatabaseConstraint(c2, level, DicomTagType_Identifier));
+      
+      index_->ApplyLookupResources(result, NULL, lookup, level, 0 /* no limit */);
+    }
   };
 }
 
@@ -716,64 +740,48 @@
   index_->SetIdentifierTag(a[2], DICOM_TAG_STUDY_INSTANCE_UID, "0");
   index_->SetIdentifierTag(a[3], DICOM_TAG_SERIES_INSTANCE_UID, "0");
 
-  std::list<std::string> s;
+  std::vector<std::string> s;
 
-  DoLookup(s, ResourceType_Study, DICOM_TAG_STUDY_INSTANCE_UID, "0");
+  DoLookupIdentifier(s, ResourceType_Study, DICOM_TAG_STUDY_INSTANCE_UID, ConstraintType_Equal, "0");
   ASSERT_EQ(2u, s.size());
   ASSERT_TRUE(std::find(s.begin(), s.end(), "a") != s.end());
   ASSERT_TRUE(std::find(s.begin(), s.end(), "c") != s.end());
 
-  DoLookup(s, ResourceType_Series, DICOM_TAG_SERIES_INSTANCE_UID, "0");
+  DoLookupIdentifier(s, ResourceType_Series, DICOM_TAG_SERIES_INSTANCE_UID, ConstraintType_Equal, "0");
   ASSERT_EQ(1u, s.size());
   ASSERT_TRUE(std::find(s.begin(), s.end(), "d") != s.end());
 
-  DoLookup(s, ResourceType_Study, DICOM_TAG_STUDY_INSTANCE_UID, "1");
+  DoLookupIdentifier(s, ResourceType_Study, DICOM_TAG_STUDY_INSTANCE_UID, ConstraintType_Equal, "1");
   ASSERT_EQ(1u, s.size());
   ASSERT_TRUE(std::find(s.begin(), s.end(), "b") != s.end());
 
-  DoLookup(s, ResourceType_Study, DICOM_TAG_STUDY_INSTANCE_UID, "1");
+  DoLookupIdentifier(s, ResourceType_Study, DICOM_TAG_STUDY_INSTANCE_UID, ConstraintType_Equal, "1");
   ASSERT_EQ(1u, s.size());
   ASSERT_TRUE(std::find(s.begin(), s.end(), "b") != s.end());
 
-  DoLookup(s, ResourceType_Series, DICOM_TAG_SERIES_INSTANCE_UID, "1");
+  DoLookupIdentifier(s, ResourceType_Series, DICOM_TAG_SERIES_INSTANCE_UID, ConstraintType_Equal, "1");
+  ASSERT_EQ(0u, s.size());
+
+  DoLookupIdentifier(s, ResourceType_Study, DICOM_TAG_STUDY_INSTANCE_UID, ConstraintType_GreaterOrEqual, "0");
+  ASSERT_EQ(3u, s.size());
+
+  DoLookupIdentifier(s, ResourceType_Study, DICOM_TAG_STUDY_INSTANCE_UID, ConstraintType_GreaterOrEqual, "1");
+  ASSERT_EQ(1u, s.size());
+
+  DoLookupIdentifier(s, ResourceType_Study, DICOM_TAG_STUDY_INSTANCE_UID, ConstraintType_GreaterOrEqual, "2");
   ASSERT_EQ(0u, s.size());
 
-  {
-    LookupIdentifierQuery query(ResourceType_Study);
-    query.AddConstraint(DICOM_TAG_STUDY_INSTANCE_UID, IdentifierConstraintType_GreaterOrEqual, "0");
-    query.Apply(s, *index_);
-    ASSERT_EQ(3u, s.size());
-  }
-
-  {
-    LookupIdentifierQuery query(ResourceType_Study);
-    query.AddConstraint(DICOM_TAG_STUDY_INSTANCE_UID, IdentifierConstraintType_GreaterOrEqual, "0");
-    query.AddConstraint(DICOM_TAG_STUDY_INSTANCE_UID, IdentifierConstraintType_SmallerOrEqual, "0");
-    query.Apply(s, *index_);
-    ASSERT_EQ(2u, s.size());
-  }
+  DoLookupIdentifier2(s, ResourceType_Study, DICOM_TAG_STUDY_INSTANCE_UID,
+                      ConstraintType_GreaterOrEqual, "0", ConstraintType_SmallerOrEqual, "0");
+  ASSERT_EQ(2u, s.size());
 
-  {
-    LookupIdentifierQuery query(ResourceType_Study);
-    query.AddConstraint(DICOM_TAG_STUDY_INSTANCE_UID, IdentifierConstraintType_GreaterOrEqual, "1");
-    query.AddConstraint(DICOM_TAG_STUDY_INSTANCE_UID, IdentifierConstraintType_SmallerOrEqual, "1");
-    query.Apply(s, *index_);
-    ASSERT_EQ(1u, s.size());
-  }
+  DoLookupIdentifier2(s, ResourceType_Study, DICOM_TAG_STUDY_INSTANCE_UID,
+                      ConstraintType_GreaterOrEqual, "1", ConstraintType_SmallerOrEqual, "1");
+  ASSERT_EQ(1u, s.size());
 
-  {
-    LookupIdentifierQuery query(ResourceType_Study);
-    query.AddConstraint(DICOM_TAG_STUDY_INSTANCE_UID, IdentifierConstraintType_GreaterOrEqual, "1");
-    query.Apply(s, *index_);
-    ASSERT_EQ(1u, s.size());
-  }
-
-  {
-    LookupIdentifierQuery query(ResourceType_Study);
-    query.AddConstraint(DICOM_TAG_STUDY_INSTANCE_UID, IdentifierConstraintType_GreaterOrEqual, "2");
-    query.Apply(s, *index_);
-    ASSERT_EQ(0u, s.size());
-  }
+  DoLookupIdentifier2(s, ResourceType_Study, DICOM_TAG_STUDY_INSTANCE_UID,
+                      ConstraintType_GreaterOrEqual, "0", ConstraintType_SmallerOrEqual, "1");
+  ASSERT_EQ(3u, s.size());
 }
 
 
@@ -858,7 +866,7 @@
 }
 
 
-TEST(LookupIdentifierQuery, NormalizeIdentifier)
+TEST(ServerIndex, NormalizeIdentifier)
 {
   ASSERT_EQ("H^L.LO", ServerToolbox::NormalizeIdentifier("   Hé^l.LO  %_  "));
   ASSERT_EQ("1.2.840.113619.2.176.2025", ServerToolbox::NormalizeIdentifier("   1.2.840.113619.2.176.2025  "));