goamazon-dynamodb

Querying DynamoSB GSI in Golang


I am very new to DynamoDB and highly likely missing something. I read docs and other materials but couldn't work out missing link.

I am trying to query GSI by user email but no luck so far. Do I have to create a GSI separate from the table?

This is how I create table and secondary index:

{
    "TableName": "BlogTable",
    "KeySchema": [
        {
            "AttributeName": "PK",
            "KeyType": "HASH"
        },
        {
            "AttributeName": "SK",
            "KeyType": "RANGE"
        }
    ],
    "AttributeDefinitions": [
        {
            "AttributeName": "PK",
            "AttributeType": "S"
        },
        {
            "AttributeName": "SK",
            "AttributeType": "S"
        }
    ],
    "GlobalSecondaryIndexes": [
        {
            "IndexName": "BlogGlobalIndex1",
            "KeySchema": [
                {
                    "AttributeName": "PK",
                    "KeyType": "HASH"
                },
                {
                    "AttributeName": "SK",
                    "KeyType": "RANGE"
                }
            ],
            "Projection": {
                "ProjectionType": "ALL"
            },
            "ProvisionedThroughput": {
                "ReadCapacityUnits": 5,
                "WriteCapacityUnits": 5
            }
        }
    ],
    "ProvisionedThroughput": {
        "ReadCapacityUnits": 5,
        "WriteCapacityUnits": 5
    }
}

This is how I put items in table:

// After this I can find item by user ID.
func (d DynamoDB) CreateUser(ctx context.Context, model store.User) error {
    model.PrimaryKey = store.PrimaryKey{
        PK: "user#" + model.ID,
        SK: "user#" + model.ID,
    }

    item, err := attributevalue.MarshalMap(model)
    if err != nil {
        return fmt.Errorf("marshal map: %w", err)
    }

    _, err = d.Client.PutItem(ctx, &dynamodb.PutItemInput{
        TableName: aws.String(d.Table),
        Item:      item,
    })
    if err != nil {
        return fmt.Errorf("put item: %w", err)
    }

    return nil
}

This is how I am trying to query GSI but it yields 0 items when looking for items by email although there are matching items. Both options below won't work.

func (d DynamoDB) FindUserByEmail(ctx context.Context, email string) (store.User, error) {
    cond1 := expression.Name("PK").Equal(expression.Value("user#" + email))
    cond2 := expression.Name("SK").Equal(expression.Value("user#" + email))
    expr, err := expression.NewBuilder().WithCondition(cond1.And(cond2)).Build()
    if err != nil {
        return store.User{}, fmt.Errorf("expression build: %w", err)
    }

    res, err := d.Client.Query(ctx, &dynamodb.QueryInput{
        TableName: aws.String(d.Table),
        IndexName: aws.String(d.GlobalIndex),

        ExpressionAttributeNames:  expr.Names(),
        ExpressionAttributeValues: expr.Values(),
        FilterExpression:          expr.Filter(),
        KeyConditionExpression:    expr.Condition(),
        ProjectionExpression:      expr.Projection(),

        // KeyConditionExpression: aws.String("PK = :PK, SK = :SK"),
        // ExpressionAttributeValues: map[string]types.AttributeValue{
        //  "PK": &types.AttributeValueMemberS{Value: "user#" + email},
        //  "SK": &types.AttributeValueMemberS{Value: "user#" + email},
        // },
        // Limit: aws.Int32(1),
    })
    if err != nil {
        return store.User{}, fmt.Errorf("get item: %w", err)
    }

    fmt.Println(res.Count) // 0
    fmt.Println(len(res.Items)) // 0

    var model store.User
// ...
    return model, nil
}

Example data fixtures:

{
    "Items": [
        {
            "SK": {
                "S": "user#1722198720607873"
            },
            "Email": {
                "S": "Shaylee54@gmail.com"
            },
            "PK": {
                "S": "user#1722198720607873"
            },
            "ID": {
                "S": "1722198720607873"
            },
            "Password": {
                "S": "5O1AMjIG8hRFeS_"
            },
            "Name": {
                "S": "Marty Ferry III"
            }
        },
        {
            "SK": {
                "S": "user#1722178078008670"
            },
            "ID": {
                "S": "1722178078008670"
            },
            "Email": {
                "S": "Carissa11@yahoo.com"
            },
            "PK": {
                "S": "user#1722178078008670"
            },
            "Name": {
                "S": "Jana Rice"
            },
            "Password": {
                "S": "OM9AfTTuwYPw_bD"
            }
        }
    ],
    "Count": 2,
    "ScannedCount": 2,
    "ConsumedCapacity": null
}

Solution

  • Your GSI has the same KeySchema as the table - so it's just a "replica". You'll have to define the key(s) which you want to query (Documentation).

    So, if your table has a field "email", use this in the KeySchema for the GSI. Then you can query it.

    "GlobalSecondaryIndexes": [{
      "IndexName": "BlogGlobalIndex1",
      "KeySchema": [
        {
          "AttributeName": "email",
          "KeyType": "HASH"
        },
        {
          "AttributeName": "SK",
                        "KeyType": "RANGE"
        }
      ],
      ...
    }]
    

    email has also to be added to the AttributeDefinitions.