github.com/cayleygraph/cayley@v0.7.7/graph/nosql/shapes.go (about)

     1  package nosql
     2  
     3  import (
     4  	"fmt"
     5  	"math"
     6  	"strconv"
     7  
     8  	"github.com/hidal-go/hidalgo/legacy/nosql"
     9  
    10  	"github.com/cayleygraph/cayley/graph"
    11  	"github.com/cayleygraph/cayley/graph/iterator"
    12  	"github.com/cayleygraph/cayley/graph/shape"
    13  	"github.com/cayleygraph/quad"
    14  )
    15  
    16  var _ shape.Optimizer = (*QuadStore)(nil)
    17  
    18  func (qs *QuadStore) OptimizeShape(s shape.Shape) (shape.Shape, bool) {
    19  	switch s := s.(type) {
    20  	case shape.Quads:
    21  		return qs.optimizeQuads(s)
    22  	case shape.Filter:
    23  		return qs.optimizeFilter(s)
    24  	case shape.Page:
    25  		return qs.optimizePage(s)
    26  	case shape.Composite:
    27  		if s2, opt := s.Simplify().Optimize(qs); opt {
    28  			return s2, true
    29  		}
    30  	}
    31  	return s, false
    32  }
    33  
    34  // Shape is a shape representing a documents query with filters
    35  type Shape struct {
    36  	Collection string              // name of the collection
    37  	Filters    []nosql.FieldFilter // filters to select documents
    38  	Limit      int64               // limits a number of documents
    39  }
    40  
    41  func (s Shape) BuildIterator(qs graph.QuadStore) graph.Iterator {
    42  	db, ok := qs.(*QuadStore)
    43  	if !ok {
    44  		return iterator.NewError(fmt.Errorf("not a nosql database: %T", qs))
    45  	}
    46  	return NewIterator(db, s.Collection, s.Filters...)
    47  }
    48  
    49  func (s Shape) Optimize(r shape.Optimizer) (shape.Shape, bool) {
    50  	return s, false
    51  }
    52  
    53  // Quads is a shape representing a quads query
    54  type Quads struct {
    55  	Links []Linkage // filters to select quads
    56  	Limit int64     // limits a number of documents
    57  }
    58  
    59  func (s Quads) BuildIterator(qs graph.QuadStore) graph.Iterator {
    60  	db, ok := qs.(*QuadStore)
    61  	if !ok {
    62  		return iterator.NewError(fmt.Errorf("not a nosql database: %T", qs))
    63  	}
    64  	return NewLinksToIterator(db, colQuads, s.Links)
    65  }
    66  
    67  func (s Quads) Optimize(r shape.Optimizer) (shape.Shape, bool) {
    68  	return s, false
    69  }
    70  
    71  const int64Adjust = 1 << 63
    72  
    73  // itos serializes int64 into a sortable string 13 chars long.
    74  func itos(i int64) string {
    75  	s := strconv.FormatUint(uint64(i)+int64Adjust, 32)
    76  	const z = "0000000000000"
    77  	return z[len(s):] + s
    78  }
    79  
    80  // stoi de-serializes int64 from a sortable string 13 chars long.
    81  func stoi(s string) int64 {
    82  	ret, err := strconv.ParseUint(s, 32, 64)
    83  	if err != nil {
    84  		//TODO handle error?
    85  		return 0
    86  	}
    87  	return int64(ret - int64Adjust)
    88  }
    89  
    90  func toFieldFilter(opt *Traits, c shape.Comparison) ([]nosql.FieldFilter, bool) {
    91  	var op nosql.FilterOp
    92  	switch c.Op {
    93  	case iterator.CompareGT:
    94  		op = nosql.GT
    95  	case iterator.CompareGTE:
    96  		op = nosql.GTE
    97  	case iterator.CompareLT:
    98  		op = nosql.LT
    99  	case iterator.CompareLTE:
   100  		op = nosql.LTE
   101  	default:
   102  		return nil, false
   103  	}
   104  	fieldPath := func(s string) []string {
   105  		return []string{fldValue, s}
   106  	}
   107  
   108  	var filters []nosql.FieldFilter
   109  	switch v := c.Val.(type) {
   110  	case quad.String:
   111  		filters = []nosql.FieldFilter{
   112  			{Path: fieldPath(fldValData), Filter: op, Value: nosql.String(v)},
   113  			{Path: fieldPath(fldIRI), Filter: nosql.NotEqual, Value: nosql.Bool(true)},
   114  			{Path: fieldPath(fldBNode), Filter: nosql.NotEqual, Value: nosql.Bool(true)},
   115  		}
   116  	case quad.IRI:
   117  		filters = []nosql.FieldFilter{
   118  			{Path: fieldPath(fldValData), Filter: op, Value: nosql.String(v)},
   119  			{Path: fieldPath(fldIRI), Filter: nosql.Equal, Value: nosql.Bool(true)},
   120  		}
   121  	case quad.BNode:
   122  		filters = []nosql.FieldFilter{
   123  			{Path: fieldPath(fldValData), Filter: op, Value: nosql.String(v)},
   124  			{Path: fieldPath(fldBNode), Filter: nosql.Equal, Value: nosql.Bool(true)},
   125  		}
   126  	case quad.Int:
   127  		if opt.Number32 && (v < math.MinInt32 || v > math.MaxInt32) {
   128  			// switch to range on string values
   129  			filters = []nosql.FieldFilter{
   130  				{Path: fieldPath(fldValStrInt), Filter: op, Value: nosql.String(itos(int64(v)))},
   131  			}
   132  		} else {
   133  			filters = []nosql.FieldFilter{
   134  				{Path: fieldPath(fldValInt), Filter: op, Value: nosql.Int(v)},
   135  			}
   136  		}
   137  	case quad.Float:
   138  		filters = []nosql.FieldFilter{
   139  			{Path: fieldPath(fldValFloat), Filter: op, Value: nosql.Float(v)},
   140  		}
   141  	case quad.Time:
   142  		filters = []nosql.FieldFilter{
   143  			{Path: fieldPath(fldValTime), Filter: op, Value: nosql.Time(v)},
   144  		}
   145  	default:
   146  		return nil, false
   147  	}
   148  	return filters, true
   149  }
   150  
   151  func (qs *QuadStore) optimizeFilter(s shape.Filter) (shape.Shape, bool) {
   152  	if _, ok := s.From.(shape.AllNodes); !ok {
   153  		return s, false
   154  	}
   155  	var (
   156  		filters []nosql.FieldFilter
   157  		left    []shape.ValueFilter
   158  	)
   159  	fieldPath := func(s string) []string {
   160  		return []string{fldValue, s}
   161  	}
   162  	for _, f := range s.Filters {
   163  		switch f := f.(type) {
   164  		case shape.Comparison:
   165  			if fld, ok := toFieldFilter(&qs.opt, f); ok {
   166  				filters = append(filters, fld...)
   167  				continue
   168  			}
   169  		case shape.Wildcard:
   170  			filters = append(filters, []nosql.FieldFilter{
   171  				{Path: fieldPath(fldValData), Filter: nosql.Regexp, Value: nosql.String(f.Regexp())},
   172  			}...)
   173  			continue
   174  		case shape.Regexp:
   175  			filters = append(filters, []nosql.FieldFilter{
   176  				{Path: fieldPath(fldValData), Filter: nosql.Regexp, Value: nosql.String(f.Re.String())},
   177  			}...)
   178  			if !f.Refs {
   179  				filters = append(filters, []nosql.FieldFilter{
   180  					{Path: fieldPath(fldIRI), Filter: nosql.NotEqual, Value: nosql.Bool(true)},
   181  					{Path: fieldPath(fldBNode), Filter: nosql.NotEqual, Value: nosql.Bool(true)},
   182  				}...)
   183  			}
   184  			continue
   185  		}
   186  		left = append(left, f)
   187  	}
   188  	if len(filters) == 0 {
   189  		return s, false
   190  	}
   191  	var ns shape.Shape = Shape{Collection: colNodes, Filters: filters}
   192  	if len(left) != 0 {
   193  		ns = shape.Filter{From: ns, Filters: left}
   194  	}
   195  	return ns, true
   196  }
   197  
   198  func (qs *QuadStore) optimizeQuads(s shape.Quads) (shape.Shape, bool) {
   199  	var (
   200  		links []Linkage
   201  		left  []shape.QuadFilter
   202  	)
   203  	for _, f := range s {
   204  		if v, ok := shape.One(f.Values); ok {
   205  			if h, ok := v.(NodeHash); ok {
   206  				links = append(links, Linkage{Dir: f.Dir, Val: h})
   207  				continue
   208  			}
   209  		}
   210  		left = append(left, f)
   211  	}
   212  	if len(links) == 0 {
   213  		return s, false
   214  	}
   215  	var ns shape.Shape = Quads{Links: links}
   216  	if len(left) != 0 {
   217  		ns = shape.Intersect{ns, shape.Quads(left)}
   218  	}
   219  	return s, true
   220  }
   221  
   222  func (qs *QuadStore) optimizePage(s shape.Page) (shape.Shape, bool) {
   223  	if s.Skip != 0 {
   224  		return s, false
   225  	}
   226  	switch f := s.From.(type) {
   227  	case shape.AllNodes:
   228  		return Shape{Collection: colNodes, Limit: s.Limit}, false
   229  	case Shape:
   230  		s.ApplyPage(shape.Page{Limit: f.Limit})
   231  		f.Limit = s.Limit
   232  		return f, true
   233  	case Quads:
   234  		s.ApplyPage(shape.Page{Limit: f.Limit})
   235  		f.Limit = s.Limit
   236  		return f, true
   237  	}
   238  	return s, false
   239  }