diff --git a/deb/list.go b/deb/list.go index 07d0fad8..eb32f5de 100644 --- a/deb/list.go +++ b/deb/list.go @@ -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 { diff --git a/deb/list_test.go b/deb/list_test.go index 16443b58..745dcc48 100644 --- a/deb/list_test.go +++ b/deb/list_test.go @@ -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) { diff --git a/deb/query.go b/deb/query.go index d90a3926..03132679 100644 --- a/deb/query.go +++ b/deb/query.go @@ -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 +}