github.com/voedger/voedger@v0.0.0-20240520144910-273e84102129/pkg/sys/builtin/impl_refintegrity.go (about) 1 /* 2 * Copyright (c) 2020-present unTill Pro, Ltd. 3 * @author Denis Gribanov 4 */ 5 6 package builtin 7 8 import ( 9 "context" 10 "errors" 11 "fmt" 12 "net/http" 13 14 "github.com/voedger/voedger/pkg/appdef" 15 "github.com/voedger/voedger/pkg/goutils/iterate" 16 "github.com/voedger/voedger/pkg/istructs" 17 "github.com/voedger/voedger/pkg/istructsmem" 18 "github.com/voedger/voedger/pkg/state" 19 coreutils "github.com/voedger/voedger/pkg/utils" 20 ) 21 22 func provideRefIntegrityValidation(cfg *istructsmem.AppConfigType) { 23 cfg.AddSyncProjectors(istructs.Projector{ 24 Name: qNameRecordsRegistryProjector, 25 Func: provideRecordsRegistryProjector(cfg), 26 }) 27 cfg.AddCUDValidators(provideRefIntegrityValidator()) 28 } 29 30 func CheckRefIntegrity(obj istructs.IRowReader, appStructs istructs.IAppStructs, wsid istructs.WSID) (err error) { 31 appDef := appStructs.AppDef() 32 objQName := obj.AsQName(appdef.SystemField_QName) 33 fields := appDef.Type(objQName).(appdef.IFields) 34 35 for _, refField := range fields.RefFields() { 36 targetID := obj.AsRecordID(refField.Name()) 37 if targetID == istructs.NullRecordID || targetID.IsRaw() { 38 continue 39 } 40 allowedTargetQNames := refField.Refs() 41 kb := appStructs.ViewRecords().KeyBuilder(QNameViewRecordsRegistry) 42 idHi := CrackID(targetID) 43 kb.PutInt64(Field_IDHi, int64(idHi)) 44 kb.PutRecordID(Field_ID, targetID) 45 registryRecord, err := appStructs.ViewRecords().Get(wsid, kb) 46 if err == nil { 47 if len(allowedTargetQNames) > 0 && !allowedTargetQNames.Contains(registryRecord.AsQName(field_QName)) { 48 return wrongQName(targetID, objQName, refField.Name(), registryRecord.AsQName(field_QName), allowedTargetQNames) 49 } 50 continue 51 } 52 if !errors.Is(err, istructsmem.ErrRecordNotFound) { 53 // notest 54 return err 55 } 56 return fmt.Errorf("%w: record ID %d referenced by %s.%s does not exist", ErrReferentialIntegrityViolation, targetID, objQName, refField.Name()) 57 } 58 59 return nil 60 } 61 62 func wrongQName(targetID istructs.RecordID, srcQName appdef.QName, srcField string, actualQName appdef.QName, allowedQNames appdef.QNames) error { 63 return fmt.Errorf("%w: record ID %d referenced by %s.%s is of QName %s whereas %v QNames are only allowed", ErrReferentialIntegrityViolation, 64 targetID, srcQName, srcField, actualQName, allowedQNames) 65 } 66 67 func provideRecordsRegistryProjector(cfg *istructsmem.AppConfigType) func(event istructs.IPLogEvent, st istructs.IState, intents istructs.IIntents) (err error) { 68 return func(event istructs.IPLogEvent, st istructs.IState, intents istructs.IIntents) (err error) { 69 argType := cfg.AppDef.Type(event.ArgumentObject().QName()) 70 if argType.Kind() == appdef.TypeKind_ODoc || argType.Kind() == appdef.TypeKind_ORecord { 71 if err := writeObjectToRegistry(event.ArgumentObject(), cfg.AppDef, st, intents, event.WLogOffset()); err != nil { 72 // notest 73 return err 74 } 75 } 76 return iterate.ForEachError(event.CUDs, func(rec istructs.ICUDRow) error { 77 if !rec.IsNew() { 78 return nil 79 } 80 return writeObjectToRegistry(rec, cfg.AppDef, st, intents, event.WLogOffset()) 81 }) 82 } 83 } 84 85 func writeObjectToRegistry(root istructs.IRowReader, appDef appdef.IAppDef, st istructs.IState, intents istructs.IIntents, wLogOffsetToStore istructs.Offset) error { 86 if err := writeRegistry(st, intents, root.AsRecordID(appdef.SystemField_ID), wLogOffsetToStore, root.AsQName(appdef.SystemField_QName)); err != nil { 87 // notest 88 return err 89 } 90 object, ok := root.(istructs.IObject) 91 if !ok { 92 return nil 93 } 94 return iterate.ForEachError(object.Containers, func(container string) (err error) { 95 return iterate.ForEachError1Arg(object.Children, container, func(child istructs.IObject) error { 96 elType := appDef.Type(child.QName()) 97 if elType.Kind() != appdef.TypeKind_ODoc && elType.Kind() != appdef.TypeKind_ORecord { 98 return nil 99 } 100 return writeObjectToRegistry(child, appDef, st, intents, wLogOffsetToStore) 101 }) 102 }) 103 } 104 105 func writeRegistry(st istructs.IState, intents istructs.IIntents, idToStore istructs.RecordID, wLogOffsetToStore istructs.Offset, qNameToStore appdef.QName) error { 106 kb, err := st.KeyBuilder(state.View, QNameViewRecordsRegistry) 107 if err != nil { 108 // notest 109 return err 110 } 111 idHi := CrackID(idToStore) 112 kb.PutInt64(Field_IDHi, int64(idHi)) 113 kb.PutRecordID(Field_ID, idToStore) 114 recordsRegistryRecBuilder, err := intents.NewValue(kb) 115 if err != nil { 116 // notest 117 return err 118 } 119 recordsRegistryRecBuilder.PutInt64(Field_WLogOffset, int64(wLogOffsetToStore)) 120 recordsRegistryRecBuilder.PutQName(field_QName, qNameToStore) 121 return nil 122 } 123 124 func provideRefIntegrityValidator() istructs.CUDValidator { 125 return istructs.CUDValidator{ 126 Match: func(cud istructs.ICUDRow, wsid istructs.WSID, cmdQName appdef.QName) bool { 127 return cmdQName != QNameCommandInit 128 }, 129 Validate: func(ctx context.Context, appStructs istructs.IAppStructs, cudRow istructs.ICUDRow, wsid istructs.WSID, cmdQName appdef.QName) (err error) { 130 if err = CheckRefIntegrity(cudRow, appStructs, wsid); err == nil { 131 return nil 132 } 133 status := http.StatusInternalServerError 134 if errors.Is(err, ErrReferentialIntegrityViolation) { 135 status = http.StatusBadRequest 136 } 137 return coreutils.WrapSysError(err, status) 138 }, 139 } 140 }