github.com/voedger/voedger@v0.0.0-20240520144910-273e84102129/pkg/sys/uniques/impl.go (about) 1 /* 2 * Copyright (c) 2020-present unTill Software Development Group B.V. 3 * @author Denis Gribanov 4 */ 5 6 package uniques 7 8 import ( 9 "bytes" 10 "context" 11 "encoding/binary" 12 "errors" 13 "fmt" 14 "net/http" 15 16 "github.com/voedger/voedger/pkg/goutils/iterate" 17 18 "github.com/voedger/voedger/pkg/appdef" 19 "github.com/voedger/voedger/pkg/istructs" 20 "github.com/voedger/voedger/pkg/istructsmem" 21 "github.com/voedger/voedger/pkg/state" 22 coreutils "github.com/voedger/voedger/pkg/utils" 23 ) 24 25 func provideApplyUniques(appDef appdef.IAppDef) func(event istructs.IPLogEvent, state istructs.IState, intents istructs.IIntents) (err error) { 26 return func(event istructs.IPLogEvent, st istructs.IState, intents istructs.IIntents) (err error) { 27 return iterate.ForEachError(event.CUDs, func(rec istructs.ICUDRow) error { 28 iUniques, ok := appDef.Type(rec.QName()).(appdef.IUniques) 29 if !ok { 30 return nil 31 } 32 for _, unique := range iUniques.Uniques() { 33 if err := handleCUD(rec, st, intents, unique.Fields(), unique.Name()); err != nil { 34 return err 35 } 36 } 37 if iUniques.UniqueField() != nil { 38 uniqueQName := rec.QName() 39 return handleCUD(rec, st, intents, []appdef.IField{iUniques.UniqueField()}, uniqueQName) 40 } 41 return nil 42 }) 43 } 44 } 45 46 func handleCUD(cud istructs.ICUDRow, st istructs.IState, intents istructs.IIntents, uniqueFields []appdef.IField, uniqueQName appdef.QName) error { 47 if cud.IsNew() { 48 return insert(st, cud, intents, uniqueFields, uniqueQName) 49 } 50 return update(st, cud, intents, uniqueFields, uniqueQName) 51 } 52 53 type uniqueViewRecord struct { 54 refRecordID istructs.RecordID 55 } 56 57 func update(st istructs.IState, rec istructs.ICUDRow, intents istructs.IIntents, uniqueFields []appdef.IField, uniqueQName appdef.QName) error { 58 // check modified fields 59 // case when we're updating unique fields is already dropped by the validator 60 // so came here -> we're updating anything but unique fields 61 // let's check activation\deactivation 62 63 kb, err := st.KeyBuilder(state.Record, rec.QName()) 64 if err != nil { 65 return err 66 } 67 kb.PutRecordID(state.Field_ID, rec.ID()) 68 currentRecord, err := st.MustExist(kb) 69 if err != nil { 70 return err 71 } 72 73 // we're updating -> unique view record exists 74 uniqueViewRecord, uniqueViewKB, ok, err := getUniqueViewRecord(st, currentRecord, uniqueFields, uniqueQName) 75 if err != nil { 76 return err 77 } 78 if !ok { 79 // was no unique, insert a record, define a unique, update the record -> no record in the view -> fo nothing to keep backward compatibility 80 // new unique will work starting from the next new record 81 // https://github.com/voedger/voedger/issues/1408 82 return nil 83 } 84 refIDToSet := istructs.NullRecordID 85 uniqueViewRecordID := uniqueViewRecord.AsRecordID(field_ID) 86 if rec.AsBool(appdef.SystemField_IsActive) { 87 if uniqueViewRecordID == istructs.NullRecordID { 88 // activating the record whereas previous combination was deactivated -> allow, update the view 89 refIDToSet = rec.ID() 90 } else { 91 // activating the already activated record, unique combination exists for that record -> allow, nothing to do 92 // note: case when uniqueViewRecordID != rec.ID() is handled already by the validator, so nothing to do here 93 return nil 94 } 95 } else { 96 if rec.ID() != uniqueViewRecordID { 97 // deactivating a record whereas unique combination exists for another record -> allow, nothing to do 98 return nil 99 } 100 } 101 uniqueViewUpdater, err := intents.UpdateValue(uniqueViewKB, uniqueViewRecord) 102 if err != nil { 103 return err 104 } 105 uniqueViewUpdater.PutRecordID(field_ID, refIDToSet) 106 return nil 107 } 108 109 func insert(state istructs.IState, rec istructs.IRowReader, intents istructs.IIntents, uniqueFields []appdef.IField, uniqueQName appdef.QName) error { 110 uniqueViewRecord, uniqueViewKB, uniqueViewRecordExists, err := getUniqueViewRecord(state, rec, uniqueFields, uniqueQName) 111 if err != nil { 112 return err 113 } 114 // no scenario whe we're inserting a deactivated record 115 var uniqueViewRecordBuilder istructs.IStateValueBuilder 116 if uniqueViewRecordExists { 117 // the olny possible case here - we're inserting a new record, the view record exists for this combination and it is relates to an inactive record 118 // case when it relates to an active record is already dropped by the validator 119 // so just update the existing view record 120 uniqueViewRecordBuilder, err = intents.UpdateValue(uniqueViewKB, uniqueViewRecord) 121 } else { 122 uniqueViewRecordBuilder, err = intents.NewValue(uniqueViewKB) 123 } 124 125 if err == nil { 126 uniqueViewRecordBuilder.PutRecordID(field_ID, rec.AsRecordID(appdef.SystemField_ID)) 127 } 128 return err 129 } 130 131 func getUniqueViewRecord(st istructs.IState, rec istructs.IRowReader, uniqueFields []appdef.IField, uniqueQName appdef.QName) (istructs.IStateValue, istructs.IStateKeyBuilder, bool, error) { 132 uniqueKeyValues, err := getUniqueKeyValuesFromRec(rec, uniqueFields, uniqueQName) 133 if err != nil { 134 return nil, nil, false, err 135 } 136 uniqueViewRecordBuilder, err := st.KeyBuilder(state.View, qNameViewUniques) 137 if err != nil { 138 // notest 139 return nil, nil, false, err 140 } 141 buildUniqueViewKeyByValues(uniqueViewRecordBuilder, uniqueQName, uniqueKeyValues) 142 sv, ok, err := st.CanExist(uniqueViewRecordBuilder) 143 return sv, uniqueViewRecordBuilder, ok, err 144 } 145 146 // new uniques -> QName of the unique, old uniques -> QName of the doc 147 func buildUniqueViewKeyByValues(kb istructs.IKeyBuilder, qName appdef.QName, uniqueKeyValues []byte) { 148 kb.PutQName(field_QName, qName) 149 kb.PutInt64(field_ValuesHash, coreutils.HashBytes(uniqueKeyValues)) 150 kb.PutBytes(field_Values, uniqueKeyValues) 151 } 152 153 func getUniqueKeyValuesFromMap(values map[string]interface{}, uniqueFields []appdef.IField, uniqueQName appdef.QName) (res []byte, err error) { 154 buf := bytes.NewBuffer(nil) 155 for _, uniqueField := range uniqueFields { 156 val := values[uniqueField.Name()] 157 if err := coreutils.CheckValueByKind(val, uniqueField.DataKind()); err != nil { 158 return nil, err 159 } 160 writeUniqueKeyValue(uniqueField, val, buf, uniqueFields) 161 } 162 return buf.Bytes(), checkUniqueKeyLen(buf, uniqueQName) 163 } 164 165 // uniqueFields is provided just to determine if should handle backward compatibility 166 func writeUniqueKeyValue(uniqueField appdef.IField, value interface{}, buf *bytes.Buffer, uniqueFields []appdef.IField) { 167 switch uniqueField.DataKind() { 168 case appdef.DataKind_string: 169 if len(uniqueFields) > 1 { 170 // backward compatibility 171 buf.WriteByte(zeroByte) 172 } 173 buf.WriteString(value.(string)) 174 case appdef.DataKind_bytes: 175 if len(uniqueFields) > 1 { 176 // backward compatibility 177 buf.WriteByte(zeroByte) 178 } 179 buf.Write(value.([]byte)) 180 default: 181 binary.Write(buf, binary.BigEndian, value) // nolint 182 } 183 } 184 185 func checkUniqueKeyLen(buf *bytes.Buffer, uniqueQName appdef.QName) error { 186 if buf.Len() > int(appdef.MaxFieldLength) { 187 return fmt.Errorf(`%w: resulting len of the unique combination "%s" is %d, max %d is allowed. Decrease len of values of unique fields`, 188 ErrUniqueValueTooLong, uniqueQName, buf.Len(), appdef.MaxFieldLength) 189 } 190 return nil 191 } 192 193 func getUniqueKeyValuesFromRec(rec istructs.IRowReader, uniqueFields []appdef.IField, uniqueQName appdef.QName) (res []byte, err error) { 194 buf := bytes.NewBuffer(nil) 195 for _, uniqueField := range uniqueFields { 196 val := coreutils.ReadByKind(uniqueField.Name(), uniqueField.DataKind(), rec) 197 writeUniqueKeyValue(uniqueField, val, buf, uniqueFields) 198 } 199 return buf.Bytes(), checkUniqueKeyLen(buf, uniqueQName) 200 } 201 202 func getCurrentUniqueViewRecord(uniquesState map[appdef.QName]map[appdef.QName]map[string]*uniqueViewRecord, 203 cudQName appdef.QName, uniqueKeyValues []byte, appStructs istructs.IAppStructs, wsid istructs.WSID, uniqueQName appdef.QName) (*uniqueViewRecord, error) { 204 // why to accumulate in a map? 205 // id: field: IsActive: Result: 206 // stored: 111: xxx - 207 // … 208 // cud(I): 222: xxx + - should be ok to insert new record 209 // … 210 // cud(J): 111: + - should be denied to restore old record 211 cudQNameUniques, ok := uniquesState[cudQName] 212 if !ok { 213 cudQNameUniques = map[appdef.QName]map[string]*uniqueViewRecord{} 214 uniquesState[cudQName] = cudQNameUniques 215 } 216 uniqueViewRecords, ok := cudQNameUniques[uniqueQName] 217 if !ok { 218 uniqueViewRecords = map[string]*uniqueViewRecord{} 219 cudQNameUniques[uniqueQName] = uniqueViewRecords 220 } 221 currentUniqueViewRecord, ok := uniqueViewRecords[string(uniqueKeyValues)] 222 if !ok { 223 currentUniqueRecordID, _, err := getUniqueIDByValues(appStructs, wsid, uniqueQName, uniqueKeyValues) 224 if err != nil { 225 return nil, err 226 } 227 currentUniqueViewRecord = &uniqueViewRecord{ 228 refRecordID: currentUniqueRecordID, 229 } 230 uniqueViewRecords[string(uniqueKeyValues)] = currentUniqueViewRecord 231 } 232 return currentUniqueViewRecord, nil 233 } 234 235 func getUniqueIDByValues(appStructs istructs.IAppStructs, wsid istructs.WSID, uniqueQName appdef.QName, uniqueKeyValues []byte) (istructs.RecordID, bool, error) { 236 kb := appStructs.ViewRecords().KeyBuilder(qNameViewUniques) 237 buildUniqueViewKeyByValues(kb, uniqueQName, uniqueKeyValues) 238 val, err := appStructs.ViewRecords().Get(wsid, kb) 239 if err == nil { 240 return val.AsRecordID(field_ID), true, nil 241 } 242 if errors.Is(err, istructsmem.ErrRecordNotFound) { 243 err = nil 244 } 245 return istructs.NullRecordID, false, err 246 } 247 248 func validateCUD(cudRec istructs.ICUDRow, appStructs istructs.IAppStructs, wsid istructs.WSID, uniqueFields []appdef.IField, uniqueQName appdef.QName, uniquesState map[appdef.QName]map[appdef.QName]map[string]*uniqueViewRecord) (err error) { 249 var uniqueKeyValues []byte 250 var rowSource istructs.IRowReader 251 cudQName := cudRec.QName() 252 if cudRec.IsNew() { 253 // insert -> will get existing values from the current CUD 254 rowSource = cudRec 255 } else { 256 // update -> will get existing values from the stored record 257 rowSource, err = appStructs.Records().Get(wsid, true, cudRec.ID()) 258 if err != nil { 259 // notest 260 return err 261 } 262 } 263 uniqueKeyValues, err = getUniqueKeyValuesFromRec(rowSource, uniqueFields, uniqueQName) 264 if err != nil { 265 return err 266 } 267 // uniqueViewRecord - is for unique combination from current cudRec 268 uniqueViewRecord, err := getCurrentUniqueViewRecord(uniquesState, cudQName, uniqueKeyValues, appStructs, wsid, uniqueQName) 269 if err != nil { 270 return err 271 } 272 if cudRec.IsNew() { 273 // !IsActive is impossible for new records anymore 274 if uniqueViewRecord.refRecordID == istructs.NullRecordID { 275 // inserting a new active record, the doc record according to this combination is inactive or does not exist -> allow, update its ID in map 276 uniqueViewRecord.refRecordID = cudRec.ID() 277 } else { 278 // inserting a new active record, the doc record according to this combination is active -> deny 279 return conflict(cudQName, uniqueViewRecord.refRecordID, uniqueQName) 280 } 281 } else { 282 // update 283 // unique view record exists because all unique fields are required. 284 // let's deny to update unique fields and handle IsActive state 285 err := iterate.ForEachError2Values(cudRec.ModifiedFields, func(cudModifiedFieldName appdef.FieldName, newValue interface{}) error { 286 for _, uniqueField := range uniqueFields { 287 if uniqueField.Name() == cudModifiedFieldName { 288 return fmt.Errorf("%v: unique field «%s» can not be changed: %w", cudQName, uniqueField.Name(), ErrUniqueFieldUpdateDeny) 289 } 290 } 291 if cudModifiedFieldName != appdef.SystemField_IsActive { 292 return nil 293 } 294 // we're updating IsActive field here. 295 isActivating := newValue.(bool) 296 if isActivating { 297 if uniqueViewRecord.refRecordID == istructs.NullRecordID { 298 // doc rec for this combination does not exist or is inactive (no matter for this cudRec or any other rec), 299 // we're activating now -> set current unique combination ref to the cudRec 300 uniqueViewRecord.refRecordID = cudRec.ID() 301 } else if uniqueViewRecord.refRecordID != cudRec.ID() { 302 // we're activating, doc rec for this combination exists, it is active and it is the another rec (not the one we're updating by the current CUD) -> deny 303 return conflict(cudQName, uniqueViewRecord.refRecordID, uniqueQName) 304 } 305 } else { 306 // deactivating 307 uniqueViewRecord.refRecordID = istructs.NullRecordID 308 } 309 return nil 310 }) 311 if err != nil { 312 return err 313 } 314 } 315 return nil 316 } 317 318 func eventUniqueValidator(ctx context.Context, rawEvent istructs.IRawEvent, appStructs istructs.IAppStructs, wsid istructs.WSID) error { 319 // cudQName uniqueQName unique-key-bytes 320 uniquesState := map[appdef.QName]map[appdef.QName]map[string]*uniqueViewRecord{} 321 return iterate.ForEachError(rawEvent.CUDs, func(cudRec istructs.ICUDRow) (err error) { 322 323 cudUniques, ok := appStructs.AppDef().Type(cudRec.QName()).(appdef.IUniques) 324 if !ok { 325 return nil 326 } 327 for _, unique := range cudUniques.Uniques() { 328 if err := validateCUD(cudRec, appStructs, wsid, unique.Fields(), unique.Name(), uniquesState); err != nil { 329 return err 330 } 331 } 332 if cudUniques.UniqueField() != nil { 333 uniqueQName := cudRec.QName() 334 if err := validateCUD(cudRec, appStructs, wsid, []appdef.IField{cudUniques.UniqueField()}, uniqueQName, uniquesState); err != nil { 335 return err 336 } 337 } 338 return nil 339 }) 340 } 341 342 func conflict(docQName appdef.QName, conflictingWithID istructs.RecordID, uniqueQName appdef.QName) error { 343 return coreutils.NewHTTPError(http.StatusConflict, fmt.Errorf(`%s: "%s" %w with ID %d`, docQName, uniqueQName, ErrUniqueConstraintViolation, conflictingWithID)) 344 }