mirror of
https://github.com/gonum/gonum.git
synced 2025-10-06 07:37:03 +08:00

These methods express queries that are currently able to be expressed with a sequence of In, Out and And queries. However, the constructed filter approach is potentially significantly more expensive and are more complex to reason about. For example it is often possible to make the following re-writes, p.Out(cond).In(cond).And(p) => p.HasAllOut(cond') and p.In(cond).Out(cond).And(p) => p.HasAllIn(cond'). The expense comes when reaching out to a commonly connected node and then coming back; the return traversal will generate a large set of fruitless candidates that then need to be filtered via the conjunction. This saves that fruitless effort.
603 lines
19 KiB
Go
603 lines
19 KiB
Go
// Copyright ©2022 The Gonum Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package rdf
|
|
|
|
import (
|
|
"io"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
|
|
"golang.org/x/exp/rand"
|
|
)
|
|
|
|
var andTests = []struct {
|
|
name string
|
|
a, b []Term
|
|
want []Term
|
|
}{
|
|
{
|
|
name: "identical",
|
|
a: []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
|
|
b: []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
|
|
want: []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
|
|
},
|
|
{
|
|
name: "identical with excess a",
|
|
a: []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
|
|
b: []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
|
|
want: []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
|
|
},
|
|
{
|
|
name: "identical with excess b",
|
|
a: []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
|
|
b: []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
|
|
want: []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
|
|
},
|
|
{
|
|
name: "b less",
|
|
a: []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
|
|
b: []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}},
|
|
want: []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}},
|
|
},
|
|
{
|
|
name: "a less",
|
|
a: []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:c>", UID: 3}},
|
|
b: []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
|
|
want: []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:c>", UID: 3}},
|
|
},
|
|
}
|
|
|
|
func TestQueryAnd(t *testing.T) {
|
|
src := rand.NewSource(1)
|
|
for _, test := range andTests {
|
|
for i := 0; i < 10; i++ {
|
|
a := Query{terms: permutedTerms(test.a, src)}
|
|
b := Query{terms: permutedTerms(test.b, src)}
|
|
|
|
got := a.And(b).Result()
|
|
sortByID(got)
|
|
sortByID(test.want)
|
|
|
|
if !reflect.DeepEqual(got, test.want) {
|
|
t.Errorf("unexpected result for test %q:\ngot: %v\nwant:%v",
|
|
test.name, got, test.want)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
var orTests = []struct {
|
|
name string
|
|
a, b []Term
|
|
want []Term
|
|
}{
|
|
{
|
|
name: "identical",
|
|
a: []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
|
|
b: []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
|
|
want: []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
|
|
},
|
|
{
|
|
name: "identical with excess a",
|
|
a: []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
|
|
b: []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
|
|
want: []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
|
|
},
|
|
{
|
|
name: "identical with excess b",
|
|
a: []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
|
|
b: []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
|
|
want: []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
|
|
},
|
|
{
|
|
name: "b less",
|
|
a: []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
|
|
b: []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}},
|
|
want: []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
|
|
},
|
|
{
|
|
name: "a less",
|
|
a: []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:c>", UID: 3}},
|
|
b: []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
|
|
want: []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
|
|
},
|
|
}
|
|
|
|
func TestQueryOr(t *testing.T) {
|
|
src := rand.NewSource(1)
|
|
for _, test := range orTests {
|
|
for i := 0; i < 10; i++ {
|
|
a := Query{terms: permutedTerms(test.a, src)}
|
|
b := Query{terms: permutedTerms(test.b, src)}
|
|
|
|
got := a.Or(b).Result()
|
|
sortByID(got)
|
|
sortByID(test.want)
|
|
|
|
if !reflect.DeepEqual(got, test.want) {
|
|
t.Errorf("unexpected result for test %q:\ngot: %v\nwant:%v",
|
|
test.name, got, test.want)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
var notTests = []struct {
|
|
name string
|
|
a, b []Term
|
|
want []Term
|
|
}{
|
|
{
|
|
name: "identical",
|
|
a: []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
|
|
b: []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
|
|
want: nil,
|
|
},
|
|
{
|
|
name: "identical with excess a",
|
|
a: []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
|
|
b: []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
|
|
want: nil,
|
|
},
|
|
{
|
|
name: "identical with excess b",
|
|
a: []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
|
|
b: []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
|
|
want: nil,
|
|
},
|
|
{
|
|
name: "b less",
|
|
a: []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
|
|
b: []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}},
|
|
want: []Term{{Value: "<ex:c>", UID: 3}},
|
|
},
|
|
{
|
|
name: "a less",
|
|
a: []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:c>", UID: 3}},
|
|
b: []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
|
|
want: nil,
|
|
},
|
|
}
|
|
|
|
func TestQueryNot(t *testing.T) {
|
|
src := rand.NewSource(1)
|
|
for _, test := range notTests {
|
|
for i := 0; i < 10; i++ {
|
|
a := Query{terms: permutedTerms(test.a, src)}
|
|
b := Query{terms: permutedTerms(test.b, src)}
|
|
|
|
got := a.Not(b).Result()
|
|
sortByID(got)
|
|
sortByID(test.want)
|
|
|
|
if !reflect.DeepEqual(got, test.want) {
|
|
t.Errorf("unexpected result for test %q:\ngot: %v\nwant:%v",
|
|
test.name, got, test.want)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
var uniqueTests = []struct {
|
|
name string
|
|
in []Term
|
|
want []Term
|
|
}{
|
|
{
|
|
name: "excess a",
|
|
in: []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
|
|
want: []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
|
|
},
|
|
{
|
|
name: "excess b",
|
|
in: []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
|
|
want: []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
|
|
},
|
|
{
|
|
name: "excess c",
|
|
in: []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}, {Value: "<ex:c>", UID: 3}},
|
|
want: []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
|
|
},
|
|
}
|
|
|
|
func TestQueryUnique(t *testing.T) {
|
|
src := rand.NewSource(1)
|
|
for _, test := range uniqueTests {
|
|
for i := 0; i < 10; i++ {
|
|
a := Query{terms: permutedTerms(test.in, src)}
|
|
|
|
got := a.Unique().Result()
|
|
sortByID(got)
|
|
sortByID(test.want)
|
|
|
|
if !reflect.DeepEqual(got, test.want) {
|
|
t.Errorf("unexpected result for test %q:\ngot: %v\nwant:%v",
|
|
test.name, got, test.want)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// filterTestGraph is used to test Has*Out and Has*In. It has a symmetry
|
|
// that means that the in an out tests have the same form, just with opposite
|
|
// directions.
|
|
const filterTestGraph = `
|
|
<ex:a> <p:1> <ex:d> .
|
|
<ex:a> <p:2> <ex:f> .
|
|
<ex:b> <p:2> <ex:d> .
|
|
<ex:c> <p:2> <ex:d> .
|
|
<ex:a> <o:n> <ex:d> .
|
|
# symmetry line.
|
|
<ex:e> <p:1> <ex:a> .
|
|
<ex:g> <p:2> <ex:a> .
|
|
<ex:e> <p:2> <ex:b> .
|
|
<ex:e> <p:2> <ex:c> .
|
|
<ex:e> <o:n> <ex:a> .
|
|
`
|
|
|
|
var hasOutTests = []struct {
|
|
name string
|
|
in []Term
|
|
fn func(*Statement) bool
|
|
cons func(q Query) Query
|
|
wantAll []Term
|
|
wantAny []Term
|
|
}{
|
|
{
|
|
name: "all",
|
|
in: []Term{{Value: "<ex:a>"}, {Value: "<ex:b>"}, {Value: "<ex:c>"}},
|
|
fn: func(s *Statement) bool { return true },
|
|
cons: func(q Query) Query {
|
|
cond := func(s *Statement) bool { return true }
|
|
return q.Out(cond).In(cond).Unique()
|
|
},
|
|
wantAll: []Term{{Value: "<ex:a>"}, {Value: "<ex:b>"}, {Value: "<ex:c>"}},
|
|
wantAny: []Term{{Value: "<ex:a>"}, {Value: "<ex:b>"}, {Value: "<ex:c>"}},
|
|
},
|
|
{
|
|
name: "none",
|
|
in: []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
|
|
fn: func(s *Statement) bool { return false },
|
|
cons: func(q Query) Query {
|
|
cond := func(s *Statement) bool { return false }
|
|
return q.Out(cond).In(cond).Unique()
|
|
},
|
|
wantAll: nil,
|
|
wantAny: nil,
|
|
},
|
|
{
|
|
name: ". <p:1> .",
|
|
in: []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
|
|
fn: func(s *Statement) bool { return s.Predicate.Value == "<p:1>" },
|
|
cons: func(q Query) Query {
|
|
cond1 := func(s *Statement) bool { return s.Predicate.Value == "<p:1>" }
|
|
cond2 := func(s *Statement) bool { return s.Predicate.Value != "<p:1>" }
|
|
return q.Out(cond1).In(cond1).Not(q.Out(cond2).In(cond2)).Unique()
|
|
},
|
|
wantAll: nil,
|
|
wantAny: []Term{{Value: "<ex:a>"}},
|
|
},
|
|
{
|
|
name: "!(. <p:1> .)",
|
|
in: []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
|
|
fn: func(s *Statement) bool { return s.Predicate.Value != "<p:1>" },
|
|
cons: func(q Query) Query {
|
|
cond1 := func(s *Statement) bool { return s.Predicate.Value != "<p:1>" }
|
|
cond2 := func(s *Statement) bool { return s.Predicate.Value == "<p:1>" }
|
|
return q.Out(cond1).In(cond1).Not(q.Out(cond2).In(cond2)).Unique()
|
|
},
|
|
wantAll: []Term{{Value: "<ex:b>"}, {Value: "<ex:c>"}},
|
|
wantAny: []Term{{Value: "<ex:a>"}, {Value: "<ex:b>"}, {Value: "<ex:c>"}},
|
|
},
|
|
{
|
|
name: "!(. <p:2> .)",
|
|
in: []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
|
|
fn: func(s *Statement) bool { return s.Predicate.Value != "<p:2>" },
|
|
cons: func(q Query) Query {
|
|
cond1 := func(s *Statement) bool { return s.Predicate.Value != "<p:2>" }
|
|
cond2 := func(s *Statement) bool { return s.Predicate.Value == "<p:2>" }
|
|
return q.Out(cond1).In(cond1).Not(q.Out(cond2).In(cond2)).Unique()
|
|
},
|
|
wantAll: nil,
|
|
wantAny: []Term{{Value: "<ex:a>"}},
|
|
},
|
|
{
|
|
name: "!(. <p:2> <ex:f>)",
|
|
in: []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
|
|
fn: func(s *Statement) bool {
|
|
return s.Predicate.Value != "<p:2>" || (s.Predicate.Value == "<p:2>" && s.Object.Value != "<ex:f>")
|
|
},
|
|
cons: func(q Query) Query {
|
|
cond := func(s *Statement) bool {
|
|
return s.Predicate.Value == "<p:2>" && s.Object.Value != "<ex:f>"
|
|
}
|
|
return q.Out(cond).In(cond).Unique()
|
|
},
|
|
wantAll: []Term{{Value: "<ex:b>"}, {Value: "<ex:c>"}},
|
|
wantAny: []Term{{Value: "<ex:a>"}, {Value: "<ex:b>"}, {Value: "<ex:c>"}},
|
|
},
|
|
{
|
|
name: "!(. <p:2> !<ex:f>)",
|
|
in: []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
|
|
fn: func(s *Statement) bool {
|
|
return s.Predicate.Value != "<p:2>" || (s.Predicate.Value == "<p:2>" && s.Object.Value == "<ex:f>")
|
|
},
|
|
cons: func(q Query) Query {
|
|
cond := func(s *Statement) bool {
|
|
return s.Predicate.Value == "<p:2>" && s.Object.Value == "<ex:f>"
|
|
}
|
|
return q.Out(cond).In(cond).Unique()
|
|
},
|
|
wantAll: []Term{{Value: "<ex:a>"}},
|
|
wantAny: []Term{{Value: "<ex:a>"}},
|
|
},
|
|
}
|
|
|
|
func TestQueryHasAllOut(t *testing.T) {
|
|
g, err := graphFromStatements(filterTestGraph)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error constructing graph: %v", err)
|
|
}
|
|
for _, test := range hasOutTests {
|
|
ids := make(map[string]int64)
|
|
for i, v := range test.in {
|
|
term, ok := g.TermFor(v.Value)
|
|
if !ok {
|
|
t.Fatalf("unexpected error constructing graph: could not get UID for term: %v", v.Value)
|
|
}
|
|
test.in[i].UID = term.UID
|
|
ids[term.Value] = term.UID
|
|
}
|
|
for i, v := range test.wantAll {
|
|
test.wantAll[i].UID = ids[v.Value]
|
|
}
|
|
|
|
a := Query{g: g, terms: test.in}
|
|
|
|
got := a.HasAllOut(test.fn).Result()
|
|
sortByID(got)
|
|
sortByID(test.wantAll)
|
|
|
|
if !reflect.DeepEqual(got, test.wantAll) {
|
|
t.Errorf("unexpected result for test %q:\ngot: %v\nwant:%v",
|
|
test.name, got, test.wantAll)
|
|
}
|
|
|
|
cons := test.cons(a).Result()
|
|
sortByID(cons)
|
|
if !reflect.DeepEqual(got, cons) {
|
|
t.Errorf("unexpected construction result for test %q:\ngot: %v\nwant:%v",
|
|
test.name, got, cons)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestQueryHasAnyOut(t *testing.T) {
|
|
g, err := graphFromStatements(filterTestGraph)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error constructing graph: %v", err)
|
|
}
|
|
for _, test := range hasOutTests {
|
|
ids := make(map[string]int64)
|
|
for i, v := range test.in {
|
|
term, ok := g.TermFor(v.Value)
|
|
if !ok {
|
|
t.Fatalf("unexpected error constructing graph: could not get UID for term: %v", v.Value)
|
|
}
|
|
test.in[i].UID = term.UID
|
|
ids[term.Value] = term.UID
|
|
}
|
|
for i, v := range test.wantAny {
|
|
test.wantAny[i].UID = ids[v.Value]
|
|
}
|
|
|
|
a := Query{g: g, terms: test.in}
|
|
|
|
got := a.HasAnyOut(test.fn).Result()
|
|
sortByID(got)
|
|
sortByID(test.wantAny)
|
|
|
|
if !reflect.DeepEqual(got, test.wantAny) {
|
|
t.Errorf("unexpected result for test %q:\ngot: %v\nwant:%v",
|
|
test.name, got, test.wantAny)
|
|
}
|
|
}
|
|
}
|
|
|
|
var hasInTests = []struct {
|
|
name string
|
|
in []Term
|
|
fn func(*Statement) bool
|
|
cons func(q Query) Query
|
|
wantAll []Term
|
|
wantAny []Term
|
|
}{
|
|
{
|
|
name: "all",
|
|
in: []Term{{Value: "<ex:a>"}, {Value: "<ex:b>"}, {Value: "<ex:c>"}},
|
|
fn: func(s *Statement) bool { return true },
|
|
cons: func(q Query) Query {
|
|
cond := func(s *Statement) bool { return true }
|
|
return q.In(cond).Out(cond).Unique()
|
|
},
|
|
wantAll: []Term{{Value: "<ex:a>"}, {Value: "<ex:b>"}, {Value: "<ex:c>"}},
|
|
wantAny: []Term{{Value: "<ex:a>"}, {Value: "<ex:b>"}, {Value: "<ex:c>"}},
|
|
},
|
|
{
|
|
name: "none",
|
|
in: []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
|
|
fn: func(s *Statement) bool { return false },
|
|
cons: func(q Query) Query {
|
|
cond := func(s *Statement) bool { return false }
|
|
return q.In(cond).Out(cond).Unique()
|
|
},
|
|
wantAll: nil,
|
|
wantAny: nil,
|
|
},
|
|
{
|
|
name: ". <p:1> .",
|
|
in: []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
|
|
fn: func(s *Statement) bool { return s.Predicate.Value == "<p:1>" },
|
|
cons: func(q Query) Query {
|
|
cond1 := func(s *Statement) bool { return s.Predicate.Value == "<p:1>" }
|
|
cond2 := func(s *Statement) bool { return s.Predicate.Value != "<p:1>" }
|
|
return q.In(cond1).Out(cond1).Not(q.In(cond2).Out(cond2)).Unique()
|
|
},
|
|
wantAll: nil,
|
|
wantAny: []Term{{Value: "<ex:a>"}},
|
|
},
|
|
{
|
|
name: "!(. <p:1> .)",
|
|
in: []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
|
|
fn: func(s *Statement) bool { return s.Predicate.Value != "<p:1>" },
|
|
cons: func(q Query) Query {
|
|
cond1 := func(s *Statement) bool { return s.Predicate.Value != "<p:1>" }
|
|
cond2 := func(s *Statement) bool { return s.Predicate.Value == "<p:1>" }
|
|
return q.In(cond1).Out(cond1).Not(q.In(cond2).Out(cond2)).Unique()
|
|
},
|
|
wantAll: []Term{{Value: "<ex:b>"}, {Value: "<ex:c>"}},
|
|
wantAny: []Term{{Value: "<ex:a>"}, {Value: "<ex:b>"}, {Value: "<ex:c>"}},
|
|
},
|
|
{
|
|
name: "!(. <p:2> .)",
|
|
in: []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
|
|
fn: func(s *Statement) bool { return s.Predicate.Value != "<p:2>" },
|
|
cons: func(q Query) Query {
|
|
cond1 := func(s *Statement) bool { return s.Predicate.Value != "<p:2>" }
|
|
cond2 := func(s *Statement) bool { return s.Predicate.Value == "<p:2>" }
|
|
return q.In(cond1).Out(cond1).Not(q.In(cond2).Out(cond2)).Unique()
|
|
},
|
|
wantAll: nil,
|
|
wantAny: []Term{{Value: "<ex:a>"}},
|
|
},
|
|
{
|
|
name: "!(<ex:f> <p:2> .)",
|
|
in: []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
|
|
fn: func(s *Statement) bool {
|
|
return s.Predicate.Value != "<p:2>" || (s.Predicate.Value == "<p:2>" && s.Subject.Value != "<ex:g>")
|
|
},
|
|
cons: func(q Query) Query {
|
|
cond := func(s *Statement) bool {
|
|
return s.Predicate.Value == "<p:2>" && s.Subject.Value != "<ex:g>"
|
|
}
|
|
return q.In(cond).Out(cond).Unique()
|
|
},
|
|
wantAll: []Term{{Value: "<ex:b>"}, {Value: "<ex:c>"}},
|
|
wantAny: []Term{{Value: "<ex:a>"}, {Value: "<ex:b>"}, {Value: "<ex:c>"}},
|
|
},
|
|
{
|
|
name: "!(!<ex:f> <p:2> .)",
|
|
in: []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
|
|
fn: func(s *Statement) bool {
|
|
return s.Predicate.Value != "<p:2>" || (s.Predicate.Value == "<p:2>" && s.Subject.Value == "<ex:g>")
|
|
},
|
|
cons: func(q Query) Query {
|
|
cond := func(s *Statement) bool {
|
|
return s.Predicate.Value == "<p:2>" && s.Subject.Value == "<ex:g>"
|
|
}
|
|
return q.In(cond).Out(cond).Unique()
|
|
},
|
|
wantAll: []Term{{Value: "<ex:a>"}},
|
|
wantAny: []Term{{Value: "<ex:a>"}},
|
|
},
|
|
}
|
|
|
|
func TestQueryHasAllIn(t *testing.T) {
|
|
g, err := graphFromStatements(filterTestGraph)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error constructing graph: %v", err)
|
|
}
|
|
for _, test := range hasInTests {
|
|
ids := make(map[string]int64)
|
|
for i, v := range test.in {
|
|
term, ok := g.TermFor(v.Value)
|
|
if !ok {
|
|
t.Fatalf("unexpected error constructing graph: could not get UID for term: %v", v.Value)
|
|
}
|
|
test.in[i].UID = term.UID
|
|
ids[term.Value] = term.UID
|
|
}
|
|
for i, v := range test.wantAll {
|
|
test.wantAll[i].UID = ids[v.Value]
|
|
}
|
|
|
|
a := Query{g: g, terms: test.in}
|
|
|
|
got := a.HasAllIn(test.fn).Result()
|
|
sortByID(got)
|
|
sortByID(test.wantAll)
|
|
|
|
if !reflect.DeepEqual(got, test.wantAll) {
|
|
t.Errorf("unexpected result for test %q:\ngot: %v\nwant:%v",
|
|
test.name, got, test.wantAll)
|
|
}
|
|
|
|
cons := test.cons(a).Result()
|
|
sortByID(cons)
|
|
if !reflect.DeepEqual(got, cons) {
|
|
t.Errorf("unexpected construction result for test %q:\ngot: %v\nwant:%v",
|
|
test.name, got, cons)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestQueryHasAnyIn(t *testing.T) {
|
|
g, err := graphFromStatements(filterTestGraph)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error constructing graph: %v", err)
|
|
}
|
|
for _, test := range hasInTests {
|
|
ids := make(map[string]int64)
|
|
for i, v := range test.in {
|
|
term, ok := g.TermFor(v.Value)
|
|
if !ok {
|
|
t.Fatalf("unexpected error constructing graph: could not get UID for term: %v", v.Value)
|
|
}
|
|
test.in[i].UID = term.UID
|
|
ids[term.Value] = term.UID
|
|
}
|
|
for i, v := range test.wantAny {
|
|
test.wantAny[i].UID = ids[v.Value]
|
|
}
|
|
|
|
a := Query{g: g, terms: test.in}
|
|
|
|
got := a.HasAnyIn(test.fn).Result()
|
|
sortByID(got)
|
|
sortByID(test.wantAny)
|
|
|
|
if !reflect.DeepEqual(got, test.wantAny) {
|
|
t.Errorf("unexpected result for test %q:\ngot: %v\nwant:%v",
|
|
test.name, got, test.wantAny)
|
|
}
|
|
}
|
|
}
|
|
|
|
func graphFromStatements(s string) (*Graph, error) {
|
|
g := NewGraph()
|
|
dec := NewDecoder(strings.NewReader(s))
|
|
for {
|
|
s, err := dec.Unmarshal()
|
|
if err != nil {
|
|
if err != io.EOF {
|
|
return nil, err
|
|
}
|
|
break
|
|
}
|
|
g.AddStatement(s)
|
|
}
|
|
return g, nil
|
|
}
|
|
|
|
func permutedTerms(t []Term, src rand.Source) []Term {
|
|
rnd := rand.New(src)
|
|
p := make([]Term, len(t))
|
|
for i, j := range rnd.Perm(len(t)) {
|
|
p[i] = t[j]
|
|
}
|
|
return p
|
|
}
|