github.com/hasnat/dolt/go@v0.0.0-20210628190320-9eb5d843fbb7/store/types/path.go (about) 1 // Copyright 2019 Dolthub, Inc. 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 // This file incorporates work covered by the following copyright and 16 // permission notice: 17 // 18 // Copyright 2016 Attic Labs, Inc. All rights reserved. 19 // Licensed under the Apache License, version 2.0: 20 // http://www.apache.org/licenses/LICENSE-2.0 21 22 package types 23 24 import ( 25 "bytes" 26 "context" 27 "errors" 28 "fmt" 29 "math" 30 "regexp" 31 "strconv" 32 "strings" 33 34 "github.com/dolthub/dolt/go/store/d" 35 "github.com/dolthub/dolt/go/store/hash" 36 ) 37 38 // For an annotation like @type, 1st capture group is the annotation. 39 // For @at(42), 1st capture group is the annotation and 3rd is the parameter. 40 // Note, @at() is valid under this regexp, code should deal with the error. 41 var annotationRe = regexp.MustCompile(`^([a-z]+)(\(([\w\-"']*)\))?`) 42 43 // A Path locates a value in Noms relative to some other value. For locating 44 // values absolutely within a database, see AbsolutePath. To locate values 45 // globally, see Spec. 46 // 47 // For more details, see: 48 // https://github.com/attic-labs/noms/blob/master/doc/spelling.md. 49 type Path []PathPart 50 51 type PathPart interface { 52 Resolve(ctx context.Context, v Value, vr ValueReader) (Value, error) 53 String() string 54 } 55 56 // ParsePath parses str into a Path, or returns an error if parsing failed. 57 func ParsePath(str string) (Path, error) { 58 if str == "" { 59 return Path{}, errors.New("empty path") 60 } 61 return constructPath(Path{}, str) 62 } 63 64 // MustParsePath parses str into a Path, or panics if parsing failed. 65 func MustParsePath(str string) Path { 66 p, err := ParsePath(str) 67 if err != nil { 68 panic(err) 69 } 70 return p 71 } 72 73 type keyIndexable interface { 74 setIntoKey(v bool) keyIndexable 75 } 76 77 func constructPath(p Path, str string) (Path, error) { 78 if len(str) == 0 { 79 return p, nil 80 } 81 82 op, tail := str[0], str[1:] 83 84 switch op { 85 case '.': 86 idx := fieldNameComponentRe.FindIndex([]byte(tail)) 87 if idx == nil { 88 return Path{}, errors.New("invalid field: " + tail) 89 } 90 p = append(p, FieldPath{tail[:idx[1]]}) 91 return constructPath(p, tail[idx[1]:]) 92 93 case '[': 94 if len(tail) == 0 { 95 return Path{}, errors.New("path ends in [") 96 } 97 98 idx, h, rem, err := ParsePathIndex(tail) 99 if err != nil { 100 return Path{}, err 101 } 102 if !strings.HasPrefix(rem, "]") { 103 return Path{}, errors.New("[ is missing closing ]") 104 } 105 d.PanicIfTrue(idx == nil && h.IsEmpty()) 106 d.PanicIfTrue(idx != nil && !h.IsEmpty()) 107 108 if idx != nil { 109 p = append(p, NewIndexPath(idx)) 110 } else { 111 p = append(p, NewHashIndexPath(h)) 112 } 113 return constructPath(p, rem[1:]) 114 115 case '@': 116 ann, hasArg, arg, rem := getAnnotation(tail) 117 118 switch ann { 119 case "at": 120 if arg == "" { 121 return Path{}, fmt.Errorf("@at annotation requires a position argument") 122 } 123 idx, err := strconv.ParseInt(arg, 10, 64) 124 if err != nil { 125 return Path{}, fmt.Errorf("invalid position: %s", arg) 126 } 127 return constructPath(append(p, NewAtAnnotation(idx)), rem) 128 129 case "key": 130 if hasArg { 131 return Path{}, fmt.Errorf("@key annotation does not support arguments") 132 } 133 if len(p) == 0 { 134 return Path{}, fmt.Errorf("cannot use @key annotation at beginning of path") 135 } 136 lastPart := p[len(p)-1] 137 if ki, ok := lastPart.(keyIndexable); ok { 138 p[len(p)-1] = ki.setIntoKey(true).(PathPart) 139 return constructPath(p, rem) 140 } 141 return Path{}, fmt.Errorf("cannot use @key annotation on: %s", lastPart.String()) 142 143 case "target": 144 if hasArg { 145 return Path{}, fmt.Errorf("@target annotation does not support arguments") 146 } 147 return constructPath(append(p, TargetAnnotation{}), rem) 148 149 case "type": 150 if hasArg { 151 return Path{}, fmt.Errorf("@type annotation does not support arguments") 152 } 153 return constructPath(append(p, TypeAnnotation{}), rem) 154 155 default: 156 return Path{}, fmt.Errorf("unsupported annotation: @%s", ann) 157 } 158 159 case ']': 160 return Path{}, errors.New("] is missing opening [") 161 162 default: 163 return Path{}, fmt.Errorf("invalid operator: %c", op) 164 } 165 } 166 167 // Resolve resolves a path relative to some value. 168 // A ValueReader is required to resolve paths that contain the @target annotation. 169 func (p Path) Resolve(ctx context.Context, v Value, vr ValueReader) (Value, error) { 170 resolved := v 171 for _, part := range p { 172 if resolved == nil { 173 break 174 } 175 176 var err error 177 resolved, err = part.Resolve(ctx, resolved, vr) 178 179 if err != nil { 180 return nil, err 181 } 182 } 183 184 return resolved, nil 185 } 186 187 func (p Path) Equals(o Path) bool { 188 if len(p) != len(o) { 189 return false 190 } 191 for i, pp := range p { 192 if pp != o[i] { 193 return false 194 } 195 } 196 return true 197 } 198 199 // Append makes a copy of a p and appends the PathPart 'pp' to it. 200 func (p Path) Append(pp PathPart) Path { 201 p1 := make(Path, len(p), len(p)+1) 202 copy(p1, p) 203 return append(p1, pp) 204 } 205 206 func (p Path) String() string { 207 strs := make([]string, 0, len(p)) 208 for _, part := range p { 209 strs = append(strs, part.String()) 210 } 211 return strings.Join(strs, "") 212 } 213 214 func (p Path) IsEmpty() bool { 215 return len(p) == 0 216 } 217 218 // FieldPath references Struct field values by name. 219 type FieldPath struct { 220 // The name of the field, e.g. `.Name`. 221 Name string 222 } 223 224 func NewFieldPath(name string) FieldPath { 225 return FieldPath{name} 226 } 227 228 func (fp FieldPath) Resolve(ctx context.Context, v Value, vr ValueReader) (Value, error) { 229 switch v := v.(type) { 230 case Struct: 231 if sv, ok, err := v.MaybeGet(fp.Name); err != nil { 232 return nil, err 233 } else if ok { 234 return sv, nil 235 } 236 case *Type: 237 if desc, ok := v.Desc.(StructDesc); ok { 238 if df, _ := desc.Field(fp.Name); df != nil { 239 return df, nil 240 } 241 } 242 } 243 return nil, nil 244 } 245 246 func (fp FieldPath) String() string { 247 return fmt.Sprintf(".%s", fp.Name) 248 } 249 250 // IndexPath ndexes into Maps and Lists by key or index. 251 type IndexPath struct { 252 // The value of the index, e.g. `[42]` or `["value"]`. If Index is a negative 253 // number and the path is resolved in a List, it means index from the back. 254 Index Value 255 // Whether this index should resolve to the key of a map, given by a `@key` 256 // annotation. Typically IntoKey is false, and indices would resolve to the 257 // values. E.g. given `{a: 42}` then `["a"]` resolves to `42`. If IntoKey is 258 // true, then it resolves to `"a"`. For IndexPath this isn't particularly 259 // useful - it's mostly provided for consistency with HashIndexPath - but 260 // note that given `{a: 42}` then `["b"]` resolves to nil, not `"b"`. 261 IntoKey bool 262 } 263 264 func NewIndexPath(idx Value) IndexPath { 265 return newIndexPath(idx, false) 266 } 267 268 func ValueCanBePathIndex(v Value) bool { 269 k := v.Kind() 270 return k == StringKind || k == BoolKind || k == FloatKind 271 } 272 273 func newIndexPath(idx Value, intoKey bool) IndexPath { 274 d.PanicIfFalse(ValueCanBePathIndex(idx)) 275 return IndexPath{idx, intoKey} 276 } 277 278 func (ip IndexPath) Resolve(ctx context.Context, v Value, vr ValueReader) (Value, error) { 279 seqIndex := func(getter func(i uint64) (Value, error)) (Value, error) { 280 n, ok := ip.Index.(Float) 281 if !ok { 282 return nil, nil 283 } 284 f := float64(n) 285 if f != math.Trunc(f) { 286 return nil, nil 287 } 288 ai, ok := getAbsoluteIndex(v, int64(f)) 289 if !ok { 290 return nil, nil 291 } 292 if ip.IntoKey { 293 return Float(ai), nil 294 } 295 return getter(ai) 296 } 297 298 switch v := v.(type) { 299 case List: 300 return seqIndex(func(i uint64) (Value, error) { return v.Get(ctx, i) }) 301 case *Type: 302 if cd, ok := v.Desc.(CompoundDesc); ok { 303 return seqIndex(func(i uint64) (Value, error) { return cd.ElemTypes[i], nil }) 304 } 305 case Map: 306 if !ip.IntoKey { 307 v, _, err := v.MaybeGet(ctx, ip.Index) 308 return v, err 309 } 310 311 if has, err := v.Has(ctx, ip.Index); err != nil { 312 return nil, err 313 } else if has { 314 return ip.Index, nil 315 } 316 } 317 318 return nil, nil 319 } 320 321 func (ip IndexPath) String() (str string) { 322 valStr, err := EncodedIndexValue(context.Background(), ip.Index) 323 324 if err != nil { 325 return "error: " + err.Error() 326 } 327 328 str = fmt.Sprintf("[%s]", valStr) 329 if ip.IntoKey { 330 str += "@key" 331 } 332 return 333 } 334 335 func (ip IndexPath) setIntoKey(v bool) keyIndexable { 336 ip.IntoKey = v 337 return ip 338 } 339 340 // Indexes into Maps by the hash of a key, or a Set by the hash of a value. 341 type HashIndexPath struct { 342 // The hash of the key or value to search for. Maps and Set are ordered, so 343 // this in O(log(size)). 344 Hash hash.Hash 345 // Whether this index should resolve to the key of a map, given by a `@key` 346 // annotation. Typically IntoKey is false, and indices would resolve to the 347 // values. E.g. given `{a: 42}` and if the hash of `"a"` is `#abcd`, then 348 // `[#abcd]` resolves to `42`. If IntoKey is true, then it resolves to `"a"`. 349 // This is useful for when Map keys aren't primitive values, e.g. a struct, 350 // since struct literals can't be spelled using a Path. 351 IntoKey bool 352 } 353 354 func NewHashIndexPath(h hash.Hash) HashIndexPath { 355 return newHashIndexPath(h, false) 356 } 357 358 func newHashIndexPath(h hash.Hash, intoKey bool) HashIndexPath { 359 d.PanicIfTrue(h.IsEmpty()) 360 return HashIndexPath{h, intoKey} 361 } 362 363 func (hip HashIndexPath) Resolve(ctx context.Context, v Value, vr ValueReader) (Value, error) { 364 var seq orderedSequence 365 var getCurrentValue func(cur *sequenceCursor) (Value, error) 366 367 switch v := v.(type) { 368 case Set: 369 // Unclear what the behavior should be if |hip.IntoKey| is true, but ignoring it for sets is arguably correct. 370 seq = v.orderedSequence 371 getCurrentValue = func(cur *sequenceCursor) (Value, error) { 372 item, err := cur.current() 373 374 if err != nil { 375 return nil, err 376 } 377 378 return item.(Value), nil 379 } 380 case Map: 381 seq = v.orderedSequence 382 if hip.IntoKey { 383 getCurrentValue = func(cur *sequenceCursor) (Value, error) { 384 item, err := cur.current() 385 386 if err != nil { 387 return nil, err 388 } 389 390 return item.(mapEntry).key, nil 391 } 392 } else { 393 getCurrentValue = func(cur *sequenceCursor) (Value, error) { 394 item, err := cur.current() 395 396 if err != nil { 397 return nil, err 398 } 399 400 return item.(mapEntry).value, nil 401 } 402 } 403 default: 404 return nil, nil 405 } 406 407 cur, err := newCursorAt(ctx, seq, orderedKeyFromHash(hip.Hash), false, false) 408 409 if err != nil { 410 return nil, err 411 } 412 413 if !cur.valid() { 414 return nil, nil 415 } 416 417 currKey, err := getCurrentKey(cur) 418 419 if err != nil { 420 return nil, err 421 } 422 423 if currKey.h != hip.Hash { 424 return nil, nil 425 } 426 427 return getCurrentValue(cur) 428 } 429 430 func (hip HashIndexPath) String() (str string) { 431 str = fmt.Sprintf("[#%s]", hip.Hash.String()) 432 if hip.IntoKey { 433 str += "@key" 434 } 435 return 436 } 437 438 func (hip HashIndexPath) setIntoKey(v bool) keyIndexable { 439 hip.IntoKey = v 440 return hip 441 } 442 443 // Parse a Noms value from the path index syntax. 444 // 4 -> types.Float 445 // "4" -> types.String 446 // true|false -> types.Boolean 447 // #<chars> -> hash.Hash 448 func ParsePathIndex(str string) (idx Value, h hash.Hash, rem string, err error) { 449 Switch: 450 switch str[0] { 451 case '"': 452 // String is complicated because ] might be quoted, and " or \ might be escaped. 453 stringBuf := bytes.Buffer{} 454 i := 1 455 456 for ; i < len(str); i++ { 457 c := str[i] 458 if c == '"' { 459 i++ 460 break 461 } 462 if c == '\\' && i < len(str)-1 { 463 i++ 464 c = str[i] 465 if c != '\\' && c != '"' { 466 err = errors.New(`only " and \ can be escaped`) 467 break Switch 468 } 469 } 470 stringBuf.WriteByte(c) 471 } 472 473 idx = String(stringBuf.String()) 474 rem = str[i:] 475 476 default: 477 idxStr := str 478 sepIdx := strings.Index(str, "]") 479 if sepIdx >= 0 { 480 idxStr = str[:sepIdx] 481 rem = str[sepIdx:] 482 } 483 if len(idxStr) == 0 { 484 err = errors.New("empty index value") 485 } else if idxStr[0] == '#' { 486 hashStr := idxStr[1:] 487 h, _ = hash.MaybeParse(hashStr) 488 if h.IsEmpty() { 489 err = errors.New("invalid hash: " + hashStr) 490 } 491 } else if idxStr == "true" { 492 idx = Bool(true) 493 } else if idxStr == "false" { 494 idx = Bool(false) 495 } else if i, err2 := strconv.ParseFloat(idxStr, 64); err2 == nil { 496 // Should we be more strict here? ParseFloat allows leading and trailing dots, and exponents. 497 idx = Float(i) 498 } else { 499 err = errors.New("invalid index: " + idxStr) 500 } 501 } 502 503 return 504 } 505 506 // TypeAnnotation is a PathPart annotation to resolve to the type of the value 507 // it's resolved in. 508 type TypeAnnotation struct { 509 } 510 511 func (ann TypeAnnotation) Resolve(ctx context.Context, v Value, vr ValueReader) (Value, error) { 512 return TypeOf(v) 513 } 514 515 func (ann TypeAnnotation) String() string { 516 return "@type" 517 } 518 519 // TargetAnnotation is a PathPart annotation to resolve to the targetValue of the Ref it is resolved on. 520 type TargetAnnotation struct { 521 } 522 523 func (ann TargetAnnotation) Resolve(ctx context.Context, v Value, vr ValueReader) (Value, error) { 524 if vr == nil { 525 d.Panic("@target annotation requires a database to resolve against") 526 } 527 if r, ok := v.(Ref); ok { 528 return r.TargetValue(ctx, vr) 529 } else { 530 return nil, nil 531 } 532 } 533 534 func (ann TargetAnnotation) String() string { 535 return "@target" 536 } 537 538 // AtAnnotation is a PathPart annotation that gets the value of a collection at 539 // a position, rather than a key. This is equivalent to IndexPath for lists, 540 // but different for sets and maps. 541 type AtAnnotation struct { 542 // Index is the position to resolve at. If negative, it means an index 543 // relative to the end of the collection. 544 Index int64 545 // IntoKey see IndexPath.IntoKey. 546 IntoKey bool 547 } 548 549 func NewAtAnnotation(idx int64) AtAnnotation { 550 return AtAnnotation{idx, false} 551 } 552 553 func (ann AtAnnotation) Resolve(ctx context.Context, v Value, vr ValueReader) (Value, error) { 554 ai, ok := getAbsoluteIndex(v, ann.Index) 555 if !ok { 556 return nil, nil 557 } 558 559 switch v := v.(type) { 560 case List: 561 if !ann.IntoKey { 562 return v.Get(ctx, ai) 563 } 564 case Set: 565 return v.At(ctx, ai) 566 case Map: 567 k, mapv, err := v.At(ctx, ai) 568 569 if err != nil { 570 return nil, err 571 } 572 573 if ann.IntoKey { 574 return k, nil 575 } 576 577 return mapv, nil 578 case *Type: 579 if cd, ok := v.Desc.(CompoundDesc); ok { 580 return cd.ElemTypes[ai], nil 581 } 582 } 583 584 return nil, nil 585 } 586 587 func (ann AtAnnotation) String() (str string) { 588 str = fmt.Sprintf("@at(%d)", ann.Index) 589 if ann.IntoKey { 590 str += "@key" 591 } 592 return 593 } 594 595 func (ann AtAnnotation) setIntoKey(v bool) keyIndexable { 596 ann.IntoKey = v 597 return ann 598 } 599 600 func getAnnotation(str string) (ann string, hasArg bool, arg, rem string) { 601 parts := annotationRe.FindStringSubmatch(str) 602 if parts == nil { 603 return 604 } 605 606 ann = parts[1] 607 hasArg = parts[2] != "" 608 arg = parts[3] 609 rem = str[len(parts[0]):] 610 return 611 } 612 613 func getAbsoluteIndex(v Value, relIdx int64) (absIdx uint64, ok bool) { 614 var l uint64 615 switch v := v.(type) { 616 case Collection: 617 l = v.Len() 618 case *Type: 619 if cd, cdOK := v.Desc.(CompoundDesc); cdOK { 620 l = uint64(len(cd.ElemTypes)) 621 } else { 622 return 623 } 624 default: 625 return 626 } 627 628 if relIdx < 0 { 629 if uint64(-relIdx) > l { 630 return 631 } 632 absIdx = l - uint64(-relIdx) 633 } else { 634 if uint64(relIdx) >= l { 635 return 636 } 637 absIdx = uint64(relIdx) 638 } 639 640 ok = true 641 return 642 }