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 }