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 }