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  }