github.com/vescale/zgraph@v0.0.0-20230410094002-959c02d50f95/planner/match.go (about)

     1  // Copyright 2022 zGraph 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 planner
    16  
    17  import (
    18  	"fmt"
    19  	"math"
    20  	"sort"
    21  
    22  	"github.com/vescale/zgraph/catalog"
    23  	"github.com/vescale/zgraph/internal/slicesext"
    24  	"github.com/vescale/zgraph/parser/ast"
    25  	"github.com/vescale/zgraph/parser/model"
    26  )
    27  
    28  type LogicalMatch struct {
    29  	baseLogicalPlan
    30  
    31  	Subgraph *Subgraph
    32  }
    33  
    34  type PhysicalMatch struct {
    35  	basePhysicalPlan
    36  	Subgraph *Subgraph
    37  }
    38  
    39  type Vertex struct {
    40  	Name   model.CIStr
    41  	Labels []*catalog.Label
    42  }
    43  
    44  // VertexPairConnection represents a connection between two vertices.
    45  type VertexPairConnection interface {
    46  	Name() model.CIStr
    47  	AnyDirected() bool
    48  	SetAnyDirected(anyDirected bool)
    49  	SrcVarName() model.CIStr
    50  	SetSrcVarName(name model.CIStr)
    51  	DstVarName() model.CIStr
    52  	SetDstVarName(name model.CIStr)
    53  }
    54  
    55  var (
    56  	_ VertexPairConnection = &Edge{}
    57  	_ VertexPairConnection = &CommonPathExpression{}
    58  	_ VertexPairConnection = &VariableLengthPath{}
    59  )
    60  
    61  type baseVertexPairConnection struct {
    62  	name        model.CIStr
    63  	srcVarName  model.CIStr
    64  	dstVarName  model.CIStr
    65  	anyDirected bool
    66  }
    67  
    68  func (b *baseVertexPairConnection) Name() model.CIStr {
    69  	return b.name
    70  }
    71  
    72  func (b *baseVertexPairConnection) SetName(name model.CIStr) {
    73  	b.name = name
    74  }
    75  
    76  func (b *baseVertexPairConnection) SrcVarName() model.CIStr {
    77  	return b.srcVarName
    78  }
    79  
    80  func (b *baseVertexPairConnection) SetSrcVarName(name model.CIStr) {
    81  	b.srcVarName = name
    82  }
    83  
    84  func (b *baseVertexPairConnection) DstVarName() model.CIStr {
    85  	return b.dstVarName
    86  }
    87  
    88  func (b *baseVertexPairConnection) SetDstVarName(name model.CIStr) {
    89  	b.dstVarName = name
    90  }
    91  
    92  func (b *baseVertexPairConnection) AnyDirected() bool {
    93  	return b.anyDirected
    94  }
    95  
    96  func (b *baseVertexPairConnection) SetAnyDirected(anyDirected bool) {
    97  	b.anyDirected = anyDirected
    98  }
    99  
   100  type Edge struct {
   101  	baseVertexPairConnection
   102  
   103  	Labels []*catalog.Label
   104  }
   105  
   106  type CommonPathExpression struct {
   107  	baseVertexPairConnection
   108  
   109  	Leftmost    *Vertex
   110  	Rightmost   *Vertex
   111  	Vertices    map[string]*Vertex
   112  	Connections map[string]VertexPairConnection
   113  	Constraints ast.ExprNode
   114  }
   115  
   116  type PathFindingGoal int
   117  
   118  const (
   119  	PathFindingAll PathFindingGoal = iota
   120  	PathFindingReaches
   121  	PathFindingShortest
   122  	PathFindingCheapest
   123  )
   124  
   125  type VariableLengthPath struct {
   126  	baseVertexPairConnection
   127  
   128  	Conn        VertexPairConnection
   129  	Goal        PathFindingGoal
   130  	MinHops     int64
   131  	MaxHops     int64
   132  	TopK        int64
   133  	WithTies    bool
   134  	Constraints ast.ExprNode
   135  	Cost        ast.ExprNode
   136  	HopSrc      *Vertex
   137  	HopDst      *Vertex
   138  }
   139  
   140  type Subgraph struct {
   141  	Vertices      map[string]*Vertex
   142  	Connections   map[string]VertexPairConnection
   143  	SingletonVars []*GraphVar
   144  	GroupVars     []*GraphVar
   145  }
   146  
   147  // GraphVar represents a graph variable in a MATCH clause.
   148  type GraphVar struct {
   149  	Name      model.CIStr
   150  	Anonymous bool
   151  }
   152  
   153  type SubgraphBuilder struct {
   154  	graph  *catalog.Graph
   155  	macros []*ast.PathPatternMacro
   156  	paths  []*ast.PathPattern
   157  
   158  	cpes          map[string]*CommonPathExpression
   159  	vertices      map[string]*Vertex
   160  	connections   map[string]VertexPairConnection
   161  	singletonVars []*GraphVar
   162  	groupVars     []*GraphVar
   163  }
   164  
   165  func NewSubgraphBuilder(graph *catalog.Graph) *SubgraphBuilder {
   166  	return &SubgraphBuilder{
   167  		graph:       graph,
   168  		cpes:        make(map[string]*CommonPathExpression),
   169  		vertices:    make(map[string]*Vertex),
   170  		connections: make(map[string]VertexPairConnection),
   171  	}
   172  }
   173  
   174  func (s *SubgraphBuilder) AddPathPatterns(paths ...*ast.PathPattern) *SubgraphBuilder {
   175  	s.paths = append(s.paths, paths...)
   176  	return s
   177  }
   178  
   179  func (s *SubgraphBuilder) AddPathPatternMacros(macros ...*ast.PathPatternMacro) *SubgraphBuilder {
   180  	s.macros = append(s.macros, macros...)
   181  	return s
   182  }
   183  
   184  func (s *SubgraphBuilder) Build() (*Subgraph, error) {
   185  	s.buildVertices()
   186  	if err := s.buildCommonPathExpressions(); err != nil {
   187  		return nil, err
   188  	}
   189  	if err := s.buildConnections(); err != nil {
   190  		return nil, err
   191  	}
   192  	sort.Slice(s.singletonVars, func(i, j int) bool {
   193  		return s.singletonVars[i].Name.L < s.singletonVars[j].Name.L
   194  	})
   195  	sort.Slice(s.groupVars, func(i, j int) bool {
   196  		return s.groupVars[i].Name.L < s.groupVars[j].Name.L
   197  	})
   198  	sg := &Subgraph{
   199  		Vertices:      s.vertices,
   200  		Connections:   s.connections,
   201  		SingletonVars: s.singletonVars,
   202  		GroupVars:     s.groupVars,
   203  	}
   204  	return sg, nil
   205  }
   206  
   207  func (s *SubgraphBuilder) buildCommonPathExpressions() error {
   208  	for _, m := range s.macros {
   209  		result, err := s.buildPathPatternMacro(m)
   210  		if err != nil {
   211  			return err
   212  		}
   213  		s.cpes[m.Name.L] = result
   214  	}
   215  	return nil
   216  }
   217  
   218  func (s *SubgraphBuilder) buildPathPatternMacro(macro *ast.PathPatternMacro) (*CommonPathExpression, error) {
   219  	if macro.Path.Tp != ast.PathPatternSimple {
   220  		return nil, fmt.Errorf("non-simple path in path pattern macro is not supported")
   221  	}
   222  	sg, err := NewSubgraphBuilder(s.graph).AddPathPatterns(macro.Path).Build()
   223  	if err != nil {
   224  		return nil, err
   225  	}
   226  
   227  	leftmostVarName := macro.Path.Vertices[0].Variable.Name.L
   228  	rightmostVarName := macro.Path.Vertices[len(macro.Path.Vertices)-1].Variable.Name.L
   229  
   230  	cpe := &CommonPathExpression{
   231  		Leftmost:    sg.Vertices[leftmostVarName],
   232  		Rightmost:   sg.Vertices[rightmostVarName],
   233  		Vertices:    sg.Vertices,
   234  		Connections: sg.Connections,
   235  		Constraints: macro.Where,
   236  	}
   237  	return cpe, nil
   238  }
   239  
   240  func (s *SubgraphBuilder) buildVertices() {
   241  	astVars := make(map[string]*ast.VariableSpec)
   242  	for _, path := range s.paths {
   243  		for _, astVertex := range path.Vertices {
   244  			astVar := astVertex.Variable
   245  			if v, ok := s.vertices[astVar.Name.L]; ok {
   246  				if labels := astVar.Labels; len(labels) > 0 {
   247  					if len(v.Labels) > 0 {
   248  						v.Labels = slicesext.FilterFunc(v.Labels, func(l *catalog.Label) bool {
   249  							return slicesext.ContainsFunc(labels, func(label model.CIStr) bool {
   250  								return l.Meta().Name.Equal(label)
   251  							})
   252  						})
   253  					} else {
   254  						for _, l := range astVar.Labels {
   255  							v.Labels = append(v.Labels, s.graph.Label(l.L))
   256  						}
   257  					}
   258  				}
   259  			} else {
   260  				s.vertices[astVar.Name.L] = s.buildVertex(astVar)
   261  				astVars[astVar.Name.L] = astVar
   262  			}
   263  		}
   264  	}
   265  	for name := range s.vertices {
   266  		astVar := astVars[name]
   267  		s.singletonVars = append(s.singletonVars, &GraphVar{
   268  			Name:      astVar.Name,
   269  			Anonymous: astVar.Anonymous,
   270  		})
   271  	}
   272  }
   273  
   274  func (s *SubgraphBuilder) buildVertex(astVar *ast.VariableSpec) *Vertex {
   275  	v := &Vertex{
   276  		Name: astVar.Name,
   277  	}
   278  	for _, l := range astVar.Labels {
   279  		v.Labels = append(v.Labels, s.graph.Label(l.L))
   280  	}
   281  	return v
   282  }
   283  
   284  func (s *SubgraphBuilder) buildConnections() error {
   285  	allConns := s.connections
   286  	for _, path := range s.paths {
   287  		for i, astConn := range path.Connections {
   288  			var (
   289  				conn VertexPairConnection
   290  				err  error
   291  			)
   292  			switch path.Tp {
   293  			case ast.PathPatternSimple:
   294  				conn, err = s.buildSimplePath(astConn)
   295  			case ast.PathPatternAny, ast.PathPatternAnyShortest, ast.PathPatternAllShortest, ast.PathPatternTopKShortest,
   296  				ast.PathPatternAnyCheapest, ast.PathPatternAllCheapest, ast.PathPatternTopKCheapest, ast.PathPatternAll:
   297  				topK := path.TopK
   298  				conn, err = s.buildVariableLengthPath(path.Tp, topK, astConn)
   299  			default:
   300  				return fmt.Errorf("unsupported path pattern type: %d", path.Tp)
   301  			}
   302  
   303  			connName, direction, err := extractConnNameAndDirection(astConn)
   304  			if err != nil {
   305  				return err
   306  			}
   307  			leftVarName := path.Vertices[i].Variable.Name
   308  			rightVarName := path.Vertices[i+1].Variable.Name
   309  			srcVarName, dstVarName, anyDirected, err := resolveAndSrcDstVarName(leftVarName, rightVarName, direction)
   310  			if err != nil {
   311  				return err
   312  			}
   313  			conn.SetAnyDirected(anyDirected)
   314  			conn.SetSrcVarName(srcVarName)
   315  			conn.SetDstVarName(dstVarName)
   316  			allConns[connName.L] = conn
   317  		}
   318  	}
   319  	return nil
   320  }
   321  
   322  func (s *SubgraphBuilder) buildSimplePath(astConn ast.VertexPairConnection) (VertexPairConnection, error) {
   323  	switch x := astConn.(type) {
   324  	case *ast.EdgePattern:
   325  		varName := x.Variable.Name
   326  		edge := &Edge{}
   327  		edge.SetName(varName)
   328  		for _, l := range x.Variable.Labels {
   329  			edge.Labels = append(edge.Labels, s.graph.Label(l.L))
   330  		}
   331  		s.singletonVars = append(s.singletonVars, &GraphVar{
   332  			Name:      varName,
   333  			Anonymous: x.Variable.Anonymous,
   334  		})
   335  		return edge, nil
   336  	case *ast.ReachabilityPathExpr:
   337  		conn, err := s.buildConnWithCpe(x.AnonymousName, x.Labels)
   338  		if err != nil {
   339  			return nil, err
   340  		}
   341  		vlp := &VariableLengthPath{Conn: conn}
   342  		vlp.SetName(x.AnonymousName)
   343  		vlp.Goal = PathFindingReaches
   344  		if x.Quantifier != nil {
   345  			vlp.MinHops = x.Quantifier.N
   346  			vlp.MaxHops = x.Quantifier.M & math.MaxInt64
   347  		} else {
   348  			vlp.MinHops = 1
   349  			vlp.MaxHops = 1
   350  		}
   351  		s.groupVars = append(s.groupVars, &GraphVar{
   352  			Name:      x.AnonymousName,
   353  			Anonymous: true,
   354  		})
   355  		return vlp, nil
   356  	default:
   357  		return nil, fmt.Errorf("unsupported ast.VertexPairConnection(%T) in simple path pattern", x)
   358  	}
   359  }
   360  
   361  func (s *SubgraphBuilder) buildVariableLengthPath(
   362  	pathTp ast.PathPatternType, topK int64, astConn ast.VertexPairConnection,
   363  ) (VertexPairConnection, error) {
   364  	x, ok := astConn.(*ast.QuantifiedPathExpr)
   365  	if !ok {
   366  		return nil, fmt.Errorf("unsupported ast.VertexPairConnection(%T) in variable-length path pattern", astConn)
   367  	}
   368  	varName := x.Edge.Variable.Name
   369  	labels := x.Edge.Variable.Labels
   370  
   371  	conn, err := s.buildConnWithCpe(varName, labels)
   372  	if err != nil {
   373  		return nil, err
   374  	}
   375  	vlp := &VariableLengthPath{Conn: conn}
   376  
   377  	switch pathTp {
   378  	case ast.PathPatternAny, ast.PathPatternAnyShortest:
   379  		vlp.Goal = PathFindingShortest
   380  		vlp.TopK = 1
   381  	case ast.PathPatternAllShortest:
   382  		vlp.Goal = PathFindingShortest
   383  		vlp.WithTies = true
   384  	case ast.PathPatternTopKShortest:
   385  		vlp.Goal = PathFindingShortest
   386  		vlp.TopK = topK
   387  	case ast.PathPatternAnyCheapest:
   388  		vlp.Goal = PathFindingCheapest
   389  		vlp.TopK = 1
   390  	case ast.PathPatternAllCheapest:
   391  		vlp.Goal = PathFindingCheapest
   392  		vlp.WithTies = true
   393  	case ast.PathPatternTopKCheapest:
   394  		vlp.Goal = PathFindingCheapest
   395  		vlp.TopK = topK
   396  	case ast.PathPatternAll:
   397  		vlp.Goal = PathFindingAll
   398  	}
   399  	if x.Quantifier != nil {
   400  		vlp.MinHops = x.Quantifier.N
   401  		vlp.MaxHops = x.Quantifier.M
   402  	} else {
   403  		vlp.MinHops = 1
   404  		vlp.MaxHops = 1
   405  	}
   406  	if x.Quantifier != nil {
   407  		vlp.MinHops = x.Quantifier.N
   408  		vlp.MaxHops = x.Quantifier.M
   409  	} else {
   410  		vlp.MinHops = 1
   411  		vlp.MaxHops = 1
   412  	}
   413  	vlp.Constraints = x.Where
   414  	vlp.Cost = x.Cost
   415  
   416  	var hopSrcVar, hopDstVar *ast.VariableSpec
   417  	if x.Source != nil {
   418  		hopSrcVar = x.Source.Variable
   419  	}
   420  	if x.Destination != nil {
   421  		hopDstVar = x.Destination.Variable
   422  	}
   423  	if x.Edge.Direction == ast.EdgeDirectionIncoming {
   424  		hopSrcVar, hopDstVar = hopDstVar, hopSrcVar
   425  	}
   426  	if hopSrcVar != nil {
   427  		vlp.HopSrc = s.buildVertex(hopSrcVar)
   428  		s.groupVars = append(s.groupVars, &GraphVar{
   429  			Name:      hopSrcVar.Name,
   430  			Anonymous: hopSrcVar.Anonymous,
   431  		})
   432  	}
   433  	if hopDstVar != nil {
   434  		vlp.HopDst = s.buildVertex(hopDstVar)
   435  		s.groupVars = append(s.groupVars, &GraphVar{
   436  			Name:      hopDstVar.Name,
   437  			Anonymous: hopDstVar.Anonymous,
   438  		})
   439  	}
   440  	s.groupVars = append(s.groupVars, &GraphVar{
   441  		Name:      varName,
   442  		Anonymous: x.Edge.Variable.Anonymous,
   443  	})
   444  	return vlp, nil
   445  }
   446  
   447  // buildConnWithCpe builds a single connection with a CPE. If label and CPE have the same name, the CPE will be used.
   448  // Currently, reference multiple CPEs or mix CPEs with normal labels is not supported.
   449  func (s *SubgraphBuilder) buildConnWithCpe(varName model.CIStr, labelOrCpeNames []model.CIStr) (VertexPairConnection, error) {
   450  	edge := &Edge{}
   451  	edge.SetName(varName)
   452  	if len(labelOrCpeNames) == 0 {
   453  		edge.Labels = s.graph.Labels()
   454  		return edge, nil
   455  	}
   456  
   457  	for _, name := range labelOrCpeNames {
   458  		if cpe, ok := s.cpes[name.L]; ok {
   459  			if len(labelOrCpeNames) != 1 {
   460  				return nil, fmt.Errorf("reference multiple CPEs or mix CPEs with normal labels is not supported")
   461  			}
   462  			// TODO: clone the CPE to avoid modifying the original one.
   463  			return cpe, nil
   464  		}
   465  		edge.Labels = append(edge.Labels, s.graph.Label(name.L))
   466  	}
   467  	return edge, nil
   468  }
   469  
   470  func resolveAndSrcDstVarName(
   471  	leftVarName, rightVarName model.CIStr, direction ast.EdgeDirection,
   472  ) (srcVarName, dstVarName model.CIStr, anyDirected bool, err error) {
   473  	switch direction {
   474  	case ast.EdgeDirectionOutgoing:
   475  		srcVarName = leftVarName
   476  		dstVarName = rightVarName
   477  	case ast.EdgeDirectionIncoming:
   478  		srcVarName = rightVarName
   479  		dstVarName = leftVarName
   480  	case ast.EdgeDirectionAnyDirected:
   481  		srcVarName = leftVarName
   482  		dstVarName = rightVarName
   483  		anyDirected = true
   484  	default:
   485  		err = fmt.Errorf("unsupported edge direction: %v", direction)
   486  	}
   487  	return
   488  }
   489  
   490  func extractConnNameAndDirection(conn ast.VertexPairConnection) (model.CIStr, ast.EdgeDirection, error) {
   491  	switch x := conn.(type) {
   492  	case *ast.EdgePattern:
   493  		return x.Variable.Name, x.Direction, nil
   494  	case *ast.ReachabilityPathExpr:
   495  		return x.AnonymousName, x.Direction, nil
   496  	case *ast.QuantifiedPathExpr:
   497  		return x.Edge.Variable.Name, x.Edge.Direction, nil
   498  	default:
   499  		return model.CIStr{}, 0, fmt.Errorf("unsupported ast.VertexPairConnection(%T)", x)
   500  	}
   501  }