github.com/cayleygraph/cayley@v0.7.7/graph/shape/shape_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 shape_test
    16  
    17  import (
    18  	"context"
    19  	"reflect"
    20  	"testing"
    21  
    22  	"github.com/cayleygraph/cayley/graph"
    23  	"github.com/cayleygraph/cayley/graph/graphmock"
    24  	. "github.com/cayleygraph/cayley/graph/shape"
    25  	"github.com/cayleygraph/quad"
    26  	"github.com/stretchr/testify/assert"
    27  	"github.com/stretchr/testify/require"
    28  )
    29  
    30  func intVal(v int) graph.Ref {
    31  	return graphmock.IntVal(v)
    32  }
    33  
    34  var _ Optimizer = ValLookup(nil)
    35  var _ graph.QuadStore = ValLookup(nil)
    36  
    37  type ValLookup map[quad.Value]graph.Ref
    38  
    39  func (qs ValLookup) OptimizeShape(s Shape) (Shape, bool) {
    40  	return s, false // emulate dumb quad store
    41  }
    42  func (qs ValLookup) ValueOf(v quad.Value) graph.Ref {
    43  	return qs[v]
    44  }
    45  
    46  func (ValLookup) NewQuadWriter() (quad.WriteCloser, error) {
    47  	panic("not implemented")
    48  }
    49  func (ValLookup) ApplyDeltas(_ []graph.Delta, _ graph.IgnoreOpts) error {
    50  	panic("not implemented")
    51  }
    52  func (ValLookup) Quad(_ graph.Ref) quad.Quad {
    53  	panic("not implemented")
    54  }
    55  func (ValLookup) QuadIterator(_ quad.Direction, _ graph.Ref) graph.Iterator {
    56  	panic("not implemented")
    57  }
    58  func (ValLookup) QuadIteratorSize(ctx context.Context, d quad.Direction, val graph.Ref) (graph.Size, error) {
    59  	panic("not implemented")
    60  }
    61  func (ValLookup) NodesAllIterator() graph.Iterator {
    62  	panic("not implemented")
    63  }
    64  func (ValLookup) QuadsAllIterator() graph.Iterator {
    65  	panic("not implemented")
    66  }
    67  func (ValLookup) NameOf(_ graph.Ref) quad.Value {
    68  	panic("not implemented")
    69  }
    70  func (ValLookup) Stats(ctx context.Context, exact bool) (graph.Stats, error) {
    71  	panic("not implemented")
    72  }
    73  func (ValLookup) Close() error {
    74  	panic("not implemented")
    75  }
    76  func (ValLookup) QuadDirection(_ graph.Ref, _ quad.Direction) graph.Ref {
    77  	panic("not implemented")
    78  }
    79  func (ValLookup) Type() string {
    80  	panic("not implemented")
    81  }
    82  
    83  func emptySet() Shape {
    84  	return NodesFrom{
    85  		Dir: quad.Predicate,
    86  		Quads: Intersect{Quads{
    87  			{Dir: quad.Object,
    88  				Values: Lookup{quad.IRI("not-existent")},
    89  			},
    90  		}},
    91  	}
    92  }
    93  
    94  var optimizeCases = []struct {
    95  	name   string
    96  	from   Shape
    97  	expect Shape
    98  	opt    bool
    99  	qs     ValLookup
   100  }{
   101  	{
   102  		name:   "all",
   103  		from:   AllNodes{},
   104  		opt:    false,
   105  		expect: AllNodes{},
   106  	},
   107  	{
   108  		name: "page min limit",
   109  		from: Page{
   110  			Limit: 5,
   111  			From: Page{
   112  				Limit: 3,
   113  				From:  AllNodes{},
   114  			},
   115  		},
   116  		opt: true,
   117  		expect: Page{
   118  			Limit: 3,
   119  			From:  AllNodes{},
   120  		},
   121  	},
   122  	{
   123  		name: "page skip and limit",
   124  		from: Page{
   125  			Skip: 3, Limit: 3,
   126  			From: Page{
   127  				Skip: 2, Limit: 5,
   128  				From: AllNodes{},
   129  			},
   130  		},
   131  		opt: true,
   132  		expect: Page{
   133  			Skip: 5, Limit: 2,
   134  			From: AllNodes{},
   135  		},
   136  	},
   137  	{
   138  		name:   "intersect tagged all",
   139  		from:   Intersect{Save{Tags: []string{"id"}, From: AllNodes{}}},
   140  		opt:    true,
   141  		expect: Save{Tags: []string{"id"}, From: AllNodes{}},
   142  	},
   143  	{
   144  		name: "intersect quads and lookup resolution",
   145  		from: Intersect{
   146  			Quads{
   147  				{Dir: quad.Subject, Values: Lookup{quad.IRI("bob")}},
   148  			},
   149  			Quads{
   150  				{Dir: quad.Object, Values: Lookup{quad.IRI("alice")}},
   151  			},
   152  		},
   153  		opt: true,
   154  		expect: Quads{
   155  			{Dir: quad.Subject, Values: Fixed{intVal(1)}},
   156  			{Dir: quad.Object, Values: Fixed{intVal(2)}},
   157  		},
   158  		qs: ValLookup{
   159  			quad.IRI("bob"):   intVal(1),
   160  			quad.IRI("alice"): intVal(2),
   161  		},
   162  	},
   163  	{
   164  		name: "intersect nodes, remove all, join intersects",
   165  		from: Intersect{
   166  			AllNodes{},
   167  			NodesFrom{Dir: quad.Subject, Quads: Quads{}},
   168  			Intersect{
   169  				Lookup{quad.IRI("alice")},
   170  				Unique{NodesFrom{Dir: quad.Object, Quads: Quads{}}},
   171  			},
   172  		},
   173  		opt: true,
   174  		expect: Intersect{
   175  			Fixed{intVal(1)},
   176  			QuadsAction{Result: quad.Subject},
   177  			Unique{QuadsAction{Result: quad.Object}},
   178  		},
   179  		qs: ValLookup{
   180  			quad.IRI("alice"): intVal(1),
   181  		},
   182  	},
   183  	{
   184  		name: "push Save out of intersect",
   185  		from: Intersect{
   186  			Save{
   187  				Tags: []string{"id"},
   188  				From: NodesFrom{Dir: quad.Subject, Quads: Quads{}},
   189  			},
   190  			Unique{NodesFrom{Dir: quad.Object, Quads: Quads{}}},
   191  		},
   192  		opt: true,
   193  		expect: Save{
   194  			Tags: []string{"id"},
   195  			From: Intersect{
   196  				QuadsAction{Result: quad.Subject},
   197  				Unique{QuadsAction{Result: quad.Object}},
   198  			},
   199  		},
   200  	},
   201  	{
   202  		name: "collapse empty set",
   203  		from: Intersect{Quads{
   204  			{Dir: quad.Subject, Values: Union{
   205  				Unique{emptySet()},
   206  			}},
   207  		}},
   208  		opt:    true,
   209  		expect: Null{},
   210  	},
   211  	{ // remove "all nodes" in intersect, merge Fixed and order them first
   212  		name: "remove all in intersect and reorder",
   213  		from: Intersect{
   214  			AllNodes{},
   215  			Fixed{intVal(1), intVal(2)},
   216  			Save{From: AllNodes{}, Tags: []string{"all"}},
   217  			Fixed{intVal(2)},
   218  		},
   219  		opt: true,
   220  		expect: Save{
   221  			From: Intersect{
   222  				Fixed{intVal(1), intVal(2)},
   223  				Fixed{intVal(2)},
   224  			},
   225  			Tags: []string{"all"},
   226  		},
   227  	},
   228  	{
   229  		name: "remove HasA-LinksTo pairs",
   230  		from: NodesFrom{
   231  			Dir: quad.Subject,
   232  			Quads: Quads{{
   233  				Dir:    quad.Subject,
   234  				Values: Fixed{intVal(1)},
   235  			}},
   236  		},
   237  		opt:    true,
   238  		expect: Fixed{intVal(1)},
   239  	},
   240  	{ // pop fixed tags to the top of the tree
   241  		name: "pop fixed tags",
   242  		from: NodesFrom{Dir: quad.Subject, Quads: Quads{
   243  			QuadFilter{Dir: quad.Predicate, Values: Intersect{
   244  				FixedTags{
   245  					Tags: map[string]graph.Ref{"foo": intVal(1)},
   246  					On: NodesFrom{Dir: quad.Subject,
   247  						Quads: Quads{
   248  							QuadFilter{Dir: quad.Object, Values: FixedTags{
   249  								Tags: map[string]graph.Ref{"bar": intVal(2)},
   250  								On:   Fixed{intVal(3)},
   251  							}},
   252  						},
   253  					},
   254  				},
   255  			}},
   256  		}},
   257  		opt: true,
   258  		expect: FixedTags{
   259  			Tags: map[string]graph.Ref{"foo": intVal(1), "bar": intVal(2)},
   260  			On: NodesFrom{Dir: quad.Subject, Quads: Quads{
   261  				QuadFilter{Dir: quad.Predicate, Values: QuadsAction{
   262  					Result: quad.Subject,
   263  					Filter: map[quad.Direction]graph.Ref{quad.Object: intVal(3)},
   264  				}},
   265  			}},
   266  		},
   267  	},
   268  	{ // remove optional empty set from intersect
   269  		name: "remove optional empty set",
   270  		from: IntersectOpt{
   271  			Sub: Intersect{
   272  				AllNodes{},
   273  				Save{From: AllNodes{}, Tags: []string{"all"}},
   274  				Fixed{intVal(2)},
   275  			},
   276  			Opt: []Shape{Save{
   277  				From: emptySet(),
   278  				Tags: []string{"name"},
   279  			}},
   280  		},
   281  		opt: true,
   282  		expect: Save{
   283  			From: Fixed{intVal(2)},
   284  			Tags: []string{"all"},
   285  		},
   286  	},
   287  	{ // push fixed node from intersect into nodes.quads
   288  		name: "push fixed into nodes.quads",
   289  		from: Intersect{
   290  			Fixed{intVal(1)},
   291  			NodesFrom{
   292  				Dir: quad.Subject,
   293  				Quads: Quads{
   294  					{Dir: quad.Predicate, Values: Fixed{intVal(2)}},
   295  					{
   296  						Dir: quad.Object,
   297  						Values: NodesFrom{
   298  							Dir: quad.Subject,
   299  							Quads: Quads{
   300  								{Dir: quad.Predicate, Values: Fixed{intVal(2)}},
   301  							},
   302  						},
   303  					},
   304  				},
   305  			},
   306  		},
   307  		opt: true,
   308  		expect: NodesFrom{
   309  			Dir: quad.Subject,
   310  			Quads: Quads{
   311  				{Dir: quad.Subject, Values: Fixed{intVal(1)}},
   312  				{Dir: quad.Predicate, Values: Fixed{intVal(2)}},
   313  				{
   314  					Dir: quad.Object,
   315  					Values: QuadsAction{
   316  						Result: quad.Subject,
   317  						Filter: map[quad.Direction]graph.Ref{
   318  							quad.Predicate: intVal(2),
   319  						},
   320  					},
   321  				},
   322  			},
   323  		},
   324  	},
   325  	{
   326  		name: "all optional",
   327  		from: Intersect{IntersectOpt{
   328  			Sub: Intersect{
   329  				Save{Tags: []string{"id"}, From: AllNodes{}},
   330  			},
   331  			Opt: []Shape{
   332  				NodesFrom{Dir: quad.Subject, Quads: Quads{
   333  					QuadFilter{Dir: quad.Object, Values: Save{Tags: []string{"status"}, From: AllNodes{}}},
   334  					QuadFilter{Dir: quad.Predicate, Values: Fixed{intVal(1)}},
   335  				}},
   336  			},
   337  		}},
   338  		opt: true,
   339  		expect: Save{
   340  			Tags: []string{"id"},
   341  			From: IntersectOpt{
   342  				Sub: Intersect{AllNodes{}},
   343  				Opt: []Shape{
   344  					QuadsAction{Result: quad.Subject,
   345  						Save:   map[quad.Direction][]string{quad.Object: {"status"}},
   346  						Filter: map[quad.Direction]graph.Ref{quad.Predicate: intVal(1)},
   347  					},
   348  				},
   349  			},
   350  		},
   351  	},
   352  }
   353  
   354  func TestOptimize(t *testing.T) {
   355  	for _, c := range optimizeCases {
   356  		t.Run(c.name, func(t *testing.T) {
   357  			qs := c.qs
   358  			got, opt := Optimize(c.from, qs)
   359  			assert.Equal(t, c.expect, got)
   360  			assert.Equal(t, c.opt, opt)
   361  		})
   362  	}
   363  }
   364  
   365  func TestWalk(t *testing.T) {
   366  	var s Shape = NodesFrom{
   367  		Dir: quad.Subject,
   368  		Quads: Quads{
   369  			{Dir: quad.Subject, Values: Fixed{intVal(1)}},
   370  			{Dir: quad.Predicate, Values: Fixed{intVal(2)}},
   371  			{
   372  				Dir: quad.Object,
   373  				Values: QuadsAction{
   374  					Result: quad.Subject,
   375  					Filter: map[quad.Direction]graph.Ref{
   376  						quad.Predicate: intVal(2),
   377  					},
   378  				},
   379  			},
   380  		},
   381  	}
   382  	var types []string
   383  	Walk(s, func(s Shape) bool {
   384  		types = append(types, reflect.TypeOf(s).String())
   385  		return true
   386  	})
   387  	require.Equal(t, []string{
   388  		"shape.NodesFrom",
   389  		"shape.Quads",
   390  		"shape.Fixed",
   391  		"shape.Fixed",
   392  		"shape.QuadsAction",
   393  	}, types)
   394  }