github.com/ethersphere/bee/v2@v2.2.0/pkg/shed/schema.go (about) 1 // Copyright 2018 The go-ethereum Authors 2 // This file is part of the go-ethereum library. 3 // 4 // The go-ethereum library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The go-ethereum library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 16 17 package shed 18 19 import ( 20 "encoding/json" 21 "errors" 22 "fmt" 23 ) 24 25 var ( 26 // LevelDB key value for storing the schema. 27 keySchema = []byte{0} 28 // LevelDB key prefix for all field type. 29 // LevelDB keys will be constructed by appending name values to this prefix. 30 keyPrefixFields byte = 1 31 // LevelDB key prefix from which indexing keys start. 32 // Every index has its own key prefix and this value defines the first one. 33 keyPrefixIndexStart byte = 2 // Q: or maybe a higher number like 7, to have more space for potential specific perfixes 34 ) 35 36 // schema is used to serialize known database structure information. 37 type schema struct { 38 Fields map[string]fieldSpec `json:"fields"` // keys are field names 39 Indexes map[byte]indexSpec `json:"indexes"` // keys are index prefix bytes 40 } 41 42 // fieldSpec holds information about a particular field. 43 // It does not need Name field as it is contained in the 44 // schema.Field map key. 45 type fieldSpec struct { 46 Type string `json:"type"` 47 } 48 49 // indexSpec holds information about a particular index. 50 // It does not contain index type, as indexes do not have type. 51 type indexSpec struct { 52 Name string `json:"name"` 53 } 54 55 // schemaFieldKey retrieves the complete LevelDB key for 56 // a particular field from the schema definition. 57 func (db *DB) schemaFieldKey(name, fieldType string) (key []byte, err error) { 58 if name == "" { 59 return nil, errors.New("field name cannot be blank") 60 } 61 if fieldType == "" { 62 return nil, errors.New("field type cannot be blank") 63 } 64 s, err := db.getSchema() 65 if err != nil { 66 return nil, fmt.Errorf("get schema: %w", err) 67 } 68 var found bool 69 for n, f := range s.Fields { 70 if n == name { 71 if f.Type != fieldType { 72 return nil, fmt.Errorf("field %q of type %q stored as %q in db", name, fieldType, f.Type) 73 } 74 break 75 } 76 } 77 if !found { 78 s.Fields[name] = fieldSpec{ 79 Type: fieldType, 80 } 81 err := db.putSchema(s) 82 if err != nil { 83 return nil, fmt.Errorf("put schema: %w", err) 84 } 85 } 86 return append([]byte{keyPrefixFields}, []byte(name)...), nil 87 } 88 89 // RenameIndex changes the schema so that an existing index name is changed 90 // while preserving its data by keeping the same internal key prefix. 91 // Renaming indexes is useful when encoding functions can be backward compatible 92 // to avoid data migrations. 93 func (db *DB) RenameIndex(name, newName string) (renamed bool, err error) { 94 if name == "" { 95 return false, errors.New("index name cannot be blank") 96 } 97 if newName == "" { 98 return false, errors.New("new index name cannot be blank") 99 } 100 if newName == name { 101 return false, nil 102 } 103 s, err := db.getSchema() 104 if err != nil { 105 return false, fmt.Errorf("get schema: %w", err) 106 } 107 for i, f := range s.Indexes { 108 if f.Name == name { 109 s.Indexes[i] = indexSpec{ 110 Name: newName, 111 } 112 return true, db.putSchema(s) 113 } 114 if f.Name == newName { 115 return true, nil 116 } 117 } 118 return false, nil 119 } 120 121 // schemaIndexID retrieves the complete LevelDB prefix for 122 // a particular index. 123 func (db *DB) schemaIndexPrefix(name string) (id byte, err error) { 124 if name == "" { 125 return 0, errors.New("index name cannot be blank") 126 } 127 s, err := db.getSchema() 128 if err != nil { 129 return 0, fmt.Errorf("get schema: %w", err) 130 } 131 nextID := keyPrefixIndexStart 132 for i, f := range s.Indexes { 133 if i >= nextID { 134 nextID = i + 1 135 } 136 if f.Name == name { 137 return i, nil 138 } 139 } 140 id = nextID 141 s.Indexes[id] = indexSpec{ 142 Name: name, 143 } 144 return id, db.putSchema(s) 145 } 146 147 // getSchema retrieves the complete schema from 148 // the database. 149 func (db *DB) getSchema() (s schema, err error) { 150 b, err := db.Get(keySchema) 151 if err != nil { 152 return s, err 153 } 154 err = json.Unmarshal(b, &s) 155 return s, err 156 } 157 158 // putSchema stores the complete schema to 159 // the database. 160 func (db *DB) putSchema(s schema) (err error) { 161 b, err := json.Marshal(s) 162 if err != nil { 163 return err 164 } 165 return db.Put(keySchema, b) 166 }