mirror of https://github.com/perkeep/perkeep.git
pkg/search: add parentof search expression
Change-Id: I74f6f5411355a0b0864739f135331ba304245ddb
This commit is contained in:
parent
d20ad1570e
commit
34ad44385b
|
@ -50,3 +50,5 @@ Usable operators:
|
|||
filename: search for permanodes of files with this filename (case sensitive)
|
||||
childrenof: Find child permanodes of a parent permanode (or prefix
|
||||
of a parent permanode): childrenof:sha1-527cf12
|
||||
parentof: Find parent permanodes of a child permanode (or prefix
|
||||
of a child permanode): parentof:sha1-527cf12
|
||||
|
|
|
@ -1287,6 +1287,26 @@ func (c *Corpus) PermanodeLatLongLocked(pn blob.Ref, at time.Time) (lat, long fl
|
|||
return
|
||||
}
|
||||
|
||||
// ForeachClaimLocked calls fn for each claim of permaNode.
|
||||
// If at is zero, all claims are yielded.
|
||||
// If at is non-zero, claims after that point are skipped.
|
||||
// If fn returns false, iteration ends.
|
||||
// Iteration is in an undefined order.
|
||||
func (c *Corpus) ForeachClaimLocked(permaNode blob.Ref, at time.Time, fn func(*camtypes.Claim) bool) {
|
||||
pm, ok := c.permanodes[permaNode]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
for _, cl := range pm.Claims {
|
||||
if !at.IsZero() && cl.Date.After(at) {
|
||||
continue
|
||||
}
|
||||
if !fn(cl) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ForeachClaimBackLocked calls fn for each claim with a value referencing br.
|
||||
// If at is zero, all claims are yielded.
|
||||
// If at is non-zero, claims after that point are skipped.
|
||||
|
|
|
@ -253,6 +253,29 @@ var parseExpressionTests = []struct {
|
|||
},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
in: "parentof:sha1-f00ba4",
|
||||
want: &SearchQuery{
|
||||
Constraint: &Constraint{
|
||||
Logical: &LogicalConstraint{
|
||||
Op: "and",
|
||||
A: skiphiddenC,
|
||||
B: &Constraint{
|
||||
Permanode: &PermanodeConstraint{
|
||||
Relation: &RelationConstraint{
|
||||
Relation: "child",
|
||||
Any: &Constraint{
|
||||
BlobRefPrefix: "sha1-f00ba4",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// Location predicates
|
||||
{
|
||||
in: "loc:Uitdam", // Small dutch town
|
||||
|
@ -760,6 +783,20 @@ var parseExpTests = []parserTestCase{
|
|||
},
|
||||
},
|
||||
|
||||
{
|
||||
in: "parentof:sha1-f00ba4",
|
||||
want: &Constraint{
|
||||
Permanode: &PermanodeConstraint{
|
||||
Relation: &RelationConstraint{
|
||||
Relation: "child",
|
||||
Any: &Constraint{
|
||||
BlobRefPrefix: "sha1-f00ba4",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: "Unmatched quote",
|
||||
in: `is:pano and "foo`,
|
||||
|
|
|
@ -105,6 +105,7 @@ func init() {
|
|||
registerKeyword(newBefore())
|
||||
registerKeyword(newAttribute())
|
||||
registerKeyword(newChildrenOf())
|
||||
registerKeyword(newParentOf())
|
||||
registerKeyword(newFormat())
|
||||
registerKeyword(newTag())
|
||||
registerKeyword(newTitle())
|
||||
|
@ -280,6 +281,33 @@ func (k childrenOf) Predicate(ctx context.Context, args []string) (*Constraint,
|
|||
return c, nil
|
||||
}
|
||||
|
||||
type parentOf struct {
|
||||
matchPrefix
|
||||
}
|
||||
|
||||
func newParentOf() keyword {
|
||||
return parentOf{newMatchPrefix("parentof")}
|
||||
}
|
||||
|
||||
func (k parentOf) Description() string {
|
||||
return "Find parent permanodes of a child permanode (or prefix of a child\n" +
|
||||
"permanode): parentof:sha1-527cf12 Only matches permanodes currently."
|
||||
}
|
||||
|
||||
func (k parentOf) Predicate(ctx context.Context, args []string) (*Constraint, error) {
|
||||
c := &Constraint{
|
||||
Permanode: &PermanodeConstraint{
|
||||
Relation: &RelationConstraint{
|
||||
Relation: "child",
|
||||
Any: &Constraint{
|
||||
BlobRefPrefix: args[0],
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
type format struct {
|
||||
matchPrefix
|
||||
}
|
||||
|
|
|
@ -697,8 +697,8 @@ type RelationConstraint struct {
|
|||
}
|
||||
|
||||
func (rc *RelationConstraint) checkValid() error {
|
||||
if rc.Relation != "parent" {
|
||||
return errors.New("only RelationConstraint.Relation of \"parent\" is currently supported")
|
||||
if rc.Relation != "parent" && rc.Relation != "child" {
|
||||
return errors.New("only RelationConstraint.Relation of \"parent\" or \"child\" is currently supported")
|
||||
}
|
||||
if (rc.Any == nil) == (rc.All == nil) {
|
||||
return errors.New("exactly one of RelationConstraint Any or All must be defined")
|
||||
|
@ -721,7 +721,18 @@ func (rc *RelationConstraint) match(s *search, pn blob.Ref, at time.Time) (ok bo
|
|||
return false, errors.New("RelationConstraint requires an in-memory corpus")
|
||||
}
|
||||
|
||||
if rc.Relation != "parent" {
|
||||
var foreachClaim func(pn blob.Ref, at time.Time, f func(cl *camtypes.Claim) bool)
|
||||
// relationRef returns the relevant blobRef from the claim if cl defines
|
||||
// the kind of relation we are looking for, (blob.Ref{}, false) otherwise.
|
||||
var relationRef func(cl *camtypes.Claim) (blob.Ref, bool)
|
||||
switch rc.Relation {
|
||||
case "parent":
|
||||
foreachClaim = corpus.ForeachClaimBackLocked
|
||||
relationRef = func(cl *camtypes.Claim) (blob.Ref, bool) { return cl.Permanode, true }
|
||||
case "child":
|
||||
foreachClaim = corpus.ForeachClaimLocked
|
||||
relationRef = func(cl *camtypes.Claim) (blob.Ref, bool) { return blob.Parse(cl.Value) }
|
||||
default:
|
||||
panic("bogus")
|
||||
}
|
||||
|
||||
|
@ -736,7 +747,7 @@ func (rc *RelationConstraint) match(s *search, pn blob.Ref, at time.Time) (ok bo
|
|||
var anyBad bool
|
||||
var lastChecked blob.Ref
|
||||
var permanodesChecked map[blob.Ref]bool // lazily created to optimize for common case of 1 match
|
||||
corpus.ForeachClaimBackLocked(pn, at, func(cl *camtypes.Claim) bool {
|
||||
foreachClaim(pn, at, func(cl *camtypes.Claim) bool {
|
||||
if !rc.matchesAttr(cl.Attr) {
|
||||
return true // skip claim
|
||||
}
|
||||
|
@ -747,7 +758,13 @@ func (rc *RelationConstraint) match(s *search, pn blob.Ref, at time.Time) (ok bo
|
|||
permanodesChecked[lastChecked] = true
|
||||
lastChecked = blob.Ref{} // back to zero
|
||||
}
|
||||
if permanodesChecked[cl.Permanode] {
|
||||
relRef, ok := relationRef(cl)
|
||||
if !ok {
|
||||
// The claim does not define the kind of relation we're looking for
|
||||
// (e.g. it sets a tag vale), so we continue to the next claim.
|
||||
return true
|
||||
}
|
||||
if permanodesChecked[relRef] {
|
||||
return true // skip checking
|
||||
}
|
||||
if !corpus.PermanodeHasAttrValueLocked(cl.Permanode, at, cl.Attr, cl.Value) {
|
||||
|
@ -755,12 +772,11 @@ func (rc *RelationConstraint) match(s *search, pn blob.Ref, at time.Time) (ok bo
|
|||
}
|
||||
|
||||
var bm camtypes.BlobMeta
|
||||
bm, err = s.blobMeta(cl.Permanode)
|
||||
bm, err = s.blobMeta(relRef)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
var ok bool
|
||||
ok, err = matcher(s, cl.Permanode, bm)
|
||||
ok, err = matcher(s, relRef, bm)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
@ -775,7 +791,7 @@ func (rc *RelationConstraint) match(s *search, pn blob.Ref, at time.Time) (ok bo
|
|||
return false // fail fast
|
||||
}
|
||||
}
|
||||
lastChecked = cl.Permanode
|
||||
lastChecked = relRef
|
||||
return true
|
||||
})
|
||||
if err != nil {
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
|
@ -53,7 +54,14 @@ type queryTest struct {
|
|||
id *indextest.IndexDeps
|
||||
itype indexType
|
||||
|
||||
Handler func() *Handler
|
||||
handlerOnce sync.Once
|
||||
newHandler func() *Handler
|
||||
handler *Handler // initialized with newHandler
|
||||
}
|
||||
|
||||
func (qt *queryTest) Handler() *Handler {
|
||||
qt.handlerOnce.Do(func() { qt.handler = qt.newHandler() })
|
||||
return qt.handler
|
||||
}
|
||||
|
||||
func querySetup(t testing.TB) (*indextest.IndexDeps, *Handler) {
|
||||
|
@ -96,7 +104,7 @@ func testQueryType(t testing.TB, fn func(*queryTest), itype indexType) {
|
|||
itype: itype,
|
||||
}
|
||||
qt.id.Fataler = t
|
||||
qt.Handler = func() *Handler {
|
||||
qt.newHandler = func() *Handler {
|
||||
h := NewHandler(idx, qt.id.SignerBlobRef)
|
||||
if itype == indexCorpusScan {
|
||||
if corpus, err = idx.KeepInMemory(); err != nil {
|
||||
|
@ -999,6 +1007,71 @@ func TestQueryChildren(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestQueryParent(t *testing.T) {
|
||||
testQueryTypes(t, memIndexTypes, func(qt *queryTest) {
|
||||
id := qt.id
|
||||
|
||||
pdir1 := id.NewPlannedPermanode("some_dir_1")
|
||||
pdir2 := id.NewPlannedPermanode("some_dir_2")
|
||||
p1 := id.NewPlannedPermanode("1")
|
||||
p2 := id.NewPlannedPermanode("2")
|
||||
p3 := id.NewPlannedPermanode("3")
|
||||
|
||||
id.AddAttribute(pdir1, "camliMember", p1.String())
|
||||
id.AddAttribute(pdir1, "camliPath:foo", p2.String())
|
||||
id.AddAttribute(pdir1, "other", p3.String())
|
||||
|
||||
id.AddAttribute(pdir2, "camliPath:bar", p1.String())
|
||||
|
||||
// Make p1, p2, and p3 actually exist. (permanodes without attributes are dead)
|
||||
id.AddAttribute(p1, "x", "x")
|
||||
id.AddAttribute(p2, "x", "x")
|
||||
id.AddAttribute(p3, "x", "x")
|
||||
|
||||
sq := &SearchQuery{
|
||||
Constraint: &Constraint{
|
||||
Permanode: &PermanodeConstraint{
|
||||
Relation: &RelationConstraint{
|
||||
Relation: "child",
|
||||
Any: &Constraint{
|
||||
BlobRefPrefix: p1.String(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
qt.wantRes(sq, pdir1, pdir2)
|
||||
|
||||
sq = &SearchQuery{
|
||||
Constraint: &Constraint{
|
||||
Permanode: &PermanodeConstraint{
|
||||
Relation: &RelationConstraint{
|
||||
Relation: "child",
|
||||
Any: &Constraint{
|
||||
BlobRefPrefix: p2.String(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
qt.wantRes(sq, pdir1)
|
||||
|
||||
sq = &SearchQuery{
|
||||
Constraint: &Constraint{
|
||||
Permanode: &PermanodeConstraint{
|
||||
Relation: &RelationConstraint{
|
||||
Relation: "child",
|
||||
Any: &Constraint{
|
||||
BlobRefPrefix: p3.String(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
qt.wantRes(sq)
|
||||
})
|
||||
}
|
||||
|
||||
// 13 permanodes are created. 1 of them the parent, 11 are children
|
||||
// (== results), 1 is unrelated to the parent.
|
||||
// limit is the limit on the number of results.
|
||||
|
|
Loading…
Reference in New Issue