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  }