Skip to content

Commit 8ec73f4

Browse files
authored
Merge pull request #14 from halprin/filter-delete
Filter Expressions for Delete
2 parents 6cca4da + a55ad19 commit 8ec73f4

7 files changed

Lines changed: 108 additions & 9 deletions

File tree

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ compile:
33

44
runTestDynamoDB:
55
docker-compose -f dynamodb-docker-compose.yml up -d
6+
echo "Navigate to http://localhost:8001 for DynamoDB-Admin"
67

78
loadTestData:
89
./generate_mass_data.sh 500

README.md

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,25 @@ _**Warning**: running this command will result in all the items in the specified
1212
is no "are you sure?" prompt._
1313

1414
```shell
15-
delete-dynamodb-items <table name> [--endpoint=URL]
15+
delete-dynamodb-items <table name> [--endpoint=URL] [--filter-expression=string] [--expression-attribute-names=JSON] [--expression-attribute-values=JSON]
1616
```
1717

1818
The program uses the default AWS credential algorithm to determine what IAM entity and region is used. E.g. the
1919
`~/.aws/credentials` file, the `AWS_*` environment variables, etc.
2020

21+
## Filter Expressions
22+
23+
You can specify a special expression to filter out items you don't want deleted. AKA, the item will be deleted if the
24+
filter matches. You can learn more about filter expressions in
25+
[AWS's DynamoDB Developer Guide](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Scan.html#Scan.FilterExpression)
26+
and the
27+
[`filter-expression` section in the AWS CLI](https://docs.aws.amazon.com/cli/latest/reference/dynamodb/scan.html).
28+
29+
Use a combination of the `--filter-expression=`, `--expression-attribute-names=`, and `--expression-attribute-values=`
30+
options. These options work the same way as the options on the AWS CLI.
31+
32+
E.g. `--filter-expression='#k > :v' --expression-attribute-names='{"#k": "number"}' --expression-attribute-values='{":v": {"N": "50"}}'`
33+
2134
### Custom Endpoint
2235

2336
You can customize the DynamoDB endpoint with the `--endpoint=` (or `-e`) option. Set it to the URL of the endpoint.
@@ -28,6 +41,5 @@ E.g. `--endpoint=http://localhost:8002`. If unspecified, the default AWS endpoi
2841
Run the following to compile your own copy from source.
2942

3043
```shell
31-
go get -v -t -d ./cmd/
3244
go build -o delete-dynamodb-items -v ./cmd/
3345
```

config/config.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ package config
22

33
var tableName *string
44
var dynamoDbEndpoint *string
5+
var filterExpression *string
6+
var expressionAttributeNames *string
7+
var expressionAttributeValues *string
58

69
func SetTableName(name string) {
710
tableName = &name
@@ -18,3 +21,27 @@ func SetDynamoDbEndpoint(endpoint string) {
1821
func GetDynamoDbEndpoint() *string {
1922
return dynamoDbEndpoint
2023
}
24+
25+
func SetFilterExpression(expression string) {
26+
filterExpression = &expression
27+
}
28+
29+
func GetFilterExpression() *string {
30+
return filterExpression
31+
}
32+
33+
func SetExpressionAttributeNames(names string) {
34+
expressionAttributeNames = &names
35+
}
36+
37+
func GetExpressionAttributeNames() *string {
38+
return expressionAttributeNames
39+
}
40+
41+
func SetExpressionAttributeValues(values string) {
42+
expressionAttributeValues = &values
43+
}
44+
45+
func GetExpressionAttributeValues() *string {
46+
return expressionAttributeValues
47+
}

dynamo/dynamo.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,11 @@ func DeleteAllItemsInTable() error {
3636
goroutinePool := parallel.NewPool(concurrency, 41944)
3737
defer goroutinePool.Release()
3838

39-
for subItemList := range getItemsGoroutine(tableName) {
39+
expressionFilter := config.GetFilterExpression()
40+
expressionAttributeNames := config.GetExpressionAttributeNames()
41+
expressionAttributeValues := config.GetExpressionAttributeValues()
42+
43+
for subItemList := range getItemsGoroutine(tableName, expressionFilter, expressionAttributeNames, expressionAttributeValues) {
4044
err = deleteItems(subItemList, tableName, goroutinePool)
4145
if err != nil {
4246
return err

dynamo/scan.go

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,57 @@
11
package dynamo
22

33
import (
4+
"encoding/json"
45
"github.com/aws/aws-sdk-go/aws"
56
"github.com/aws/aws-sdk-go/service/dynamodb"
67
"log"
78
)
89

9-
func getItemsGoroutine(tableName string) chan []map[string]*dynamodb.AttributeValue {
10+
type expressionAttributeNamesType map[string]*string
11+
type expressionAttributeValuesType map[string]*dynamodb.AttributeValue
12+
13+
func getItemsGoroutine(tableName string, filterExpression *string, expressionAttributeNames *string, expressionAttributeValues *string) chan []map[string]*dynamodb.AttributeValue {
1014
yield := make(chan []map[string]*dynamodb.AttributeValue)
1115

16+
var names expressionAttributeNamesType
17+
if expressionAttributeNames != nil {
18+
err := json.Unmarshal([]byte(*expressionAttributeNames), &names)
19+
if err != nil {
20+
log.Printf("Failed to unmarshal the expression attribute names, %+v", err)
21+
return nil
22+
}
23+
}
24+
25+
var values expressionAttributeValuesType
26+
if expressionAttributeValues != nil {
27+
err := json.Unmarshal([]byte(*expressionAttributeValues), &values)
28+
if err != nil {
29+
log.Printf("Failed to unmarshal the expression attribute values, %+v", err)
30+
return nil
31+
}
32+
}
33+
1234
go func() {
1335
scanInput := &dynamodb.ScanInput{
14-
TableName: aws.String(tableName),
36+
TableName: aws.String(tableName),
37+
FilterExpression: filterExpression,
38+
ExpressionAttributeNames: names,
39+
ExpressionAttributeValues: values,
1540
}
1641

1742
for {
1843
log.Println("Scanning items")
1944

2045
scanOutput, err := dynamoService.Scan(scanInput)
2146
if err != nil {
22-
log.Println("Failed to scan the items")
47+
log.Printf("Failed to scan the items, %+v", err)
2348
break
2449
}
2550

2651
yield <- scanOutput.Items
2752

2853
if scanOutput.LastEvaluatedKey != nil && len(scanOutput.LastEvaluatedKey) > 0 {
29-
//there are still items to scan, the the key to start scanning from again
54+
//there are still items to scan, set the key to start scanning from again
3055
scanInput.ExclusiveStartKey = scanOutput.LastEvaluatedKey
3156
} else {
3257
//no more items to scan, break out

external/cli/cli.go

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package cli
33
import (
44
"github.com/halprin/delete-dynamodb-items/config"
55
"github.com/teris-io/cli"
6+
"log"
67
"os"
78
)
89

@@ -11,12 +12,25 @@ func FillConfig() {
1112
tableNameCliArg := cli.NewArg("table name", "The name of the table for which all the items will be deleted").WithType(cli.TypeString)
1213
endpointCliOption := cli.NewOption(endpointKey, "A URL of the DynamoDB endpoint to use").WithChar('e').WithType(cli.TypeString)
1314

14-
parser := cli.New("Deletes all the items in a DynamoDB table").WithArg(tableNameCliArg).WithOption(endpointCliOption)
15+
filterExpressionKey := "filter-expression"
16+
expressionAttributeNamesKey := "expression-attribute-names"
17+
expressionAttributeValuesKey := "expression-attribute-values"
18+
filterExpressionOption := cli.NewOption(filterExpressionKey, "A filter expression determines which items within the Scan results should be returned to you. All of the other results are discarded.").WithType(cli.TypeString)
19+
expressionAttributeNamesOption := cli.NewOption(expressionAttributeNamesKey, "Way to specify names in the filter expression that are DynamoDB reserved words.").WithType(cli.TypeString)
20+
expressionAttributeValuesOption := cli.NewOption(expressionAttributeValuesKey, "Way to specify values in the filter expression that are DynamoDB reserved words.").WithType(cli.TypeString)
21+
22+
parser := cli.New("Deletes all the items in a DynamoDB table").
23+
WithArg(tableNameCliArg).
24+
WithOption(endpointCliOption).
25+
WithOption(filterExpressionOption).
26+
WithOption(expressionAttributeNamesOption).
27+
WithOption(expressionAttributeValuesOption)
1528

1629
invocation, arguments, options, err := parser.Parse(os.Args)
1730
help, helpExistsInOptions := options["help"]
1831

1932
if err != nil {
33+
log.Printf("Error: %+v", err)
2034
_ = parser.Usage(invocation, os.Stdout)
2135
os.Exit(1)
2236
} else if helpExistsInOptions && help == "true" {
@@ -31,4 +45,19 @@ func FillConfig() {
3145
if endpointExistsInOptions {
3246
config.SetDynamoDbEndpoint(endpoint)
3347
}
48+
49+
expression, expressionExistsInOptions := options[filterExpressionKey]
50+
if expressionExistsInOptions {
51+
config.SetFilterExpression(expression)
52+
}
53+
54+
names, namesExistsInOptions := options[expressionAttributeNamesKey]
55+
if namesExistsInOptions {
56+
config.SetExpressionAttributeNames(names)
57+
}
58+
59+
values, valuesExistsInOptions := options[expressionAttributeValuesKey]
60+
if valuesExistsInOptions {
61+
config.SetExpressionAttributeValues(values)
62+
}
3463
}

generate_mass_data.sh

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ items_ending=']}'
1212
lorem_ipsum='Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec a efficitur nunc. Morbi fermentum sem metus, vel venenatis leo porttitor quis. Etiam maximus neque a pharetra viverra. Sed turpis lacus, blandit ac tortor elementum, scelerisque feugiat risus. Nam malesuada augue et purus aliquet, et semper dolor cursus. Suspendisse volutpat dolor nec efficitur rutrum. Aliquam leo libero, posuere eget vulputate in, luctus nec nibh. Donec eu tellus eu libero scelerisque molestie. Ut sed pretium nibh. Donec suscipit eget dui quis lacinia. Aliquam non pulvinar massa, nec blandit lectus. Cras sollicitudin rhoncus ex. Nunc ipsum dui, dictum in risus nec, convallis rutrum justo. In tempor dui nisl, in fringilla massa vehicula ac. Donec a ipsum luctus, venenatis magna ut, venenatis risus. Vivamus eu dapibus odio. Aenean dapibus urna orci, sed pharetra nunc dapibus ac. Praesent ornare, felis sit amet mattis faucibus, odio arcu laoreet arcu, eu blandit nisi turpis cursus enim.'
1313

1414
for ((index = 1 ; index <= num_items ; index++)); do
15-
current_request="{\"PutRequest\": {\"Item\": {\"id\": {\"S\": \"$(uuidgen)\"}, \"text\": {\"S\": \"${lorem_ipsum}\"}}}}"
15+
rand_number=$((RANDOM % 100)) # not actually random, but lazy with the modulus
16+
current_request="{\"PutRequest\": {\"Item\": {\"id\": {\"S\": \"$(uuidgen)\"}, \"text\": {\"S\": \"${lorem_ipsum}\"}, \"number\": {\"N\": \"${rand_number}\"}}}}"
1617
items_middle="${items_middle}${current_request},"
1718
if [[ $((index % 25)) == 0 ]]; then
1819
items_middle=${items_middle::${#items_middle}-1}

0 commit comments

Comments
 (0)