PkgQueries, concept of 'Searchable', rewrite Filter using PackageQueries.

This commit is contained in:
Andrey Smirnov
2014-07-12 00:15:33 +04:00
parent d54ef1e921
commit c485cf41f7
3 changed files with 147 additions and 55 deletions
+14 -46
View File
@@ -5,7 +5,6 @@ import (
"github.com/smira/aptly/aptly"
"github.com/smira/aptly/utils"
"sort"
"strings"
)
// Dependency options
@@ -333,11 +332,19 @@ func (l *PackageList) PrepareIndex() {
l.indexed = true
}
// Query searches package index using parsed query
//func (l *PackageList) Query(, allMatches bool) (searchResults []*Package) {
//}
// Scan searches package index using full scan
func (l *PackageList) Scan(q PackageQuery) (result *PackageList) {
result = NewPackageList()
for _, pkg := range l.packagesIndex {
if q.Matches(pkg) {
result.Add(pkg)
}
}
// Search searches package index for specified package(s)
return
}
// Search searches package index for specified package(s) using optimized queries
func (l *PackageList) Search(dep Dependency, allMatches bool) (searchResults []*Package) {
if !l.indexed {
panic("list not indexed, can't search")
@@ -377,7 +384,7 @@ func (l *PackageList) Search(dep Dependency, allMatches bool) (searchResults []*
}
// Filter filters package index by specified queries (ORed together), possibly pulling dependencies
func (l *PackageList) Filter(queries []string, withDependencies bool, source *PackageList, dependencyOptions int, architecturesList []string) (*PackageList, error) {
func (l *PackageList) Filter(queries []PackageQuery, withDependencies bool, source *PackageList, dependencyOptions int, architecturesList []string) (*PackageList, error) {
if !l.indexed {
panic("list not indexed, can't filter")
}
@@ -385,46 +392,7 @@ func (l *PackageList) Filter(queries []string, withDependencies bool, source *Pa
result := NewPackageList()
for _, query := range queries {
isDepQuery := strings.IndexAny(query, " (){}=<>") != -1
if !isDepQuery {
// try to interpret query as package string representation
// convert Package.String() to Package.Key()
i := strings.Index(query, "_")
if i != -1 {
pkg, query := query[:i], query[i+1:]
j := strings.LastIndex(query, "_")
if j != -1 {
version, arch := query[:j], query[j+1:]
p := l.packages["P"+arch+" "+pkg+" "+version]
if p != nil {
result.Add(p)
continue
}
}
}
}
// try as dependency
dep, err := ParseDependency(query)
if err != nil {
if isDepQuery {
return nil, err
}
// parsing failed, but probably that wasn't a dep query
continue
}
i := sort.Search(len(l.packagesIndex), func(j int) bool { return l.packagesIndex[j].Name >= dep.Pkg })
for i < len(l.packagesIndex) && l.packagesIndex[i].Name == dep.Pkg {
p := l.packagesIndex[i]
if p.MatchesDependency(dep) {
result.Add(p)
}
i++
}
result.Append(query.Query(l))
}
if withDependencies {
+26 -9
View File
@@ -301,10 +301,7 @@ func (s *PackageListSuite) TestSearch(c *C) {
}
func (s *PackageListSuite) TestFilter(c *C) {
c.Check(func() { s.list.Filter([]string{"abcd_0.3_i386"}, false, nil, 0, nil) }, Panics, "list not indexed, can't filter")
_, err := s.il.Filter([]string{"app >3)"}, false, nil, 0, nil)
c.Check(err, ErrorMatches, "unable to parse dependency.*")
c.Check(func() { s.list.Filter([]PackageQuery{&PkgQuery{"abcd", "0.3", "i386"}}, false, nil, 0, nil) }, Panics, "list not indexed, can't filter")
plString := func(l *PackageList) string {
list := make([]string, 0, l.Len())
@@ -317,25 +314,45 @@ func (s *PackageListSuite) TestFilter(c *C) {
return strings.Join(list, " ")
}
result, err := s.il.Filter([]string{"app_1.1~bp1_i386"}, false, nil, 0, nil)
result, err := s.il.Filter([]PackageQuery{&PkgQuery{"app", "1.1~bp1", "i386"}}, false, nil, 0, nil)
c.Check(err, IsNil)
c.Check(plString(result), Equals, "app_1.1~bp1_i386")
result, err = s.il.Filter([]string{"app_1.1~bp1_i386", "dpkg_1.7_source", "dpkg_1.8_amd64"}, false, nil, 0, nil)
result, err = s.il.Filter([]PackageQuery{&PkgQuery{"app", "1.1~bp1", "i386"}, &PkgQuery{"dpkg", "1.7", "source"},
&PkgQuery{"dpkg", "1.8", "amd64"}}, false, nil, 0, nil)
c.Check(err, IsNil)
c.Check(plString(result), Equals, "app_1.1~bp1_i386 dpkg_1.7_source")
result, err = s.il.Filter([]string{"app", "dpkg (>>1.6.1-3)", "app (>=1.0)", "xyz", "aa (>>3.0)"}, false, nil, 0, nil)
result, err = s.il.Filter([]PackageQuery{
&DependencyQuery{Dep: Dependency{Pkg: "app"}},
&DependencyQuery{Dep: Dependency{Pkg: "dpkg", Relation: VersionGreater, Version: "1.6.1-3"}},
&DependencyQuery{Dep: Dependency{Pkg: "app", Relation: VersionGreaterOrEqual, Version: "1.0"}},
&DependencyQuery{Dep: Dependency{Pkg: "xyz"}},
&DependencyQuery{Dep: Dependency{Pkg: "aa", Relation: VersionGreater, Version: "3.0"}}}, false, nil, 0, nil)
c.Check(err, IsNil)
c.Check(plString(result), Equals, "app_1.0_s390 app_1.1~bp1_amd64 app_1.1~bp1_arm app_1.1~bp1_i386 dpkg_1.7_i386 dpkg_1.7_source")
result, err = s.il.Filter([]string{"app {i386}"}, true, NewPackageList(), 0, []string{"i386"})
result, err = s.il.Filter([]PackageQuery{&DependencyQuery{Dep: Dependency{Pkg: "app", Architecture: "i386"}}}, true, NewPackageList(), 0, []string{"i386"})
c.Check(err, IsNil)
c.Check(plString(result), Equals, "app_1.1~bp1_i386 data_1.1~bp1_all dpkg_1.7_i386 lib_1.0_i386 mailer_3.5.8_i386")
result, err = s.il.Filter([]string{"app (>=0.9)", "lib", "data"}, true, NewPackageList(), 0, []string{"i386", "amd64"})
result, err = s.il.Filter([]PackageQuery{
&DependencyQuery{Dep: Dependency{Pkg: "app", Relation: VersionGreaterOrEqual, Version: "0.9"}},
&DependencyQuery{Dep: Dependency{Pkg: "lib"}},
&DependencyQuery{Dep: Dependency{Pkg: "data"}}}, true, NewPackageList(), 0, []string{"i386", "amd64"})
c.Check(err, IsNil)
c.Check(plString(result), Equals, "app_1.0_s390 app_1.1~bp1_amd64 app_1.1~bp1_arm app_1.1~bp1_i386 data_1.1~bp1_all dpkg_1.6.1-3_amd64 dpkg_1.7_i386 lib_1.0_i386 mailer_3.5.8_i386")
result, err = s.il.Filter([]PackageQuery{&OrQuery{&PkgQuery{"app", "1.1~bp1", "i386"},
&DependencyQuery{Dep: Dependency{Pkg: "dpkg", Relation: VersionGreater, Version: "1.6.1-3"}}}}, false, nil, 0, nil)
c.Check(err, IsNil)
c.Check(plString(result), Equals, "app_1.1~bp1_i386 dpkg_1.7_i386 dpkg_1.7_source")
result, err = s.il.Filter([]PackageQuery{&AndQuery{&PkgQuery{"app", "1.1~bp1", "i386"},
&DependencyQuery{Dep: Dependency{Pkg: "dpkg", Relation: VersionGreater, Version: "1.6.1-3"}}}}, false, nil, 0, nil)
c.Check(err, IsNil)
c.Check(plString(result), Equals, "")
}
func (s *PackageListSuite) TestVerifyDependencies(c *C) {
+107
View File
@@ -2,7 +2,12 @@ package deb
// PackageQuery is interface of predicate on Package
type PackageQuery interface {
// Matches calculates match of condition against package
Matches(pkg *Package) bool
// Searchable returns if search strategy is possible for this query
Searchable() bool
// Query performs search on package list
Query(list *PackageList) *PackageList
}
// OrQuery is L | R
@@ -27,6 +32,13 @@ type FieldQuery struct {
Value string
}
// PkgQuery is search request against specific package
type PkgQuery struct {
Pkg string
Version string
Arch string
}
// DependencyQuery is generic Debian-dependency like query
type DependencyQuery struct {
Dep Dependency
@@ -37,22 +49,117 @@ func (q *OrQuery) Matches(pkg *Package) bool {
return q.L.Matches(pkg) || q.R.Matches(pkg)
}
// Searchable is true only if both parts are searchable
func (q *OrQuery) Searchable() bool {
return q.L.Searchable() && q.R.Searchable()
}
// Query strategy depends on nodes
func (q *OrQuery) Query(list *PackageList) (result *PackageList) {
if q.Searchable() {
result = q.L.Query(list)
result.Append(q.R.Query(list))
} else {
result = list.Scan(q)
}
return
}
// Matches if both of L, R matches
func (q *AndQuery) Matches(pkg *Package) bool {
return q.L.Matches(pkg) && q.R.Matches(pkg)
}
// Searchable is true if any of the parts are searchable
func (q *AndQuery) Searchable() bool {
return q.L.Searchable() || q.R.Searchable()
}
// Query strategy depends on nodes
func (q *AndQuery) Query(list *PackageList) (result *PackageList) {
if !q.Searchable() {
result = list.Scan(q)
} else {
if q.L.Searchable() {
result = q.L.Query(list)
result = result.Scan(q.R)
} else {
result = q.R.Query(list)
result = result.Scan(q.L)
}
}
return
}
// Matches if not matches
func (q *NotQuery) Matches(pkg *Package) bool {
return !q.Q.Matches(pkg)
}
// Searchable is false
func (q *NotQuery) Searchable() bool {
return false
}
// NotQuery strategy is scan always
func (q *NotQuery) Query(list *PackageList) (result *PackageList) {
result = list.Scan(q)
return
}
// Matches on generic field
func (q *FieldQuery) Matches(pkg *Package) bool {
panic("not implemented yet")
}
// Query runs iteration through list
func (q *FieldQuery) Query(list *PackageList) (result *PackageList) {
panic("not implemented yet")
}
// Searchable depends on the query
func (q *FieldQuery) Searchable() bool {
return false
}
// Matches on dependency condition
func (q *DependencyQuery) Matches(pkg *Package) bool {
return pkg.MatchesDependency(q.Dep)
}
// Searchable is always true for dependency query
func (q *DependencyQuery) Searchable() bool {
return true
}
// Query runs PackageList.Search
func (q *DependencyQuery) Query(list *PackageList) (result *PackageList) {
result = NewPackageList()
for _, pkg := range list.Search(q.Dep, true) {
result.Add(pkg)
}
return
}
// Matches on specific properties
func (q *PkgQuery) Matches(pkg *Package) bool {
return pkg.Name == q.Pkg && pkg.Version == q.Version && pkg.Architecture == q.Arch
}
// Searchable is always true for package query
func (q *PkgQuery) Searchable() bool {
return true
}
// Query looks up specific package
func (q *PkgQuery) Query(list *PackageList) (result *PackageList) {
result = NewPackageList()
pkg := list.packages["P"+q.Arch+" "+q.Pkg+" "+q.Version]
if pkg != nil {
result.Add(pkg)
}
return
}