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 }