Improve PoolQuery to allow queries on dependencies. (bnc #475682)
authorMichael Andres <ma@suse.de>
Fri, 29 May 2009 18:20:50 +0000 (20:20 +0200)
committerMichael Andres <ma@suse.de>
Fri, 29 May 2009 18:20:50 +0000 (20:20 +0200)
tests/zypp/PoolQuery_test.cc
zypp/PoolQuery.cc
zypp/PoolQuery.h

index 933a389..96372db 100644 (file)
@@ -35,6 +35,24 @@ struct PrintAndCount
   unsigned _count;
 };
 
+void dumpQ( std::ostream & str, const PoolQuery & q, bool verbose = true )
+{
+  q.begin();
+  str << q << endl;
+  unsigned nc = 0;
+  if ( 1 )
+  {
+    for_( it, q.begin(), q.end() )
+    {
+      ++nc;
+      if ( verbose )
+        str << it << endl;
+    }
+    str << "--> MATCHES: " << nc << endl;
+  }
+}
+
+
 #if 0
 BOOST_AUTO_TEST_CASE(pool_query_experiment)
 {
@@ -58,7 +76,6 @@ BOOST_AUTO_TEST_CASE(pool_query_experiment)
 }
 #endif
 
-
 /////////////////////////////////////////////////////////////////////////////
 //  0xx basic queries
 /////////////////////////////////////////////////////////////////////////////
@@ -650,3 +667,71 @@ BOOST_AUTO_TEST_CASE(pool_query_equal)
   BOOST_CHECK(q==q4);
   BOOST_CHECK(q4!=q3);
 }
+
+/////////////////////////////////////////////////////////////////////////////
+//  Dependency Query
+/////////////////////////////////////////////////////////////////////////////
+
+BOOST_AUTO_TEST_CASE(addDependency)
+{
+  {
+    cout << "****addDependency1****"  << endl;
+    PoolQuery q;
+    q.setCaseSensitive( false );
+    q.setMatchSubstring();
+    q.addString( "libzypp" );
+    q.addDependency( sat::SolvAttr::provides, "FOO" ); // ! finds 'perl(CPAN::InfoObj)' 'foO'
+    std::for_each(q.begin(), q.end(), PrintAndCount());
+    BOOST_CHECK_EQUAL( q.size(), 12 );
+  }
+  {
+    cout << "****addDependency2****"  << endl;
+    PoolQuery q;
+    q.setCaseSensitive( false );
+    q.setMatchSubstring();
+    q.addString( "libzypp" );
+    q.addDependency( sat::SolvAttr::provides, "FOO", Rel::GT, Edition("5.0") );
+    std::for_each(q.begin(), q.end(), PrintAndCount());
+    //dumpQ( std::cout, q );
+    BOOST_CHECK_EQUAL( q.size(), 6 );
+  }
+
+  {
+    cout << "****addDependency3****"  << endl;
+    PoolQuery q;
+    // includes wine
+    q.addDependency( sat::SolvAttr::provides, "kernel" );
+    std::for_each(q.begin(), q.end(), PrintAndCount());
+    //dumpQ( std::cout, q );
+    BOOST_CHECK_EQUAL( q.size(), 12 );
+  }
+  {
+    cout << "****addDependency4****"  << endl;
+    PoolQuery q;
+    // no wine
+    q.addDependency( sat::SolvAttr::name, "kernel" );
+    std::for_each(q.begin(), q.end(), PrintAndCount());
+    //dumpQ( std::cout, q );
+    BOOST_CHECK_EQUAL( q.size(), 11 );
+  }
+  {
+    cout << "****addDependency5****"  << endl;
+    PoolQuery q;
+    // Capability always matches exact
+    q.addDependency( sat::SolvAttr::provides, Capability("kernel") );
+    std::for_each(q.begin(), q.end(), PrintAndCount());
+    //dumpQ( std::cout, q );
+    BOOST_CHECK_EQUAL( q.size(), 2 );
+  }
+  {
+    cout << "****addDependency6****"  << endl;
+    PoolQuery q;
+    // non dependecy + Capability matches solvable name!
+    q.addDependency( sat::SolvAttr::summary, Capability("kernel") );
+    std::for_each(q.begin(), q.end(), PrintAndCount());
+    //dumpQ( std::cout, q );
+    BOOST_CHECK_EQUAL( q.size(), 0 ); // non dependecy
+  }
+}
+
+
index b0715a6..375a349 100644 (file)
@@ -13,7 +13,7 @@
 #include <sstream>
 
 #include "zypp/base/Gettext.h"
-#include "zypp/base/Logger.h"
+#include "zypp/base/LogTools.h"
 #include "zypp/base/Algorithm.h"
 #include "zypp/base/String.h"
 #include "zypp/repo/RepoException.h"
@@ -67,6 +67,12 @@ namespace zypp
         , attrMatcher( attrMatcher_r )
       {}
 
+      AttrMatchData( sat::SolvAttr attr_r, const sat::AttrMatcher & attrMatcher_r, const Predicate & predicate_r )
+        : attr( attr_r )
+        , attrMatcher( attrMatcher_r )
+        , predicate( predicate_r )
+      {}
+
       sat::SolvAttr    attr;
       sat::AttrMatcher attrMatcher;
       Predicate        predicate;
@@ -79,6 +85,84 @@ namespace zypp
 
     typedef std::list<AttrMatchData> AttrMatchList;
 
+    /////////////////////////////////////////////////////////////////
+    // some Helpers and Predicates
+    /////////////////////////////////////////////////////////////////
+
+    bool isDependencyAttribute( sat::SolvAttr attr_r )
+    {
+      static sat::SolvAttr deps[] = {
+        SolvAttr::provides,
+        SolvAttr::requires,
+        SolvAttr::recommends,
+        SolvAttr::obsoletes,
+        SolvAttr::conflicts,
+        SolvAttr::suggests,
+        SolvAttr::supplements,
+        SolvAttr::enhances,
+      };
+      for_( it, arrayBegin(deps), arrayEnd(deps) )
+        if ( *it == attr_r )
+          return true;
+      return false;
+    }
+
+    /** Whether the current capabilities edition range ovelaps.
+     * Query asserts \a iter_r points to a capability and we
+     * have to check the range only.
+     */
+    struct EditionRangePredicate
+    {
+      EditionRangePredicate( const Rel & op, const Edition & edition )
+        : _range( op, edition )
+      {}
+
+      bool operator()( sat::LookupAttr::iterator iter_r )
+      {
+        CapDetail cap( iter_r.id() );
+        if ( ! cap.isSimple() )
+          return false;
+        if ( cap.isNamed() ) // no range to match
+          return true;
+        return overlaps( Edition::MatchRange( cap.op(), cap.ed() ), _range );
+      }
+
+      Edition::MatchRange _range;
+    };
+
+    /** Whether the current Solvables edition is within a given range. */
+    struct SolvableRangePredicate
+    {
+      SolvableRangePredicate( const Rel & op, const Edition & edition )
+        : _range( op, edition )
+      {}
+
+      bool operator()( sat::LookupAttr::iterator iter_r )
+      {
+        return overlaps( Edition::MatchRange( Rel::EQ, iter_r.inSolvable().edition() ), _range );
+      }
+
+      Edition::MatchRange _range;
+    };
+
+    /** Whether the current capability matches a given one.
+     * Query asserts \a iter_r points to a capability and we
+     * have to check the match only.
+     */
+    struct CapabilityMatchPredicate
+    {
+      CapabilityMatchPredicate( Capability cap_r )
+        : _cap( cap_r )
+      {}
+
+      bool operator()( sat::LookupAttr::iterator iter_r ) const
+      {
+        return _cap.matches( iter_r.asType<Capability>() ) == CapMatch::yes;
+      }
+
+      Capability _cap;
+    };
+
   } /////////////////////////////////////////////////////////////////
   // namespace
   ///////////////////////////////////////////////////////////////////
@@ -111,6 +195,8 @@ namespace zypp
     StrContainer _strings;
     /** Raw attributes */
     AttrRawStrMap _attrs;
+    /** Uncompiled attributes with predicate. */
+    AttrMatchList _uncompiledPredicated;
 
     /** Sat solver search flags */
     Match _flags;
@@ -199,11 +285,7 @@ namespace zypp
     //     create regex; store in rcstrings; if more strings flag regex;
     if (_attrs.empty())
     {
-      rcstrings = createRegex(_strings, cflags);
-      if (_strings.size() > 1) // switch to regex for multiple strings
-        cflags.setModeRegex();
-      _attrMatchList.push_back( AttrMatchData( sat::SolvAttr::allAttr,
-                                sat::AttrMatcher( rcstrings, cflags ) ) );
+      ; // A default 'query-all' will be added after all sources are processed.
     }
 
     // // ONE ATTRIBUTE
@@ -305,7 +387,50 @@ attremptycheckend:
       }
     }
 
-    // Check here, whether all involved regex compile.
+    // Now handle any predicated queries
+    if ( ! _uncompiledPredicated.empty() )
+    {
+      StrContainer global;
+      invokeOnEach( _strings.begin(), _strings.end(), EmptyFilter(), MyInserter(global) );
+      for_( it, _uncompiledPredicated.begin(), _uncompiledPredicated.end() )
+      {
+        if ( it->attrMatcher.flags().mode() == Match::OTHER )
+        {
+          // need to compile:
+          StrContainer joined( global );
+          const std::string & mstr( it->attrMatcher.searchstring() );
+          if ( ! mstr.empty() )
+            joined.insert( mstr );
+
+          cflags = _flags;
+          rcstrings = createRegex( joined, cflags );
+          if ( joined.size() > 1 ) // switch to regex for multiple strings
+            cflags.setModeRegex();
+
+          _attrMatchList.push_back( AttrMatchData( it->attr,
+                                    sat::AttrMatcher( rcstrings, cflags ),
+                                        it->predicate ) );
+        }
+        else
+        {
+          // copy matcher
+         _attrMatchList.push_back( *it );
+        }
+      }
+    }
+
+    // If no attributes defined at all, then add 'query all'
+    if ( _attrMatchList.empty() )
+    {
+      cflags = _flags;
+      rcstrings = createRegex( _strings, cflags );
+      if ( _strings.size() > 1 ) // switch to regex for multiple strings
+        cflags.setModeRegex();
+      _attrMatchList.push_back( AttrMatchData( sat::SolvAttr::allAttr,
+                                sat::AttrMatcher( rcstrings, cflags ) ) );
+    }
+
+    // Finally check here, whether all involved regex compile.
     for_( it, _attrMatchList.begin(), _attrMatchList.end() )
     {
       it->attrMatcher.compile(); // throws on error
@@ -460,6 +585,12 @@ attremptycheckend:
       o << endl;
     }
 
+    o << "predicated: " << endl;
+    for_( it, _uncompiledPredicated.begin(), _uncompiledPredicated.end() )
+    {
+      o << "* " << *it << endl;
+    }
+
     // compiled
     o << "last attribute matcher compiled: " << endl;
     if ( _attrMatchList.empty() )
@@ -501,18 +632,59 @@ attremptycheckend:
     _pimpl->_repos.insert(repoalias);
   }
 
-
   void PoolQuery::addKind(const ResKind & kind)
   { _pimpl->_kinds.insert(kind); }
 
-
   void PoolQuery::addString(const string & value)
   { _pimpl->_strings.insert(value); }
 
-
   void PoolQuery::addAttribute(const sat::SolvAttr & attr, const std::string & value)
   { _pimpl->_attrs[attr].insert(value); }
 
+  void PoolQuery::addDependency( const sat::SolvAttr & attr, const std::string & name, const Rel & op, const Edition & edition )
+  {
+    switch ( op.inSwitch() )
+    {
+      case Rel::ANY_e: // no additional constraint on edition.
+        addAttribute( attr, name );
+        return;
+
+      case Rel::NONE_e:        // will never match.
+        return;
+
+      default: // go and add the predicated query (uncompiled)
+        break;
+    }
+
+    // Match::OTHER indicates need to compile.
+    sat::AttrMatcher matcher( name, Match::OTHER );
+
+    AttrMatchData::Predicate pred;
+    if ( isDependencyAttribute( attr ) )
+      pred = EditionRangePredicate( op, edition );
+    else
+      pred = SolvableRangePredicate( op, edition );
+
+    _pimpl->_uncompiledPredicated.push_back( AttrMatchData( attr, matcher, pred ) );
+  }
+
+  void PoolQuery::addDependency( const sat::SolvAttr & attr, Capability cap_r )
+  {
+    CapDetail cap( cap_r );
+    if ( ! cap.isSimple() ) // will never match.
+      return;
+
+    // Matches STRING per default. (won't get compiled!)
+    sat::AttrMatcher matcher( cap.name().asString() );
+
+    AttrMatchData::Predicate pred;
+    if ( isDependencyAttribute( attr ) )
+      pred = CapabilityMatchPredicate( cap_r );
+    else
+      pred = SolvableRangePredicate( cap.op(), cap.ed() );
+
+    _pimpl->_uncompiledPredicated.push_back( AttrMatchData( attr, matcher, pred ) );
+  }
 
   void PoolQuery::setEdition(const Edition & edition, const Rel & op)
   {
index b41d775..cd80ffc 100644 (file)
@@ -209,48 +209,94 @@ namespace zypp
      *
      * \see sat::SolvAttr
      */
-    void addAttribute(const sat::SolvAttr & attr, const std::string & value = "");
-#if 0
-    /**
-     * Query for dependencies matching a broken down capability.
+    void addAttribute( const sat::SolvAttr & attr, const std::string & value = "" );
+
+    /** \name Filter by dependencies matching a broken down capability <tt>name [op edition]</tt>.
+     *
+     * The capabilities \c name part may be defined as query string
+     * like with \ref addAttribute. Globing and regex are supported.
+     * Global query strings defined by \ref addString are considered.
+     *
+     * So without any <tt>op edition</tt> addDependency behaves the
+     * same as \ref addAttribute. If an edition range is given, matches
+     * are restricted accordingly. Thete are various overloads, so pick
+     * the one you like best.
      *
-     * The capabilities \c name part may be defined as ordinary query
-     * string (\see \ref addAttribute), so globing and regex are supported.
      * \code
-     *   addDependency( sat::SolvAttr::provides, "kde*", Edition("2.0"), Rel::GE );
+     * {
+     *   setMatchGlob();
+     *   setCaseSensitive( false );
+     *   addDependency( sat::SolvAttr::provides, "kde*", Rel::EQ, Edition("2.0") );
+     *   addDependency( sat::SolvAttr::provides, "kde*", Edition("2.0") ); // same as above
+     * }
+     * {
+     *   setMatchGlob();
+     *   setCaseSensitive( false );
+     *   addString( "kde*" );
+     *   addDependency( sat::SolvAttr::provides, Rel::EQ, Edition("2.0") );// same as above
+     *   addDependency( sat::SolvAttr::provides, Edition("2.0") );         // same as above
+     * }
+     * \endcode
+     *
+     * \note Thre's also a version of \ref addDependency provided, that takes a
+     * complete \ref Capability as argument. This always requires an exact match
+     * of the name part (as the resolver would do it).
+     *
+     * This is the list of valid dependency attributes:
+     * \code
+     *   SolvAttr::provides
+     *   SolvAttr::obsoletes
+     *   SolvAttr::conflicts
+     *   SolvAttr::requires
+     *   SolvAttr::recommends
+     *   SolvAttr::suggests
+     *   SolvAttr::supplements
+     *   SolvAttr::enhances
+     * \endcode
+     *
+     * \note <b>What happens if a non dependency attribute is passed?<\b>
+     * If an edition range is given, it is matched against the matching
+     * solvables edition instead. Without edition range it behaves the
+     * same as \ref addAttribute.
+     *
+     * \code
+     *   // Find all packages providing "kernel > 2.0"
+     *   addDependency( sat::SolvAttr::provides, "kernel", Rel::GT, Edition("2.0") );
+     *
+     *   // // Find all packages named "kernel" and with edition "> 2.0"
+     *   addDependency( sat::SolvAttr::name, "kernel", Rel::GT, Edition("2.0") );
      * \endcode
-     * \throws Exception in case \a attr is not a dependency attribute.
      */
-    void addDependency( const sat::SolvAttr & attr, const std::string & name,
-                        const Edition & edition, const Rel & op = Rel::EQ );
-    /** \overload Query provides */
-    void addProvides( const std::string & name, const Edition & edition, const Rel & op = Rel::EQ )
-    { addDependency( sat::SolvAttr::provides, name, edition, op ); }
-    /** \overload Query requires */
-    void addRequires( const std::string & name, const Edition & edition, const Rel & op = Rel::EQ )
-    { addDependency( sat::SolvAttr::requires, name, edition, op ); }
-    /** \overload Query obsoletes */
-    void addObsoletes( const std::string & name, const Edition & edition, const Rel & op = Rel::EQ )
-    { addDependency( sat::SolvAttr::obsoletes, name, edition, op ); }
-    /** \overload Query conflicts */
-    void addConflicts( const std::string & name, const Edition & edition, const Rel & op = Rel::EQ )
-    { addDependency( sat::SolvAttr::conflicts, name, edition, op ); }
-    /** \overload Query recommends */
-    void addRecommends( const std::string & name, const Edition & edition, const Rel & op = Rel::EQ )
-    { addDependency( sat::SolvAttr::recommends, name, edition, op ); }
-    /** \overload Query suggests */
-    void addSuggests( const std::string & name, const Edition & edition, const Rel & op = Rel::EQ )
-    { addDependency( sat::SolvAttr::suggests, name, edition, op ); }
-    /** \overload Query supplements */
-    void addSupplements( const std::string & name, const Edition & edition, const Rel & op = Rel::EQ )
-    { addDependency( sat::SolvAttr::supplements, name, edition, op ); }
-    /** \overload Query enhances */
-    void addEnhances( const std::string & name, const Edition & edition, const Rel & op = Rel::EQ )
-    { addDependency( sat::SolvAttr::enhances, name, edition, op ); }
-
-    /** \overload Query taking a \ref Capability (always exact name match) */
+    //@{
+    /** Query <tt>"name|global op edition"</tt>. */
+    void addDependency( const sat::SolvAttr & attr, const std::string & name, const Rel & op, const Edition & edition );
+
+    /** \overload Query <tt>"name|global == edition"</tt>. */
+    void addDependency( const sat::SolvAttr & attr, const std::string & name, const Edition & edition )
+    { addDependency( attr, name, Rel::EQ, edition ); }
+
+    /** \overload Query <tt>"name|global"</tt>. */
+    void addDependency( const sat::SolvAttr & attr, const std::string & name )
+    { addDependency( attr, name, Rel::ANY, Edition() ); }
+
+    /** \overload Query <tt>"global op edition"</tt>.*/
+    void addDependency( const sat::SolvAttr & attr, const Rel & op, const Edition & edition )
+    { addDependency( attr, std::string(), op, edition ); }
+
+    /** \overload Query <tt>"global == edition"</tt>. */
+    void addDependency( const sat::SolvAttr & attr, const Edition & edition )
+    { addDependency( attr, std::string(), Rel::EQ, edition ); }
+
+    /** \overload Query <tt>"global"</tt>. */
+    void addDependency( const sat::SolvAttr & attr )
+    { addDependency( attr, std::string(), Rel::ANY, Edition() ); }
+
+    /** \overload Query taking a \ref Capability (always exact name match).
+     * \note If a non dependency attribute is passed, the \ref Capability
+     * will always be matched against the Solvables \c name and \c edition.
+    */
     void addDependency( const sat::SolvAttr & attr, Capability cap_r );
-#endif
+    //@}
 
     /**
      * Set version condition. This will filter out solvables not matching