gonum.org/v1/gonum@v0.14.0/graph/formats/rdf/query_test.go (about)

     1  // Copyright ©2022 The Gonum Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package rdf
     6  
     7  import (
     8  	"io"
     9  	"reflect"
    10  	"strings"
    11  	"testing"
    12  
    13  	"golang.org/x/exp/rand"
    14  )
    15  
    16  var andTests = []struct {
    17  	name string
    18  	a, b []Term
    19  	want []Term
    20  }{
    21  	{
    22  		name: "identical",
    23  		a:    []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
    24  		b:    []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
    25  		want: []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
    26  	},
    27  	{
    28  		name: "identical with excess a",
    29  		a:    []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
    30  		b:    []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
    31  		want: []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
    32  	},
    33  	{
    34  		name: "identical with excess b",
    35  		a:    []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
    36  		b:    []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
    37  		want: []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
    38  	},
    39  	{
    40  		name: "b less",
    41  		a:    []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
    42  		b:    []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}},
    43  		want: []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}},
    44  	},
    45  	{
    46  		name: "a less",
    47  		a:    []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:c>", UID: 3}},
    48  		b:    []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
    49  		want: []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:c>", UID: 3}},
    50  	},
    51  }
    52  
    53  func TestQueryAnd(t *testing.T) {
    54  	src := rand.NewSource(1)
    55  	for _, test := range andTests {
    56  		for i := 0; i < 10; i++ {
    57  			a := Query{terms: permutedTerms(test.a, src)}
    58  			b := Query{terms: permutedTerms(test.b, src)}
    59  
    60  			got := a.And(b).Result()
    61  			sortByID(got)
    62  			sortByID(test.want)
    63  
    64  			if !reflect.DeepEqual(got, test.want) {
    65  				t.Errorf("unexpected result for test %q:\ngot: %v\nwant:%v",
    66  					test.name, got, test.want)
    67  			}
    68  		}
    69  	}
    70  }
    71  
    72  var orTests = []struct {
    73  	name string
    74  	a, b []Term
    75  	want []Term
    76  }{
    77  	{
    78  		name: "identical",
    79  		a:    []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
    80  		b:    []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
    81  		want: []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
    82  	},
    83  	{
    84  		name: "identical with excess a",
    85  		a:    []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
    86  		b:    []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
    87  		want: []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
    88  	},
    89  	{
    90  		name: "identical with excess b",
    91  		a:    []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
    92  		b:    []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
    93  		want: []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
    94  	},
    95  	{
    96  		name: "b less",
    97  		a:    []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
    98  		b:    []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}},
    99  		want: []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
   100  	},
   101  	{
   102  		name: "a less",
   103  		a:    []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:c>", UID: 3}},
   104  		b:    []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
   105  		want: []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
   106  	},
   107  }
   108  
   109  func TestQueryOr(t *testing.T) {
   110  	src := rand.NewSource(1)
   111  	for _, test := range orTests {
   112  		for i := 0; i < 10; i++ {
   113  			a := Query{terms: permutedTerms(test.a, src)}
   114  			b := Query{terms: permutedTerms(test.b, src)}
   115  
   116  			got := a.Or(b).Result()
   117  			sortByID(got)
   118  			sortByID(test.want)
   119  
   120  			if !reflect.DeepEqual(got, test.want) {
   121  				t.Errorf("unexpected result for test %q:\ngot: %v\nwant:%v",
   122  					test.name, got, test.want)
   123  			}
   124  		}
   125  	}
   126  }
   127  
   128  var notTests = []struct {
   129  	name string
   130  	a, b []Term
   131  	want []Term
   132  }{
   133  	{
   134  		name: "identical",
   135  		a:    []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
   136  		b:    []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
   137  		want: nil,
   138  	},
   139  	{
   140  		name: "identical with excess a",
   141  		a:    []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
   142  		b:    []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
   143  		want: nil,
   144  	},
   145  	{
   146  		name: "identical with excess b",
   147  		a:    []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
   148  		b:    []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
   149  		want: nil,
   150  	},
   151  	{
   152  		name: "b less",
   153  		a:    []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
   154  		b:    []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}},
   155  		want: []Term{{Value: "<ex:c>", UID: 3}},
   156  	},
   157  	{
   158  		name: "a less",
   159  		a:    []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:c>", UID: 3}},
   160  		b:    []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
   161  		want: nil,
   162  	},
   163  }
   164  
   165  func TestQueryNot(t *testing.T) {
   166  	src := rand.NewSource(1)
   167  	for _, test := range notTests {
   168  		for i := 0; i < 10; i++ {
   169  			a := Query{terms: permutedTerms(test.a, src)}
   170  			b := Query{terms: permutedTerms(test.b, src)}
   171  
   172  			got := a.Not(b).Result()
   173  			sortByID(got)
   174  			sortByID(test.want)
   175  
   176  			if !reflect.DeepEqual(got, test.want) {
   177  				t.Errorf("unexpected result for test %q:\ngot: %v\nwant:%v",
   178  					test.name, got, test.want)
   179  			}
   180  		}
   181  	}
   182  }
   183  
   184  func TestQueryRepeat(t *testing.T) {
   185  	const filterTestGraph = `
   186  <ex:a> <p:1> <ex:b> .
   187  <ex:b> <p:1> <ex:c> .
   188  <ex:c> <p:1> <ex:d> .
   189  <ex:d> <p:1> <ex:e> .
   190  <ex:e> <p:1> <ex:f> .
   191  <ex:a> <p:2> <ex:_b> .
   192  <ex:b> <p:2> <ex:_c> .
   193  <ex:c> <p:2> <ex:_d> .
   194  <ex:d> <p:2> <ex:_e> .
   195  <ex:e> <p:2> <ex:_f> .
   196  `
   197  
   198  	want := []string{"<ex:a>", "<ex:b>", "<ex:c>", "<ex:d>", "<ex:e>", "<ex:f>"}
   199  
   200  	g, err := graphFromStatements(filterTestGraph)
   201  	if err != nil {
   202  		t.Fatalf("unexpected error constructing graph: %v", err)
   203  	}
   204  	start, ok := g.TermFor("<ex:a>")
   205  	if !ok {
   206  		t.Fatal("could not get start term")
   207  	}
   208  	for _, limit := range []int{0, 1, 2, 5, 100} {
   209  		got := []string{}
   210  		var i int
   211  		result := g.Query(start).Repeat(func(q Query) (Query, bool) {
   212  			if i >= limit {
   213  				return q, false
   214  			}
   215  			i++
   216  			q = q.Out(func(s *Statement) bool {
   217  				ok := s.Predicate.Value == "<p:1>"
   218  				if ok {
   219  					got = append(got, s.Object.Value)
   220  				}
   221  				return ok
   222  			})
   223  			return q, true
   224  		}).Unique().Result()
   225  
   226  		n := limit
   227  		if n >= len(want) {
   228  			n = len(want) - 1
   229  		}
   230  		if !reflect.DeepEqual(got, want[1:n+1]) {
   231  			t.Errorf("unexpected capture for limit=%d: got:%v want:%v",
   232  				limit, got, want[:n])
   233  		}
   234  
   235  		switch {
   236  		case limit < len(want):
   237  			if len(result) == 0 || result[0].Value != want[i] {
   238  				t.Errorf("unexpected result for limit=%d: got:%v want:%v",
   239  					limit, result[0], want[i])
   240  			}
   241  		default:
   242  			if len(result) != 0 {
   243  				t.Errorf("unexpected result for limit=%d: got: %v want:none",
   244  					limit, result[0])
   245  			}
   246  		}
   247  	}
   248  }
   249  
   250  var uniqueTests = []struct {
   251  	name string
   252  	in   []Term
   253  	want []Term
   254  }{
   255  	{
   256  		name: "excess a",
   257  		in:   []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
   258  		want: []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
   259  	},
   260  	{
   261  		name: "excess b",
   262  		in:   []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
   263  		want: []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
   264  	},
   265  	{
   266  		name: "excess c",
   267  		in:   []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}, {Value: "<ex:c>", UID: 3}},
   268  		want: []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
   269  	},
   270  }
   271  
   272  func TestQueryUnique(t *testing.T) {
   273  	src := rand.NewSource(1)
   274  	for _, test := range uniqueTests {
   275  		for i := 0; i < 10; i++ {
   276  			a := Query{terms: permutedTerms(test.in, src)}
   277  
   278  			got := a.Unique().Result()
   279  			sortByID(got)
   280  			sortByID(test.want)
   281  
   282  			if !reflect.DeepEqual(got, test.want) {
   283  				t.Errorf("unexpected result for test %q:\ngot: %v\nwant:%v",
   284  					test.name, got, test.want)
   285  			}
   286  		}
   287  	}
   288  }
   289  
   290  // filterTestGraph is used to test Has*Out and Has*In. It has a symmetry
   291  // that means that the in an out tests have the same form, just with opposite
   292  // directions.
   293  const filterTestGraph = `
   294  <ex:a> <p:1> <ex:d> .
   295  <ex:a> <p:2> <ex:f> .
   296  <ex:b> <p:2> <ex:d> .
   297  <ex:c> <p:2> <ex:d> .
   298  <ex:a> <o:n> <ex:d> .
   299  # symmetry line.
   300  <ex:e> <p:1> <ex:a> .
   301  <ex:g> <p:2> <ex:a> .
   302  <ex:e> <p:2> <ex:b> .
   303  <ex:e> <p:2> <ex:c> .
   304  <ex:e> <o:n> <ex:a> .
   305  `
   306  
   307  var hasOutTests = []struct {
   308  	name    string
   309  	in      []Term
   310  	fn      func(*Statement) bool
   311  	cons    func(q Query) Query
   312  	wantAll []Term
   313  	wantAny []Term
   314  }{
   315  	{
   316  		name: "all",
   317  		in:   []Term{{Value: "<ex:a>"}, {Value: "<ex:b>"}, {Value: "<ex:c>"}},
   318  		fn:   func(s *Statement) bool { return true },
   319  		cons: func(q Query) Query {
   320  			cond := func(s *Statement) bool { return true }
   321  			return q.Out(cond).In(cond).Unique()
   322  		},
   323  		wantAll: []Term{{Value: "<ex:a>"}, {Value: "<ex:b>"}, {Value: "<ex:c>"}},
   324  		wantAny: []Term{{Value: "<ex:a>"}, {Value: "<ex:b>"}, {Value: "<ex:c>"}},
   325  	},
   326  	{
   327  		name: "none",
   328  		in:   []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
   329  		fn:   func(s *Statement) bool { return false },
   330  		cons: func(q Query) Query {
   331  			cond := func(s *Statement) bool { return false }
   332  			return q.Out(cond).In(cond).Unique()
   333  		},
   334  		wantAll: nil,
   335  		wantAny: nil,
   336  	},
   337  	{
   338  		name: ". <p:1> .",
   339  		in:   []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
   340  		fn:   func(s *Statement) bool { return s.Predicate.Value == "<p:1>" },
   341  		cons: func(q Query) Query {
   342  			cond1 := func(s *Statement) bool { return s.Predicate.Value == "<p:1>" }
   343  			cond2 := func(s *Statement) bool { return s.Predicate.Value != "<p:1>" }
   344  			return q.Out(cond1).In(cond1).Not(q.Out(cond2).In(cond2)).Unique()
   345  		},
   346  		wantAll: nil,
   347  		wantAny: []Term{{Value: "<ex:a>"}},
   348  	},
   349  	{
   350  		name: "!(. <p:1> .)",
   351  		in:   []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
   352  		fn:   func(s *Statement) bool { return s.Predicate.Value != "<p:1>" },
   353  		cons: func(q Query) Query {
   354  			cond1 := func(s *Statement) bool { return s.Predicate.Value != "<p:1>" }
   355  			cond2 := func(s *Statement) bool { return s.Predicate.Value == "<p:1>" }
   356  			return q.Out(cond1).In(cond1).Not(q.Out(cond2).In(cond2)).Unique()
   357  		},
   358  		wantAll: []Term{{Value: "<ex:b>"}, {Value: "<ex:c>"}},
   359  		wantAny: []Term{{Value: "<ex:a>"}, {Value: "<ex:b>"}, {Value: "<ex:c>"}},
   360  	},
   361  	{
   362  		name: "!(. <p:2> .)",
   363  		in:   []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
   364  		fn:   func(s *Statement) bool { return s.Predicate.Value != "<p:2>" },
   365  		cons: func(q Query) Query {
   366  			cond1 := func(s *Statement) bool { return s.Predicate.Value != "<p:2>" }
   367  			cond2 := func(s *Statement) bool { return s.Predicate.Value == "<p:2>" }
   368  			return q.Out(cond1).In(cond1).Not(q.Out(cond2).In(cond2)).Unique()
   369  		},
   370  		wantAll: nil,
   371  		wantAny: []Term{{Value: "<ex:a>"}},
   372  	},
   373  	{
   374  		name: "!(. <p:2>  <ex:f>)",
   375  		in:   []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
   376  		fn: func(s *Statement) bool {
   377  			return s.Predicate.Value != "<p:2>" || (s.Predicate.Value == "<p:2>" && s.Object.Value != "<ex:f>")
   378  		},
   379  		cons: func(q Query) Query {
   380  			cond := func(s *Statement) bool {
   381  				return s.Predicate.Value == "<p:2>" && s.Object.Value != "<ex:f>"
   382  			}
   383  			return q.Out(cond).In(cond).Unique()
   384  		},
   385  		wantAll: []Term{{Value: "<ex:b>"}, {Value: "<ex:c>"}},
   386  		wantAny: []Term{{Value: "<ex:a>"}, {Value: "<ex:b>"}, {Value: "<ex:c>"}},
   387  	},
   388  	{
   389  		name: "!(. <p:2>  !<ex:f>)",
   390  		in:   []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
   391  		fn: func(s *Statement) bool {
   392  			return s.Predicate.Value != "<p:2>" || (s.Predicate.Value == "<p:2>" && s.Object.Value == "<ex:f>")
   393  		},
   394  		cons: func(q Query) Query {
   395  			cond := func(s *Statement) bool {
   396  				return s.Predicate.Value == "<p:2>" && s.Object.Value == "<ex:f>"
   397  			}
   398  			return q.Out(cond).In(cond).Unique()
   399  		},
   400  		wantAll: []Term{{Value: "<ex:a>"}},
   401  		wantAny: []Term{{Value: "<ex:a>"}},
   402  	},
   403  }
   404  
   405  func TestQueryHasAllOut(t *testing.T) {
   406  	g, err := graphFromStatements(filterTestGraph)
   407  	if err != nil {
   408  		t.Fatalf("unexpected error constructing graph: %v", err)
   409  	}
   410  	for _, test := range hasOutTests {
   411  		ids := make(map[string]int64)
   412  		for i, v := range test.in {
   413  			term, ok := g.TermFor(v.Value)
   414  			if !ok {
   415  				t.Fatalf("unexpected error constructing graph: could not get UID for term: %v", v.Value)
   416  			}
   417  			test.in[i].UID = term.UID
   418  			ids[term.Value] = term.UID
   419  		}
   420  		for i, v := range test.wantAll {
   421  			test.wantAll[i].UID = ids[v.Value]
   422  		}
   423  
   424  		a := Query{g: g, terms: test.in}
   425  
   426  		got := a.HasAllOut(test.fn).Result()
   427  		sortByID(got)
   428  		sortByID(test.wantAll)
   429  
   430  		if !reflect.DeepEqual(got, test.wantAll) {
   431  			t.Errorf("unexpected result for test %q:\ngot: %v\nwant:%v",
   432  				test.name, got, test.wantAll)
   433  		}
   434  
   435  		cons := test.cons(a).Result()
   436  		sortByID(cons)
   437  		if !reflect.DeepEqual(got, cons) {
   438  			t.Errorf("unexpected construction result for test %q:\ngot: %v\nwant:%v",
   439  				test.name, got, cons)
   440  		}
   441  	}
   442  }
   443  
   444  func TestQueryHasAnyOut(t *testing.T) {
   445  	g, err := graphFromStatements(filterTestGraph)
   446  	if err != nil {
   447  		t.Fatalf("unexpected error constructing graph: %v", err)
   448  	}
   449  	for _, test := range hasOutTests {
   450  		ids := make(map[string]int64)
   451  		for i, v := range test.in {
   452  			term, ok := g.TermFor(v.Value)
   453  			if !ok {
   454  				t.Fatalf("unexpected error constructing graph: could not get UID for term: %v", v.Value)
   455  			}
   456  			test.in[i].UID = term.UID
   457  			ids[term.Value] = term.UID
   458  		}
   459  		for i, v := range test.wantAny {
   460  			test.wantAny[i].UID = ids[v.Value]
   461  		}
   462  
   463  		a := Query{g: g, terms: test.in}
   464  
   465  		got := a.HasAnyOut(test.fn).Result()
   466  		sortByID(got)
   467  		sortByID(test.wantAny)
   468  
   469  		if !reflect.DeepEqual(got, test.wantAny) {
   470  			t.Errorf("unexpected result for test %q:\ngot: %v\nwant:%v",
   471  				test.name, got, test.wantAny)
   472  		}
   473  	}
   474  }
   475  
   476  var hasInTests = []struct {
   477  	name    string
   478  	in      []Term
   479  	fn      func(*Statement) bool
   480  	cons    func(q Query) Query
   481  	wantAll []Term
   482  	wantAny []Term
   483  }{
   484  	{
   485  		name: "all",
   486  		in:   []Term{{Value: "<ex:a>"}, {Value: "<ex:b>"}, {Value: "<ex:c>"}},
   487  		fn:   func(s *Statement) bool { return true },
   488  		cons: func(q Query) Query {
   489  			cond := func(s *Statement) bool { return true }
   490  			return q.In(cond).Out(cond).Unique()
   491  		},
   492  		wantAll: []Term{{Value: "<ex:a>"}, {Value: "<ex:b>"}, {Value: "<ex:c>"}},
   493  		wantAny: []Term{{Value: "<ex:a>"}, {Value: "<ex:b>"}, {Value: "<ex:c>"}},
   494  	},
   495  	{
   496  		name: "none",
   497  		in:   []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
   498  		fn:   func(s *Statement) bool { return false },
   499  		cons: func(q Query) Query {
   500  			cond := func(s *Statement) bool { return false }
   501  			return q.In(cond).Out(cond).Unique()
   502  		},
   503  		wantAll: nil,
   504  		wantAny: nil,
   505  	},
   506  	{
   507  		name: ". <p:1> .",
   508  		in:   []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
   509  		fn:   func(s *Statement) bool { return s.Predicate.Value == "<p:1>" },
   510  		cons: func(q Query) Query {
   511  			cond1 := func(s *Statement) bool { return s.Predicate.Value == "<p:1>" }
   512  			cond2 := func(s *Statement) bool { return s.Predicate.Value != "<p:1>" }
   513  			return q.In(cond1).Out(cond1).Not(q.In(cond2).Out(cond2)).Unique()
   514  		},
   515  		wantAll: nil,
   516  		wantAny: []Term{{Value: "<ex:a>"}},
   517  	},
   518  	{
   519  		name: "!(. <p:1> .)",
   520  		in:   []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
   521  		fn:   func(s *Statement) bool { return s.Predicate.Value != "<p:1>" },
   522  		cons: func(q Query) Query {
   523  			cond1 := func(s *Statement) bool { return s.Predicate.Value != "<p:1>" }
   524  			cond2 := func(s *Statement) bool { return s.Predicate.Value == "<p:1>" }
   525  			return q.In(cond1).Out(cond1).Not(q.In(cond2).Out(cond2)).Unique()
   526  		},
   527  		wantAll: []Term{{Value: "<ex:b>"}, {Value: "<ex:c>"}},
   528  		wantAny: []Term{{Value: "<ex:a>"}, {Value: "<ex:b>"}, {Value: "<ex:c>"}},
   529  	},
   530  	{
   531  		name: "!(. <p:2> .)",
   532  		in:   []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
   533  		fn:   func(s *Statement) bool { return s.Predicate.Value != "<p:2>" },
   534  		cons: func(q Query) Query {
   535  			cond1 := func(s *Statement) bool { return s.Predicate.Value != "<p:2>" }
   536  			cond2 := func(s *Statement) bool { return s.Predicate.Value == "<p:2>" }
   537  			return q.In(cond1).Out(cond1).Not(q.In(cond2).Out(cond2)).Unique()
   538  		},
   539  		wantAll: nil,
   540  		wantAny: []Term{{Value: "<ex:a>"}},
   541  	},
   542  	{
   543  		name: "!(<ex:f> <p:2>  .)",
   544  		in:   []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
   545  		fn: func(s *Statement) bool {
   546  			return s.Predicate.Value != "<p:2>" || (s.Predicate.Value == "<p:2>" && s.Subject.Value != "<ex:g>")
   547  		},
   548  		cons: func(q Query) Query {
   549  			cond := func(s *Statement) bool {
   550  				return s.Predicate.Value == "<p:2>" && s.Subject.Value != "<ex:g>"
   551  			}
   552  			return q.In(cond).Out(cond).Unique()
   553  		},
   554  		wantAll: []Term{{Value: "<ex:b>"}, {Value: "<ex:c>"}},
   555  		wantAny: []Term{{Value: "<ex:a>"}, {Value: "<ex:b>"}, {Value: "<ex:c>"}},
   556  	},
   557  	{
   558  		name: "!(!<ex:f> <p:2>  .)",
   559  		in:   []Term{{Value: "<ex:a>", UID: 1}, {Value: "<ex:b>", UID: 2}, {Value: "<ex:c>", UID: 3}},
   560  		fn: func(s *Statement) bool {
   561  			return s.Predicate.Value != "<p:2>" || (s.Predicate.Value == "<p:2>" && s.Subject.Value == "<ex:g>")
   562  		},
   563  		cons: func(q Query) Query {
   564  			cond := func(s *Statement) bool {
   565  				return s.Predicate.Value == "<p:2>" && s.Subject.Value == "<ex:g>"
   566  			}
   567  			return q.In(cond).Out(cond).Unique()
   568  		},
   569  		wantAll: []Term{{Value: "<ex:a>"}},
   570  		wantAny: []Term{{Value: "<ex:a>"}},
   571  	},
   572  }
   573  
   574  func TestQueryHasAllIn(t *testing.T) {
   575  	g, err := graphFromStatements(filterTestGraph)
   576  	if err != nil {
   577  		t.Fatalf("unexpected error constructing graph: %v", err)
   578  	}
   579  	for _, test := range hasInTests {
   580  		ids := make(map[string]int64)
   581  		for i, v := range test.in {
   582  			term, ok := g.TermFor(v.Value)
   583  			if !ok {
   584  				t.Fatalf("unexpected error constructing graph: could not get UID for term: %v", v.Value)
   585  			}
   586  			test.in[i].UID = term.UID
   587  			ids[term.Value] = term.UID
   588  		}
   589  		for i, v := range test.wantAll {
   590  			test.wantAll[i].UID = ids[v.Value]
   591  		}
   592  
   593  		a := Query{g: g, terms: test.in}
   594  
   595  		got := a.HasAllIn(test.fn).Result()
   596  		sortByID(got)
   597  		sortByID(test.wantAll)
   598  
   599  		if !reflect.DeepEqual(got, test.wantAll) {
   600  			t.Errorf("unexpected result for test %q:\ngot: %v\nwant:%v",
   601  				test.name, got, test.wantAll)
   602  		}
   603  
   604  		cons := test.cons(a).Result()
   605  		sortByID(cons)
   606  		if !reflect.DeepEqual(got, cons) {
   607  			t.Errorf("unexpected construction result for test %q:\ngot: %v\nwant:%v",
   608  				test.name, got, cons)
   609  		}
   610  	}
   611  }
   612  
   613  func TestQueryHasAnyIn(t *testing.T) {
   614  	g, err := graphFromStatements(filterTestGraph)
   615  	if err != nil {
   616  		t.Fatalf("unexpected error constructing graph: %v", err)
   617  	}
   618  	for _, test := range hasInTests {
   619  		ids := make(map[string]int64)
   620  		for i, v := range test.in {
   621  			term, ok := g.TermFor(v.Value)
   622  			if !ok {
   623  				t.Fatalf("unexpected error constructing graph: could not get UID for term: %v", v.Value)
   624  			}
   625  			test.in[i].UID = term.UID
   626  			ids[term.Value] = term.UID
   627  		}
   628  		for i, v := range test.wantAny {
   629  			test.wantAny[i].UID = ids[v.Value]
   630  		}
   631  
   632  		a := Query{g: g, terms: test.in}
   633  
   634  		got := a.HasAnyIn(test.fn).Result()
   635  		sortByID(got)
   636  		sortByID(test.wantAny)
   637  
   638  		if !reflect.DeepEqual(got, test.wantAny) {
   639  			t.Errorf("unexpected result for test %q:\ngot: %v\nwant:%v",
   640  				test.name, got, test.wantAny)
   641  		}
   642  	}
   643  }
   644  
   645  func graphFromStatements(s string) (*Graph, error) {
   646  	g := NewGraph()
   647  	dec := NewDecoder(strings.NewReader(s))
   648  	for {
   649  		s, err := dec.Unmarshal()
   650  		if err != nil {
   651  			if err != io.EOF {
   652  				return nil, err
   653  			}
   654  			break
   655  		}
   656  		g.AddStatement(s)
   657  	}
   658  	return g, nil
   659  }
   660  
   661  func permutedTerms(t []Term, src rand.Source) []Term {
   662  	rnd := rand.New(src)
   663  	p := make([]Term, len(t))
   664  	for i, j := range rnd.Perm(len(t)) {
   665  		p[i] = t[j]
   666  	}
   667  	return p
   668  }