Skip to content

Commit

Permalink
added support for DELETE statements in update item query
Browse files Browse the repository at this point in the history
  • Loading branch information
mindler-olli committed Apr 10, 2024
1 parent 1d01c88 commit 0389cf3
Show file tree
Hide file tree
Showing 9 changed files with 132 additions and 9 deletions.
5 changes: 5 additions & 0 deletions src/nodes/deleteUpdateExpression.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export type DeleteUpdateExpression = {
readonly kind: "DeleteUpdateExpression";
readonly key: string;
readonly value: unknown;
};
2 changes: 2 additions & 0 deletions src/nodes/updateExpression.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { AddUpdateExpression } from "./addUpdateExpression";
import { DeleteUpdateExpression } from "./deleteUpdateExpression";
import { RemoveUpdateExpression } from "./removeUpdateExpression";
import { SetUpdateExpression } from "./setUpdateExpression";

Expand All @@ -7,4 +8,5 @@ export type UpdateExpression = {
readonly setUpdateExpressions: SetUpdateExpression[];
readonly removeUpdateExpressions: RemoveUpdateExpression[];
readonly addUpdateExpressions: AddUpdateExpression[];
readonly deleteUpdateExpressions: DeleteUpdateExpression[];
};
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,21 @@ exports[`UpdateItemQueryBuilder > handles update item query with ADD statements
}
`;

exports[`UpdateItemQueryBuilder > handles update item query with DELETE statements 1`] = `
{
"dataTimestamp": 2,
"nested": {
"nestedSet": Set {
"5",
},
},
"someSet": Set {
"1",
},
"userId": "1",
}
`;

exports[`UpdateItemQueryBuilder > handles update item query with REMOVE statements 1`] = `
{
"dataTimestamp": 200,
Expand Down
28 changes: 28 additions & 0 deletions src/queryBuilders/updateItemQueryBuilder.integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,4 +93,32 @@ describe("UpdateItemQueryBuilder", () => {

expect(foundItem).toMatchSnapshot();
});

it("handles update item query with DELETE statements", async () => {
await tsynamoClient
.putItem("myTable")
.item({
userId: "1",
dataTimestamp: 2,
someSet: new Set(["1", "2", "3"]),
nested: {
nestedSet: new Set(["4", "5"]),
},
})
.execute();

await tsynamoClient
.updateItem("myTable")
.keys({ userId: "1", dataTimestamp: 2 })
.delete("someSet", new Set(["2", "3"]))
.delete("nested.nestedSet", new Set(["4"]))
.execute();

const foundItem = await tsynamoClient
.getItem("myTable")
.keys({ userId: "1", dataTimestamp: 2 })
.execute();

expect(foundItem).toMatchSnapshot();
});
});
42 changes: 38 additions & 4 deletions src/queryBuilders/updateItemQueryBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@ import { UpdateNode } from "../nodes/updateNode";
import { QueryCompiler } from "../queryCompiler";
import {
ExecuteOutput,
FilteredKeys,
GetFromPath,
ObjectKeyPaths,
PickNonKeys,
PickPk,
PickSkRequired,
StripKeys,
PickNonKeys,
} from "../typeHelpers";
import { preventAwait } from "../util/preventAwait";
import {
Expand Down Expand Up @@ -120,13 +121,23 @@ export interface UpdateItemQueryBuilderInterface<
attribute: Key
): UpdateItemQueryBuilderInterface<DDB, Table, O>;

// TODO: This should only be supported for keys that are of type NUMBER or SET
add<Key extends ObjectKeyPaths<PickNonKeys<DDB[Table]>>>(
add<
Key extends ObjectKeyPaths<
FilteredKeys<PickNonKeys<DDB[Table]>, Set<unknown> | number>
>
>(
attribute: Key,
value: StripKeys<GetFromPath<DDB[Table], Key>>
): UpdateItemQueryBuilderInterface<DDB, Table, O>;

// TODO: Add support for DELETE action
delete<
Key extends ObjectKeyPaths<
FilteredKeys<PickNonKeys<DDB[Table]>, Set<unknown>>
>
>(
attribute: Key,
value: StripKeys<GetFromPath<DDB[Table], Key>>
): UpdateItemQueryBuilderInterface<DDB, Table, O>;

returnValues(
option: ReturnValuesOptions
Expand Down Expand Up @@ -338,6 +349,29 @@ export class UpdateItemQueryBuilder<
});
}

delete<
Key extends ObjectKeyPaths<FilteredKeys<PickNonKeys<DDB[Table]>, Set<any>>>
>(
attribute: Key,
value: StripKeys<GetFromPath<DDB[Table], Key>>
): UpdateItemQueryBuilderInterface<DDB, Table, O> {
return new UpdateItemQueryBuilder<DDB, Table, O>({
...this.#props,
node: {
...this.#props.node,
updateExpression: {
...this.#props.node.updateExpression,
deleteUpdateExpressions:
this.#props.node.updateExpression.deleteUpdateExpressions.concat({
kind: "DeleteUpdateExpression",
key: attribute,
value,
}),
},
},
});
}

returnValues(
option: ReturnValuesOptions
): UpdateItemQueryBuilderInterface<DDB, Table, O> {
Expand Down
32 changes: 31 additions & 1 deletion src/queryCompiler/queryCompiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
} from "./compilerUtil";
import { RemoveUpdateExpression } from "../nodes/removeUpdateExpression";
import { AddUpdateExpression } from "../nodes/addUpdateExpression";
import { DeleteUpdateExpression } from "../nodes/deleteUpdateExpression";

export class QueryCompiler {
compile(rootNode: QueryNode): QueryCommand;
Expand Down Expand Up @@ -485,7 +486,18 @@ export class QueryCompiler {
.join(", ");
}

// TODO: Compile ADD actions
if (node.deleteUpdateExpressions.length > 0) {
res += " DELETE ";
res += node.deleteUpdateExpressions
.map((deleteUpdateExpression) => {
return this.compileDeleteUpdateExpression(
deleteUpdateExpression,
updateExpressionAttributeValues,
attributeNames
);
})
.join(", ");
}

return res;
}
Expand Down Expand Up @@ -660,4 +672,22 @@ export class QueryCompiler {

return `${attributeName} ${attributeValue}`;
}

compileDeleteUpdateExpression(
node: DeleteUpdateExpression,
updateExpressionAttributeValues: Map<string, unknown>,
attributeNames: Map<string, string>
) {
const { expressionAttributeName, attributeNameMap } =
this.compileAttributeName(node.key);

const attributeName = expressionAttributeName;
mergeObjectIntoMap(attributeNames, attributeNameMap);

const offset = updateExpressionAttributeValues.size;
const attributeValue = `:deleteUpdateExpressionValue${offset}`;
updateExpressionAttributeValues.set(attributeValue, node.value);

return `${attributeName} ${attributeValue}`;
}
}
1 change: 1 addition & 0 deletions src/queryCreator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ export class QueryCreator<DDB> {
setUpdateExpressions: [],
removeUpdateExpressions: [],
addUpdateExpressions: [],
deleteUpdateExpressions: []
},
},
ddbClient: this.#props.ddbClient,
Expand Down
15 changes: 11 additions & 4 deletions src/typeHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,17 +96,24 @@ export type SelectAttributes<
>
>;

type IsArray<T> = T extends unknown[] ? true : false;

export type FilteredKeys<T, U> = {
[K in keyof T]: T[K] extends U
? T[K]
: T[K] extends object
? FilteredKeys<T[K], U>
// TODO: Add support for recursively checking tuple values here
[K in keyof T]: IsArray<T[K]> extends false
? T[K] extends U
? T[K]
: T[K] extends object
? FilteredKeys<T[K], U>
: never
: never;
};

export type DeepPartial<T> = {
[P in keyof T]?: T[P] extends Array<infer U>
? Array<DeepPartial<U>>
: T[P] extends Set<unknown>
? T[P]
: T[P] extends object
? DeepPartial<T[P]>
: T[P];
Expand Down
1 change: 1 addition & 0 deletions test/testFixture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export interface DDB {
nestedNested: {
nestedNestedBoolean: boolean;
};
nestedSet: Set<string>
};
tags: string[];
someSet: Set<string>;
Expand Down

0 comments on commit 0389cf3

Please sign in to comment.