pkg/search: add parentof search expression

Change-Id: I74f6f5411355a0b0864739f135331ba304245ddb
This commit is contained in:
Attila Tajti 2016-02-02 19:12:45 +01:00
parent d20ad1570e
commit 34ad44385b
6 changed files with 187 additions and 11 deletions

View File

@ -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

View File

@ -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.

View File

@ -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`,

View File

@ -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
}

View File

@ -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 {

View File

@ -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.