github.com/voedger/voedger@v0.0.0-20240520144910-273e84102129/pkg/istructsmem/internal/singletons/impl.go (about)

     1  /*
     2   * Copyright (c) 2021-present Sigma-Soft, Ltd.
     3   * @author: Nikolay Nikitin
     4   */
     5  
     6  package singletons
     7  
     8  import (
     9  	"context"
    10  	"encoding/binary"
    11  	"errors"
    12  	"fmt"
    13  
    14  	"github.com/voedger/voedger/pkg/appdef"
    15  	"github.com/voedger/voedger/pkg/istorage"
    16  	"github.com/voedger/voedger/pkg/istructs"
    17  	"github.com/voedger/voedger/pkg/istructsmem/internal/consts"
    18  	"github.com/voedger/voedger/pkg/istructsmem/internal/utils"
    19  	"github.com/voedger/voedger/pkg/istructsmem/internal/vers"
    20  )
    21  
    22  func newSingletons() *Singletons {
    23  	return &Singletons{
    24  		qNames: make(map[appdef.QName]istructs.RecordID),
    25  		ids:    make(map[istructs.RecordID]appdef.QName),
    26  		lastID: istructs.FirstSingletonID - 1,
    27  	}
    28  }
    29  
    30  // Returns ID for singleton with specified QName
    31  func (st *Singletons) ID(qName appdef.QName) (istructs.RecordID, error) {
    32  	if id, ok := st.qNames[qName]; ok {
    33  		return id, nil
    34  	}
    35  	return istructs.NullRecordID, fmt.Errorf("unable to find singleton ID for type «%v»: %w", qName, ErrNameNotFound)
    36  }
    37  
    38  // Loads all singletons IDs from storage, add all known application singletons and store if some changes.
    39  // Must be called at application starts
    40  func (st *Singletons) Prepare(storage istorage.IAppStorage, versions *vers.Versions, appDef appdef.IAppDef) (err error) {
    41  	if err = st.load(storage, versions); err != nil {
    42  		return err
    43  	}
    44  
    45  	if appDef != nil {
    46  		if err = st.collectAllSingletons(appDef); err != nil {
    47  			return err
    48  		}
    49  	}
    50  
    51  	if st.changes > 0 {
    52  		if err := st.store(storage, versions); err != nil {
    53  			return err
    54  		}
    55  	}
    56  
    57  	return nil
    58  }
    59  
    60  // loads all stored singleton IDs from storage
    61  func (st *Singletons) load(storage istorage.IAppStorage, versions *vers.Versions) (err error) {
    62  	ver := versions.Get(vers.SysSingletonsVersion)
    63  	switch ver {
    64  	case vers.UnknownVersion: // no system singletons view exists in storage
    65  		return nil
    66  	case ver01:
    67  		return st.load01(storage)
    68  	}
    69  
    70  	return fmt.Errorf("unable load singleton IDs from system view version %v: %w", ver, vers.ErrorInvalidVersion)
    71  }
    72  
    73  // Loads singletons IDs from storage using ver01 codec
    74  func (st *Singletons) load01(storage istorage.IAppStorage) error {
    75  
    76  	readSingleton := func(cCols, value []byte) error {
    77  		qName, err := appdef.ParseQName(string(cCols))
    78  		if err != nil {
    79  			return err
    80  		}
    81  		id := istructs.RecordID(binary.BigEndian.Uint64(value))
    82  
    83  		st.qNames[qName] = id
    84  		st.ids[id] = qName
    85  
    86  		if st.lastID < id {
    87  			st.lastID = id
    88  		}
    89  
    90  		return nil
    91  	}
    92  
    93  	pKey := utils.ToBytes(consts.SysView_SingletonIDs, ver01)
    94  	return storage.Read(context.Background(), pKey, nil, nil, readSingleton)
    95  }
    96  
    97  // Collect all application singleton IDs
    98  func (st *Singletons) collectAllSingletons(appDef appdef.IAppDef) (err error) {
    99  	appDef.Types(
   100  		func(t appdef.IType) {
   101  			if singleton, ok := t.(appdef.ISingleton); ok {
   102  				if singleton.Singleton() {
   103  					err = errors.Join(err,
   104  						st.collectSingleton(singleton.QName()))
   105  				}
   106  			}
   107  		})
   108  
   109  	return err
   110  }
   111  
   112  // collectSingleton checks is singleton in cache. If not then adds it with new ID
   113  func (st *Singletons) collectSingleton(qname appdef.QName) error {
   114  
   115  	if _, ok := st.qNames[qname]; ok {
   116  		return nil // already known singleton
   117  	}
   118  
   119  	for id := st.lastID + 1; id < istructs.MaxSingletonID; id++ {
   120  		if _, ok := st.ids[id]; !ok {
   121  			st.qNames[qname] = id
   122  			st.ids[id] = qname
   123  			st.lastID = id
   124  			st.changes++
   125  			return nil
   126  		}
   127  	}
   128  
   129  	return ErrSingletonIDsExceeds
   130  }
   131  
   132  // stores singletons IDs using latestVersion codec
   133  func (st *Singletons) store(storage istorage.IAppStorage, versions *vers.Versions) (err error) {
   134  	pKey := utils.ToBytes(consts.SysView_SingletonIDs, latestVersion)
   135  
   136  	batch := make([]istorage.BatchItem, 0)
   137  	for qName, id := range st.qNames {
   138  		if id >= istructs.FirstSingletonID {
   139  			item := istorage.BatchItem{
   140  				PKey:  pKey,
   141  				CCols: []byte(qName.String()),
   142  				Value: utils.ToBytes(uint64(id)),
   143  			}
   144  			batch = append(batch, item)
   145  		}
   146  	}
   147  
   148  	if err = storage.PutBatch(batch); err != nil {
   149  		return fmt.Errorf("error store application singleton IDs to storage: %w", err)
   150  	}
   151  
   152  	if ver := versions.Get(vers.SysSingletonsVersion); ver != latestVersion {
   153  		if err = versions.Put(vers.SysSingletonsVersion, latestVersion); err != nil {
   154  			return fmt.Errorf("error store singletons system view version: %w", err)
   155  		}
   156  	}
   157  
   158  	st.changes = 0
   159  	return nil
   160  }