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 }