github.com/hasnat/dolt/go@v0.0.0-20210628190320-9eb5d843fbb7/store/spec/absolute_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 spec 23 24 import ( 25 "context" 26 "errors" 27 "fmt" 28 "regexp" 29 30 "github.com/dolthub/dolt/go/store/d" 31 32 "github.com/dolthub/dolt/go/store/datas" 33 "github.com/dolthub/dolt/go/store/hash" 34 "github.com/dolthub/dolt/go/store/types" 35 ) 36 37 var datasetCapturePrefixRe = regexp.MustCompile("^(" + datas.DatasetRe.String() + ")") 38 39 // AbsolutePath describes the location of a Value within a Noms database. 40 // 41 // To locate a value relative to some other value, see Path. To locate a value 42 // globally, see Spec. 43 // 44 // For more on paths, absolute paths, and specs, see: 45 // https://github.com/attic-labs/noms/blob/master/doc/spelling.md. 46 type AbsolutePath struct { 47 // Dataset is the dataset this AbsolutePath is rooted at. Only one of 48 // Dataset and Hash should be set. 49 Dataset string 50 // Hash is the hash this AbsolutePath is rooted at. Only one of Dataset and 51 // Hash should be set. 52 Hash hash.Hash 53 // Path is the relative path from Dataset or Hash. This can be empty. In 54 // that case, the AbsolutePath describes the value at either Dataset or 55 // Hash. 56 Path types.Path 57 } 58 59 // NewAbsolutePath attempts to parse 'str' and return an AbsolutePath. 60 func NewAbsolutePath(str string) (AbsolutePath, error) { 61 if len(str) == 0 { 62 return AbsolutePath{}, errors.New("empty path") 63 } 64 65 var h hash.Hash 66 var dataset string 67 var pathStr string 68 69 if str[0] == '#' { 70 tail := str[1:] 71 if len(tail) < hash.StringLen { 72 return AbsolutePath{}, errors.New("invalid hash: " + tail) 73 } 74 75 hashStr := tail[:hash.StringLen] 76 if h2, ok := hash.MaybeParse(hashStr); ok { 77 h = h2 78 } else { 79 return AbsolutePath{}, errors.New("invalid hash: " + hashStr) 80 } 81 82 pathStr = tail[hash.StringLen:] 83 } else { 84 datasetParts := datasetCapturePrefixRe.FindStringSubmatch(str) 85 if datasetParts == nil { 86 return AbsolutePath{}, fmt.Errorf("invalid dataset name: %s", str) 87 } 88 89 dataset = datasetParts[1] 90 pathStr = str[len(dataset):] 91 } 92 93 if len(pathStr) == 0 { 94 return AbsolutePath{Hash: h, Dataset: dataset}, nil 95 } 96 97 path, err := types.ParsePath(pathStr) 98 if err != nil { 99 return AbsolutePath{}, err 100 } 101 102 return AbsolutePath{Hash: h, Dataset: dataset, Path: path}, nil 103 } 104 105 // Resolve returns the Value reachable by 'p' in 'db'. 106 func (p AbsolutePath) Resolve(ctx context.Context, db datas.Database) (val types.Value) { 107 if len(p.Dataset) > 0 { 108 var ok bool 109 ds, err := db.GetDataset(ctx, p.Dataset) 110 d.PanicIfError(err) 111 112 if val, ok = ds.MaybeHead(); !ok { 113 val = nil 114 } 115 } else if !p.Hash.IsEmpty() { 116 var err error 117 val, err = db.ReadValue(ctx, p.Hash) 118 d.PanicIfError(err) 119 } else { 120 panic("Unreachable") 121 } 122 123 if val != nil && p.Path != nil { 124 var err error 125 val, err = p.Path.Resolve(ctx, val, db) 126 d.PanicIfError(err) 127 } 128 return 129 } 130 131 func (p AbsolutePath) IsEmpty() bool { 132 return p.Dataset == "" && p.Hash.IsEmpty() 133 } 134 135 func (p AbsolutePath) String() (str string) { 136 if p.IsEmpty() { 137 return "" 138 } 139 140 if len(p.Dataset) > 0 { 141 str = p.Dataset 142 } else if !p.Hash.IsEmpty() { 143 str = "#" + p.Hash.String() 144 } else { 145 panic("Unreachable") 146 } 147 148 return str + p.Path.String() 149 } 150 151 // ReadAbsolutePaths attempts to parse each path in 'paths' and resolve them. 152 // If any path fails to parse correctly or if any path can be resolved to an 153 // existing Noms Value, then this function returns (nil, error). 154 func ReadAbsolutePaths(ctx context.Context, db datas.Database, paths ...string) ([]types.Value, error) { 155 r := make([]types.Value, 0, len(paths)) 156 for _, ps := range paths { 157 p, err := NewAbsolutePath(ps) 158 if err != nil { 159 return nil, fmt.Errorf("invalid input path '%s'", ps) 160 } 161 162 v := p.Resolve(ctx, db) 163 if v == nil { 164 return nil, fmt.Errorf("input path '%s' does not exist in database", ps) 165 } 166 167 r = append(r, v) 168 } 169 return r, nil 170 }