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 }