diff --git a/pkg/search/query.go b/pkg/search/query.go index bfca8ff8e..921499062 100644 --- a/pkg/search/query.go +++ b/pkg/search/query.go @@ -345,11 +345,39 @@ type PermanodeConstraint struct { // (which must be a blobref) must be a part of. ValueInSet *Constraint `json:"valueInSet"` + // Relation optionally specifies a constraint based on relations + // to other permanodes (e.g. camliMember or camliPath sets). + // You can use it to test the properties of a parent, ancestor, + // child, or progeny. + Relation *RelationConstraint + // TODO: // NumClaims *IntConstraint // by owner // Owner blob.Ref // search for permanodes by an owner } +type RelationConstraint struct { + // Relation must be one of: + // + // * "child" + // * "progeny" (any level down) + // * "parent" (immediate parent only) + // * "ancestor" (any level up) + Relation string + + // EdgeType optionally specifies an edge type. + // By default it matches "camliMember" and "camliPath:*". + EdgeType string + + // After finding all the nodes matching the Relation and + // EdgeType, either one or all (depending on whether Any or + // All is set) must then match for the RelationConstraint + // itself to match. + // + // It is an error to set both. + Any, All *Constraint +} + // search is the state of an in-progress search type search struct { h *Handler diff --git a/pkg/search/query_test.go b/pkg/search/query_test.go index 9f2bf0009..bcda0999c 100644 --- a/pkg/search/query_test.go +++ b/pkg/search/query_test.go @@ -612,6 +612,62 @@ func TestQueryPermanodeValueAll(t *testing.T) { }) } +// permanodes tagged "foo" or those in sets where the parent +// permanode set itself is tagged "foo". +func TestQueryPermanodeTaggedViaParent(t *testing.T) { + t.Skip("TODO: finish implementing") + + testQuery(t, func(qt *queryTest) { + id := qt.id + + ptagged := id.NewPlannedPermanode("tagged_photo") + pindirect := id.NewPlannedPermanode("via_parent") + pset := id.NewPlannedPermanode("set") + pboth := id.NewPlannedPermanode("both") // funny directly and via its parent + pnotfunny := id.NewPlannedPermanode("not_funny") + + id.SetAttribute(ptagged, "tag", "funny") + id.SetAttribute(pset, "tag", "funny") + id.SetAttribute(pboth, "tag", "funny") + id.AddAttribute(pset, "camliMember", pindirect.String()) + id.AddAttribute(pset, "camliMember", pboth.String()) + id.SetAttribute(pnotfunny, "tag", "boring") + + sq := &SearchQuery{ + Constraint: &Constraint{ + Logical: &LogicalConstraint{ + Op: "or", + + // Those tagged funny directly: + A: &Constraint{ + Permanode: &PermanodeConstraint{ + Attr: "tag", + Value: "funny", + }, + }, + + // Those tagged funny indirectly: + B: &Constraint{ + Permanode: &PermanodeConstraint{ + Relation: &RelationConstraint{ + Relation: "ancestor", // "parent", "child", "progeny" + // Counter-part to "Any" is "All". Only one may be set. + Any: &Constraint{ + Permanode: &PermanodeConstraint{ + Attr: "tag", + Value: "funny", + }, + }, + }, + }, + }, + }, + }, + } + qt.wantRes(sq, ptagged, pset, pboth, pindirect) + }) +} + func TestLimitDoesntDeadlock(t *testing.T) { // TODO: care about classic (allIndexTypes) too? testQueryTypes(t, memIndexTypes, func(qt *queryTest) {