diff --git a/pkg/index/index.go b/pkg/index/index.go index c62217c5d..7f7001778 100644 --- a/pkg/index/index.go +++ b/pkg/index/index.go @@ -554,9 +554,44 @@ func (x *Index) GetFileInfo(fileRef *blobref.BlobRef) (*search.FileInfo, error) return fi, nil } -func (x *Index) EdgesTo(ref *blobref.BlobRef, opts *search.EdgesToOpts) ([]*search.Edge, error) { - // unimplemented. - return nil, errors.New("TODO: implement") +func (x *Index) EdgesTo(ref *blobref.BlobRef, opts *search.EdgesToOpts) (edges []*search.Edge, err error) { + it := x.queryPrefix(keyEdgeBackward, ref) + defer closeIterator(it, &err) + permanodeParents := map[string]*blobref.BlobRef{} // blobref key => blobref set + for it.Next() { + keyPart := strings.Split(it.Key(), "|")[1:] + if len(keyPart) < 2 { + continue + } + parent := keyPart[1] + parentRef := blobref.Parse(parent) + if parentRef == nil { + continue + } + valPart := strings.Split(it.Value(), "|") + if len(valPart) < 2 { + continue + } + parentType, parentName := valPart[0], valPart[1] + if parentType == "permanode" { + permanodeParents[parent] = parentRef + } else { + edges = append(edges, &search.Edge{ + From: parentRef, + FromType: parentType, + FromTitle: parentName, + To: ref, + }) + } + } + for _, parentRef := range permanodeParents { + edges = append(edges, &search.Edge{ + From: parentRef, + FromType: "permanode", + To: ref, + }) + } + return edges, nil } func (x *Index) Storage() IndexStorage { return x.s } diff --git a/pkg/index/index_test.go b/pkg/index/index_test.go index 2f7e6d078..d804542ac 100644 --- a/pkg/index/index_test.go +++ b/pkg/index/index_test.go @@ -55,6 +55,10 @@ func TestFiles_Memory(t *testing.T) { indextest.Files(t, index.NewMemoryIndex) } +func TestEdgesTo_Memory(t *testing.T) { + indextest.EdgesTo(t, index.NewMemoryIndex) +} + var ( // those dirs are not packages implementing indexers, // hence we do not want to check them. @@ -62,7 +66,7 @@ var ( // A map is used in hasAllRequiredTests to note which required // tests have been found in a package, by setting the corresponding // booleans to true. Those are the keys for this map. - requiredTests = []string{"TestIndex_", "TestPathsOfSignerTarget_", "TestFiles_"} + requiredTests = []string{"TestIndex_", "TestPathsOfSignerTarget_", "TestFiles_", "TestEdgesTo_"} ) // This function checks that all the functions using the tests diff --git a/pkg/index/indextest/tests.go b/pkg/index/indextest/tests.go index 809fd1a24..9345838ee 100644 --- a/pkg/index/indextest/tests.go +++ b/pkg/index/indextest/tests.go @@ -488,3 +488,37 @@ func Files(t *testing.T, initIdx func() *index.Index) { } } } + +func EdgesTo(t *testing.T, initIdx func() *index.Index) { + idx := initIdx() + id := NewIndexDeps(idx) + id.Fataler = t + + // pn1 ---member---> pn2 + pn1 := id.NewPermanode() + pn2 := id.NewPermanode() + id.AddAttribute(pn1, "camliMember", pn2.String()) + + t.Logf("edge %s --> %s", pn1, pn2) + + id.dumpIndex(t) + + // Look for pn1 + { + edges, err := idx.EdgesTo(pn2, nil) + if err != nil { + t.Fatal(err) + } + if len(edges) != 1 { + t.Fatalf("num edges = %d; want 1", len(edges)) + } + wantEdge := &search.Edge{ + From: pn1, + To: pn2, + FromType: "permanode", + } + if got, want := edges[0].String(), wantEdge.String(); got != want { + t.Errorf("Wrong edge.\n GOT: %v\nWANT: %v", got, want) + } + } +} diff --git a/pkg/index/keys.go b/pkg/index/keys.go index f0a4c66b2..658ea3d65 100644 --- a/pkg/index/keys.go +++ b/pkg/index/keys.go @@ -205,8 +205,8 @@ var ( keyEdgeBackward = &keyType{ "edgeback", []part{ - {"child", typeBlobRef}, // the thing we want to find parent(s) of - {"parent", typeBlobRef}, // the parent (e.g. permanode blobref) + {"child", typeBlobRef}, // the edge target; thing we want to find parent(s) of + {"parent", typeBlobRef}, // the parent / edge source (e.g. permanode blobref) // the blobref is the blob establishing the relationship // (for a permanode: the claim; for static: often same as parent) {"blobref", typeBlobRef}, diff --git a/pkg/index/mongo/mongoindex_test.go b/pkg/index/mongo/mongoindex_test.go index b2a3cb899..2bcb46875 100644 --- a/pkg/index/mongo/mongoindex_test.go +++ b/pkg/index/mongo/mongoindex_test.go @@ -93,3 +93,7 @@ func TestPathsOfSignerTarget_Mongo(t *testing.T) { func TestFiles_Mongo(t *testing.T) { mongoTester{}.test(t, indextest.Files) } + +func TestEdgesTo_Mongo(t *testing.T) { + mongoTester{}.test(t, indextest.EdgesTo) +} diff --git a/pkg/index/mysql/mysql_test.go b/pkg/index/mysql/mysql_test.go index 202239796..87fc1b00b 100644 --- a/pkg/index/mysql/mysql_test.go +++ b/pkg/index/mysql/mysql_test.go @@ -104,3 +104,7 @@ func TestPathsOfSignerTarget_MySQL(t *testing.T) { func TestFiles_MySQL(t *testing.T) { mysqlTester{}.test(t, indextest.Files) } + +func TestEdgesTo_MySQL(t *testing.T) { + mysqlTester{}.test(t, indextest.EdgesTo) +} diff --git a/pkg/index/postgres/postgres_test.go b/pkg/index/postgres/postgres_test.go index b1e76d2e2..a1d124ecf 100644 --- a/pkg/index/postgres/postgres_test.go +++ b/pkg/index/postgres/postgres_test.go @@ -142,3 +142,11 @@ func TestFiles_Postgres(t *testing.T) { } postgresTester{}.test(t, indextest.Files) } + +func TestEdgesTo_Postgres(t *testing.T) { + if testing.Short() { + t.Logf("skipping test in short mode") + return + } + postgresTester{}.test(t, indextest.EdgesTo) +} diff --git a/pkg/index/sqlite/sqlite_test.go b/pkg/index/sqlite/sqlite_test.go index bb6a781ff..fbd42a3e7 100644 --- a/pkg/index/sqlite/sqlite_test.go +++ b/pkg/index/sqlite/sqlite_test.go @@ -95,3 +95,7 @@ func TestPathsOfSignerTarget_SQLite(t *testing.T) { func TestFiles_SQLite(t *testing.T) { sqliteTester{}.test(t, indextest.Files) } + +func TestEdgesTo_SQLite(t *testing.T) { + sqliteTester{}.test(t, indextest.EdgesTo) +} diff --git a/pkg/search/search.go b/pkg/search/search.go index e1f6bb4cf..656b13955 100644 --- a/pkg/search/search.go +++ b/pkg/search/search.go @@ -137,6 +137,10 @@ type Edge struct { To *blobref.BlobRef } +func (e *Edge) String() string { + return fmt.Sprintf("[edge from:%s to:%s type:%s title:%s]", e.From, e.To, e.FromType, e.FromTitle) +} + type Index interface { // dest must be closed, even when returning an error. // limit is <= 0 for default. smallest possible default is 0