github.com/cayleygraph/cayley@v0.7.7/graph/graphtest/graphtest.go (about)

     1  package graphtest
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"math"
     7  	"sort"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/cayleygraph/cayley/graph"
    12  	"github.com/cayleygraph/cayley/graph/graphtest/testutil"
    13  	"github.com/cayleygraph/cayley/graph/iterator"
    14  	"github.com/cayleygraph/cayley/graph/path/pathtest"
    15  	"github.com/cayleygraph/cayley/graph/shape"
    16  	"github.com/cayleygraph/cayley/schema"
    17  	"github.com/cayleygraph/cayley/writer"
    18  	"github.com/cayleygraph/quad"
    19  	"github.com/stretchr/testify/assert"
    20  	"github.com/stretchr/testify/require"
    21  )
    22  
    23  type Config struct {
    24  	NoPrimitives bool
    25  	UnTyped      bool // converts all values to Raw representation
    26  	TimeInMs     bool
    27  	TimeInMcs    bool
    28  	TimeRound    bool
    29  	PageSize     int // result page size for pagination (large iterator) tests
    30  
    31  	OptimizesComparison bool
    32  
    33  	AlwaysRunIntegration bool // always run integration tests
    34  
    35  	SkipDeletedFromIterator  bool
    36  	SkipSizeCheckAfterDelete bool
    37  }
    38  
    39  var graphTests = []struct {
    40  	name string
    41  	test func(t testing.TB, gen testutil.DatabaseFunc, conf *Config)
    42  }{
    43  	{"load one quad", TestLoadOneQuad},
    44  	{"load dup", TestLoadDup},
    45  	{"load dup single", TestLoadDupSingle},
    46  	{"load dup raw", TestLoadDupRaw},
    47  	{"delete quad", TestDeleteQuad},
    48  	{"sizes", TestSizes},
    49  	{"iterator", TestIterator},
    50  	{"hasa", TestHasA},
    51  	{"set iterator", TestSetIterator},
    52  	{"deleted from iterator", TestDeletedFromIterator},
    53  	{"load typed quad", TestLoadTypedQuads},
    54  	{"add and remove", TestAddRemove},
    55  	{"node delete", TestNodeDelete},
    56  	{"iterators and next result order", TestIteratorsAndNextResultOrderA},
    57  	{"compare typed values", TestCompareTypedValues},
    58  	{"schema", TestSchema},
    59  	{"delete reinserted", TestDeleteReinserted},
    60  	{"delete reinserted dup", TestDeleteReinsertedDup},
    61  }
    62  
    63  func TestAll(t *testing.T, gen testutil.DatabaseFunc, conf *Config) {
    64  	if conf == nil {
    65  		conf = &Config{}
    66  	}
    67  	for _, gt := range graphTests {
    68  		t.Run(gt.name, func(t *testing.T) {
    69  			gt.test(t, gen, conf)
    70  		})
    71  	}
    72  	t.Run("writers", func(t *testing.T) {
    73  		TestWriters(t, gen, conf)
    74  	})
    75  	t.Run("1k", func(t *testing.T) {
    76  		t.Run("tx", func(t *testing.T) {
    77  			Test1K(t, gen, conf)
    78  		})
    79  		t.Run("batch", func(t *testing.T) {
    80  			Test1KBatch(t, gen, conf)
    81  		})
    82  	})
    83  	t.Run("paths", func(t *testing.T) {
    84  		pathtest.RunTestMorphisms(t, gen)
    85  	})
    86  	t.Run("integration", func(t *testing.T) {
    87  		TestIntegration(t, gen, conf.AlwaysRunIntegration)
    88  	})
    89  }
    90  
    91  func BenchmarkAll(b *testing.B, gen testutil.DatabaseFunc, conf *Config) {
    92  	b.Run("import", func(b *testing.B) {
    93  		BenchmarkImport(b, gen)
    94  	})
    95  	b.Run("integration", func(b *testing.B) {
    96  		BenchmarkIntegration(b, gen, conf.AlwaysRunIntegration)
    97  	})
    98  }
    99  
   100  // This is a simple test graph.
   101  //
   102  //    +---+                        +---+
   103  //    | A |-------               ->| F |<--
   104  //    +---+       \------>+---+-/  +---+   \--+---+
   105  //                 ------>|#B#|      |        | E |
   106  //    +---+-------/      >+---+      |        +---+
   107  //    | C |             /            v
   108  //    +---+           -/           +---+
   109  //      ----    +---+/             |#G#|
   110  //          \-->|#D#|------------->+---+
   111  //              +---+
   112  //
   113  func MakeQuadSet() []quad.Quad {
   114  	return []quad.Quad{
   115  		quad.Make("A", "follows", "B", nil),
   116  		quad.Make("C", "follows", "B", nil),
   117  		quad.Make("C", "follows", "D", nil),
   118  		quad.Make("D", "follows", "B", nil),
   119  		quad.Make("B", "follows", "F", nil),
   120  		quad.Make("F", "follows", "G", nil),
   121  		quad.Make("D", "follows", "G", nil),
   122  		quad.Make("E", "follows", "F", nil),
   123  		quad.Make("B", "status", "cool", "status_graph"),
   124  		quad.Make("D", "status", "cool", "status_graph"),
   125  		quad.Make("G", "status", "cool", "status_graph"),
   126  	}
   127  }
   128  
   129  func IteratedQuads(t testing.TB, qs graph.QuadStore, it graph.Iterator) []quad.Quad {
   130  	ctx := context.TODO()
   131  	var res quad.ByQuadString
   132  	for it.Next(ctx) {
   133  		res = append(res, qs.Quad(it.Result()))
   134  	}
   135  	require.Nil(t, it.Err())
   136  	sort.Sort(res)
   137  	if res == nil {
   138  		return []quad.Quad(nil) // GopherJS seems to have a bug with this type conversion for a nil value
   139  	}
   140  	return res
   141  }
   142  
   143  func ExpectIteratedQuads(t testing.TB, qs graph.QuadStore, it graph.Iterator, exp []quad.Quad, sortQuads bool) {
   144  	got := IteratedQuads(t, qs, it)
   145  	if sortQuads {
   146  		sort.Sort(quad.ByQuadString(exp))
   147  		sort.Sort(quad.ByQuadString(got))
   148  	}
   149  	if len(exp) == 0 {
   150  		exp = nil // GopherJS seems to have a bug with nil value
   151  	}
   152  	require.Equal(t, exp, got)
   153  }
   154  
   155  func ExpectIteratedRawStrings(t testing.TB, qs graph.QuadStore, it graph.Iterator, exp []string) {
   156  	//sort.Strings(exp)
   157  	got := IteratedStrings(t, qs, it)
   158  	//sort.Strings(got)
   159  	require.Equal(t, exp, got)
   160  }
   161  
   162  func ExpectIteratedValues(t testing.TB, qs graph.QuadStore, it graph.Iterator, exp []quad.Value, sortVals bool) {
   163  	//sort.Strings(exp)
   164  	got := IteratedValues(t, qs, it)
   165  	//sort.Strings(got)
   166  	if sortVals {
   167  		exp = append([]quad.Value{}, exp...)
   168  		sort.Sort(quad.ByValueString(exp))
   169  	}
   170  
   171  	require.Equal(t, len(exp), len(got), "%v\nvs\n%v", exp, got)
   172  	for i := range exp {
   173  		if eq, ok := exp[i].(quad.Equaler); ok {
   174  			require.True(t, eq.Equal(got[i]))
   175  		} else {
   176  			require.True(t, exp[i] == got[i], "%v\nvs\n%v\n\n%v\nvs\n%v", exp[i], got[i], exp, got)
   177  		}
   178  	}
   179  }
   180  
   181  func IteratedStrings(t testing.TB, qs graph.QuadStore, it graph.Iterator) []string {
   182  	ctx := context.TODO()
   183  	var res []string
   184  	for it.Next(ctx) {
   185  		res = append(res, quad.ToString(qs.NameOf(it.Result())))
   186  	}
   187  	require.Nil(t, it.Err())
   188  	sort.Strings(res)
   189  	return res
   190  }
   191  
   192  func IteratedValues(t testing.TB, qs graph.QuadStore, it graph.Iterator) []quad.Value {
   193  	ctx := context.TODO()
   194  	var res []quad.Value
   195  	for it.Next(ctx) {
   196  		res = append(res, qs.NameOf(it.Result()))
   197  	}
   198  	require.Nil(t, it.Err())
   199  	sort.Sort(quad.ByValueString(res))
   200  	return res
   201  }
   202  
   203  func TestLoadOneQuad(t testing.TB, gen testutil.DatabaseFunc, c *Config) {
   204  	qs, opts, closer := gen(t)
   205  	defer closer()
   206  
   207  	w := testutil.MakeWriter(t, qs, opts)
   208  
   209  	q := quad.Make(
   210  		"Something",
   211  		"points_to",
   212  		"Something Else",
   213  		"context",
   214  	)
   215  
   216  	err := w.AddQuad(q)
   217  	require.NoError(t, err)
   218  	for _, pq := range []quad.String{"Something", "points_to", "Something Else", "context"} {
   219  		tok := qs.ValueOf(pq)
   220  		require.NotNil(t, tok, "quad store failed to find value: %q", pq)
   221  		val := qs.NameOf(tok)
   222  		require.NotNil(t, val, "quad store failed to decode value: %q", pq)
   223  		require.Equal(t, pq, val, "quad store failed to roundtrip value: %q", pq)
   224  	}
   225  	exp := graph.Stats{
   226  		Nodes: graph.Size{Size: 4, Exact: true},
   227  		Quads: graph.Size{Size: 1, Exact: true},
   228  	}
   229  	st, err := qs.Stats(context.Background(), true)
   230  	require.NoError(t, err)
   231  	require.Equal(t, exp, st, "Unexpected quadstore size")
   232  
   233  	ExpectIteratedQuads(t, qs, qs.QuadsAllIterator(), []quad.Quad{q}, false)
   234  }
   235  
   236  func testLoadDup(t testing.TB, gen testutil.DatabaseFunc, c *Config, single bool) {
   237  	qs, opts, closer := gen(t)
   238  	defer closer()
   239  
   240  	w := testutil.MakeWriter(t, qs, opts)
   241  
   242  	q := quad.Make(
   243  		"Something",
   244  		"points_to",
   245  		"Something Else",
   246  		"context",
   247  	)
   248  
   249  	if single {
   250  		err := w.AddQuadSet([]quad.Quad{q, q})
   251  		require.NoError(t, err)
   252  	} else {
   253  		err := w.AddQuad(q)
   254  		require.NoError(t, err)
   255  		err = w.AddQuad(q)
   256  		require.NoError(t, err)
   257  	}
   258  
   259  	exp := graph.Stats{
   260  		Nodes: graph.Size{Size: 4, Exact: true},
   261  		Quads: graph.Size{Size: 1, Exact: true},
   262  	}
   263  	st, err := qs.Stats(context.Background(), true)
   264  	require.NoError(t, err)
   265  	require.Equal(t, exp, st, "Unexpected quadstore size")
   266  
   267  	ExpectIteratedQuads(t, qs, qs.QuadsAllIterator(), []quad.Quad{q}, false)
   268  }
   269  
   270  func TestLoadDup(t testing.TB, gen testutil.DatabaseFunc, c *Config) {
   271  	testLoadDup(t, gen, c, false)
   272  }
   273  
   274  func TestLoadDupSingle(t testing.TB, gen testutil.DatabaseFunc, c *Config) {
   275  	testLoadDup(t, gen, c, true)
   276  }
   277  
   278  func TestLoadDupRaw(t testing.TB, gen testutil.DatabaseFunc, c *Config) {
   279  	qs, _, closer := gen(t)
   280  	defer closer()
   281  
   282  	q := quad.Make(
   283  		"Something",
   284  		"points_to",
   285  		"Something Else",
   286  		"context",
   287  	)
   288  
   289  	err := qs.ApplyDeltas([]graph.Delta{
   290  		{Quad: q, Action: graph.Add},
   291  		{Quad: q, Action: graph.Add},
   292  	}, graph.IgnoreOpts{IgnoreDup: true})
   293  	require.NoError(t, err)
   294  
   295  	exp := graph.Stats{
   296  		Nodes: graph.Size{Size: 4, Exact: true},
   297  		Quads: graph.Size{Size: 1, Exact: true},
   298  	}
   299  	st, err := qs.Stats(context.Background(), true)
   300  	require.NoError(t, err)
   301  	require.Equal(t, exp, st, "Unexpected quadstore size")
   302  
   303  	ExpectIteratedQuads(t, qs, qs.QuadsAllIterator(), []quad.Quad{q}, false)
   304  }
   305  
   306  func TestWriters(t *testing.T, gen testutil.DatabaseFunc, c *Config) {
   307  	t.Run("batch", func(t *testing.T) {
   308  		qs, _, closer := gen(t)
   309  		defer closer()
   310  
   311  		w, err := qs.NewQuadWriter()
   312  		require.NoError(t, err)
   313  		defer w.Close()
   314  
   315  		quads := MakeQuadSet()
   316  		q1 := quads[:len(quads)/2]
   317  		q2 := quads[len(q1):]
   318  
   319  		n, err := w.WriteQuads(q1)
   320  		require.NoError(t, err)
   321  		require.Equal(t, len(q1), n)
   322  
   323  		n, err = w.WriteQuads(q2)
   324  		require.NoError(t, err)
   325  		require.Equal(t, len(q2), n)
   326  
   327  		err = w.Close()
   328  		require.NoError(t, err)
   329  
   330  		ExpectIteratedQuads(t, qs, qs.QuadsAllIterator(), quads, true)
   331  	})
   332  	for _, mis := range []bool{false, true} {
   333  		for _, dup := range []bool{false, true} {
   334  			name := []byte("__")
   335  			if dup {
   336  				name[0] = 'd'
   337  			}
   338  			if mis {
   339  				name[1] = 'm'
   340  			}
   341  			t.Run(string(name), func(t *testing.T) {
   342  				qs, _, closer := gen(t)
   343  				defer closer()
   344  
   345  				w, err := writer.NewSingle(qs, graph.IgnoreOpts{
   346  					IgnoreDup: dup, IgnoreMissing: mis,
   347  				})
   348  				require.NoError(t, err)
   349  
   350  				quads := func(arr ...quad.Quad) {
   351  					ExpectIteratedQuads(t, qs, qs.QuadsAllIterator(), arr, false)
   352  				}
   353  
   354  				deltaErr := func(exp, err error) {
   355  					if exp == graph.ErrQuadNotExist && mis {
   356  						require.NoError(t, err)
   357  						return
   358  					} else if exp == graph.ErrQuadExists && dup {
   359  						require.NoError(t, err)
   360  						return
   361  					}
   362  					e, ok := err.(*graph.DeltaError)
   363  					require.True(t, ok, "expected delta error, got: %T (%v)", err, err)
   364  					require.Equal(t, exp, e.Err)
   365  				}
   366  
   367  				// add one quad
   368  				q := quad.Make("a", "b", "c", nil)
   369  				err = w.AddQuad(q)
   370  				require.NoError(t, err)
   371  				quads(q)
   372  
   373  				// try to add the same quad again
   374  				err = w.AddQuad(q)
   375  				deltaErr(graph.ErrQuadExists, err)
   376  				quads(q)
   377  
   378  				// remove quad with non-existent node
   379  				err = w.RemoveQuad(quad.Make("a", "b", "not-existent", nil))
   380  				deltaErr(graph.ErrQuadNotExist, err)
   381  
   382  				// remove non-existent quads
   383  				err = w.RemoveQuad(quad.Make("a", "c", "b", nil))
   384  				deltaErr(graph.ErrQuadNotExist, err)
   385  				err = w.RemoveQuad(quad.Make("c", "b", "a", nil))
   386  				deltaErr(graph.ErrQuadNotExist, err)
   387  
   388  				// make sure store is still in correct state
   389  				quads(q)
   390  
   391  				// remove existing quad
   392  				err = w.RemoveQuad(q)
   393  				require.NoError(t, err)
   394  				quads()
   395  
   396  				// add the same quad again
   397  				err = w.AddQuad(q)
   398  				require.NoError(t, err)
   399  				quads(q)
   400  			})
   401  		}
   402  	}
   403  }
   404  
   405  func Test1K(t *testing.T, gen testutil.DatabaseFunc, c *Config) {
   406  	qs, _, closer := gen(t)
   407  	defer closer()
   408  
   409  	pg := c.PageSize
   410  	if pg == 0 {
   411  		pg = 100
   412  	}
   413  	n := pg*3 + 1
   414  
   415  	w, err := writer.NewSingle(qs, graph.IgnoreOpts{})
   416  	require.NoError(t, err)
   417  
   418  	qw := graph.NewWriter(w)
   419  	exp := make([]quad.Quad, 0, n)
   420  	for i := 0; i < n; i++ {
   421  		q := quad.Make(i, i, i, nil)
   422  		exp = append(exp, q)
   423  		qw.WriteQuad(q)
   424  	}
   425  	err = qw.Flush()
   426  	require.NoError(t, err)
   427  
   428  	ExpectIteratedQuads(t, qs, qs.QuadsAllIterator(), exp, true)
   429  }
   430  
   431  func Test1KBatch(t *testing.T, gen testutil.DatabaseFunc, c *Config) {
   432  	qs, _, closer := gen(t)
   433  	defer closer()
   434  
   435  	pg := c.PageSize
   436  	if pg == 0 {
   437  		pg = 100
   438  	}
   439  	n := pg*3 + 1
   440  
   441  	exp := make([]quad.Quad, 0, n)
   442  	for i := 0; i < n; i++ {
   443  		q := quad.Make(i, i, i, nil)
   444  		exp = append(exp, q)
   445  	}
   446  
   447  	qw, err := qs.NewQuadWriter()
   448  	require.NoError(t, err)
   449  	defer qw.Close()
   450  
   451  	n, err = qw.WriteQuads(exp)
   452  	require.NoError(t, err)
   453  	require.Equal(t, len(exp), n)
   454  
   455  	err = qw.Close()
   456  	require.NoError(t, err)
   457  
   458  	ExpectIteratedQuads(t, qs, qs.QuadsAllIterator(), exp, true)
   459  }
   460  
   461  type ValueSizer interface {
   462  	SizeOf(graph.Ref) int64
   463  }
   464  
   465  func TestSizes(t testing.TB, gen testutil.DatabaseFunc, conf *Config) {
   466  	qs, opts, closer := gen(t)
   467  	defer closer()
   468  
   469  	w := testutil.MakeWriter(t, qs, opts)
   470  
   471  	err := w.AddQuadSet(MakeQuadSet())
   472  	require.NoError(t, err)
   473  
   474  	exp := graph.Stats{
   475  		Nodes: graph.Size{Size: 11, Exact: true},
   476  		Quads: graph.Size{Size: 11, Exact: true},
   477  	}
   478  	st, err := qs.Stats(context.Background(), true)
   479  	require.NoError(t, err)
   480  	require.Equal(t, exp, st, "Unexpected quadstore size")
   481  
   482  	if qss, ok := qs.(ValueSizer); ok {
   483  		s := qss.SizeOf(qs.ValueOf(quad.String("B")))
   484  		require.Equal(t, int64(5), s, "Unexpected quadstore value size")
   485  	}
   486  
   487  	err = w.RemoveQuad(quad.Make(
   488  		"A",
   489  		"follows",
   490  		"B",
   491  		nil,
   492  	))
   493  	require.NoError(t, err)
   494  	err = w.RemoveQuad(quad.Make(
   495  		"A",
   496  		"follows",
   497  		"B",
   498  		nil,
   499  	))
   500  	require.True(t, graph.IsQuadNotExist(err))
   501  	if !conf.SkipSizeCheckAfterDelete {
   502  		exp = graph.Stats{
   503  			Nodes: graph.Size{Size: 10, Exact: true},
   504  			Quads: graph.Size{Size: 10, Exact: true},
   505  		}
   506  		st, err := qs.Stats(context.Background(), true)
   507  		require.NoError(t, err)
   508  		require.Equal(t, exp, st, "Unexpected quadstore size after RemoveQuad")
   509  	} else {
   510  		exp = graph.Stats{
   511  			Nodes: graph.Size{Size: 10, Exact: true},
   512  			Quads: graph.Size{Size: 11, Exact: true},
   513  		}
   514  		st, err := qs.Stats(context.Background(), true)
   515  		require.NoError(t, err)
   516  		require.Equal(t, exp, st, "Unexpected quadstore size")
   517  	}
   518  
   519  	if qss, ok := qs.(ValueSizer); ok {
   520  		s := qss.SizeOf(qs.ValueOf(quad.String("B")))
   521  		require.Equal(t, int64(4), s, "Unexpected quadstore value size")
   522  	}
   523  }
   524  
   525  func TestIterator(t testing.TB, gen testutil.DatabaseFunc, _ *Config) {
   526  	ctx := context.TODO()
   527  	qs, opts, closer := gen(t)
   528  	defer closer()
   529  
   530  	testutil.MakeWriter(t, qs, opts, MakeQuadSet()...)
   531  
   532  	var it graph.Iterator
   533  
   534  	it = qs.NodesAllIterator()
   535  	require.NotNil(t, it)
   536  
   537  	size, _ := it.Size()
   538  	require.True(t, size > 0 && size < 23, "Unexpected size: %v", size)
   539  	// TODO: leveldb had this test
   540  	//if exact {
   541  	//	t.Errorf("Got unexpected exact result.")
   542  	//}
   543  
   544  	optIt, changed := it.Optimize()
   545  	require.True(t, !changed && optIt == it, "Optimize unexpectedly changed iterator: %v, %T(%p) vs %T(%p)", changed, optIt, optIt, it, it)
   546  
   547  	expect := []string{
   548  		"A",
   549  		"B",
   550  		"C",
   551  		"D",
   552  		"E",
   553  		"F",
   554  		"G",
   555  		"follows",
   556  		"status",
   557  		"cool",
   558  		"status_graph",
   559  	}
   560  	sort.Strings(expect)
   561  	for i := 0; i < 2; i++ {
   562  		got := IteratedStrings(t, qs, it)
   563  		sort.Strings(got)
   564  		require.Equal(t, expect, got, "Unexpected iterated result on repeat %d", i)
   565  		it.Reset()
   566  	}
   567  
   568  	for _, pq := range expect {
   569  		ok := it.Contains(ctx, qs.ValueOf(quad.Raw(pq)))
   570  		require.NoError(t, it.Err())
   571  		require.True(t, ok, "Failed to find and check %q correctly", pq)
   572  
   573  	}
   574  	// FIXME(kortschak) Why does this fail?
   575  	/*
   576  		for _, pq := range []string{"baller"} {
   577  			if it.Contains(qs.ValueOf(pq)) {
   578  				t.Errorf("Failed to check %q correctly", pq)
   579  			}
   580  		}
   581  	*/
   582  	it.Reset()
   583  
   584  	it = qs.QuadsAllIterator()
   585  	optIt, changed = it.Optimize()
   586  	require.True(t, !changed && optIt == it, "Optimize unexpectedly changed iterator: %v, %T", changed, optIt)
   587  
   588  	require.True(t, it.Next(ctx))
   589  
   590  	q := qs.Quad(it.Result())
   591  	require.Nil(t, it.Err())
   592  	require.True(t, q.IsValid(), "Invalid quad returned: %q", q)
   593  	set := MakeQuadSet()
   594  	var ok bool
   595  	for _, e := range set {
   596  		if e.String() == q.String() {
   597  			ok = true
   598  			break
   599  		}
   600  	}
   601  	require.True(t, ok, "Failed to find %q during iteration, got:%q", q, set)
   602  }
   603  
   604  func TestHasA(t testing.TB, gen testutil.DatabaseFunc, conf *Config) {
   605  	qs, opts, closer := gen(t)
   606  	defer closer()
   607  
   608  	testutil.MakeWriter(t, qs, opts, MakeQuadSet()...)
   609  
   610  	var it graph.Iterator = iterator.NewHasA(qs,
   611  		iterator.NewLinksTo(qs, qs.NodesAllIterator(), quad.Predicate),
   612  		quad.Predicate)
   613  	defer it.Close()
   614  
   615  	it, _ = it.Optimize()
   616  
   617  	var exp []quad.Value
   618  	for i := 0; i < 8; i++ {
   619  		exp = append(exp, quad.Raw("follows"))
   620  	}
   621  	for i := 0; i < 3; i++ {
   622  		exp = append(exp, quad.Raw("status"))
   623  	}
   624  	ExpectIteratedValues(t, qs, it, exp, false)
   625  }
   626  
   627  func TestSetIterator(t testing.TB, gen testutil.DatabaseFunc, _ *Config) {
   628  	qs, opts, closer := gen(t)
   629  	defer closer()
   630  
   631  	testutil.MakeWriter(t, qs, opts, MakeQuadSet()...)
   632  
   633  	expectIteratedQuads := func(it graph.Iterator, exp []quad.Quad) {
   634  		ExpectIteratedQuads(t, qs, it, exp, false)
   635  	}
   636  
   637  	// Subject iterator.
   638  	it := qs.QuadIterator(quad.Subject, qs.ValueOf(quad.String("C")))
   639  
   640  	expectIteratedQuads(it, []quad.Quad{
   641  		quad.Make("C", "follows", "B", nil),
   642  		quad.Make("C", "follows", "D", nil),
   643  	})
   644  	it.Reset()
   645  
   646  	and := iterator.NewAnd(
   647  		qs.QuadsAllIterator(),
   648  		it,
   649  	)
   650  
   651  	expectIteratedQuads(and, []quad.Quad{
   652  		quad.Make("C", "follows", "B", nil),
   653  		quad.Make("C", "follows", "D", nil),
   654  	})
   655  
   656  	// Object iterator.
   657  	it = qs.QuadIterator(quad.Object, qs.ValueOf(quad.String("F")))
   658  
   659  	expectIteratedQuads(it, []quad.Quad{
   660  		quad.Make("B", "follows", "F", nil),
   661  		quad.Make("E", "follows", "F", nil),
   662  	})
   663  
   664  	and = iterator.NewAnd(
   665  		qs.QuadIterator(quad.Subject, qs.ValueOf(quad.String("B"))),
   666  		it,
   667  	)
   668  
   669  	expectIteratedQuads(and, []quad.Quad{
   670  		quad.Make("B", "follows", "F", nil),
   671  	})
   672  
   673  	// Predicate iterator.
   674  	it = qs.QuadIterator(quad.Predicate, qs.ValueOf(quad.String("status")))
   675  
   676  	expectIteratedQuads(it, []quad.Quad{
   677  		quad.Make("B", "status", "cool", "status_graph"),
   678  		quad.Make("D", "status", "cool", "status_graph"),
   679  		quad.Make("G", "status", "cool", "status_graph"),
   680  	})
   681  
   682  	// Label iterator.
   683  	it = qs.QuadIterator(quad.Label, qs.ValueOf(quad.String("status_graph")))
   684  
   685  	expectIteratedQuads(it, []quad.Quad{
   686  		quad.Make("B", "status", "cool", "status_graph"),
   687  		quad.Make("D", "status", "cool", "status_graph"),
   688  		quad.Make("G", "status", "cool", "status_graph"),
   689  	})
   690  	it.Reset()
   691  
   692  	// Order is important
   693  	and = iterator.NewAnd(
   694  		qs.QuadIterator(quad.Subject, qs.ValueOf(quad.String("B"))),
   695  		it,
   696  	)
   697  
   698  	expectIteratedQuads(and, []quad.Quad{
   699  		quad.Make("B", "status", "cool", "status_graph"),
   700  	})
   701  	it.Reset()
   702  
   703  	// Order is important
   704  	and = iterator.NewAnd(
   705  		it,
   706  		qs.QuadIterator(quad.Subject, qs.ValueOf(quad.String("B"))),
   707  	)
   708  
   709  	expectIteratedQuads(and, []quad.Quad{
   710  		quad.Make("B", "status", "cool", "status_graph"),
   711  	})
   712  }
   713  
   714  func TestDeleteQuad(t testing.TB, gen testutil.DatabaseFunc, _ *Config) {
   715  	qs, opts, closer := gen(t)
   716  	defer closer()
   717  
   718  	w := testutil.MakeWriter(t, qs, opts, MakeQuadSet()...)
   719  
   720  	vn := qs.ValueOf(quad.Raw("E"))
   721  	require.NotNil(t, vn)
   722  
   723  	it := qs.QuadIterator(quad.Subject, vn)
   724  	ExpectIteratedQuads(t, qs, it, []quad.Quad{
   725  		quad.Make("E", "follows", "F", nil),
   726  	}, false)
   727  	it.Close()
   728  
   729  	err := w.RemoveQuad(quad.Make("E", "follows", "F", nil))
   730  	require.NoError(t, err)
   731  
   732  	it = qs.QuadIterator(quad.Subject, qs.ValueOf(quad.Raw("E")))
   733  	ExpectIteratedQuads(t, qs, it, nil, false)
   734  	it.Close()
   735  
   736  	it = qs.QuadsAllIterator()
   737  	ExpectIteratedQuads(t, qs, it, []quad.Quad{
   738  		quad.Make("A", "follows", "B", nil),
   739  		quad.Make("C", "follows", "B", nil),
   740  		quad.Make("C", "follows", "D", nil),
   741  		quad.Make("D", "follows", "B", nil),
   742  		quad.Make("B", "follows", "F", nil),
   743  		quad.Make("F", "follows", "G", nil),
   744  		quad.Make("D", "follows", "G", nil),
   745  		quad.Make("B", "status", "cool", "status_graph"),
   746  		quad.Make("D", "status", "cool", "status_graph"),
   747  		quad.Make("G", "status", "cool", "status_graph"),
   748  	}, true)
   749  	it.Close()
   750  }
   751  
   752  func TestDeletedFromIterator(t testing.TB, gen testutil.DatabaseFunc, conf *Config) {
   753  	if conf.SkipDeletedFromIterator {
   754  		t.SkipNow()
   755  	}
   756  	qs, opts, closer := gen(t)
   757  	defer closer()
   758  
   759  	w := testutil.MakeWriter(t, qs, opts, MakeQuadSet()...)
   760  
   761  	// Subject iterator.
   762  	it := qs.QuadIterator(quad.Subject, qs.ValueOf(quad.Raw("E")))
   763  
   764  	ExpectIteratedQuads(t, qs, it, []quad.Quad{
   765  		quad.Make("E", "follows", "F", nil),
   766  	}, false)
   767  
   768  	it.Reset()
   769  
   770  	w.RemoveQuad(quad.Make("E", "follows", "F", nil))
   771  
   772  	ExpectIteratedQuads(t, qs, it, nil, false)
   773  }
   774  
   775  func TestLoadTypedQuads(t testing.TB, gen testutil.DatabaseFunc, conf *Config) {
   776  	qs, opts, closer := gen(t)
   777  	defer closer()
   778  
   779  	w := testutil.MakeWriter(t, qs, opts)
   780  
   781  	values := []quad.Value{
   782  		quad.BNode("A"), quad.IRI("name"), quad.String("B"), quad.IRI("graph"),
   783  		quad.IRI("B"), quad.Raw("<type>"),
   784  		quad.TypedString{Value: "10", Type: "int"},
   785  		quad.LangString{Value: "value", Lang: "en"},
   786  		quad.Int(-123456789),
   787  		quad.Float(-12345e-6),
   788  		quad.Bool(true),
   789  		quad.Time(time.Now()),
   790  	}
   791  
   792  	err := w.AddQuadSet([]quad.Quad{
   793  		{values[0], values[1], values[2], values[3]},
   794  		{values[4], values[5], values[6], nil},
   795  		{values[4], values[5], values[7], nil},
   796  		{values[0], values[1], values[8], nil},
   797  		{values[0], values[1], values[9], nil},
   798  		{values[0], values[1], values[10], nil},
   799  		{values[0], values[1], values[11], nil},
   800  	})
   801  	require.NoError(t, err)
   802  	for _, pq := range values {
   803  		got := qs.NameOf(qs.ValueOf(pq))
   804  		if !conf.UnTyped {
   805  			if pt, ok := pq.(quad.Time); ok {
   806  				var trim int64
   807  				if conf.TimeInMcs {
   808  					trim = 1000
   809  				} else if conf.TimeInMs {
   810  					trim = 1000000
   811  				}
   812  				if trim > 0 {
   813  					tm := time.Time(pt)
   814  					seconds := tm.Unix()
   815  					nanos := int64(tm.Sub(time.Unix(seconds, 0)))
   816  					if conf.TimeRound {
   817  						nanos = (nanos/trim + ((nanos/(trim/10))%10)/5) * trim
   818  					} else {
   819  						nanos = (nanos / trim) * trim
   820  					}
   821  					pq = quad.Time(time.Unix(seconds, nanos).UTC())
   822  				}
   823  			}
   824  			if eq, ok := pq.(quad.Equaler); ok {
   825  				assert.True(t, eq.Equal(got), "Failed to roundtrip %q (%T), got %q (%T)", pq, pq, got, got)
   826  			} else {
   827  				assert.Equal(t, pq, got, "Failed to roundtrip %q (%T)", pq, pq)
   828  			}
   829  			// check if we can get received value again (hash roundtrip)
   830  			got2 := qs.NameOf(qs.ValueOf(got))
   831  			assert.Equal(t, got, got2, "Failed to use returned value to get it again")
   832  		} else {
   833  			assert.Equal(t, quad.StringOf(pq), quad.StringOf(got), "Failed to roundtrip raw %q (%T)", pq, pq)
   834  		}
   835  	}
   836  	exp := graph.Stats{
   837  		Nodes: graph.Size{Size: 12, Exact: true},
   838  		Quads: graph.Size{Size: 7, Exact: true},
   839  	}
   840  	st, err := qs.Stats(context.Background(), true)
   841  	require.NoError(t, err)
   842  	require.Equal(t, exp, st, "Unexpected quadstore size")
   843  }
   844  
   845  // TODO(dennwc): add tests to verify that QS behaves in a right way with IgnoreOptions,
   846  // returns ErrQuadExists, ErrQuadNotExists is doing rollback.
   847  func TestAddRemove(t testing.TB, gen testutil.DatabaseFunc, conf *Config) {
   848  	qs, opts, closer := gen(t)
   849  	defer closer()
   850  
   851  	if opts == nil {
   852  		opts = make(graph.Options)
   853  	}
   854  	opts["ignore_duplicate"] = true
   855  
   856  	w := testutil.MakeWriter(t, qs, opts, MakeQuadSet()...)
   857  
   858  	exp := graph.Stats{
   859  		Nodes: graph.Size{Size: 11, Exact: true},
   860  		Quads: graph.Size{Size: 11, Exact: true},
   861  	}
   862  	st, err := qs.Stats(context.Background(), true)
   863  	require.NoError(t, err)
   864  	require.Equal(t, exp, st, "Unexpected quadstore size")
   865  
   866  	all := qs.NodesAllIterator()
   867  	expect := []string{
   868  		"A",
   869  		"B",
   870  		"C",
   871  		"D",
   872  		"E",
   873  		"F",
   874  		"G",
   875  		"cool",
   876  		"follows",
   877  		"status",
   878  		"status_graph",
   879  	}
   880  	ExpectIteratedRawStrings(t, qs, all, expect)
   881  
   882  	// Add more quads, some conflicts
   883  	err = w.AddQuadSet([]quad.Quad{
   884  		quad.Make("A", "follows", "B", nil), // duplicate
   885  		quad.Make("F", "follows", "B", nil),
   886  		quad.Make("C", "follows", "D", nil), // duplicate
   887  		quad.Make("X", "follows", "B", nil),
   888  	})
   889  	assert.Nil(t, err, "AddQuadSet failed")
   890  
   891  	exp = graph.Stats{
   892  		Nodes: graph.Size{Size: 12, Exact: true},
   893  		Quads: graph.Size{Size: 13, Exact: true},
   894  	}
   895  	st, err = qs.Stats(context.Background(), true)
   896  	require.NoError(t, err)
   897  	require.Equal(t, exp, st, "Unexpected quadstore size")
   898  
   899  	all = qs.NodesAllIterator()
   900  	expect = []string{
   901  		"A",
   902  		"B",
   903  		"C",
   904  		"D",
   905  		"E",
   906  		"F",
   907  		"G",
   908  		"X",
   909  		"cool",
   910  		"follows",
   911  		"status",
   912  		"status_graph",
   913  	}
   914  	ExpectIteratedRawStrings(t, qs, all, expect)
   915  
   916  	// Remove quad
   917  	toRemove := quad.Make("X", "follows", "B", nil)
   918  	err = w.RemoveQuad(toRemove)
   919  	require.Nil(t, err, "RemoveQuad failed")
   920  	err = w.RemoveQuad(toRemove)
   921  	require.True(t, graph.IsQuadNotExist(err), "expected not exists error, got: %v", err)
   922  
   923  	expect = []string{
   924  		"A",
   925  		"B",
   926  		"C",
   927  		"D",
   928  		"E",
   929  		"F",
   930  		"G",
   931  		"cool",
   932  		"follows",
   933  		"status",
   934  		"status_graph",
   935  	}
   936  	ExpectIteratedRawStrings(t, qs, all, nil)
   937  	all = qs.NodesAllIterator()
   938  	ExpectIteratedRawStrings(t, qs, all, expect)
   939  }
   940  
   941  func TestIteratorsAndNextResultOrderA(t testing.TB, gen testutil.DatabaseFunc, conf *Config) {
   942  	ctx := context.TODO()
   943  	qs, opts, closer := gen(t)
   944  	defer closer()
   945  
   946  	testutil.MakeWriter(t, qs, opts, MakeQuadSet()...)
   947  
   948  	exp := graph.Stats{
   949  		Nodes: graph.Size{Size: 11, Exact: true},
   950  		Quads: graph.Size{Size: 11, Exact: true},
   951  	}
   952  	st, err := qs.Stats(context.Background(), true)
   953  	require.NoError(t, err)
   954  	require.Equal(t, exp, st, "Unexpected quadstore size")
   955  
   956  	fixed := iterator.NewFixed(qs.ValueOf(quad.Raw("C")))
   957  
   958  	fixed2 := iterator.NewFixed(qs.ValueOf(quad.Raw("follows")))
   959  
   960  	all := qs.NodesAllIterator()
   961  
   962  	const allTag = "all"
   963  	innerAnd := iterator.NewAnd(
   964  		iterator.NewLinksTo(qs, fixed2, quad.Predicate),
   965  		iterator.NewLinksTo(qs, iterator.Tag(all, allTag), quad.Object),
   966  	)
   967  
   968  	hasa := iterator.NewHasA(qs, innerAnd, quad.Subject)
   969  	outerAnd := iterator.NewAnd(fixed, hasa)
   970  
   971  	require.True(t, outerAnd.Next(ctx), "Expected one matching subtree")
   972  
   973  	val := outerAnd.Result()
   974  	require.Equal(t, quad.Raw("C"), qs.NameOf(val))
   975  
   976  	var (
   977  		got    []string
   978  		expect = []string{"B", "D"}
   979  	)
   980  	for {
   981  		m := make(map[string]graph.Ref, 1)
   982  		outerAnd.TagResults(m)
   983  		got = append(got, quad.ToString(qs.NameOf(m[allTag])))
   984  		if !outerAnd.NextPath(ctx) {
   985  			break
   986  		}
   987  	}
   988  	sort.Strings(got)
   989  
   990  	require.Equal(t, expect, got)
   991  
   992  	require.True(t, !outerAnd.Next(ctx), "More than one possible top level output?")
   993  }
   994  
   995  const lt, lte, gt, gte = iterator.CompareLT, iterator.CompareLTE, iterator.CompareGT, iterator.CompareGTE
   996  
   997  var tzero = time.Unix(time.Now().Unix(), 0)
   998  
   999  var casesCompare = []struct {
  1000  	op     iterator.Operator
  1001  	val    quad.Value
  1002  	expect []quad.Value
  1003  }{
  1004  	{lt, quad.BNode("b"), []quad.Value{
  1005  		quad.BNode("alice"),
  1006  	}},
  1007  	{lte, quad.BNode("bob"), []quad.Value{
  1008  		quad.BNode("alice"), quad.BNode("bob"),
  1009  	}},
  1010  	{lt, quad.String("b"), []quad.Value{
  1011  		quad.String("alice"),
  1012  	}},
  1013  	{lte, quad.String("bob"), []quad.Value{
  1014  		quad.String("alice"), quad.String("bob"),
  1015  	}},
  1016  	{gte, quad.String("b"), []quad.Value{
  1017  		quad.String("bob"), quad.String("charlie"), quad.String("dani"),
  1018  	}},
  1019  	{lt, quad.IRI("b"), []quad.Value{
  1020  		quad.IRI("alice"),
  1021  	}},
  1022  	{lte, quad.IRI("bob"), []quad.Value{
  1023  		quad.IRI("alice"), quad.IRI("bob"),
  1024  	}},
  1025  	{lte, quad.IRI("bob"), []quad.Value{
  1026  		quad.IRI("alice"), quad.IRI("bob"),
  1027  	}},
  1028  	{gte, quad.Int(111), []quad.Value{
  1029  		quad.Int(112), quad.Int(math.MaxInt64 - 1), quad.Int(math.MaxInt64),
  1030  	}},
  1031  	{gte, quad.Int(110), []quad.Value{
  1032  		quad.Int(110), quad.Int(112), quad.Int(math.MaxInt64 - 1), quad.Int(math.MaxInt64),
  1033  	}},
  1034  	{lt, quad.Int(20), []quad.Value{
  1035  		quad.Int(math.MinInt64 + 1), quad.Int(math.MinInt64),
  1036  	}},
  1037  	{lte, quad.Int(20), []quad.Value{
  1038  		quad.Int(math.MinInt64 + 1), quad.Int(math.MinInt64), quad.Int(20),
  1039  	}},
  1040  	{lte, quad.Time(tzero.Add(time.Hour)), []quad.Value{
  1041  		quad.Time(tzero), quad.Time(tzero.Add(time.Hour)),
  1042  	}},
  1043  	{gt, quad.Time(tzero.Add(time.Hour)), []quad.Value{
  1044  		quad.Time(tzero.Add(time.Hour * 49)), quad.Time(tzero.Add(time.Hour * 24 * 365)),
  1045  	}},
  1046  	// precision tests
  1047  	{gt, quad.Int(math.MaxInt64 - 1), []quad.Value{
  1048  		quad.Int(math.MaxInt64),
  1049  	}},
  1050  	{gte, quad.Int(math.MaxInt64 - 1), []quad.Value{
  1051  		quad.Int(math.MaxInt64 - 1), quad.Int(math.MaxInt64),
  1052  	}},
  1053  	{lt, quad.Int(math.MinInt64 + 1), []quad.Value{
  1054  		quad.Int(math.MinInt64),
  1055  	}},
  1056  	{lte, quad.Int(math.MinInt64 + 1), []quad.Value{
  1057  		quad.Int(math.MinInt64 + 1), quad.Int(math.MinInt64),
  1058  	}},
  1059  }
  1060  
  1061  func TestCompareTypedValues(t testing.TB, gen testutil.DatabaseFunc, conf *Config) {
  1062  	if conf.UnTyped {
  1063  		t.SkipNow()
  1064  	}
  1065  	qs, opts, closer := gen(t)
  1066  	defer closer()
  1067  
  1068  	w := testutil.MakeWriter(t, qs, opts)
  1069  
  1070  	t1 := tzero
  1071  	t2 := t1.Add(time.Hour)
  1072  	t3 := t2.Add(time.Hour * 48)
  1073  	t4 := t1.Add(time.Hour * 24 * 365)
  1074  
  1075  	quads := []quad.Quad{
  1076  		{quad.BNode("alice"), quad.BNode("bob"), quad.BNode("charlie"), quad.BNode("dani")},
  1077  		{quad.IRI("alice"), quad.IRI("bob"), quad.IRI("charlie"), quad.IRI("dani")},
  1078  		{quad.String("alice"), quad.String("bob"), quad.String("charlie"), quad.String("dani")},
  1079  		{quad.Int(100), quad.Int(112), quad.Int(110), quad.Int(20)},
  1080  		{quad.Time(t1), quad.Time(t2), quad.Time(t3), quad.Time(t4)},
  1081  		// test precision as well
  1082  		{quad.Int(math.MaxInt64), quad.Int(math.MaxInt64 - 1), quad.Int(math.MinInt64 + 1), quad.Int(math.MinInt64)},
  1083  	}
  1084  
  1085  	err := w.AddQuadSet(quads)
  1086  	require.NoError(t, err)
  1087  
  1088  	var vals []quad.Value
  1089  	for _, q := range quads {
  1090  		for _, d := range quad.Directions {
  1091  			if v := q.Get(d); v != nil {
  1092  				vals = append(vals, v)
  1093  			}
  1094  		}
  1095  	}
  1096  	ExpectIteratedValues(t, qs, qs.NodesAllIterator(), vals, true)
  1097  
  1098  	for _, c := range casesCompare {
  1099  		//t.Log(c.op, c.val)
  1100  		it := iterator.NewComparison(qs.NodesAllIterator(), c.op, c.val, qs)
  1101  		ExpectIteratedValues(t, qs, it, c.expect, true)
  1102  	}
  1103  
  1104  	for _, c := range casesCompare {
  1105  		s := shape.Compare(shape.AllNodes{}, c.op, c.val)
  1106  		ns, ok := shape.Optimize(s, qs)
  1107  		require.Equal(t, conf.OptimizesComparison, ok)
  1108  		if conf.OptimizesComparison {
  1109  			require.NotEqual(t, s, ns)
  1110  		} else {
  1111  			require.Equal(t, s, ns)
  1112  		}
  1113  		nit := shape.BuildIterator(qs, ns)
  1114  		ExpectIteratedValues(t, qs, nit, c.expect, true)
  1115  	}
  1116  }
  1117  
  1118  func TestNodeDelete(t testing.TB, gen testutil.DatabaseFunc, conf *Config) {
  1119  	qs, opts, closer := gen(t)
  1120  	defer closer()
  1121  
  1122  	w := testutil.MakeWriter(t, qs, opts, MakeQuadSet()...)
  1123  
  1124  	del := quad.Raw("D")
  1125  
  1126  	err := w.RemoveNode(del)
  1127  	require.NoError(t, err)
  1128  
  1129  	exp := MakeQuadSet()
  1130  	for i := 0; i < len(exp); i++ {
  1131  		for _, d := range quad.Directions {
  1132  			if exp[i].Get(d) == del {
  1133  				exp = append(exp[:i], exp[i+1:]...)
  1134  				i--
  1135  				break
  1136  			}
  1137  		}
  1138  	}
  1139  	ExpectIteratedQuads(t, qs, qs.QuadsAllIterator(), exp, true)
  1140  
  1141  	ExpectIteratedValues(t, qs, qs.NodesAllIterator(), []quad.Value{
  1142  		quad.Raw("A"),
  1143  		quad.Raw("B"),
  1144  		quad.Raw("C"),
  1145  		quad.Raw("E"),
  1146  		quad.Raw("F"),
  1147  		quad.Raw("G"),
  1148  		quad.Raw("cool"),
  1149  		quad.Raw("follows"),
  1150  		quad.Raw("status"),
  1151  		quad.Raw("status_graph"),
  1152  	}, true)
  1153  }
  1154  
  1155  func TestSchema(t testing.TB, gen testutil.DatabaseFunc, conf *Config) {
  1156  	qs, opts, closer := gen(t)
  1157  	defer closer()
  1158  
  1159  	w := testutil.MakeWriter(t, qs, opts, MakeQuadSet()...)
  1160  
  1161  	type Person struct {
  1162  		_         struct{}   `quad:"@type > ex:Person"`
  1163  		ID        quad.IRI   `quad:"@id" json:"id"`
  1164  		Name      string     `quad:"ex:name" json:"name"`
  1165  		Something []quad.IRI `quad:"isParentOf < *,optional" json:"something"`
  1166  	}
  1167  	p := Person{
  1168  		ID:   quad.IRI("ex:bob"),
  1169  		Name: "Bob",
  1170  	}
  1171  
  1172  	sch := schema.NewConfig()
  1173  
  1174  	qw := graph.NewWriter(w)
  1175  	id, err := sch.WriteAsQuads(qw, p)
  1176  	require.NoError(t, err)
  1177  	err = qw.Close()
  1178  	require.NoError(t, err)
  1179  	require.Equal(t, p.ID, id)
  1180  
  1181  	var p2 Person
  1182  	err = sch.LoadTo(nil, qs, &p2, id)
  1183  	require.NoError(t, err)
  1184  	require.Equal(t, p, p2)
  1185  }
  1186  
  1187  func TestDeleteReinserted(t testing.TB, gen testutil.DatabaseFunc, _ *Config) {
  1188  	qs, opts, closer := gen(t)
  1189  	defer closer()
  1190  
  1191  	w := testutil.MakeWriter(t, qs, opts, MakeQuadSet()...)
  1192  
  1193  	err := w.AddQuadSet([]quad.Quad{
  1194  		quad.Make("<bob>", "<status>", "Feeling happy", nil),
  1195  		quad.Make("<sally>", "<follows>", "<jim>", nil),
  1196  	})
  1197  	require.NoError(t, err, "Add quadset failed")
  1198  
  1199  	ctx := context.TODO()
  1200  
  1201  	q := quad.Make("<bob>", "<follows>", "<sally>", nil)
  1202  	for i := 0; i < 2; i++ {
  1203  		err = w.AddQuad(q)
  1204  		require.NoError(t, err, "Add quad failed")
  1205  		err = w.RemoveQuad(q)
  1206  		require.NoError(t, err, "Remove quad failed")
  1207  		refs, err := graph.RefsOf(ctx, qs, []quad.Value{
  1208  			q.Subject, q.Predicate, q.Object,
  1209  		})
  1210  		require.NoError(t, err, "Get values failed")
  1211  		require.Len(t, refs, 3)
  1212  		for _, r := range refs {
  1213  			require.NotNil(t, r)
  1214  		}
  1215  	}
  1216  }
  1217  
  1218  func TestDeleteReinsertedDup(t testing.TB, gen testutil.DatabaseFunc, _ *Config) {
  1219  	qs, opts, closer := gen(t)
  1220  	defer closer()
  1221  
  1222  	w := testutil.MakeWriter(t, qs, opts, MakeQuadSet()...)
  1223  
  1224  	err := w.AddQuadSet([]quad.Quad{
  1225  		quad.Make("<bob>", "<status>", "Feeling happy", nil),
  1226  		quad.Make("<sally>", "<follows>", "<jim>", nil),
  1227  	})
  1228  	require.NoError(t, err, "Add quadset failed")
  1229  
  1230  	ctx := context.TODO()
  1231  
  1232  	q := quad.Make("<bob>", "<follows>", "<x>", nil)
  1233  	for i := 0; i < 2; i++ {
  1234  		err = w.AddQuad(q)
  1235  		require.NoError(t, err, "Add quad failed")
  1236  		// must be ignored
  1237  		err = w.AddQuad(q)
  1238  		require.NoError(t, err, "Add quad failed")
  1239  		err = w.RemoveQuad(q)
  1240  		require.NoError(t, err, "Remove quad failed")
  1241  
  1242  		refs, err := graph.RefsOf(ctx, qs, []quad.Value{
  1243  			q.Subject, q.Predicate,
  1244  		})
  1245  		require.NoError(t, err, "Get values failed")
  1246  		require.Len(t, refs, 2)
  1247  		for _, r := range refs {
  1248  			require.NotNil(t, r)
  1249  		}
  1250  
  1251  		// the node should be garbage-collected
  1252  		refs, err = graph.RefsOf(ctx, qs, []quad.Value{
  1253  			q.Object,
  1254  		})
  1255  		if err == nil {
  1256  			// FIXME(dennwc): the graphlog.SplitDeltas adds an increment even though the quad is duplicated and ignored
  1257  			t.Skip("value must be garbage-collected")
  1258  		}
  1259  	}
  1260  }
  1261  
  1262  func irif(format string, args ...interface{}) quad.IRI {
  1263  	return quad.IRI(fmt.Sprintf(format, args...))
  1264  }
  1265  
  1266  func BenchmarkImport(b *testing.B, gen testutil.DatabaseFunc) {
  1267  	b.StopTimer()
  1268  
  1269  	qs, _, closer := gen(b)
  1270  	defer closer()
  1271  
  1272  	w, err := qs.NewQuadWriter()
  1273  	require.NoError(b, err)
  1274  	defer w.Close()
  1275  
  1276  	const (
  1277  		mult     = 10
  1278  		perBatch = 100
  1279  	)
  1280  
  1281  	quads := make([]quad.Quad, 0, mult*b.N)
  1282  	for i := 0; i < mult*b.N; i++ {
  1283  		quads = append(quads, quad.Quad{
  1284  			Subject:   irif("n%d", i/5),
  1285  			Predicate: quad.IRI("sub"),
  1286  			Object:    irif("n%d", i/2+i%2),
  1287  		})
  1288  	}
  1289  
  1290  	b.ResetTimer()
  1291  	b.StartTimer()
  1292  	for len(quads) > 0 {
  1293  		batch := quads
  1294  		if len(batch) > perBatch {
  1295  			batch = batch[:perBatch]
  1296  		}
  1297  		n, err := w.WriteQuads(batch)
  1298  		if err != nil {
  1299  			b.Fatal(err)
  1300  		} else if n != len(batch) {
  1301  			b.Fatal(n)
  1302  		}
  1303  		quads = quads[len(batch):]
  1304  	}
  1305  	err = w.Close()
  1306  	if err != nil {
  1307  		b.Fatal(err)
  1308  	}
  1309  	b.StopTimer()
  1310  }