From bf9f69bcfe0b7eae5a302777b6b232dcf4f55f3f Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Wed, 23 Apr 2014 11:13:47 -0700 Subject: [PATCH] search: allow searching by permanode location, not just file location. Allows searching foursquare checkins by location. Change-Id: Ib33d0b435a9bbc17cb860549b41d119f727197f0 --- pkg/index/corpus.go | 70 +++++++++++++++++++++++++++++++++++++++++++-- pkg/search/expr.go | 31 ++++++++++++-------- pkg/search/query.go | 17 ++++++++++- 3 files changed, 103 insertions(+), 15 deletions(-) diff --git a/pkg/index/corpus.go b/pkg/index/corpus.go index 53309174e..e8f09777b 100644 --- a/pkg/index/corpus.go +++ b/pkg/index/corpus.go @@ -868,8 +868,44 @@ func (c *Corpus) AppendPermanodeAttrValues(dst []string, return c.AppendPermanodeAttrValuesLocked(dst, permaNode, attr, at, signerFilter) } -// AppendPermanodeAttrValuesLocked is the version of AppendPermanodeAttrValues that assumes -// the Corpus is already locked with RLock. +// PermanodeAttrValueLocked returns a single-valued attribute or "". +func (c *Corpus) PermanodeAttrValueLocked(permaNode blob.Ref, + attr string, + at time.Time, + signerFilter blob.Ref) string { + pm, ok := c.permanodes[permaNode] + if !ok { + return "" + } + if at.IsZero() { + at = time.Now() + } + var v string + for _, cl := range pm.Claims { + if cl.Attr != attr || cl.Date.After(at) { + continue + } + if signerFilter.Valid() && signerFilter != cl.Signer { + continue + } + switch cl.Type { + case string(schema.DelAttributeClaim): + if cl.Value == "" { + v = "" + } else if v == cl.Value { + v = "" + } + case string(schema.SetAttributeClaim): + v = cl.Value + case string(schema.AddAttributeClaim): + if v == "" { + v = cl.Value + } + } + } + return v +} + func (c *Corpus) AppendPermanodeAttrValuesLocked(dst []string, permaNode blob.Ref, attr string, @@ -997,6 +1033,36 @@ func (c *Corpus) FileLatLongLocked(fileRef blob.Ref) (lat, long float64, ok bool return ll.lat, ll.long, true } +// zero value of at means current +func (c *Corpus) PermanodeLatLongLocked(pn blob.Ref, at time.Time) (lat, long float64, ok bool) { + nodeType := c.PermanodeAttrValueLocked(pn, "camliNodeType", at, blob.Ref{}) + if nodeType == "" { + return + } + // TODO: make these pluggable, e.g. registered from an importer or something? + // How will that work when they're out-of-process? + if nodeType == "foursquare.com:checkin" { + venuePn, hasVenue := blob.Parse(c.PermanodeAttrValueLocked(pn, "foursquareVenuePermanode", at, blob.Ref{})) + if !hasVenue { + return + } + return c.PermanodeLatLongLocked(venuePn, at) + } + if nodeType == "foursquare.com:venue" { + var err error + lat, err = strconv.ParseFloat(c.PermanodeAttrValueLocked(pn, "latitude", at, blob.Ref{}), 64) + if err != nil { + return + } + long, err = strconv.ParseFloat(c.PermanodeAttrValueLocked(pn, "longitude", at, blob.Ref{}), 64) + if err != nil { + return + } + return lat, long, true + } + 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. diff --git a/pkg/search/expr.go b/pkg/search/expr.go index c75e31eb3..1b15b039b 100644 --- a/pkg/search/expr.go +++ b/pkg/search/expr.go @@ -504,24 +504,31 @@ func parseLocationAtom(ctx *context.Context, word string) (*Constraint, error) { if len(rects) == 0 { return nil, fmt.Errorf("No location found for %q", where) } - var locConstraint *Constraint + var c *Constraint for i, rect := range rects { - rectConstraint := permOfFile(&FileConstraint{ - IsImage: true, - Location: &LocationConstraint{ - West: rect.SouthWest.Long, - East: rect.NorthEast.Long, - North: rect.NorthEast.Lat, - South: rect.SouthWest.Lat, - }, + loc := &LocationConstraint{ + West: rect.SouthWest.Long, + East: rect.NorthEast.Long, + North: rect.NorthEast.Lat, + South: rect.SouthWest.Lat, + } + fileLoc := permOfFile(&FileConstraint{ + IsImage: true, + Location: loc, }) + permLoc := &Constraint{ + Permanode: &PermanodeConstraint{ + Location: loc, + }, + } + rectConstraint := orConst(fileLoc, permLoc) if i == 0 { - locConstraint = rectConstraint + c = rectConstraint } else { - locConstraint = orConst(locConstraint, rectConstraint) + c = orConst(c, rectConstraint) } } - return locConstraint, nil + return c, nil } if word == "has:location" { c := permOfFile(&FileConstraint{ diff --git a/pkg/search/query.go b/pkg/search/query.go index 3c81cc7b8..7e5c987d6 100644 --- a/pkg/search/query.go +++ b/pkg/search/query.go @@ -601,6 +601,11 @@ type PermanodeConstraint struct { // child, or progeny. Relation *RelationConstraint `json:"relation,omitempty"` + // Location optionally restricts matches to permanodes having + // this location. This only affects permanodes with a known + // type to have an lat/long location. + Location *LocationConstraint `json:"location,omitempty"` + // Continue is for internal use. Continue *PermanodeContinueConstraint `json:"-"` @@ -1120,7 +1125,7 @@ var numPermanodeFields = reflect.TypeOf(PermanodeConstraint{}).NumField() // hasValueConstraint returns true if one or more constraints that check an attribute's value are set. func (c *PermanodeConstraint) hasValueConstraint() bool { // If a field has been added or removed, update this after adding the new field to the return statement if necessary. - const expectedFields = 14 + const expectedFields = 15 if numPermanodeFields != expectedFields { panic(fmt.Sprintf("PermanodeConstraint field count changed (now %v rather than %v)", numPermanodeFields, expectedFields)) } @@ -1206,6 +1211,16 @@ func (c *PermanodeConstraint) blobMatches(s *search, br blob.Ref, bm camtypes.Bl } } + if c.Location != nil { + if corpus == nil { + return false, nil + } + lat, long, ok := corpus.PermanodeLatLongLocked(br, c.At) + if !ok || !c.Location.matchesLatLong(lat, long) { + return false, nil + } + } + if cc := c.Continue; cc != nil { if corpus == nil { // Requires an in-memory index for infinite