sensedeep / dynamodb-onetable

DynamoDB access and management for one table designs with NodeJS
https://doc.onetable.io/
MIT License
690 stars 109 forks source link

Update operation does not remove object from array #519

Closed martinmicunda closed 7 months ago

martinmicunda commented 10 months ago

Describe the bug

The onetable doesn't overwrite array of objects and instead try to set each field in the object during update operation. Example: in database I could have 2 photos in the array, so during update user can change the name and remove one photo from array and I would expect the update operation to just overwrite the array but instead it tries to update fields in the object and it never remove item from array. Same issue happens when I pass empty array, none of photos are removed, as this is update operation I would expect to overwrite array and not try to update object each field in the array. Is this expecting behaviour?

Right now I have to do something like this, but if I have multiple arrays I need to do it for each array.

    const {photos, ...dynamoEntityWithoutPhotos} = dynamoEntity;

    const result = await fromPromise(
      this.db.update(dynamoEntityWithoutPhotos, {
        remove: ['status'],
        log: true,
        // TODO: I need to set below because empty array is not pick it up by onetable??
        set: {photos: dynamoEntity.photos},
      }),
      (error) => error as OneTableError,
    );

Update operation log

{
  "cmd": {
    "ConditionExpression": "(attribute_exists(#_0)) and (attribute_exists(#_1))",
    "ExpressionAttributeNames": {
      "#_0": "PK",
      "#_1": "SK",
      "#_5": "name",
      "#_6": "photos",
      "#_7": "key",
      "#_8": "createdAt",
      "#_11": "updatedAt",
      "#_12": "id"
    },
    "ExpressionAttributeValues": {
      ":_2": {
        "S": "90ddfd76-d8ef-4a71-b381-56b95e741847/855f95eb-a7b4-45fc-8487-fe86e3af6220.png"
      },
      ":_3": {
        "S": "2024-01-19T11:06:54.225Z"
      },
      ":_4": {
        "S": "image/png"
      },
      ":_5": {
        "S": ""
      },
      ":_6": {
        "S": "2024-01-18T21:18:23.475Z"
      },
      ":_7": {
        "S": "2024-01-19T11:12:45.963Z"
      },
      ":_10": {
        "S": "Fix Vent Fan"
      }
    },
    "TableName": "dev-home",
    "ReturnValues": "ALL_NEW",
    "UpdateExpression": "set #_6[0].#_7 = :_2, #_6[0].#_8 = :_3, #_6[0].#_9 = :_4, #_6[0].#_10 = :_5, #_8 = :_6, #_11 = :_7, #_12 = :_8, #_5 = :_10",
    "Key": {
      "PK": {
        "S": "HOME#90ddfd76-d8ef-4a71-b381-56b95e741847"
      },
      "SK": {
        "S": "PROJECT#23633923-3bc4-47f3-8027-950590213c80"
      }
    }
  },
  "items": [
    {
      "createdAt": "2024-01-18T21:18:23.475Z",
      "updatedAt": "2024-01-19T11:12:45.963Z",
      "id": "23633923-3bc4-47f3-8027-950590213c80",
      "name": "Fix Vent Fan",
      "photos": [
        {
          "key": "90ddfd76-d8ef-4a71-b381-56b95e741847/855f95eb-a7b4-45fc-8487-fe86e3af6220.png",
          "createdAt": "2024-01-19T11:06:54.225Z",
          "contentType": "image/png",
          "comment": ""
        },
        {
          "key": "90ddfd76-d8ef-4a71-b381-56b95e741847/cbfc2ffa-a745-4788-8c3b-7cb007673911.png",
          "createdAt": "2024-01-19T11:06:54.225Z",
          "contentType": "image/png",
          "comment": ""
        }
      ],
    }
  ],
  "op": "update",
  "properties": {
    "photos": [
      {
        "key": "90ddfd76-d8ef-4a71-b381-56b95e741847/855f95eb-a7b4-45fc-8487-fe86e3af6220.png",
        "createdAt": "2024-01-19T11:06:54.225Z",
        "contentType": "image/png",
        "comment": ""
      }
    ],
    "createdAt": "2024-01-18T21:18:23.475Z",
    "updatedAt": "2024-01-19T11:12:45.963Z",
    "PK": "HOME#90ddfd76-d8ef-4a71-b381-56b95e741847",
    "SK": "PROJECT#23633923-3bc4-47f3-8027-950590213c80",
    "id": "23633923-3bc4-47f3-8027-950590213c80",
    "name": "Fix Vent Fan",
    "type": "Project"
  },
  "params": {
    "exists": true,
    "parse": true,
    "high": true,
    "remove": [
      "status"
    ],
    "log": true,
    "checked": true
  }
}

Schema

const schema = {
  format: 'onetable:1.1.0',
  version: '1.0.0',
  indexes: {
    primary: { hash: 'PK', sort: 'SK' },
  },
  models: {
    Project: {
      ...schemaBase,
      PK: { type: String, value: 'HOME#${homeId}', required: true },
      SK: { type: String, value: 'PROJECT#${id}', required: true },
      id: { type: String, required: true },
      name: { type: String },
      photos: {
        type: Array,
        required: true,
        default: [],
        items: {
          type: Object,
          schema: {
            key: { type: String, required: true },
            createdAt: { type: String, required: true },
            contentType: { type: String, required: true },
            isHero: { type: Boolean },
            comment: { type: String },
            hasMoved: { type: Boolean },
          },
        },
      }
    }
  } as const,
  params: {
    ...schemaParamsBase,
  },
};
mobsense commented 9 months ago

Depends on your settings of partial. If partial is true (the default now), it updates nested objects and arrays partially.

You can provide {partial: false} for the schema or for one API to override for that API and do an update of the top level property (array) and not element by element.