github.com/voedger/voedger@v0.0.0-20240520144910-273e84102129/pkg/sys/collection/collection.go (about) 1 /* 2 * Copyright (c) 2021-present unTill Pro, Ltd. 3 * 4 * @author Michael Saigachenko 5 */ 6 7 package collection 8 9 import ( 10 "github.com/voedger/voedger/pkg/goutils/iterate" 11 12 "github.com/voedger/voedger/pkg/appdef" 13 "github.com/voedger/voedger/pkg/istructs" 14 "github.com/voedger/voedger/pkg/state" 15 ) 16 17 type kbAndID struct { 18 istructs.IStateKeyBuilder 19 id istructs.RecordID 20 isNew bool 21 } 22 23 func collectionProjector(appDef appdef.IAppDef) istructs.Projector { 24 return istructs.Projector{ 25 Name: QNameProjectorCollection, 26 Func: func(event istructs.IPLogEvent, s istructs.IState, intents istructs.IIntents) (err error) { 27 is := &idService{ 28 state: s, 29 cache: make(map[istructs.RecordID]istructs.IRecord), 30 } 31 32 newKey := func(docQname appdef.QName, docID, elementID istructs.RecordID) (kb istructs.IStateKeyBuilder, err error) { 33 kb, err = s.KeyBuilder(state.View, QNameCollectionView) 34 if err != nil { 35 // notest 36 return 37 } 38 kb.PutInt32(Field_PartKey, PartitionKeyCollection) 39 kb.PutQName(Field_DocQName, docQname) 40 kb.PutRecordID(field_DocID, docID) 41 kb.PutRecordID(field_ElementID, elementID) 42 return 43 } 44 45 apply := func(kb istructs.IStateKeyBuilder, record istructs.IRecord, isNew bool) (err error) { 46 if !isNew { 47 sv, ok, err := s.CanExist(kb) 48 if err != nil { 49 // notest 50 return err 51 } 52 if ok && sv.AsInt64(state.ColOffset) >= int64(event.WLogOffset()) { 53 // skip for idempotency 54 return nil 55 } 56 } 57 vb, err := intents.NewValue(kb) 58 if err != nil { 59 // notest 60 return err 61 } 62 vb.PutInt64(state.ColOffset, int64(event.WLogOffset())) 63 vb.PutRecord(Field_Record, record) 64 return nil 65 } 66 67 keyBuildersAndIDs := []kbAndID{} 68 err = iterate.ForEachError(event.CUDs, func(rec istructs.ICUDRow) error { 69 kind := appDef.Type(rec.QName()).Kind() 70 if kind != appdef.TypeKind_CDoc && kind != appdef.TypeKind_CRecord { 71 return nil 72 } 73 kb, err := is.state.KeyBuilder(state.Record, appdef.NullQName) 74 if err != nil { 75 // notest 76 return err 77 } 78 kb.PutRecordID(state.Field_ID, rec.ID()) 79 keyBuildersAndIDs = append(keyBuildersAndIDs, kbAndID{ 80 IStateKeyBuilder: kb, 81 id: rec.ID(), 82 isNew: rec.IsNew(), 83 }) 84 return nil 85 }) 86 if err != nil { 87 // notest 88 return err 89 } 90 91 keyBuilders := make([]istructs.IStateKeyBuilder, len(keyBuildersAndIDs)) 92 for i, kbID := range keyBuildersAndIDs { 93 keyBuilders[i] = kbID.IStateKeyBuilder 94 } 95 err = is.state.MustExistAll(keyBuilders, func(key istructs.IKeyBuilder, sv istructs.IStateValue, ok bool) (err error) { 96 record := sv.AsRecord("") 97 is.cache[record.ID()] = record 98 return nil 99 }) 100 if err != nil { 101 return err 102 } 103 for _, kbAndID := range keyBuildersAndIDs { 104 record := is.cache[kbAndID.id] 105 root, err := is.findRootByID(record.ID()) 106 if err != nil { 107 return err 108 } 109 elementID := record.ID() 110 if record.ID() == root.ID() { 111 elementID = istructs.NullRecordID 112 } 113 kb, err := newKey(root.QName(), root.ID(), elementID) 114 if err != nil { 115 // notest 116 return err 117 } 118 if err = apply(kb, record, kbAndID.isNew); err != nil { 119 return err 120 } 121 } 122 return nil 123 }, 124 } 125 } 126 127 type idService struct { 128 state istructs.IState 129 cache map[istructs.RecordID]istructs.IRecord 130 } 131 132 func (s *idService) findRecordByID(id istructs.RecordID) (record istructs.IRecord, err error) { 133 kb, err := s.state.KeyBuilder(state.Record, appdef.NullQName) 134 if err != nil { 135 return nil, err 136 } 137 kb.PutRecordID(state.Field_ID, id) 138 139 sv, err := s.state.MustExist(kb) 140 if err != nil { 141 return nil, err 142 } 143 return sv.AsRecord(""), nil 144 } 145 146 func (s *idService) findRootByID(id istructs.RecordID) (root istructs.IRecord, err error) { 147 rec := s.cache[id] 148 if rec == nil { 149 if rec, err = s.findRecordByID(id); err != nil { 150 return nil, err 151 } 152 s.cache[id] = rec 153 } 154 if rec.Parent() == istructs.NullRecordID { 155 return rec, nil 156 } 157 return s.findRootByID(rec.Parent()) 158 }