From 6578e671343cea8fc2765ab6d502f79128dffd63 Mon Sep 17 00:00:00 2001 From: Roger Leite Lucena Date: Tue, 15 Sep 2020 21:23:47 -0300 Subject: [PATCH] Add tests for the FILTER execution in the volatile driver (in "memory_test.go") --- storage/memory/memory_test.go | 615 ++++++++++++++++++++++++++++++++++ 1 file changed, 615 insertions(+) diff --git a/storage/memory/memory_test.go b/storage/memory/memory_test.go index 059533c7..b7ba2171 100644 --- a/storage/memory/memory_test.go +++ b/storage/memory/memory_test.go @@ -237,6 +237,17 @@ func getTestTemporalTriples(t *testing.T) []*triple.Triple { }) } +func getTestTriplesFilter(t *testing.T) []*triple.Triple { + return createTriples(t, []string{ + "/u\t\"meet\"@[2012-04-10T04:21:00Z]\t/u", + "/u\t\"meet\"@[2013-04-10T04:21:00Z]\t/u", + "/u\t\"meet\"@[2014-04-10T04:21:00Z]\t/u", + "/u\t\"meet\"@[2014-04-10T04:21:00Z]\t/u", + "/_\t\"_predicate\"@[]\t\"meet\"@[2020-04-10T04:21:00Z]", + "/_\t\"_predicate\"@[]\t\"meet\"@[2021-04-10T04:21:00Z]", + }) +} + func TestAddRemoveTriples(t *testing.T) { ts, ctx := getTestTriples(t), context.Background() g, _ := NewStore().NewGraph(ctx, "test") @@ -303,6 +314,65 @@ func TestObjectsLatestAnchor(t *testing.T) { } } +func TestObjectsFilter(t *testing.T) { + ts, ctx := getTestTriplesFilter(t), context.Background() + g, _ := NewStore().NewGraph(ctx, "test") + if err := g.AddTriples(ctx, ts); err != nil { + t.Fatalf("g.AddTriples(_) failed failed to add test triples with error: %v", err) + } + + testTable := []struct { + lo *storage.LookupOptions + s *node.Node + p *predicate.Predicate + want map[string]int + }{ + { + lo: &storage.LookupOptions{FilterOptions: &storage.FilteringOptions{Operation: "latest", Field: "predicate"}}, + s: testutil.MustBuildNodeFromStrings(t, "/u", "john"), + p: testutil.MustBuildPredicate(t, `"meet"@[2012-04-10T04:21:00Z]`), + want: map[string]int{"/u": 1}, + }, + { + lo: &storage.LookupOptions{FilterOptions: &storage.FilteringOptions{Operation: "latest", Field: "predicate"}}, + s: testutil.MustBuildNodeFromStrings(t, "/u", "john"), + p: testutil.MustBuildPredicate(t, `"meet"@[2014-04-10T04:21:00Z]`), + want: map[string]int{"/u": 1, "/u": 1}, + }, + { + lo: &storage.LookupOptions{FilterOptions: &storage.FilteringOptions{Operation: "latest", Field: "object"}}, + s: testutil.MustBuildNodeFromStrings(t, "/_", "bn"), + p: testutil.MustBuildPredicate(t, `"_predicate"@[]`), + want: map[string]int{`"meet"@[2021-04-10T04:21:00Z]`: 1}, + }, + } + + for _, entry := range testTable { + // To avoid blocking on the test we use a buffered channel of size 100. On a real + // usage of the driver you would like to call the graph operation on a separated + // goroutine using a sync.WaitGroup to collect the error code eventually. + os := make(chan *triple.Object, 100) + s := entry.s + p := entry.p + if err := g.Objects(ctx, s, p, entry.lo, os); err != nil { + t.Fatalf("g.Objects(%s, %s, %s) = %v; want nil", s, p, entry.lo, err) + } + for o := range os { + oStr := o.String() + if _, ok := entry.want[oStr]; !ok { + t.Fatalf("g.Objects(%s, %s, %s) retrieved unexpected %s", s, p, entry.lo, oStr) + } + entry.want[oStr] = entry.want[oStr] - 1 + if entry.want[oStr] == 0 { + delete(entry.want, oStr) + } + } + if len(entry.want) != 0 { + t.Errorf("g.Objects(%s, %s, %s) failed to retrieve some expected elements: %v", s, p, entry.lo, entry.want) + } + } +} + func TestSubjects(t *testing.T) { ts, ctx := getTestTriples(t), context.Background() g, _ := NewStore().NewGraph(ctx, "test") @@ -356,6 +426,65 @@ func TestSubjectsLatestAnchor(t *testing.T) { } } +func TestSubjectsFilter(t *testing.T) { + ts, ctx := getTestTriplesFilter(t), context.Background() + g, _ := NewStore().NewGraph(ctx, "test") + if err := g.AddTriples(ctx, ts); err != nil { + t.Fatalf("g.AddTriples(_) failed failed to add test triples with error: %v", err) + } + + testTable := []struct { + lo *storage.LookupOptions + p *predicate.Predicate + o *triple.Object + want map[string]int + }{ + { + lo: &storage.LookupOptions{FilterOptions: &storage.FilteringOptions{Operation: "latest", Field: "predicate"}}, + p: testutil.MustBuildPredicate(t, `"meet"@[2012-04-10T04:21:00Z]`), + o: triple.NewNodeObject(testutil.MustBuildNodeFromStrings(t, "/u", "mary")), + want: map[string]int{"/u": 1}, + }, + { + lo: &storage.LookupOptions{FilterOptions: &storage.FilteringOptions{Operation: "latest", Field: "predicate"}}, + p: testutil.MustBuildPredicate(t, `"meet"@[2014-04-10T04:21:00Z]`), + o: triple.NewNodeObject(testutil.MustBuildNodeFromStrings(t, "/u", "mary")), + want: map[string]int{"/u": 1}, + }, + { + lo: &storage.LookupOptions{FilterOptions: &storage.FilteringOptions{Operation: "latest", Field: "object"}}, + p: testutil.MustBuildPredicate(t, `"_predicate"@[]`), + o: triple.NewPredicateObject(testutil.MustBuildPredicate(t, `"meet"@[2020-04-10T04:21:00Z]`)), + want: map[string]int{"/_": 1}, + }, + } + + for _, entry := range testTable { + // To avoid blocking on the test we use a buffered channel of size 100. On a real + // usage of the driver you would like to call the graph operation on a separated + // goroutine using a sync.WaitGroup to collect the error code eventually. + ss := make(chan *node.Node, 100) + p := entry.p + o := entry.o + if err := g.Subjects(ctx, p, o, entry.lo, ss); err != nil { + t.Fatalf("g.Subjects(%s, %s, %s) = %v; want nil", p, o, entry.lo, err) + } + for s := range ss { + sStr := s.String() + if _, ok := entry.want[sStr]; !ok { + t.Fatalf("g.Subjects(%s, %s, %s) retrieved unexpected %s", p, o, entry.lo, sStr) + } + entry.want[sStr] = entry.want[sStr] - 1 + if entry.want[sStr] == 0 { + delete(entry.want, sStr) + } + } + if len(entry.want) != 0 { + t.Errorf("g.Subjects(%s, %s, %s) failed to retrieve some expected elements: %v", p, o, entry.lo, entry.want) + } + } +} + func TestPredicatesForSubjectAndObject(t *testing.T) { ts, ctx := getTestTriples(t), context.Background() g, _ := NewStore().NewGraph(ctx, "test") @@ -380,6 +509,7 @@ func TestPredicatesForSubjectAndObject(t *testing.T) { t.Errorf("g.PredicatesForSubjectAndObject(%s, %s) failed to retrieve 1 predicate, got %d instead", ts[0].Subject(), ts[0].Object(), cnt) } } + func TestPredicatesForSubjectAndObjectLatestAnchor(t *testing.T) { ts, ctx := getTestTemporalTriples(t), context.Background() g, _ := NewStore().NewGraph(ctx, "test") @@ -406,6 +536,65 @@ func TestPredicatesForSubjectAndObjectLatestAnchor(t *testing.T) { } } +func TestPredicatesForSubjectAndObjectFilter(t *testing.T) { + ts, ctx := getTestTriplesFilter(t), context.Background() + g, _ := NewStore().NewGraph(ctx, "test") + if err := g.AddTriples(ctx, ts); err != nil { + t.Fatalf("g.AddTriples(_) failed failed to add test triples with error: %v", err) + } + + testTable := []struct { + lo *storage.LookupOptions + s *node.Node + o *triple.Object + want map[string]int + }{ + { + lo: &storage.LookupOptions{FilterOptions: &storage.FilteringOptions{Operation: "latest", Field: "predicate"}}, + s: testutil.MustBuildNodeFromStrings(t, "/u", "john"), + o: triple.NewNodeObject(testutil.MustBuildNodeFromStrings(t, "/u", "mary")), + want: map[string]int{`"meet"@[2014-04-10T04:21:00Z]`: 1}, + }, + { + lo: &storage.LookupOptions{FilterOptions: &storage.FilteringOptions{Operation: "latest", Field: "predicate"}}, + s: testutil.MustBuildNodeFromStrings(t, "/u", "john"), + o: triple.NewNodeObject(testutil.MustBuildNodeFromStrings(t, "/u", "bob")), + want: map[string]int{`"meet"@[2014-04-10T04:21:00Z]`: 1}, + }, + { + lo: &storage.LookupOptions{FilterOptions: &storage.FilteringOptions{Operation: "latest", Field: "object"}}, + s: testutil.MustBuildNodeFromStrings(t, "/_", "bn"), + o: triple.NewPredicateObject(testutil.MustBuildPredicate(t, `"meet"@[2020-04-10T04:21:00Z]`)), + want: map[string]int{`"_predicate"@[]`: 1}, + }, + } + + for _, entry := range testTable { + // To avoid blocking on the test we use a buffered channel of size 100. On a real + // usage of the driver you would like to call the graph operation on a separated + // goroutine using a sync.WaitGroup to collect the error code eventually. + pp := make(chan *predicate.Predicate, 100) + s := entry.s + o := entry.o + if err := g.PredicatesForSubjectAndObject(ctx, s, o, entry.lo, pp); err != nil { + t.Fatalf("g.PredicatesForSubjectAndObject(%s, %s, %s) = %v; want nil", s, o, entry.lo, err) + } + for p := range pp { + pStr := p.String() + if _, ok := entry.want[pStr]; !ok { + t.Fatalf("g.PredicatesForSubjectAndObject(%s, %s, %s) retrieved unexpected %s", s, o, entry.lo, pStr) + } + entry.want[pStr] = entry.want[pStr] - 1 + if entry.want[pStr] == 0 { + delete(entry.want, pStr) + } + } + if len(entry.want) != 0 { + t.Errorf("g.PredicatesForSubjectAndObject(%s, %s, %s) failed to retrieve some expected elements: %v", s, o, entry.lo, entry.want) + } + } +} + func TestPredicatesForSubject(t *testing.T) { ts, ctx := getTestTriples(t), context.Background() g, _ := NewStore().NewGraph(ctx, "test") @@ -457,6 +646,55 @@ func TestPredicatesForSubjectLatestAnchor(t *testing.T) { } } +func TestPredicatesForSubjectFilter(t *testing.T) { + ts, ctx := getTestTriplesFilter(t), context.Background() + g, _ := NewStore().NewGraph(ctx, "test") + if err := g.AddTriples(ctx, ts); err != nil { + t.Fatalf("g.AddTriples(_) failed failed to add test triples with error: %v", err) + } + + testTable := []struct { + lo *storage.LookupOptions + s *node.Node + want map[string]int + }{ + { + lo: &storage.LookupOptions{FilterOptions: &storage.FilteringOptions{Operation: "latest", Field: "predicate"}}, + s: testutil.MustBuildNodeFromStrings(t, "/u", "john"), + want: map[string]int{`"meet"@[2014-04-10T04:21:00Z]`: 2}, + }, + { + lo: &storage.LookupOptions{FilterOptions: &storage.FilteringOptions{Operation: "latest", Field: "object"}}, + s: testutil.MustBuildNodeFromStrings(t, "/_", "bn"), + want: map[string]int{`"_predicate"@[]`: 1}, + }, + } + + for _, entry := range testTable { + // To avoid blocking on the test we use a buffered channel of size 100. On a real + // usage of the driver you would like to call the graph operation on a separated + // goroutine using a sync.WaitGroup to collect the error code eventually. + pp := make(chan *predicate.Predicate, 100) + s := entry.s + if err := g.PredicatesForSubject(ctx, s, entry.lo, pp); err != nil { + t.Fatalf("g.PredicatesForSubject(%s, %s) = %v; want nil", s, entry.lo, err) + } + for p := range pp { + pStr := p.String() + if _, ok := entry.want[pStr]; !ok { + t.Fatalf("g.PredicatesForSubject(%s, %s) retrieved unexpected %s", s, entry.lo, pStr) + } + entry.want[pStr] = entry.want[pStr] - 1 + if entry.want[pStr] == 0 { + delete(entry.want, pStr) + } + } + if len(entry.want) != 0 { + t.Errorf("g.PredicatesForSubject(%s, %s) failed to retrieve some expected elements: %v", s, entry.lo, entry.want) + } + } +} + func TestPredicatesForObject(t *testing.T) { ts, ctx := getTestTriples(t), context.Background() g, _ := NewStore().NewGraph(ctx, "test") @@ -481,6 +719,7 @@ func TestPredicatesForObject(t *testing.T) { t.Errorf("g.PredicatesForObject(%s) failed to retrieve 1 predicate, got %d instead", ts[0].Object(), cnt) } } + func TestPredicatesForObjectLatestAnchor(t *testing.T) { ts, ctx := getTestTemporalTriples(t), context.Background() g, _ := NewStore().NewGraph(ctx, "test") @@ -507,6 +746,60 @@ func TestPredicatesForObjectLatestAnchor(t *testing.T) { } } +func TestPredicatesForObjectFilter(t *testing.T) { + ts, ctx := getTestTriplesFilter(t), context.Background() + g, _ := NewStore().NewGraph(ctx, "test") + if err := g.AddTriples(ctx, ts); err != nil { + t.Fatalf("g.AddTriples(_) failed failed to add test triples with error: %v", err) + } + + testTable := []struct { + lo *storage.LookupOptions + o *triple.Object + want map[string]int + }{ + { + lo: &storage.LookupOptions{FilterOptions: &storage.FilteringOptions{Operation: "latest", Field: "predicate"}}, + o: triple.NewNodeObject(testutil.MustBuildNodeFromStrings(t, "/u", "mary")), + want: map[string]int{`"meet"@[2014-04-10T04:21:00Z]`: 1}, + }, + { + lo: &storage.LookupOptions{FilterOptions: &storage.FilteringOptions{Operation: "latest", Field: "predicate"}}, + o: triple.NewNodeObject(testutil.MustBuildNodeFromStrings(t, "/u", "bob")), + want: map[string]int{`"meet"@[2014-04-10T04:21:00Z]`: 1}, + }, + { + lo: &storage.LookupOptions{FilterOptions: &storage.FilteringOptions{Operation: "latest", Field: "object"}}, + o: triple.NewPredicateObject(testutil.MustBuildPredicate(t, `"meet"@[2020-04-10T04:21:00Z]`)), + want: map[string]int{`"_predicate"@[]`: 1}, + }, + } + + for _, entry := range testTable { + // To avoid blocking on the test we use a buffered channel of size 100. On a real + // usage of the driver you would like to call the graph operation on a separated + // goroutine using a sync.WaitGroup to collect the error code eventually. + pp := make(chan *predicate.Predicate, 100) + o := entry.o + if err := g.PredicatesForObject(ctx, o, entry.lo, pp); err != nil { + t.Fatalf("g.PredicatesForObject(%s, %s) = %v; want nil", o, entry.lo, err) + } + for p := range pp { + pStr := p.String() + if _, ok := entry.want[pStr]; !ok { + t.Fatalf("g.PredicatesForObject(%s, %s) retrieved unexpected %s", o, entry.lo, pStr) + } + entry.want[pStr] = entry.want[pStr] - 1 + if entry.want[pStr] == 0 { + delete(entry.want, pStr) + } + } + if len(entry.want) != 0 { + t.Errorf("g.PredicatesForObject(%s, %s) failed to retrieve some expected elements: %v", o, entry.lo, entry.want) + } + } +} + func TestTriplesForSubject(t *testing.T) { ts, ctx := getTestTriples(t), context.Background() g, _ := NewStore().NewGraph(ctx, "test") @@ -555,6 +848,55 @@ func TestTriplesForSubjectLatestAnchor(t *testing.T) { } } +func TestTriplesForSubjectFilter(t *testing.T) { + ts, ctx := getTestTriplesFilter(t), context.Background() + g, _ := NewStore().NewGraph(ctx, "test") + if err := g.AddTriples(ctx, ts); err != nil { + t.Fatalf("g.AddTriples(_) failed failed to add test triples with error: %v", err) + } + + testTable := []struct { + lo *storage.LookupOptions + s *node.Node + want map[string]int + }{ + { + lo: &storage.LookupOptions{FilterOptions: &storage.FilteringOptions{Operation: "latest", Field: "predicate"}}, + s: testutil.MustBuildNodeFromStrings(t, "/u", "john"), + want: map[string]int{`/u "meet"@[2014-04-10T04:21:00Z] /u`: 1, `/u "meet"@[2014-04-10T04:21:00Z] /u`: 1}, + }, + { + lo: &storage.LookupOptions{FilterOptions: &storage.FilteringOptions{Operation: "latest", Field: "object"}}, + s: testutil.MustBuildNodeFromStrings(t, "/_", "bn"), + want: map[string]int{`/_ "_predicate"@[] "meet"@[2021-04-10T04:21:00Z]`: 1}, + }, + } + + for _, entry := range testTable { + // To avoid blocking on the test we use a buffered channel of size 100. On a real + // usage of the driver you would like to call the graph operation on a separated + // goroutine using a sync.WaitGroup to collect the error code eventually. + trpls := make(chan *triple.Triple, 100) + s := entry.s + if err := g.TriplesForSubject(ctx, s, entry.lo, trpls); err != nil { + t.Fatalf("g.TriplesForSubject(%s, %s) = %v; want nil", s, entry.lo, err) + } + for trpl := range trpls { + tStr := trpl.String() + if _, ok := entry.want[tStr]; !ok { + t.Fatalf("g.TriplesForSubject(%s, %s) retrieved unexpected %s", s, entry.lo, tStr) + } + entry.want[tStr] = entry.want[tStr] - 1 + if entry.want[tStr] == 0 { + delete(entry.want, tStr) + } + } + if len(entry.want) != 0 { + t.Errorf("g.TriplesForSubject(%s, %s) failed to retrieve some expected elements: %v", s, entry.lo, entry.want) + } + } +} + func TestTriplesForPredicate(t *testing.T) { ts, ctx := getTestTriples(t), context.Background() g, _ := NewStore().NewGraph(ctx, "test") @@ -576,6 +918,7 @@ func TestTriplesForPredicate(t *testing.T) { t.Errorf("g.triplesForPredicate(%s) failed to retrieve 3 predicates, got %d instead", ts[0].Predicate(), cnt) } } + func TestTriplesForPredicateLatestAnchor(t *testing.T) { ts, ctx := getTestTemporalTriples(t), context.Background() g, _ := NewStore().NewGraph(ctx, "test") @@ -602,6 +945,60 @@ func TestTriplesForPredicateLatestAnchor(t *testing.T) { } } +func TestTriplesForPredicateFilter(t *testing.T) { + ts, ctx := getTestTriplesFilter(t), context.Background() + g, _ := NewStore().NewGraph(ctx, "test") + if err := g.AddTriples(ctx, ts); err != nil { + t.Fatalf("g.AddTriples(_) failed failed to add test triples with error: %v", err) + } + + testTable := []struct { + lo *storage.LookupOptions + p *predicate.Predicate + want map[string]int + }{ + { + lo: &storage.LookupOptions{FilterOptions: &storage.FilteringOptions{Operation: "latest", Field: "predicate"}}, + p: testutil.MustBuildPredicate(t, `"meet"@[2012-04-10T04:21:00Z]`), + want: map[string]int{`/u "meet"@[2012-04-10T04:21:00Z] /u`: 1}, + }, + { + lo: &storage.LookupOptions{FilterOptions: &storage.FilteringOptions{Operation: "latest", Field: "predicate"}}, + p: testutil.MustBuildPredicate(t, `"meet"@[2014-04-10T04:21:00Z]`), + want: map[string]int{`/u "meet"@[2014-04-10T04:21:00Z] /u`: 1, `/u "meet"@[2014-04-10T04:21:00Z] /u`: 1}, + }, + { + lo: &storage.LookupOptions{FilterOptions: &storage.FilteringOptions{Operation: "latest", Field: "object"}}, + p: testutil.MustBuildPredicate(t, `"_predicate"@[]`), + want: map[string]int{`/_ "_predicate"@[] "meet"@[2021-04-10T04:21:00Z]`: 1}, + }, + } + + for _, entry := range testTable { + // To avoid blocking on the test we use a buffered channel of size 100. On a real + // usage of the driver you would like to call the graph operation on a separated + // goroutine using a sync.WaitGroup to collect the error code eventually. + trpls := make(chan *triple.Triple, 100) + p := entry.p + if err := g.TriplesForPredicate(ctx, p, entry.lo, trpls); err != nil { + t.Fatalf("g.TriplesForPredicate(%s, %s) = %v; want nil", p, entry.lo, err) + } + for trpl := range trpls { + tStr := trpl.String() + if _, ok := entry.want[tStr]; !ok { + t.Fatalf("g.TriplesForPredicate(%s, %s) retrieved unexpected %s", p, entry.lo, tStr) + } + entry.want[tStr] = entry.want[tStr] - 1 + if entry.want[tStr] == 0 { + delete(entry.want, tStr) + } + } + if len(entry.want) != 0 { + t.Errorf("g.TriplesForPredicate(%s, %s) failed to retrieve some expected elements: %v", p, entry.lo, entry.want) + } + } +} + func TestTriplesForObject(t *testing.T) { ts, ctx := getTestTriples(t), context.Background() g, _ := NewStore().NewGraph(ctx, "test") @@ -650,6 +1047,60 @@ func TestTriplesForObjectLatestAnchor(t *testing.T) { } } +func TestTriplesForObjectFilter(t *testing.T) { + ts, ctx := getTestTriplesFilter(t), context.Background() + g, _ := NewStore().NewGraph(ctx, "test") + if err := g.AddTriples(ctx, ts); err != nil { + t.Fatalf("g.AddTriples(_) failed failed to add test triples with error: %v", err) + } + + testTable := []struct { + lo *storage.LookupOptions + o *triple.Object + want map[string]int + }{ + { + lo: &storage.LookupOptions{FilterOptions: &storage.FilteringOptions{Operation: "latest", Field: "predicate"}}, + o: triple.NewNodeObject(testutil.MustBuildNodeFromStrings(t, "/u", "mary")), + want: map[string]int{`/u "meet"@[2014-04-10T04:21:00Z] /u`: 1}, + }, + { + lo: &storage.LookupOptions{FilterOptions: &storage.FilteringOptions{Operation: "latest", Field: "predicate"}}, + o: triple.NewNodeObject(testutil.MustBuildNodeFromStrings(t, "/u", "bob")), + want: map[string]int{`/u "meet"@[2014-04-10T04:21:00Z] /u`: 1}, + }, + { + lo: &storage.LookupOptions{FilterOptions: &storage.FilteringOptions{Operation: "latest", Field: "object"}}, + o: triple.NewPredicateObject(testutil.MustBuildPredicate(t, `"meet"@[2020-04-10T04:21:00Z]`)), + want: map[string]int{`/_ "_predicate"@[] "meet"@[2020-04-10T04:21:00Z]`: 1}, + }, + } + + for _, entry := range testTable { + // To avoid blocking on the test we use a buffered channel of size 100. On a real + // usage of the driver you would like to call the graph operation on a separated + // goroutine using a sync.WaitGroup to collect the error code eventually. + trpls := make(chan *triple.Triple, 100) + o := entry.o + if err := g.TriplesForObject(ctx, o, entry.lo, trpls); err != nil { + t.Fatalf("g.TriplesForObject(%s, %s) = %v; want nil", o, entry.lo, err) + } + for trpl := range trpls { + tStr := trpl.String() + if _, ok := entry.want[tStr]; !ok { + t.Fatalf("g.TriplesForObject(%s, %s) retrieved unexpected %s", o, entry.lo, tStr) + } + entry.want[tStr] = entry.want[tStr] - 1 + if entry.want[tStr] == 0 { + delete(entry.want, tStr) + } + } + if len(entry.want) != 0 { + t.Errorf("g.TriplesForObject(%s, %s) failed to retrieve some expected elements: %v", o, entry.lo, entry.want) + } + } +} + func TestTriplesForObjectWithLimit(t *testing.T) { ts := createTriples(t, []string{ "/u\t\"kissed\"@[2015-01-01T00:00:00-09:00]\t/u", @@ -714,6 +1165,7 @@ func TestTriplesForSubjectAndPredicate(t *testing.T) { t.Errorf("g.TriplesForSubjectAndPredicate(%s, %s) failed to retrieve 3 predicates, got %d instead", ts[0].Subject(), ts[0].Predicate(), cnt) } } + func TestTriplesForSubjectAndPredicateLatestAnchor(t *testing.T) { ts, ctx := getTestTemporalTriples(t), context.Background() g, _ := NewStore().NewGraph(ctx, "test") @@ -740,6 +1192,65 @@ func TestTriplesForSubjectAndPredicateLatestAnchor(t *testing.T) { } } +func TestTriplesForSubjectAndPredicateFilter(t *testing.T) { + ts, ctx := getTestTriplesFilter(t), context.Background() + g, _ := NewStore().NewGraph(ctx, "test") + if err := g.AddTriples(ctx, ts); err != nil { + t.Fatalf("g.AddTriples(_) failed failed to add test triples with error: %v", err) + } + + testTable := []struct { + lo *storage.LookupOptions + s *node.Node + p *predicate.Predicate + want map[string]int + }{ + { + lo: &storage.LookupOptions{FilterOptions: &storage.FilteringOptions{Operation: "latest", Field: "predicate"}}, + s: testutil.MustBuildNodeFromStrings(t, "/u", "john"), + p: testutil.MustBuildPredicate(t, `"meet"@[2012-04-10T04:21:00Z]`), + want: map[string]int{`/u "meet"@[2012-04-10T04:21:00Z] /u`: 1}, + }, + { + lo: &storage.LookupOptions{FilterOptions: &storage.FilteringOptions{Operation: "latest", Field: "predicate"}}, + s: testutil.MustBuildNodeFromStrings(t, "/u", "john"), + p: testutil.MustBuildPredicate(t, `"meet"@[2014-04-10T04:21:00Z]`), + want: map[string]int{`/u "meet"@[2014-04-10T04:21:00Z] /u`: 1, `/u "meet"@[2014-04-10T04:21:00Z] /u`: 1}, + }, + { + lo: &storage.LookupOptions{FilterOptions: &storage.FilteringOptions{Operation: "latest", Field: "object"}}, + s: testutil.MustBuildNodeFromStrings(t, "/_", "bn"), + p: testutil.MustBuildPredicate(t, `"_predicate"@[]`), + want: map[string]int{`/_ "_predicate"@[] "meet"@[2021-04-10T04:21:00Z]`: 1}, + }, + } + + for _, entry := range testTable { + // To avoid blocking on the test we use a buffered channel of size 100. On a real + // usage of the driver you would like to call the graph operation on a separated + // goroutine using a sync.WaitGroup to collect the error code eventually. + trpls := make(chan *triple.Triple, 100) + s := entry.s + p := entry.p + if err := g.TriplesForSubjectAndPredicate(ctx, s, p, entry.lo, trpls); err != nil { + t.Fatalf("g.TriplesForSubjectAndPredicate(%s, %s, %s) = %v; want nil", s, p, entry.lo, err) + } + for trpl := range trpls { + tStr := trpl.String() + if _, ok := entry.want[tStr]; !ok { + t.Fatalf("g.TriplesForSubjectAndPredicate(%s, %s, %s) retrieved unexpected %s", s, p, entry.lo, tStr) + } + entry.want[tStr] = entry.want[tStr] - 1 + if entry.want[tStr] == 0 { + delete(entry.want, tStr) + } + } + if len(entry.want) != 0 { + t.Errorf("g.TriplesForSubjectAndPredicate(%s, %s, %s) failed to retrieve some expected elements: %v", s, p, entry.lo, entry.want) + } + } +} + func TestTriplesForPredicateAndObject(t *testing.T) { ts, ctx := getTestTriples(t), context.Background() g, _ := NewStore().NewGraph(ctx, "test") @@ -788,6 +1299,65 @@ func TestTriplesForPredicateAndObjectLatestAnchor(t *testing.T) { } } +func TestTriplesForPredicateAndObjectFilter(t *testing.T) { + ts, ctx := getTestTriplesFilter(t), context.Background() + g, _ := NewStore().NewGraph(ctx, "test") + if err := g.AddTriples(ctx, ts); err != nil { + t.Fatalf("g.AddTriples(_) failed failed to add test triples with error: %v", err) + } + + testTable := []struct { + lo *storage.LookupOptions + p *predicate.Predicate + o *triple.Object + want map[string]int + }{ + { + lo: &storage.LookupOptions{FilterOptions: &storage.FilteringOptions{Operation: "latest", Field: "predicate"}}, + p: testutil.MustBuildPredicate(t, `"meet"@[2012-04-10T04:21:00Z]`), + o: triple.NewNodeObject(testutil.MustBuildNodeFromStrings(t, "/u", "mary")), + want: map[string]int{`/u "meet"@[2012-04-10T04:21:00Z] /u`: 1}, + }, + { + lo: &storage.LookupOptions{FilterOptions: &storage.FilteringOptions{Operation: "latest", Field: "predicate"}}, + p: testutil.MustBuildPredicate(t, `"meet"@[2014-04-10T04:21:00Z]`), + o: triple.NewNodeObject(testutil.MustBuildNodeFromStrings(t, "/u", "mary")), + want: map[string]int{`/u "meet"@[2014-04-10T04:21:00Z] /u`: 1}, + }, + { + lo: &storage.LookupOptions{FilterOptions: &storage.FilteringOptions{Operation: "latest", Field: "object"}}, + p: testutil.MustBuildPredicate(t, `"_predicate"@[]`), + o: triple.NewPredicateObject(testutil.MustBuildPredicate(t, `"meet"@[2020-04-10T04:21:00Z]`)), + want: map[string]int{`/_ "_predicate"@[] "meet"@[2020-04-10T04:21:00Z]`: 1}, + }, + } + + for _, entry := range testTable { + // To avoid blocking on the test we use a buffered channel of size 100. On a real + // usage of the driver you would like to call the graph operation on a separated + // goroutine using a sync.WaitGroup to collect the error code eventually. + trpls := make(chan *triple.Triple, 100) + p := entry.p + o := entry.o + if err := g.TriplesForPredicateAndObject(ctx, p, o, entry.lo, trpls); err != nil { + t.Fatalf("g.TriplesForPredicateAndObject(%s, %s, %s) = %v; want nil", p, o, entry.lo, err) + } + for trpl := range trpls { + tStr := trpl.String() + if _, ok := entry.want[tStr]; !ok { + t.Fatalf("g.TriplesForPredicateAndObject(%s, %s, %s) retrieved unexpected %s", p, o, entry.lo, tStr) + } + entry.want[tStr] = entry.want[tStr] - 1 + if entry.want[tStr] == 0 { + delete(entry.want, tStr) + } + } + if len(entry.want) != 0 { + t.Errorf("g.TriplesForPredicateAndObject(%s, %s, %s) failed to retrieve some expected elements: %v", p, o, entry.lo, entry.want) + } + } +} + func TestExists(t *testing.T) { ts, ctx := getTestTriples(t), context.Background() g, _ := NewStore().NewGraph(ctx, "test") @@ -852,3 +1422,48 @@ func TestTriplesLatestAnchor(t *testing.T) { t.Errorf("g.TriplesForPredicateAndObject(%s, %s) failed to retrieve 1 predicates, got %d instead", ts[0].Predicate(), ts[0].Object(), cnt) } } + +func TestTriplesFilter(t *testing.T) { + ts, ctx := getTestTriplesFilter(t), context.Background() + g, _ := NewStore().NewGraph(ctx, "test") + if err := g.AddTriples(ctx, ts); err != nil { + t.Fatalf("g.AddTriples(_) failed failed to add test triples with error: %v", err) + } + + testTable := []struct { + lo *storage.LookupOptions + want map[string]int + }{ + { + lo: &storage.LookupOptions{FilterOptions: &storage.FilteringOptions{Operation: "latest", Field: "predicate"}}, + want: map[string]int{`/u "meet"@[2014-04-10T04:21:00Z] /u`: 1, `/u "meet"@[2014-04-10T04:21:00Z] /u`: 1}, + }, + { + lo: &storage.LookupOptions{FilterOptions: &storage.FilteringOptions{Operation: "latest", Field: "object"}}, + want: map[string]int{`/_ "_predicate"@[] "meet"@[2021-04-10T04:21:00Z]`: 1}, + }, + } + + for _, entry := range testTable { + // To avoid blocking on the test we use a buffered channel of size 100. On a real + // usage of the driver you would like to call the graph operation on a separated + // goroutine using a sync.WaitGroup to collect the error code eventually. + trpls := make(chan *triple.Triple, 100) + if err := g.Triples(ctx, entry.lo, trpls); err != nil { + t.Fatalf("g.Triples(%s) = %v; want nil", entry.lo, err) + } + for trpl := range trpls { + tStr := trpl.String() + if _, ok := entry.want[tStr]; !ok { + t.Fatalf("g.Triples(%s) retrieved unexpected %s", entry.lo, tStr) + } + entry.want[tStr] = entry.want[tStr] - 1 + if entry.want[tStr] == 0 { + delete(entry.want, tStr) + } + } + if len(entry.want) != 0 { + t.Errorf("g.Triples(%s) failed to retrieve some expected elements: %v", entry.lo, entry.want) + } + } +}