github.com/cockroachdb/pebble@v1.1.1-0.20240513155919-3622ade60459/objstorage/objstorageprovider/remoteobjcat/version_edit.go (about)

     1  // Copyright 2023 The LevelDB-Go and Pebble Authors. All rights reserved. Use
     2  // of this source code is governed by a BSD-style license that can be found in
     3  // the LICENSE file.
     4  
     5  package remoteobjcat
     6  
     7  import (
     8  	"bufio"
     9  	"encoding/binary"
    10  	"io"
    11  
    12  	"github.com/cockroachdb/errors"
    13  	"github.com/cockroachdb/pebble/internal/base"
    14  	"github.com/cockroachdb/pebble/internal/invariants"
    15  	"github.com/cockroachdb/pebble/objstorage"
    16  	"github.com/cockroachdb/pebble/objstorage/remote"
    17  )
    18  
    19  // VersionEdit is a modification to the remote object state which can be encoded
    20  // into a record.
    21  //
    22  // TODO(radu): consider adding creation and deletion time for debugging purposes.
    23  type VersionEdit struct {
    24  	NewObjects     []RemoteObjectMetadata
    25  	DeletedObjects []base.DiskFileNum
    26  	CreatorID      objstorage.CreatorID
    27  }
    28  
    29  const (
    30  	// tagNewObject is followed by the FileNum, creator ID, creator FileNum,
    31  	// cleanup method, optional new object tags, and ending with a 0 byte.
    32  	tagNewObject = 1
    33  	// tagDeletedObject is followed by the FileNum.
    34  	tagDeletedObject = 2
    35  	// tagCreatorID is followed by the Creator ID for this store. This ID can
    36  	// never change.
    37  	tagCreatorID = 3
    38  	// tagNewObjectLocator is an optional tag inside the tagNewObject payload. It
    39  	// is followed by the encoded length of the locator string and the string.
    40  	tagNewObjectLocator = 4
    41  	// tagNewObjectCustomName is an optional tag inside the tagNewObject payload.
    42  	// It is followed by the encoded length of the custom object name string
    43  	// followed by the string.
    44  	tagNewObjectCustomName = 5
    45  )
    46  
    47  // Object type values. We don't want to encode FileType directly because it is
    48  // more general (and we want freedom to change it in the future).
    49  const (
    50  	objTypeTable = 1
    51  )
    52  
    53  func objTypeToFileType(objType uint64) (base.FileType, error) {
    54  	switch objType {
    55  	case objTypeTable:
    56  		return base.FileTypeTable, nil
    57  	default:
    58  		return 0, errors.Newf("unknown object type %d", objType)
    59  	}
    60  }
    61  
    62  func fileTypeToObjType(fileType base.FileType) (uint64, error) {
    63  	switch fileType {
    64  	case base.FileTypeTable:
    65  		return objTypeTable, nil
    66  
    67  	default:
    68  		return 0, errors.Newf("unknown object type for file type %d", fileType)
    69  	}
    70  }
    71  
    72  // Encode encodes an edit to the specified writer.
    73  func (v *VersionEdit) Encode(w io.Writer) error {
    74  	buf := make([]byte, 0, binary.MaxVarintLen64*(len(v.NewObjects)*10+len(v.DeletedObjects)*2+2))
    75  	for _, meta := range v.NewObjects {
    76  		objType, err := fileTypeToObjType(meta.FileType)
    77  		if err != nil {
    78  			return err
    79  		}
    80  		buf = binary.AppendUvarint(buf, uint64(tagNewObject))
    81  		buf = binary.AppendUvarint(buf, uint64(meta.FileNum.FileNum()))
    82  		buf = binary.AppendUvarint(buf, objType)
    83  		buf = binary.AppendUvarint(buf, uint64(meta.CreatorID))
    84  		buf = binary.AppendUvarint(buf, uint64(meta.CreatorFileNum.FileNum()))
    85  		buf = binary.AppendUvarint(buf, uint64(meta.CleanupMethod))
    86  		if meta.Locator != "" {
    87  			buf = binary.AppendUvarint(buf, uint64(tagNewObjectLocator))
    88  			buf = encodeString(buf, string(meta.Locator))
    89  		}
    90  		if meta.CustomObjectName != "" {
    91  			buf = binary.AppendUvarint(buf, uint64(tagNewObjectCustomName))
    92  			buf = encodeString(buf, meta.CustomObjectName)
    93  		}
    94  		// Append 0 as the terminator for optional new object tags.
    95  		buf = binary.AppendUvarint(buf, 0)
    96  	}
    97  
    98  	for _, dfn := range v.DeletedObjects {
    99  		buf = binary.AppendUvarint(buf, uint64(tagDeletedObject))
   100  		buf = binary.AppendUvarint(buf, uint64(dfn.FileNum()))
   101  	}
   102  	if v.CreatorID.IsSet() {
   103  		buf = binary.AppendUvarint(buf, uint64(tagCreatorID))
   104  		buf = binary.AppendUvarint(buf, uint64(v.CreatorID))
   105  	}
   106  	_, err := w.Write(buf)
   107  	return err
   108  }
   109  
   110  // Decode decodes an edit from the specified reader.
   111  func (v *VersionEdit) Decode(r io.Reader) error {
   112  	br, ok := r.(io.ByteReader)
   113  	if !ok {
   114  		br = bufio.NewReader(r)
   115  	}
   116  	for {
   117  		tag, err := binary.ReadUvarint(br)
   118  		if err == io.EOF {
   119  			break
   120  		}
   121  		if err != nil {
   122  			return err
   123  		}
   124  
   125  		err = nil
   126  		switch tag {
   127  		case tagNewObject:
   128  			var fileNum, creatorID, creatorFileNum, cleanupMethod uint64
   129  			var locator, customName string
   130  			var fileType base.FileType
   131  			fileNum, err = binary.ReadUvarint(br)
   132  			if err == nil {
   133  				var objType uint64
   134  				objType, err = binary.ReadUvarint(br)
   135  				if err == nil {
   136  					fileType, err = objTypeToFileType(objType)
   137  				}
   138  			}
   139  			if err == nil {
   140  				creatorID, err = binary.ReadUvarint(br)
   141  			}
   142  			if err == nil {
   143  				creatorFileNum, err = binary.ReadUvarint(br)
   144  			}
   145  			if err == nil {
   146  				cleanupMethod, err = binary.ReadUvarint(br)
   147  			}
   148  			for err == nil {
   149  				var optionalTag uint64
   150  				optionalTag, err = binary.ReadUvarint(br)
   151  				if err != nil || optionalTag == 0 {
   152  					break
   153  				}
   154  
   155  				switch optionalTag {
   156  				case tagNewObjectLocator:
   157  					locator, err = decodeString(br)
   158  
   159  				case tagNewObjectCustomName:
   160  					customName, err = decodeString(br)
   161  
   162  				default:
   163  					err = errors.Newf("unknown newObject tag %d", optionalTag)
   164  				}
   165  			}
   166  
   167  			if err == nil {
   168  				v.NewObjects = append(v.NewObjects, RemoteObjectMetadata{
   169  					FileNum:          base.FileNum(fileNum).DiskFileNum(),
   170  					FileType:         fileType,
   171  					CreatorID:        objstorage.CreatorID(creatorID),
   172  					CreatorFileNum:   base.FileNum(creatorFileNum).DiskFileNum(),
   173  					CleanupMethod:    objstorage.SharedCleanupMethod(cleanupMethod),
   174  					Locator:          remote.Locator(locator),
   175  					CustomObjectName: customName,
   176  				})
   177  			}
   178  
   179  		case tagDeletedObject:
   180  			var fileNum uint64
   181  			fileNum, err = binary.ReadUvarint(br)
   182  			if err == nil {
   183  				v.DeletedObjects = append(v.DeletedObjects, base.FileNum(fileNum).DiskFileNum())
   184  			}
   185  
   186  		case tagCreatorID:
   187  			var id uint64
   188  			id, err = binary.ReadUvarint(br)
   189  			if err == nil {
   190  				v.CreatorID = objstorage.CreatorID(id)
   191  			}
   192  
   193  		default:
   194  			err = errors.Newf("unknown tag %d", tag)
   195  		}
   196  
   197  		if err != nil {
   198  			if err == io.EOF {
   199  				return errCorruptCatalog
   200  			}
   201  			return err
   202  		}
   203  	}
   204  	return nil
   205  }
   206  
   207  func encodeString(buf []byte, s string) []byte {
   208  	buf = binary.AppendUvarint(buf, uint64(len(s)))
   209  	buf = append(buf, []byte(s)...)
   210  	return buf
   211  }
   212  
   213  func decodeString(br io.ByteReader) (string, error) {
   214  	length, err := binary.ReadUvarint(br)
   215  	if err != nil || length == 0 {
   216  		return "", err
   217  	}
   218  	buf := make([]byte, length)
   219  	for i := range buf {
   220  		buf[i], err = br.ReadByte()
   221  		if err != nil {
   222  			return "", err
   223  		}
   224  	}
   225  	return string(buf), nil
   226  }
   227  
   228  var errCorruptCatalog = base.CorruptionErrorf("pebble: corrupt remote object catalog")
   229  
   230  // Apply the version edit to a creator ID and a map of objects.
   231  func (v *VersionEdit) Apply(
   232  	creatorID *objstorage.CreatorID, objects map[base.DiskFileNum]RemoteObjectMetadata,
   233  ) error {
   234  	if v.CreatorID.IsSet() {
   235  		*creatorID = v.CreatorID
   236  	}
   237  	for _, meta := range v.NewObjects {
   238  		if invariants.Enabled {
   239  			if _, exists := objects[meta.FileNum]; exists {
   240  				return errors.AssertionFailedf("version edit adds existing object %s", meta.FileNum)
   241  			}
   242  		}
   243  		objects[meta.FileNum] = meta
   244  	}
   245  	for _, fileNum := range v.DeletedObjects {
   246  		if invariants.Enabled {
   247  			if _, exists := objects[fileNum]; !exists {
   248  				return errors.AssertionFailedf("version edit deletes non-existent object %s", fileNum)
   249  			}
   250  		}
   251  		delete(objects, fileNum)
   252  	}
   253  	return nil
   254  }