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

     1  /*
     2   * Copyright (c) 2021-present Sigma-Soft, Ltd.
     3   * @author: Nikolay Nikitin
     4   */
     5  
     6  package qnames
     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 newQNames() *QNames {
    23  	return &QNames{
    24  		qNames: make(map[appdef.QName]QNameID),
    25  		ids:    make(map[QNameID]appdef.QName),
    26  		lastID: QNameIDSysLast,
    27  	}
    28  }
    29  
    30  // Returns ID for specified QName
    31  func (names *QNames) ID(qName appdef.QName) (QNameID, error) {
    32  	if id, ok := names.qNames[qName]; ok {
    33  		return id, nil
    34  	}
    35  	return 0, fmt.Errorf("unknown QName «%v»: %w", qName, ErrNameNotFound)
    36  }
    37  
    38  // Retrieve QName for specified ID
    39  func (names *QNames) QName(id QNameID) (qName appdef.QName, err error) {
    40  	qName, ok := names.ids[id]
    41  	if ok {
    42  		return qName, nil
    43  	}
    44  
    45  	return appdef.NullQName, fmt.Errorf("unknown QName ID «%v»: %w", id, ErrIDNotFound)
    46  }
    47  
    48  // Reads all application QNames from storage, add all system and application QNames and write result to storage if some changes. Must be called at application starts
    49  func (names *QNames) Prepare(storage istorage.IAppStorage, versions *vers.Versions, appDef appdef.IAppDef, resources istructs.IResources) error {
    50  	if err := names.load(storage, versions); err != nil {
    51  		return err
    52  	}
    53  
    54  	if err := names.collectAll(appDef, resources); err != nil {
    55  		return err
    56  	}
    57  
    58  	if names.changes > 0 {
    59  		if err := names.store(storage, versions); err != nil {
    60  			return err
    61  		}
    62  	}
    63  
    64  	return nil
    65  }
    66  
    67  // Collect all system and application QName IDs
    68  func (names *QNames) collectAll(appDef appdef.IAppDef, r istructs.IResources) (err error) {
    69  
    70  	// system QNames
    71  	names.
    72  		collectSys(appdef.NullQName, NullQNameID).
    73  		collectSys(istructs.QNameForError, QNameIDForError).
    74  		collectSys(istructs.QNameCommandCUD, QNameIDCommandCUD).
    75  		collectSys(istructs.QNameForCorruptedData, QNameIDForCorruptedData)
    76  
    77  	if appDef != nil {
    78  		appDef.Types(
    79  			func(t appdef.IType) {
    80  				err = errors.Join(err,
    81  					names.collect(t.QName()))
    82  				if uu, ok := t.(appdef.IUniques); ok {
    83  					for _, u := range uu.Uniques() {
    84  						err = errors.Join(err,
    85  							names.collect(u.Name()))
    86  					}
    87  				}
    88  			})
    89  	}
    90  
    91  	if r != nil {
    92  		r.Resources(
    93  			func(q appdef.QName) {
    94  				err = errors.Join(err,
    95  					names.collect(q))
    96  			})
    97  	}
    98  
    99  	return err
   100  }
   101  
   102  // Checks is exists ID for application QName in cache. If not then adds it with new ID
   103  func (names *QNames) collect(qName appdef.QName) error {
   104  	if _, ok := names.qNames[qName]; ok {
   105  		return nil // already known QName
   106  	}
   107  
   108  	for id := names.lastID + 1; id < MaxAvailableQNameID; id++ {
   109  		if _, ok := names.ids[id]; !ok {
   110  			names.qNames[qName] = id
   111  			names.ids[id] = qName
   112  			names.lastID = id
   113  			names.changes++
   114  			return nil
   115  		}
   116  	}
   117  
   118  	return ErrQNameIDsExceeds
   119  }
   120  
   121  // Adds system QName to cache
   122  func (names *QNames) collectSys(qName appdef.QName, id QNameID) *QNames {
   123  	names.qNames[qName] = id
   124  	names.ids[id] = qName
   125  	return names
   126  }
   127  
   128  // loads all stored QNames from storage
   129  func (names *QNames) load(storage istorage.IAppStorage, versions *vers.Versions) (err error) {
   130  
   131  	ver := versions.Get(vers.SysQNamesVersion)
   132  	switch ver {
   133  	case vers.UnknownVersion: // no sys.QName storage exists
   134  		return nil
   135  	case ver01:
   136  		return names.load01(storage)
   137  	}
   138  
   139  	return fmt.Errorf("unknown version of QNames system view (%v): %w", ver, vers.ErrorInvalidVersion)
   140  }
   141  
   142  // loads all stored QNames from storage version ver01
   143  func (names *QNames) load01(storage istorage.IAppStorage) error {
   144  
   145  	readQName := func(cCols, value []byte) error {
   146  		qName, err := appdef.ParseQName(string(cCols))
   147  		if err != nil {
   148  			return err
   149  		}
   150  		id := binary.BigEndian.Uint16(value)
   151  		if id == NullQNameID {
   152  			return nil // deleted QName
   153  		}
   154  
   155  		if id <= QNameIDSysLast {
   156  			return fmt.Errorf("unexpected ID (%v) is loaded from QNames system view: %w", id, ErrWrongQNameID)
   157  		}
   158  
   159  		names.qNames[qName] = id
   160  		names.ids[id] = qName
   161  
   162  		if names.lastID < id {
   163  			names.lastID = id
   164  		}
   165  
   166  		return nil
   167  	}
   168  	pKey := utils.ToBytes(consts.SysView_QNames, ver01)
   169  	return storage.Read(context.Background(), pKey, nil, nil, readQName)
   170  }
   171  
   172  // Stores all known QNames to storage
   173  func (names *QNames) store(storage istorage.IAppStorage, versions *vers.Versions) (err error) {
   174  	pKey := utils.ToBytes(consts.SysView_QNames, ver01)
   175  
   176  	batch := make([]istorage.BatchItem, 0)
   177  	for qName, id := range names.qNames {
   178  		if (id > QNameIDSysLast) ||
   179  			(qName != appdef.NullQName) && (id == NullQNameID) { // deleted QName
   180  			item := istorage.BatchItem{
   181  				PKey:  pKey,
   182  				CCols: []byte(qName.String()),
   183  				Value: utils.ToBytes(id),
   184  			}
   185  			batch = append(batch, item)
   186  		}
   187  	}
   188  
   189  	if err = storage.PutBatch(batch); err != nil {
   190  		return fmt.Errorf("error store application QName IDs to storage: %w", err)
   191  	}
   192  
   193  	if ver := versions.Get(vers.SysQNamesVersion); ver != latestVersion {
   194  		if err = versions.Put(vers.SysQNamesVersion, latestVersion); err != nil {
   195  			return fmt.Errorf("error store QNames system view version: %w", err)
   196  		}
   197  	}
   198  
   199  	names.changes = 0
   200  	return nil
   201  }