{-# LANGUAGE DataKinds #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE NoImplicitPrelude #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE TypeFamilies #-} {-# OPTIONS_GHC -fno-warn-unused-imports #-} -- Module : Network.AWS.DynamoDB.Query -- Copyright : (c) 2013-2014 Brendan Hay -- License : This Source Code Form is subject to the terms of -- the Mozilla Public License, v. 2.0. -- A copy of the MPL can be found in the LICENSE file or -- you can obtain it at http://mozilla.org/MPL/2.0/. -- Maintainer : Brendan Hay -- Stability : experimental -- Portability : non-portable (GHC extensions) -- | A Query operation directly accesses items from a table using the table -- primary key, or from an index using the index key. You must provide a -- specific hash key value. You can narrow the scope of the query by using -- comparison operators on the range key value, or on the index key. You can -- use the ScanIndexForward parameter to get results in forward or reverse -- order, by range key or by index key. Queries that do not return results -- consume the minimum number of read capacity units for that type of read -- operation. If the total number of items meeting the query criteria exceeds -- the result set size limit of 1 MB, the query stops and results are returned -- to the user with LastEvaluatedKey to continue the query in a subsequent -- operation. Unlike a Scan operation, a Query operation never returns both an -- empty result set and a LastEvaluatedKey. The LastEvaluatedKey is only -- provided if the results exceed 1 MB, or if you have used Limit. You can -- query a table, a local secondary index, or a global secondary index. For a -- query on a table or on a local secondary index, you can set ConsistentRead -- to true and obtain a strongly consistent result. Global secondary indexes -- support eventually consistent reads only, so do not specify ConsistentRead -- when querying a global secondary index. -- -- module Network.AWS.DynamoDB.Query ( -- * Request Query -- ** Request constructor , query -- ** Request lenses , qAttributesToGet , qConditionalOperator , qConsistentRead , qExclusiveStartKey , qExpressionAttributeNames , qExpressionAttributeValues , qFilterExpression , qIndexName , qKeyConditions , qLimit , qProjectionExpression , qQueryFilter , qReturnConsumedCapacity , qScanIndexForward , qSelect , qTableName -- * Response , QueryResponse -- ** Response constructor , queryResponse -- ** Response lenses , qrConsumedCapacity , qrCount , qrItems , qrLastEvaluatedKey , qrScannedCount ) where import Network.AWS.Prelude import Network.AWS.Request.JSON import Network.AWS.DynamoDB.Types import qualified GHC.Exts data Query = Query { _qAttributesToGet :: List1 "AttributesToGet" Text , _qConditionalOperator :: Maybe ConditionalOperator , _qConsistentRead :: Maybe Bool , _qExclusiveStartKey :: Map Text AttributeValue , _qExpressionAttributeNames :: Map Text Text , _qExpressionAttributeValues :: Map Text AttributeValue , _qFilterExpression :: Maybe Text , _qIndexName :: Maybe Text , _qKeyConditions :: Map Text Condition , _qLimit :: Maybe Nat , _qProjectionExpression :: Maybe Text , _qQueryFilter :: Map Text Condition , _qReturnConsumedCapacity :: Maybe ReturnConsumedCapacity , _qScanIndexForward :: Maybe Bool , _qSelect :: Maybe Select , _qTableName :: Text } deriving (Eq, Show) -- | 'Query' constructor. -- -- The fields accessible through corresponding lenses are: -- -- * 'qAttributesToGet' @::@ 'NonEmpty' 'Text' -- -- * 'qConditionalOperator' @::@ 'Maybe' 'ConditionalOperator' -- -- * 'qConsistentRead' @::@ 'Maybe' 'Bool' -- -- * 'qExclusiveStartKey' @::@ 'HashMap' 'Text' 'AttributeValue' -- -- * 'qExpressionAttributeNames' @::@ 'HashMap' 'Text' 'Text' -- -- * 'qExpressionAttributeValues' @::@ 'HashMap' 'Text' 'AttributeValue' -- -- * 'qFilterExpression' @::@ 'Maybe' 'Text' -- -- * 'qIndexName' @::@ 'Maybe' 'Text' -- -- * 'qKeyConditions' @::@ 'HashMap' 'Text' 'Condition' -- -- * 'qLimit' @::@ 'Maybe' 'Natural' -- -- * 'qProjectionExpression' @::@ 'Maybe' 'Text' -- -- * 'qQueryFilter' @::@ 'HashMap' 'Text' 'Condition' -- -- * 'qReturnConsumedCapacity' @::@ 'Maybe' 'ReturnConsumedCapacity' -- -- * 'qScanIndexForward' @::@ 'Maybe' 'Bool' -- -- * 'qSelect' @::@ 'Maybe' 'Select' -- -- * 'qTableName' @::@ 'Text' -- query :: Text -- ^ 'qTableName' -> NonEmpty Text -- ^ 'qAttributesToGet' -> Query query p1 p2 = Query { _qTableName = p1 , _qAttributesToGet = withIso _List1 (const id) p2 , _qIndexName = Nothing , _qSelect = Nothing , _qLimit = Nothing , _qConsistentRead = Nothing , _qKeyConditions = mempty , _qQueryFilter = mempty , _qConditionalOperator = Nothing , _qScanIndexForward = Nothing , _qExclusiveStartKey = mempty , _qReturnConsumedCapacity = Nothing , _qProjectionExpression = Nothing , _qFilterExpression = Nothing , _qExpressionAttributeNames = mempty , _qExpressionAttributeValues = mempty } -- | There is a newer parameter available. Use ProjectionExpression instead. -- Note that if you use AttributesToGet and ProjectionExpression at the same -- time, DynamoDB will return a ValidationException exception. This -- parameter allows you to retrieve lists or maps; however, it cannot -- retrieve individual list or map elements. The names of one or more -- attributes to retrieve. If no attribute names are specified, then all -- attributes will be returned. If any of the requested attributes are not -- found, they will not appear in the result. Note that AttributesToGet has -- no effect on provisioned throughput consumption. DynamoDB determines -- capacity units consumed based on item size, not on the amount of data -- that is returned to an application. You cannot use both AttributesToGet -- and Select together in a Query request, unless the value for Select is -- SPECIFIC_ATTRIBUTES. (This usage is equivalent to specifying -- AttributesToGet without any value for Select.) If you query a local -- secondary index and request only attributes that are projected into that -- index, the operation will read only the index and not the table. If any -- of the requested attributes are not projected into the local secondary -- index, DynamoDB will fetch each of these attributes from the parent -- table. This extra fetching incurs additional throughput cost and latency. -- If you query a global secondary index, you can only request attributes -- that are projected into the index. Global secondary index queries cannot -- fetch attributes from the parent table. qAttributesToGet :: Lens' Query (NonEmpty Text) qAttributesToGet = lens _qAttributesToGet (\s a -> s { _qAttributesToGet = a }) . _List1 -- | There is a newer parameter available. Use ConditionExpression instead. -- Note that if you use ConditionalOperator and ConditionExpression at the -- same time, DynamoDB will return a ValidationException exception. This -- parameter does not support lists or maps. A logical operator to apply to -- the conditions in the QueryFilter map: AND - If all of the conditions -- evaluate to true, then the entire map evaluates to true. OR - If at least -- one of the conditions evaluate to true, then the entire map evaluates to -- true. If you omit ConditionalOperator, then AND is the default. The -- operation will succeed only if the entire map evaluates to true. qConditionalOperator :: Lens' Query (Maybe ConditionalOperator) qConditionalOperator = lens _qConditionalOperator (\s a -> s { _qConditionalOperator = a }) -- | A value that if set to true, then the operation uses strongly consistent -- reads; otherwise, eventually consistent reads are used. Strongly -- consistent reads are not supported on global secondary indexes. If you -- query a global secondary index with ConsistentRead set to true, you will -- receive an error message. qConsistentRead :: Lens' Query (Maybe Bool) qConsistentRead = lens _qConsistentRead (\s a -> s { _qConsistentRead = a }) -- | The primary key of the first item that this operation will evaluate. Use -- the value that was returned for LastEvaluatedKey in the previous -- operation. The data type for ExclusiveStartKey must be String, Number or -- Binary. No set data types are allowed. qExclusiveStartKey :: Lens' Query (HashMap Text AttributeValue) qExclusiveStartKey = lens _qExclusiveStartKey (\s a -> s { _qExclusiveStartKey = a }) . _Map -- | One or more substitution tokens for simplifying complex expressions. The -- following are some use cases for an ExpressionAttributeNames value: To -- shorten an attribute name that is very long or unwieldy in an expression. -- To create a placeholder for repeating occurrences of an attribute name in -- an expression. To prevent special characters in an attribute name from -- being misinterpreted in an expression. Use the # character in an -- expression to dereference an attribute name. For example, consider the -- following expression: order.customerInfo.LastName = "Smith" OR -- order.customerInfo.LastName = "Jones" Now suppose that you specified the -- following for ExpressionAttributeNames: -- {"n":"order.customerInfo.LastName"} The expression can now be simplified -- as follows: #n = "Smith" OR #n = "Jones". qExpressionAttributeNames :: Lens' Query (HashMap Text Text) qExpressionAttributeNames = lens _qExpressionAttributeNames (\s a -> s { _qExpressionAttributeNames = a }) . _Map -- | One or more values that can be substituted in an expression. Use the : -- character in an expression to dereference an attribute value. For -- example, consider the following expression: ProductStatus IN -- ("Available","Backordered","Discontinued") Now suppose that you specified -- the following for ExpressionAttributeValues: { "a":{"S":"Available"}, -- "b":{"S":"Backordered"}, "d":{"S":"Discontinued"} } The expression can -- now be simplified as follows: ProductStatus IN (:a,:b,:c). qExpressionAttributeValues :: Lens' Query (HashMap Text AttributeValue) qExpressionAttributeValues = lens _qExpressionAttributeValues (\s a -> s { _qExpressionAttributeValues = a }) . _Map -- | A condition that evaluates the query results and returns only the desired -- values. The condition you specify is applied to the items queried; any -- items that do not match the expression are not returned. qFilterExpression :: Lens' Query (Maybe Text) qFilterExpression = lens _qFilterExpression (\s a -> s { _qFilterExpression = a }) -- | The name of an index to query. This index can be any local secondary -- index or global secondary index on the table. qIndexName :: Lens' Query (Maybe Text) qIndexName = lens _qIndexName (\s a -> s { _qIndexName = a }) -- | The selection criteria for the query. For a query on a table, you can -- have conditions only on the table primary key attributes. You must -- specify the hash key attribute name and value as an EQ condition. You can -- optionally specify a second condition, referring to the range key -- attribute. For a query on an index, you can have conditions only on the -- index key attributes. You must specify the index hash attribute name and -- value as an EQ condition. You can optionally specify a second condition, -- referring to the index key range attribute. Each KeyConditions element -- consists of an attribute name to compare, along with the following: -- AttributeValueList - One or more values to evaluate against the supplied -- attribute. The number of values in the list depends on the -- ComparisonOperator being used. For type Number, value comparisons are -- numeric. String value comparisons for greater than, equals, or less than -- are based on ASCII character code values. For example, a is greater than -- A, and aa is greater than B. For a list of code values, see -- http://en.wikipedia.org/wiki/ASCII#ASCII_printable_characters. For -- Binary, DynamoDB treats each byte of the binary data as unsigned when it -- compares binary values, for example when evaluating query expressions. -- ComparisonOperator - A comparator for evaluating attributes, for example, -- equals, greater than, less than, and so on. For KeyConditions, only the -- following comparison operators are supported: EQ | LE | LT | GE | GT | -- BEGINS_WITH | BETWEEN The following are descriptions of these comparison -- operators. EQ : Equal. AttributeValueList can contain only one -- AttributeValue of type String, Number, or Binary (not a set type). If an -- item contains an AttributeValue element of a different type than the one -- specified in the request, the value does not match. For example, -- {"S":"6"} does not equal {"N":"6"}. Also, {"N":"6"} does not equal -- {"NS":["6", "2", "1"]}. LE : Less than or equal. AttributeValueList can -- contain only one AttributeValue element of type String, Number, or Binary -- (not a set type). If an item contains an AttributeValue element of a -- different type than the one specified in the request, the value does not -- match. For example, {"S":"6"} does not equal {"N":"6"}. Also, {"N":"6"} -- does not compare to {"NS":["6", "2", "1"]}. LT : Less than. -- AttributeValueList can contain only one AttributeValue of type String, -- Number, or Binary (not a set type). If an item contains an AttributeValue -- element of a different type than the one specified in the request, the -- value does not match. For example, {"S":"6"} does not equal {"N":"6"}. -- Also, {"N":"6"} does not compare to {"NS":["6", "2", "1"]}. GE : Greater -- than or equal. AttributeValueList can contain only one AttributeValue -- element of type String, Number, or Binary (not a set type). If an item -- contains an AttributeValue element of a different type than the one -- specified in the request, the value does not match. For example, -- {"S":"6"} does not equal {"N":"6"}. Also, {"N":"6"} does not compare to -- {"NS":["6", "2", "1"]}. GT : Greater than. AttributeValueList can contain -- only one AttributeValue element of type String, Number, or Binary (not a -- set type). If an item contains an AttributeValue element of a different -- type than the one specified in the request, the value does not match. For -- example, {"S":"6"} does not equal {"N":"6"}. Also, {"N":"6"} does not -- compare to {"NS":["6", "2", "1"]}. BEGINS_WITH : Checks for a prefix. -- AttributeValueList can contain only one AttributeValue of type String or -- Binary (not a Number or a set type). The target attribute of the -- comparison must be of type String or Binary (not a Number or a set type). -- BETWEEN : Greater than or equal to the first value, and less than or -- equal to the second value. AttributeValueList must contain two -- AttributeValue elements of the same type, either String, Number, or -- Binary (not a set type). A target attribute matches if the target value -- is greater than, or equal to, the first element and less than, or equal -- to, the second element. If an item contains an AttributeValue element of -- a different type than the one specified in the request, the value does -- not match. For example, {"S":"6"} does not compare to {"N":"6"}. Also, -- {"N":"6"} does not compare to {"NS":["6", "2", "1"]} For usage examples -- of AttributeValueList and ComparisonOperator, see Legacy Conditional -- Parameters in the Amazon DynamoDB Developer Guide. qKeyConditions :: Lens' Query (HashMap Text Condition) qKeyConditions = lens _qKeyConditions (\s a -> s { _qKeyConditions = a }) . _Map -- | The maximum number of items to evaluate (not necessarily the number of -- matching items). If DynamoDB processes the number of items up to the -- limit while processing the results, it stops the operation and returns -- the matching values up to that point, and a key in LastEvaluatedKey to -- apply in a subsequent operation, so that you can pick up where you left -- off. Also, if the processed data set size exceeds 1 MB before DynamoDB -- reaches this limit, it stops the operation and returns the matching -- values up to the limit, and a key in LastEvaluatedKey to apply in a -- subsequent operation to continue the operation. For more information, see -- Query and Scan in the Amazon DynamoDB Developer Guide. qLimit :: Lens' Query (Maybe Natural) qLimit = lens _qLimit (\s a -> s { _qLimit = a }) . mapping _Nat -- | One or more attributes to retrieve from the table. These attributes can -- include scalars, sets, or elements of a JSON document. The attributes in -- the expression must be separated by commas. If no attribute names are -- specified, then all attributes will be returned. If any of the requested -- attributes are not found, they will not appear in the result. qProjectionExpression :: Lens' Query (Maybe Text) qProjectionExpression = lens _qProjectionExpression (\s a -> s { _qProjectionExpression = a }) -- | There is a newer parameter available. Use FilterExpression instead. Note -- that if you use QueryFilter and FilterExpression at the same time, -- DynamoDB will return a ValidationException exception. This parameter does -- not support lists or maps. A condition that evaluates the query results -- and returns only the desired values. If you specify more than one -- condition in the QueryFilter map, then by default all of the conditions -- must evaluate to true. In other words, the conditions are ANDed together. -- (You can use the ConditionalOperator parameter to OR the conditions -- instead. If you do this, then at least one of the conditions must -- evaluate to true, rather than all of them.) Each QueryFilter element -- consists of an attribute name to compare, along with the following: -- AttributeValueList - One or more values to evaluate against the supplied -- attribute. The number of values in the list depends on the operator -- specified in ComparisonOperator. For type Number, value comparisons are -- numeric. String value comparisons for greater than, equals, or less than -- are based on ASCII character code values. For example, a is greater than -- A, and aa is greater than B. For a list of code values, see -- http://en.wikipedia.org/wiki/ASCII#ASCII_printable_characters. For type -- Binary, DynamoDB treats each byte of the binary data as unsigned when it -- compares binary values, for example when evaluating query expressions. -- For information on specifying data types in JSON, see JSON Data Format in -- the Amazon DynamoDB Developer Guide. ComparisonOperator - A comparator -- for evaluating attributes. For example, equals, greater than, less than, -- etc. The following comparison operators are available: EQ | NE | LE | LT -- | GE | GT | NOT_NULL | NULL | CONTAINS | NOT_CONTAINS | BEGINS_WITH | IN -- | BETWEEN For complete descriptions of all comparison operators, see -- API_Condition.html. qQueryFilter :: Lens' Query (HashMap Text Condition) qQueryFilter = lens _qQueryFilter (\s a -> s { _qQueryFilter = a }) . _Map qReturnConsumedCapacity :: Lens' Query (Maybe ReturnConsumedCapacity) qReturnConsumedCapacity = lens _qReturnConsumedCapacity (\s a -> s { _qReturnConsumedCapacity = a }) -- | A value that specifies ascending (true) or descending (false) traversal -- of the index. DynamoDB returns results reflecting the requested order -- determined by the range key. If the data type is Number, the results are -- returned in numeric order. For type String, the results are returned in -- order of ASCII character code values. For type Binary, DynamoDB treats -- each byte of the binary data as unsigned when it compares binary values. -- If ScanIndexForward is not specified, the results are returned in -- ascending order. qScanIndexForward :: Lens' Query (Maybe Bool) qScanIndexForward = lens _qScanIndexForward (\s a -> s { _qScanIndexForward = a }) -- | The attributes to be returned in the result. You can retrieve all item -- attributes, specific item attributes, the count of matching items, or in -- the case of an index, some or all of the attributes projected into the -- index. ALL_ATTRIBUTES - Returns all of the item attributes from the -- specified table or index. If you query a local secondary index, then for -- each matching item in the index DynamoDB will fetch the entire item from -- the parent table. If the index is configured to project all item -- attributes, then all of the data can be obtained from the local secondary -- index, and no fetching is required. ALL_PROJECTED_ATTRIBUTES - Allowed -- only when querying an index. Retrieves all attributes that have been -- projected into the index. If the index is configured to project all -- attributes, this return value is equivalent to specifying ALL_ATTRIBUTES. -- COUNT - Returns the number of matching items, rather than the matching -- items themselves. SPECIFIC_ATTRIBUTES - Returns only the attributes -- listed in AttributesToGet. This return value is equivalent to specifying -- AttributesToGet without specifying any value for Select. If you query a -- local secondary index and request only attributes that are projected into -- that index, the operation will read only the index and not the table. If -- any of the requested attributes are not projected into the local -- secondary index, DynamoDB will fetch each of these attributes from the -- parent table. This extra fetching incurs additional throughput cost and -- latency. If you query a global secondary index, you can only request -- attributes that are projected into the index. Global secondary index -- queries cannot fetch attributes from the parent table. If neither Select -- nor AttributesToGet are specified, DynamoDB defaults to ALL_ATTRIBUTES -- when accessing a table, and ALL_PROJECTED_ATTRIBUTES when accessing an -- index. You cannot use both Select and AttributesToGet together in a -- single request, unless the value for Select is SPECIFIC_ATTRIBUTES. (This -- usage is equivalent to specifying AttributesToGet without any value for -- Select.). qSelect :: Lens' Query (Maybe Select) qSelect = lens _qSelect (\s a -> s { _qSelect = a }) -- | The name of the table containing the requested items. qTableName :: Lens' Query Text qTableName = lens _qTableName (\s a -> s { _qTableName = a }) data QueryResponse = QueryResponse { _qrConsumedCapacity :: Maybe ConsumedCapacity , _qrCount :: Maybe Int , _qrItems :: List "Items" (Map Text AttributeValue) , _qrLastEvaluatedKey :: Map Text AttributeValue , _qrScannedCount :: Maybe Int } deriving (Eq, Show) -- | 'QueryResponse' constructor. -- -- The fields accessible through corresponding lenses are: -- -- * 'qrConsumedCapacity' @::@ 'Maybe' 'ConsumedCapacity' -- -- * 'qrCount' @::@ 'Maybe' 'Int' -- -- * 'qrItems' @::@ ['HashMap' 'Text' 'AttributeValue'] -- -- * 'qrLastEvaluatedKey' @::@ 'HashMap' 'Text' 'AttributeValue' -- -- * 'qrScannedCount' @::@ 'Maybe' 'Int' -- queryResponse :: QueryResponse queryResponse = QueryResponse { _qrItems = mempty , _qrCount = Nothing , _qrScannedCount = Nothing , _qrLastEvaluatedKey = mempty , _qrConsumedCapacity = Nothing } qrConsumedCapacity :: Lens' QueryResponse (Maybe ConsumedCapacity) qrConsumedCapacity = lens _qrConsumedCapacity (\s a -> s { _qrConsumedCapacity = a }) -- | The number of items in the response. If you used a QueryFilter in the -- request, then Count is the number of items returned after the filter was -- applied, and ScannedCount is the number of matching items before> the -- filter was applied. If you did not use a filter in the request, then -- Count and ScannedCount are the same. qrCount :: Lens' QueryResponse (Maybe Int) qrCount = lens _qrCount (\s a -> s { _qrCount = a }) -- | An array of item attributes that match the query criteria. Each element -- in this array consists of an attribute name and the value for that -- attribute. qrItems :: Lens' QueryResponse [HashMap Text AttributeValue] qrItems = lens _qrItems (\s a -> s { _qrItems = a }) . _List -- | The primary key of the item where the operation stopped, inclusive of the -- previous result set. Use this value to start a new operation, excluding -- this value in the new request. If LastEvaluatedKey is empty, then the -- "last page" of results has been processed and there is no more data to be -- retrieved. If LastEvaluatedKey is not empty, it does not necessarily mean -- that there is more data in the result set. The only way to know when you -- have reached the end of the result set is when LastEvaluatedKey is empty. qrLastEvaluatedKey :: Lens' QueryResponse (HashMap Text AttributeValue) qrLastEvaluatedKey = lens _qrLastEvaluatedKey (\s a -> s { _qrLastEvaluatedKey = a }) . _Map -- | The number of items evaluated, before any QueryFilter is applied. A high -- ScannedCount value with few, or no, Count results indicates an -- inefficient Query operation. For more information, see Count and -- ScannedCount in the Amazon DynamoDB Developer Guide. If you did not use a -- filter in the request, then ScannedCount is the same as Count. qrScannedCount :: Lens' QueryResponse (Maybe Int) qrScannedCount = lens _qrScannedCount (\s a -> s { _qrScannedCount = a }) instance ToPath Query where toPath = const "/" instance ToQuery Query where toQuery = const mempty instance ToHeaders Query instance ToJSON Query where toJSON Query{..} = object [ "TableName" .= _qTableName , "IndexName" .= _qIndexName , "Select" .= _qSelect , "AttributesToGet" .= _qAttributesToGet , "Limit" .= _qLimit , "ConsistentRead" .= _qConsistentRead , "KeyConditions" .= _qKeyConditions , "QueryFilter" .= _qQueryFilter , "ConditionalOperator" .= _qConditionalOperator , "ScanIndexForward" .= _qScanIndexForward , "ExclusiveStartKey" .= _qExclusiveStartKey , "ReturnConsumedCapacity" .= _qReturnConsumedCapacity , "ProjectionExpression" .= _qProjectionExpression , "FilterExpression" .= _qFilterExpression , "ExpressionAttributeNames" .= _qExpressionAttributeNames , "ExpressionAttributeValues" .= _qExpressionAttributeValues ] instance AWSRequest Query where type Sv Query = DynamoDB type Rs Query = QueryResponse request = post "Query" response = jsonResponse instance FromJSON QueryResponse where parseJSON = withObject "QueryResponse" $ \o -> QueryResponse <$> o .:? "ConsumedCapacity" <*> o .:? "Count" <*> o .: "Items" <*> o .: "LastEvaluatedKey" <*> o .:? "ScannedCount" instance AWSPager Query where page rq rs | stop (rq ^. qExclusiveStartKey) = Nothing | otherwise = Just $ rq & qExclusiveStartKey .~ rs ^. qrLastEvaluatedKey