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

     1  // Copyright 2014 The Cayley Authors. All rights reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package memstore
    16  
    17  import (
    18  	"context"
    19  	"reflect"
    20  	"sort"
    21  	"testing"
    22  
    23  	"github.com/cayleygraph/cayley/graph"
    24  	"github.com/cayleygraph/cayley/graph/graphtest"
    25  	"github.com/cayleygraph/cayley/graph/iterator"
    26  	"github.com/cayleygraph/cayley/graph/shape"
    27  	"github.com/cayleygraph/cayley/writer"
    28  	"github.com/cayleygraph/quad"
    29  	"github.com/stretchr/testify/require"
    30  )
    31  
    32  // This is a simple test graph.
    33  //
    34  //    +---+                        +---+
    35  //    | A |-------               ->| F |<--
    36  //    +---+       \------>+---+-/  +---+   \--+---+
    37  //                 ------>|#B#|      |        | E |
    38  //    +---+-------/      >+---+      |        +---+
    39  //    | C |             /            v
    40  //    +---+           -/           +---+
    41  //      ----    +---+/             |#G#|
    42  //          \-->|#D#|------------->+---+
    43  //              +---+
    44  //
    45  var simpleGraph = []quad.Quad{
    46  	quad.MakeRaw("A", "follows", "B", ""),
    47  	quad.MakeRaw("C", "follows", "B", ""),
    48  	quad.MakeRaw("C", "follows", "D", ""),
    49  	quad.MakeRaw("D", "follows", "B", ""),
    50  	quad.MakeRaw("B", "follows", "F", ""),
    51  	quad.MakeRaw("F", "follows", "G", ""),
    52  	quad.MakeRaw("D", "follows", "G", ""),
    53  	quad.MakeRaw("E", "follows", "F", ""),
    54  	quad.MakeRaw("B", "status", "cool", "status_graph"),
    55  	quad.MakeRaw("D", "status", "cool", "status_graph"),
    56  	quad.MakeRaw("G", "status", "cool", "status_graph"),
    57  }
    58  
    59  func makeTestStore(data []quad.Quad) (*QuadStore, graph.QuadWriter, []pair) {
    60  	seen := make(map[string]struct{})
    61  	qs := New()
    62  	var (
    63  		val int64
    64  		ind []pair
    65  	)
    66  	writer, _ := writer.NewSingleReplication(qs, nil)
    67  	for _, t := range data {
    68  		for _, dir := range quad.Directions {
    69  			qp := t.GetString(dir)
    70  			if _, ok := seen[qp]; !ok && qp != "" {
    71  				val++
    72  				ind = append(ind, pair{qp, val})
    73  				seen[qp] = struct{}{}
    74  			}
    75  		}
    76  
    77  		writer.AddQuad(t)
    78  		val++
    79  	}
    80  	return qs, writer, ind
    81  }
    82  
    83  func TestMemstore(t *testing.T) {
    84  	graphtest.TestAll(t, func(t testing.TB) (graph.QuadStore, graph.Options, func()) {
    85  		return New(), nil, func() {}
    86  	}, &graphtest.Config{
    87  		AlwaysRunIntegration: true,
    88  	})
    89  }
    90  
    91  func BenchmarkMemstore(b *testing.B) {
    92  	graphtest.BenchmarkAll(b, func(t testing.TB) (graph.QuadStore, graph.Options, func()) {
    93  		return New(), nil, func() {}
    94  	}, &graphtest.Config{
    95  		AlwaysRunIntegration: true,
    96  	})
    97  }
    98  
    99  type pair struct {
   100  	query string
   101  	value int64
   102  }
   103  
   104  func TestMemstoreValueOf(t *testing.T) {
   105  	qs, _, index := makeTestStore(simpleGraph)
   106  	exp := graph.Stats{
   107  		Nodes: graph.Size{Size: 11, Exact: true},
   108  		Quads: graph.Size{Size: 11, Exact: true},
   109  	}
   110  	st, err := qs.Stats(context.Background(), true)
   111  	require.NoError(t, err)
   112  	require.Equal(t, exp, st, "Unexpected quadstore size")
   113  
   114  	for _, test := range index {
   115  		v := qs.ValueOf(quad.Raw(test.query))
   116  		switch v := v.(type) {
   117  		default:
   118  			t.Errorf("ValueOf(%q) returned unexpected type, got:%T expected int64", test.query, v)
   119  		case bnode:
   120  			require.Equal(t, test.value, int64(v))
   121  		}
   122  	}
   123  }
   124  
   125  func TestIteratorsAndNextResultOrderA(t *testing.T) {
   126  	ctx := context.TODO()
   127  	qs, _, _ := makeTestStore(simpleGraph)
   128  
   129  	fixed := iterator.NewFixed()
   130  	fixed.Add(qs.ValueOf(quad.Raw("C")))
   131  
   132  	fixed2 := iterator.NewFixed()
   133  	fixed2.Add(qs.ValueOf(quad.Raw("follows")))
   134  
   135  	all := qs.NodesAllIterator()
   136  
   137  	const allTag = "all"
   138  	innerAnd := iterator.NewAnd(
   139  		iterator.NewLinksTo(qs, fixed2, quad.Predicate),
   140  		iterator.NewLinksTo(qs, iterator.Tag(all, allTag), quad.Object),
   141  	)
   142  
   143  	hasa := iterator.NewHasA(qs, innerAnd, quad.Subject)
   144  	outerAnd := iterator.NewAnd(fixed, hasa)
   145  
   146  	if !outerAnd.Next(ctx) {
   147  		t.Error("Expected one matching subtree")
   148  	}
   149  	val := outerAnd.Result()
   150  	if qs.NameOf(val) != quad.Raw("C") {
   151  		t.Errorf("Matching subtree should be %s, got %s", "barak", qs.NameOf(val))
   152  	}
   153  
   154  	var (
   155  		got    []string
   156  		expect = []string{"B", "D"}
   157  	)
   158  	for {
   159  		m := make(map[string]graph.Ref, 1)
   160  		outerAnd.TagResults(m)
   161  		got = append(got, quad.ToString(qs.NameOf(m[allTag])))
   162  		if !outerAnd.NextPath(ctx) {
   163  			break
   164  		}
   165  	}
   166  	sort.Strings(got)
   167  
   168  	if !reflect.DeepEqual(got, expect) {
   169  		t.Errorf("Unexpected result, got:%q expect:%q", got, expect)
   170  	}
   171  
   172  	if outerAnd.Next(ctx) {
   173  		t.Error("More than one possible top level output?")
   174  	}
   175  }
   176  
   177  func TestLinksToOptimization(t *testing.T) {
   178  	qs, _, _ := makeTestStore(simpleGraph)
   179  
   180  	lto := shape.BuildIterator(qs, shape.Quads{
   181  		{Dir: quad.Object, Values: shape.Lookup{quad.Raw("cool")}},
   182  	})
   183  
   184  	newIt, changed := lto.Optimize()
   185  	if changed {
   186  		t.Errorf("unexpected optimization step")
   187  	}
   188  	if _, ok := newIt.(*Iterator); !ok {
   189  		t.Fatal("Didn't swap out to LLRB")
   190  	}
   191  }
   192  
   193  func TestRemoveQuad(t *testing.T) {
   194  	ctx := context.TODO()
   195  	qs, w, _ := makeTestStore(simpleGraph)
   196  
   197  	err := w.RemoveQuad(quad.Make(
   198  		"E",
   199  		"follows",
   200  		"F",
   201  		nil,
   202  	))
   203  
   204  	if err != nil {
   205  		t.Error("Couldn't remove quad", err)
   206  	}
   207  
   208  	fixed := iterator.NewFixed()
   209  	fixed.Add(qs.ValueOf(quad.Raw("E")))
   210  
   211  	fixed2 := iterator.NewFixed()
   212  	fixed2.Add(qs.ValueOf(quad.Raw("follows")))
   213  
   214  	innerAnd := iterator.NewAnd(
   215  		iterator.NewLinksTo(qs, fixed, quad.Subject),
   216  		iterator.NewLinksTo(qs, fixed2, quad.Predicate),
   217  	)
   218  
   219  	hasa := iterator.NewHasA(qs, innerAnd, quad.Object)
   220  
   221  	newIt, _ := hasa.Optimize()
   222  	if newIt.Next(ctx) {
   223  		t.Error("E should not have any followers.")
   224  	}
   225  }
   226  
   227  func TestTransaction(t *testing.T) {
   228  	qs, w, _ := makeTestStore(simpleGraph)
   229  	st, err := qs.Stats(context.Background(), true)
   230  	require.NoError(t, err)
   231  
   232  	tx := graph.NewTransaction()
   233  	tx.AddQuad(quad.Make(
   234  		"E",
   235  		"follows",
   236  		"G",
   237  		nil))
   238  	tx.RemoveQuad(quad.Make(
   239  		"Non",
   240  		"existent",
   241  		"quad",
   242  		nil))
   243  
   244  	err = w.ApplyTransaction(tx)
   245  	if err == nil {
   246  		t.Error("Able to remove a non-existent quad")
   247  	}
   248  	st2, err := qs.Stats(context.Background(), true)
   249  	require.NoError(t, err)
   250  	require.Equal(t, st, st2, "Appended a new quad in a failed transaction")
   251  }