github.com/voedger/voedger@v0.0.0-20240520144910-273e84102129/pkg/istorage/bbolt/impl.go (about)

     1  /*
     2   * Copyright (c) 2022-present Sigma-Soft, Ltd.
     3   * @author: Dmitry Molchanovsky
     4   * @author: Maxim Geraskin (refactoring)
     5   */
     6  
     7  package bbolt
     8  
     9  import (
    10  	"bytes"
    11  	"context"
    12  	"os"
    13  	"path/filepath"
    14  
    15  	bolt "go.etcd.io/bbolt"
    16  
    17  	"github.com/voedger/voedger/pkg/istorage"
    18  	coreutils "github.com/voedger/voedger/pkg/utils"
    19  )
    20  
    21  type appStorageFactory struct {
    22  	bboltParams ParamsType
    23  }
    24  
    25  func (p *appStorageFactory) AppStorage(appName istorage.SafeAppName) (s istorage.IAppStorage, err error) {
    26  	dbName := filepath.Join(p.bboltParams.DBDir, appName.String()+".db")
    27  	exists, err := coreutils.Exists(dbName)
    28  	if err != nil {
    29  		// notest
    30  		return nil, err
    31  	}
    32  	if !exists {
    33  		return nil, istorage.ErrStorageDoesNotExist
    34  	}
    35  	db, err := bolt.Open(dbName, coreutils.FileMode_rw_rw_rw_, bolt.DefaultOptions)
    36  	if err != nil {
    37  		// notest
    38  		return nil, err
    39  	}
    40  	return &appStorageType{db}, nil
    41  }
    42  
    43  func (p *appStorageFactory) Init(appName istorage.SafeAppName) error {
    44  	dbName := filepath.Join(p.bboltParams.DBDir, appName.String()+".db")
    45  	exists, err := coreutils.Exists(dbName)
    46  	if err != nil {
    47  		// notest
    48  		return err
    49  	}
    50  	if exists {
    51  		return istorage.ErrStorageAlreadyExists
    52  	}
    53  	if err = os.MkdirAll(p.bboltParams.DBDir, coreutils.FileMode_rwxrwxrwx); err != nil {
    54  		// notest
    55  		return err
    56  	}
    57  	db, err := bolt.Open(dbName, coreutils.FileMode_rw_rw_rw_, bolt.DefaultOptions)
    58  	if err != nil {
    59  		// notest
    60  		return err
    61  	}
    62  	return db.Close()
    63  }
    64  
    65  // bolt cannot use empty keys so we declare nullKey
    66  var nullKey = []byte{0}
    67  
    68  // if the key is empty or equal to nil, then convert it to nullKey
    69  func safeKey(value []byte) []byte {
    70  	if len(value) == 0 {
    71  		return nullKey
    72  	}
    73  	return value
    74  }
    75  
    76  // if the key is nullKey, then convert it to nil
    77  func unSafeKey(value []byte) []byte {
    78  	if len(value) == 0 || (len(value) == 1 && value[0] == 0) {
    79  		return nil
    80  	}
    81  	return value
    82  }
    83  
    84  // implemetation for istorage.IAppStorage.
    85  type appStorageType struct {
    86  	db *bolt.DB
    87  }
    88  
    89  // istorage.IAppStorage.Put(pKey []byte, cCols []byte, value []byte) (err error)
    90  func (s *appStorageType) Put(pKey []byte, cCols []byte, value []byte) (err error) {
    91  	err = s.db.Update(func(tx *bolt.Tx) error {
    92  		b, e := tx.CreateBucketIfNotExists(pKey)
    93  		if e != nil {
    94  			// notest
    95  			return e
    96  		}
    97  		return b.Put(safeKey(cCols), unSafeKey(value))
    98  	})
    99  	return err
   100  }
   101  
   102  // istorage.IAppStorage.PutBatch(items []BatchItem) (err error)
   103  func (s *appStorageType) PutBatch(items []istorage.BatchItem) (err error) {
   104  	err = s.db.Update(func(tx *bolt.Tx) error {
   105  
   106  		for i := 0; i < len(items); i++ {
   107  
   108  			PKey := items[i].PKey
   109  			b, e := tx.CreateBucketIfNotExists(PKey)
   110  			if e != nil {
   111  				// notest
   112  				return e
   113  			}
   114  
   115  			e = b.Put(safeKey(items[i].CCols), items[i].Value)
   116  			if e != nil {
   117  				return e
   118  			}
   119  		}
   120  
   121  		return nil
   122  	})
   123  
   124  	return err
   125  }
   126  
   127  // istorage.IAppStorage.Get(pKey []byte, cCols []byte, data *[]byte) (ok bool, err error)
   128  func (s *appStorageType) Get(pKey []byte, cCols []byte, data *[]byte) (ok bool, err error) {
   129  	*data = (*data)[0:0]
   130  
   131  	err = s.db.View(func(tx *bolt.Tx) error {
   132  		ok = false
   133  		bucket := tx.Bucket(pKey)
   134  		if bucket == nil {
   135  			return nil
   136  		}
   137  
   138  		v := bucket.Get(safeKey(cCols))
   139  		if v == nil {
   140  			return nil
   141  		}
   142  		*data = append(*data, v...)
   143  		ok = true
   144  		return nil
   145  	})
   146  
   147  	return ok, err
   148  }
   149  
   150  // istorage.IAppStorage.Read(ctx context.Context, pKey []byte, startCCols []byte, finishCCols []byte, cb ReadCallback) (err error)
   151  func (s *appStorageType) Read(ctx context.Context, pKey []byte, startCCols, finishCCols []byte, cb istorage.ReadCallback) (err error) {
   152  
   153  	if (len(startCCols) > 0) && (len(finishCCols) > 0) && (bytes.Compare(startCCols, finishCCols) >= 0) {
   154  		return nil // absurd range
   155  	}
   156  
   157  	err = s.db.View(func(tx *bolt.Tx) error {
   158  
   159  		startCCols = unSafeKey(startCCols)
   160  		finishCCols = unSafeKey(finishCCols)
   161  
   162  		bucket := tx.Bucket(pKey)
   163  		if bucket == nil {
   164  			return nil
   165  		}
   166  
   167  		var (
   168  			k []byte
   169  			v []byte
   170  		)
   171  
   172  		cr := bucket.Cursor()
   173  		if startCCols == nil {
   174  			k, v = cr.First()
   175  		} else {
   176  			k, v = cr.Seek(startCCols)
   177  		}
   178  
   179  		var e error
   180  
   181  		for (k != nil) && (finishCCols == nil || string(k) <= string(finishCCols)) {
   182  
   183  			if ctx.Err() != nil {
   184  				return nil
   185  			}
   186  
   187  			if cb != nil {
   188  				e = cb(unSafeKey(k), unSafeKey(v))
   189  				if e != nil {
   190  					return e
   191  				}
   192  			}
   193  			k, v = cr.Next()
   194  		}
   195  
   196  		return nil
   197  	})
   198  
   199  	return err
   200  }
   201  
   202  // istorage.IAppStorage.GetBatch(pKey []byte, items []GetBatchItem) (err error)
   203  func (s *appStorageType) GetBatch(pKey []byte, items []istorage.GetBatchItem) (err error) {
   204  	err = s.db.View(func(tx *bolt.Tx) error {
   205  
   206  		bucket := tx.Bucket(pKey)
   207  		if bucket == nil {
   208  			for i := 0; i < len(items); i++ {
   209  				items[i].Ok = false
   210  			}
   211  			return nil
   212  		}
   213  		for i := 0; i < len(items); i++ {
   214  			v := bucket.Get(safeKey(items[i].CCols))
   215  			items[i].Ok = v != nil
   216  			*items[i].Data = append((*items[i].Data)[0:0], v...)
   217  		}
   218  		return nil
   219  	})
   220  
   221  	return err
   222  }