Executing Queries

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"
      }
    },
    {
      "IndexName": "gsi2pk-gsi2sk-index",
      "KeySchema": [
        {
          "AttributeName": "gsi2pk",
          "KeyType": "HASH"
        },
        {
          "AttributeName": "gsi2sk",
          "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 StoreLocations = new Entity(
  {
    model: {
      service: "MallStoreDirectory",
      entity: "MallStore",
      version: "1",
    },
    attributes: {
      cityId: {
        type: "string",
        required: true,
      },
      mallId: {
        type: "string",
        required: true,
      },
      storeId: {
        type: "string",
        required: true,
      },
      buildingId: {
        type: "string",
        required: true,
      },
      unitId: {
        type: "string",
        required: true,
      },
      category: {
        type: [
          "spite store",
          "food/coffee",
          "food/meal",
          "clothing",
          "electronics",
          "department",
          "misc",
        ],
        required: true,
      },
      leaseEndDate: {
        type: "string",
        required: true,
      },
      rent: {
        type: "string",
        required: true,
        validate: /^(\d+\.\d{2})$/,
      },
      discount: {
        type: "string",
        required: false,
        default: "0.00",
        validate: /^(\d+\.\d{2})$/,
      },
      tenants: {
        type: "set",
        items: "string",
      },
      warnings: {
        type: "number",
        default: 0,
      },
      deposit: {
        type: "number",
      },
      contact: {
        type: "set",
        items: "string",
      },
      rentalAgreement: {
        type: "list",
        items: {
          type: "map",
          properties: {
            type: {
              type: "string",
            },
            detail: {
              type: "string",
            },
          },
        },
      },
      petFee: {
        type: "number",
      },
      fees: {
        type: "number",
      },
      tags: {
        type: "set",
        items: "string",
      },
    },
    indexes: {
      stores: {
        pk: {
          field: "pk",
          composite: ["cityId", "mallId"],
        },
        sk: {
          field: "sk",
          composite: ["buildingId", "storeId"],
        },
      },
      units: {
        index: "gsi1pk-gsi1sk-index",
        pk: {
          field: "gsi1pk",
          composite: ["mallId"],
        },
        sk: {
          field: "gsi1sk",
          composite: ["buildingId", "unitId"],
        },
      },
      leases: {
        index: "gsi2pk-gsi2sk-index",
        pk: {
          field: "gsi2pk",
          composite: ["storeId"],
        },
        sk: {
          field: "gsi2sk",
          composite: ["leaseEndDate"],
        },
      },
    },
  },
  { table, client },
);
(Example code on this page that references the entity StoreLocations uses the following Entity and Table Definition found below)

In DynamoDB, an index serves as a data structure designed to improve the efficiency of data queries within a table. DynamoDB offers two types of indexes: local secondary indexes and global secondary indexes.

A local secondary index enables queries based on the table’s primary key while offering the flexibility to use a different sort key. This feature is useful for querying data in ways that differ from the default sort key associated with the table’s primary key.

On the other hand, a global secondary index allows for queries based on a completely different primary key than the one associated with the table. This is particularly useful for querying data unrelated to the table’s primary key, or for alternative query methods.

Overall, utilizing indexes in DynamoDB can significantly improve query performance, especially in large tables with extensive data. Efficient querying is crucial for optimal application performance.

In ElectroDB, queries are constructed around the ‘Access Patterns’ defined in the Schema. They can leverage partial key ‘Composite Attributes’ to enable efficient lookups. ElectroDB defines partition and sort keys as composites of one or more attributes, which is elaborated in the Modeling Indexes section.

Query Methods

Retrieving data from DynamoDB is accomplished by using one of the four query methods: query, scan, get, and batchGet. The query method is used to retrieve data from a table or index by performing a key lookup. The scan method is used to retrieve data from a table by scanning the entire table. The get method is used to retrieve a single item from a table by performing a key lookup. The batchGet method is used to retrieve multiple items from a table by performing multiple key lookups.

Additionally, ElectroDB offers a few additional methods built on top of DynamoDB’s offerings: find and match. These methods will perform a query or scan depending on the provided arguments. Below is a table that explains each ElectroDB method, which DynamoDB operation the method maps to, and a short description of the method’s purpose.

ElectroDB MethodDynamoDB MethodPurpose
getgetReturns a single item, specified by its primary key.
batchGetbatchGetReturns a set of attributes for multiple items from one or more tables.
queryqueryQueries a table or index for items that match the specified primary key value.
scanscanScans the table and returns all matching items.
matchquery or scanThe match method will attempt to build the most performant index keys with the attributes provided. ElectroDB identifies which index it can be “most” fulfilled with the index provided, and will fallback to a scan if no index can be built.
findquery or scanthe find method performs the same index seeking behavior but will also apply all provided values as filters instead of only using provided attributes to build index keys.

Mutation Methods

DynamoDB offers three methods for updating and creating records: put, update, and batchWrite. For the uninitiated, all three of these methods will create an item if it doesn’t exist. The difference between put/batchWrite and update this that a put will overwrite the existing item while an update will only modify the fields provided if the item already exists.

Additionally, ElectroDB offers a few mutation methods beyond put, update, and delete to more ergonomically fit your use case. Below is a table that explains each ElectroDB method, which DynamoDB operation the method maps to, and a short description of the method’s purpose.

ElectroDB MethodDynamoDB MethodPurpose
putput, batchWriteCreates or overwrites an existing item with the values provided
batchPutbatchWriteCreates or overwrites multiple items with the values provided
createputCreates an item if the item does not currently exist, or throws if the item exists
upsertupdateUpsert is similar to put in that it will create a record if one does not exist, except upsert perform an update if that record already exists.
updateupdatePerforms update on an existing record or creates a new record per the DynamoDB spec (read more)
patchupdatePerforms an update on existing item or throws if that item does not already exist.
deletedelete, batchWriteDeletes an item regardless of whether or not the specified item exists
removedeleteDeletes an item or throws if the item does not currently exist

Execution Methods

All query chains end with either a .go(), .params() method invocation. These terminal methods will either execute the query to DynamoDB (.go()) or return formatted parameters for use with the DynamoDB docClient (.params()).

Both .params() and .go() take a query configuration object which is detailed more in the section Execution Options.

Params

The params method ends a query chain, and synchronously formats your query into an object ready for the DynamoDB docClient.

For more information on the options available in the config object, checkout the section Execution Options

Example

const params = MallStores.query
  .leases({ mallId })
  .between({ leaseEndDate: "2020-06-01" }, { leaseEndDate: "2020-07-31" })
  .where(({ rent }, { lte }) => lte(rent, "5000.00"))
  .params();

Output

{
  "IndexName": "idx2",
  "TableName": "electro",
  "ExpressionAttributeNames": {
    "#rent": "rent",
    "#pk": "idx2pk",
    "#sk1": "idx2sk"
  },
  "ExpressionAttributeValues": {
    ":rent1": "5000.00",
    ":pk": "$mallstoredirectory_1#mallid_eastpointe",
    ":sk1": "$mallstore#leaseenddate_2020-06-01#rent_",
    ":sk2": "$mallstore#leaseenddate_2020-07-31#rent_"
  },
  "KeyConditionExpression": "#pk = :pk and #sk1 BETWEEN :sk1 AND :sk2",
  "FilterExpression": "#rent <= :rent1"
}

Go

The go method ends a query chain, and asynchronously queries DynamoDB with the client provided in the model.

For more information on the options available in the config object, check out the section Execution Options

Example

const results = await MallStores.query
  .leases({ mallId })
  .between({ leaseEndDate: "2020-06-01" }, { leaseEndDate: "2020-07-31" })
  .where(({ rent }, { lte }) => lte(rent, "5000.00"))
  .go();

Output

{
  data: Array<YOUR_SCHEMA>,
  cursor: string | undefined
}

Execution Options

Execution options can be added the .params() and .go() to change query behavior or add customer parameters to a query.

By default, ElectroDB enables you to work with records as the names and properties defined in the model. Additionally, it removes the need to deal directly with the docClient parameters which can be complex for a team without as much experience with DynamoDB. The Query Options object can be passed to both the .params() and .go() methods when building you query. Below are the options available:

Query options can be added the .params() and .go() to change query behavior or add customer parameters to a query.

By default, ElectroDB enables you to work with records as the names and properties defined in the model. Additionally, it removes the need to deal directly with the docClient parameters which can be complex for a team without as much experience with DynamoDB. The Query Options object can be passed to both the .params() and .go() methods when building you query. Below are the options available:

{
  params?: object;
  table?: string;
  data?: 'raw' | 'includeKeys' | 'attributes';
  pager?: 'raw' | 'cursor';
  originalErr?: boolean;
  response?: "default" | "none" | "all_old" | "updated_old" | "all_new" | "updated_new";
  ignoreOwnership?: boolean;
  limit?: number;
  count?: number;
  pages?: number | 'all';
  logger?: (event) => void;
  listeners Array<(event) => void>;
  attributes?: string[];
  order?: 'asc' | 'desc';
};

Query Execution Options

params

Default Value: {} Properties added to this object will be merged onto the params sent to the document client. Any conflicts with ElectroDB will favor the params specified here. Consider this an escape hatch for options not yet supported by ElectroDB.

table

Default Value: (from constructor) Use a different table than the one defined in Service Options and/or Entity Options.

attributes

Default Value: (all attributes) The attributes query option allows you to specify ProjectionExpression Attributes for your get or query operation.

data

Default Value: "attributes" Accepts the values 'raw', 'includeKeys', 'attributes' or undefined. Use 'raw' to return query results as they were returned by the docClient. Use 'includeKeys' to include item partition and sort key values in your return object. By default, ElectroDB does not return partition, sort, or global keys in its response.

originalErr

Default Value: false By default, ElectroDB alters the stacktrace of any exceptions thrown by the DynamoDB client to give better visibility to the developer. Set this value equal to true to turn off this functionality and return the error unchanged.

response

Default Value: "default" Used as a convenience for applying the DynamoDB parameter ReturnValues. The options here are the same as the parameter values for the DocumentClient except lowercase. The "none" option will cause the method to return null and will bypass ElectroDB’s response formatting — useful if formatting performance is a concern.

ignoreOwnership

Default Value: false By default, ElectroDB interrogates items returned from a query for the presence of matching entity “identifiers”. This helps to ensure other entities, or other versions of an entity, are filtered from your results. If you are using ElectroDB with an existing table/dataset you can turn off this feature by setting this property to true.

limit

Default Value: none Used a convenience wrapper for the DynamoDB parameter Limit [read more]. When limit is paired option pages:"all", ElectroDB will paginate DynamoDB until the limit is at least reached or all items for that query have been returned; this means you may receive more items than your specified limit but never less.

Note: If your use case requires returning a specific number of items at a time, the count option is the better option.

count

Default Value: none A target for the number of items to return from DynamoDB. If this option is passed, Queries on entities will paginate DynamoDB until the items found match the count is reached or all items for that query have been returned. It is important to understand that this option may result in ElectroDB making multiple calls to DynamoDB to satisfy the count. For this reason, you should also consider using the pages option to limit the number of requests (or “pages”) made to DynamoDB.

pages

Default Value: 1 How many DynamoDB pages should a query iterate through before stopping. To have ElectroDB automatically paginate through all results, pass the string value 'all'.

order

Default Value: ‘asc’ Convenience option for ScanIndexForward, to the change the order of queries based on your index’s Sort Key — valid options include ‘asc’ and ‘desc’. [read more]

listeners

Default Value: [] An array of callbacks that are invoked when internal ElectroDB events occur.

logger

Default Value: none A convenience option for a single event listener that semantically can be used for logging.