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  }