Repository: codenotary/immudb
File: pkg/database/all_ops.go (Go)

Code before:

				e = EncodeEntrySpec(x.Kv.Key, schema.KVMetadataFromProto(x.Kv.Metadata), x.Kv.Value)

			case *schema.Op_Ref:
				if len(x.Ref.Key) == 0 || len(x.Ref.ReferencedKey) == 0 {
					return nil, store.ErrIllegalArguments
				}

				if x.Ref.AtTx > 0 && !x.Ref.BoundRef {
					return nil, store.ErrIllegalArguments
				}

				if req.NoWait && (x.Ref.AtTx != 0 || !x.Ref.BoundRef) {
					return nil, ErrNoWaitOperationMustBeSelfContained
				}

				_, exists := kmap[sha256.Sum256(x.Ref.ReferencedKey)]

				if req.NoWait && !exists {
					return nil, ErrNoWaitOperationMustBeSelfContained
				}

				if !req.NoWait {
					// check key does not exists or it's already a reference
					entry, err := d.getAt(EncodeKey(x.Ref.Key), 0, 0, index, tx)
					if err != nil && err != store.ErrKeyNotFound {
						return nil, err
					}
					if entry != nil && entry.ReferencedBy == nil {
						return nil, ErrFinalKeyCannotBeConvertedIntoReference
					}

					if !exists || x.Ref.AtTx > 0 {
						// check referenced key exists and it's not a reference
						refEntry, err := d.getAt(EncodeKey(x.Ref.ReferencedKey), x.Ref.AtTx, 0, index, tx)
						if err != nil {
							return nil, err
						}
						if refEntry.ReferencedBy != nil {
							return nil, ErrReferencedKeyCannotBeAReference
						}
					}
				}

				// reference arguments are converted in regular key value items and then atomically inserted
				if x.Ref.BoundRef && x.Ref.AtTx == 0 {
					e = EncodeReference(x.Ref.Key, nil, x.Ref.ReferencedKey, txID)
				} else {
					e = EncodeReference(x.Ref.Key, nil, x.Ref.ReferencedKey, x.Ref.AtTx)
				}


Review comment (suggestion):
We could also check here if the referenced entry from current transaction is not a reference itself,

also we could check if there was not previous entry with the same key name in the same ExecAll and that it was not changed from value to a reference with the current operation (or to restrict to only one value in whole ExecAll for one key but this could break backwards compatibility with some odd applications).

Code after:
				}

				e = EncodeEntrySpec(x.Kv.Key, schema.KVMetadataFromProto(x.Kv.Metadata), x.Kv.Value)

			case *schema.Op_Ref:
				if len(x.Ref.Key) == 0 || len(x.Ref.ReferencedKey) == 0 {
					return nil, store.ErrIllegalArguments
				}

				if x.Ref.AtTx > 0 && !x.Ref.BoundRef {
					return nil, store.ErrIllegalArguments
				}

				if req.NoWait && (x.Ref.AtTx != 0 || !x.Ref.BoundRef) {
					return nil, fmt.Errorf(
						"%w: can only set references to keys added within same transaction, please use bound references with AtTx set to 0",
						ErrNoWaitOperationMustBeSelfContained)
				}

				_, exists := kmap[sha256.Sum256(x.Ref.ReferencedKey)]

				if req.NoWait && !exists {
					return nil, fmt.Errorf(
						"%w: can not create a reference to a key that was not set in the same transaction",
						ErrNoWaitOperationMustBeSelfContained)
				}

				if !req.NoWait {
					// check key does not exists or it's already a reference
					entry, err := d.getAt(EncodeKey(x.Ref.Key), 0, 0, index, tx)
					if err != nil && err != store.ErrKeyNotFound {
						return nil, err
					}
					if entry != nil && entry.ReferencedBy == nil {
						return nil, ErrFinalKeyCannotBeConvertedIntoReference
					}

					if !exists || x.Ref.AtTx > 0 {
						// check referenced key exists and it's not a reference
						refEntry, err := d.getAt(EncodeKey(x.Ref.ReferencedKey), x.Ref.AtTx, 0, index, tx)
						if err != nil {
							return nil, err
						}
						if refEntry.ReferencedBy != nil {
							return nil, ErrReferencedKeyCannotBeAReference
						}
					}
				}

				// reference arguments are converted in regular key value items and then atomically inserted
				if x.Ref.BoundRef && x.Ref.AtTx == 0 {