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 code on this page that references the entityExample 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 }, );
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 Method | DynamoDB Method | Purpose |
---|---|---|
get | get | Returns a single item, specified by its primary key. |
batchGet | batchGet | Returns a set of attributes for multiple items from one or more tables. |
query | query | Queries a table or index for items that match the specified primary key value. |
scan | scan | Scans the table and returns all matching items. |
match | query or scan | The 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. |
find | query or scan | the 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 Method | DynamoDB Method | Purpose |
---|---|---|
put | put , batchWrite | Creates or overwrites an existing item with the values provided |
batchPut | batchWrite | Creates or overwrites multiple items with the values provided |
create | put | Creates an item if the item does not currently exist, or throws if the item exists |
upsert | update | Upsert 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. |
update | update | Performs update on an existing record or creates a new record per the DynamoDB spec (read more) |
patch | update | Performs an update on existing item or throws if that item does not already exist. |
delete | delete , batchWrite | Deletes an item regardless of whether or not the specified item exists |
remove | delete | Deletes 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.