Using KEYS_ONLY Global Secondary Indexes

It is common to define a Global Secondary Index using a KEYS_ONLY projection. This ProjectionType helps keep indexes as small as possible which can reduce cost. Unfortunately, because ElectroDB abstracts away key management, querying this index type with ElectroDB requires unique considerations.

Example Setup

Example Setup

Table Definition
{
    "TableName": "electro",
    "KeySchema":[
{
    "AttributeName":"pk",
    "KeyType":"HASH"
},
{
    "AttributeName":"sk",
    "KeyType":"RANGE"
}
    ],
    "AttributeDefinitions":[
{
    "AttributeName":"pk",
    "AttributeType":"S"
},
{
    "AttributeName":"sk",
    "AttributeType":"S"
},
{
    "AttributeName":"gsi1pk",
    "AttributeType":"S"
},
{
    "AttributeName":"gsi1sk",
    "AttributeType":"S"
}
    ],
    "GlobalSecondaryIndexes":[
{
    "IndexName":"gsi1pk-gsi1sk-index",
    "KeySchema":[
{
    "AttributeName":"gsi1pk",
    "KeyType":"HASH"
},
{
    "AttributeName":"gsi1sk",
    "KeyType":"RANGE"
}
    ],
    "Projection":{
    "ProjectionType":"ALL"
}
}
    ],
    "BillingMode":"PAY_PER_REQUEST"
}
Example Entity
import DynamoDB from "aws-sdk/clients/dynamodb";
import { Entity } from 'electrodb';

const client = new DynamoDB.DocumentClient();

const table = 'electro';

const assets = new Entity({
    model: {
        entity: 'assets',
        version: '1',
        service: 'inventory'
    },
    attributes: {
        assetId: {
            type: 'string'
        },
        accountId: {
            type: 'string'
        },
        name: {
            type: 'string'
        },
        description: {
            type: 'string'
        },
        city: {
            type: 'string'
        },
        county: {
            type: 'string'
        },
        state: {
            type: 'string'
        },
        zip: {
            type: 'string'
        }
    },
    indexes: {
        assets: {
            pk: {
                field: 'pk',
                composite: ['accountId']
            },
            sk: {
                field: 'sk',
                composite: ['assetId']
            }
        },
        locations: {
            collection: 'geographics',
            index: 'gsi1pk-gsi1sk-index',
            pk: {
                field: 'gsi1pk',
                composite: ['state'],
            },
            sk: {
                field: 'gsi1sk',
                composite: ['county', 'city', 'zip'],
            }
        }
}
}, { table, client });

Hydrating Queries

The execution option hydrate can be used instruct ElectroDB to perform a query followed by an immediate batchGet to retrieve each individual item. Hydrate is available when performing query, match, or find operations.

_Note: Without hydration, a KEYS_ONLY index will likely fail. This is because ElectroDB adds additional attribute filters to queries to aid with entity isolation. These attributes won’t be present on a KEYS_ONLY index, so DynamoDB will throw. To circumvent this, checkout the Returning Keys section below.

// the locations index (gsi1pk-gsi1sk-index) is a KEYS_ONLY projection
const { data, cursor } = await assets.query
  .locations({ state: "Georgia" })
  .go({ hydrate: true });

Returning Keys

If you do not wish to “hydrate” your query response, you still have ways you can retrieve the keys returned by DynamoDB. The first mechanism is to use the query execution options { ignoreOwnership: true, data: 'includeKeys' }.

Note: When using typescript these options do not impact the typing of the returned object so this approach may require casting

Include Keys

The query execution options { ignoreOwnership: true, data: 'includeKeys' } allow you to return keys on the data array. The option ignoreOwnership is also used here to limit filters applied to query. ElectroDB filters items that it cannot identify as belonging to an Entity; KEYS_ONLY items are then indistinguishable from unidentifiable items.

// The elements in the `data` array are just the keys of the index, despite the typing saying otherwise.
const { data, cursor } = await assets.query
  .locations({ state: "Georgia" })
  .go({ ignoreOwnership: true, data: "includeKeys" });

Raw Responses

The query execution option { data: 'raw' } will return the raw response from DynamoDB. This is one way you can access the results of a KEYS_ONLY index.

// result is the actual DynamoDB response, despite the typing saying otherwise.
const result: any = await assets.query
  .locations({ state: "Georgia" })
  .go({ data: "raw" });
const { Items, LastEvaluatedKey } = result;

References