awslabs / dynamodb-data-mapper-js

A schema-based data mapper for Amazon DynamoDB.
https://awslabs.github.io/dynamodb-data-mapper-js/
Apache License 2.0
816 stars 106 forks source link

Using ConditionExpression in Put Queries seems to be flawed, or broken #207

Open bowenbrinegar opened 2 years ago

bowenbrinegar commented 2 years ago

Using the documentation of both this repository (in the Operations with Expressions section) and dynamodb-expression, I believe there is a specific issue with using conditions on put query.

(a) the documentation of this repo is missing a parenthesis (possibly un-needed UpdateExpression as well) (b) the follow populates a putItem query successfully over and over

Model:

@table('tests')
class Test {
    @hashKey()
    id?: string;

    @rangeKey()
    name?: string;
}

Action:

import {
  FunctionExpression,
  AttributePath,
  UpdateExpression
} from '@aws/dynamodb-expressions';

import { v4 as uuidv4 } from 'uuid';

function createData() {
  const obj = Object.assign(new Test, {
    id: uuidv4(),
    name: 'same name'
  })
  const condition =  new FunctionExpression('attribute_not_exists', new AttributePath('name'));
  return mapper.put(obj, { condition: condition })
}

createData() // should work with no existing record
createData() // should throw error as name attribute already exists, but does not throw error

(c) all tests cases for FunctionExpression (at least in packages/dynamodb-expressions/src/FunctionExpression.spec.ts) appear to be Red Herrings, not actually testing if a expression works past weather it will accept parameters

(d) there also appear to be no put tests in packages/dynamodb-data-mapper/src/DataMapper.spec.ts only available for delete & query methods

bowenbrinegar commented 2 years ago

It appears the issue has to due with Hashes can be unique, Hash & Range Combinations can be unique, however Range's cannot be unique?

jeskew commented 2 years ago

The documentation on this is a bit confusing, but conditional expressions on a PUT are executed against the item that will be overwritten; they are not executed against the table. Range keys are only unique within the same partition (i.e., hash key), so the table described could contain the following records without any issue:

- id: a451f82d-e0d9-461f-a1e3-f77aeb16a029
  name: Bob
- id: 77fb87c6-2158-48f0-b06a-903a6f84b7fe
  name: Bob
- id: 0dbded84-dd52-4e54-8a57-17d1c1057698
  name: Bob

You can write as many records with the same name as you'd like, provided they all have a different id. attribute_not_exists(name) can be used to prevent duplicates from entering the table (because DynamoDB will look up the item to be overwritten by the id and name composite key, see that it already has a name attribute, then evaluate the conditional expression to false), but createData() is actually creating a unique record on each invocation.