diff --git a/apps/wing-console/console/server/src/index.ts b/apps/wing-console/console/server/src/index.ts index 7c652f2d7f5..544d57b5cc8 100644 --- a/apps/wing-console/console/server/src/index.ts +++ b/apps/wing-console/console/server/src/index.ts @@ -45,6 +45,8 @@ export type { LayoutComponentType, } from "./utils/createRouter.js"; +export * from "@winglang/sdk/lib/ex/index.js"; + export type RouteNames = keyof inferRouterInputs | undefined; export { isTermsAccepted } from "./utils/terms-and-conditions.js"; diff --git a/apps/wing-console/console/ui/src/features/dynamodb-table-interaction-view.tsx b/apps/wing-console/console/ui/src/features/dynamodb-table-interaction-view.tsx index cb9a7ace8d8..80072538d31 100644 --- a/apps/wing-console/console/ui/src/features/dynamodb-table-interaction-view.tsx +++ b/apps/wing-console/console/ui/src/features/dynamodb-table-interaction-view.tsx @@ -58,7 +58,7 @@ export const DynamodbTableInteractionView = ({ useEffect(() => { if (table.data?.rows) { - const rows = table.data.rows.map((row) => { + const rows = table.data.rows.items.map((row) => { return { data: row, error: "", @@ -76,16 +76,18 @@ export const DynamodbTableInteractionView = ({ -
- -
+ {table.data && ( +
+ +
+ )} diff --git a/apps/wing-console/console/ui/src/services/use-dynamodb-table.ts b/apps/wing-console/console/ui/src/services/use-dynamodb-table.ts index 6f8ede8ce70..29ba199fc98 100644 --- a/apps/wing-console/console/ui/src/services/use-dynamodb-table.ts +++ b/apps/wing-console/console/ui/src/services/use-dynamodb-table.ts @@ -23,7 +23,7 @@ export const useDynamodbTable = ({ resourcePath }: UseTableOptions) => { const removeRow = useCallback( async (index: number) => { - const row = table.data?.rows[index]; + const row = table.data?.rows.items[index]; if (!row) { return; } diff --git a/docs/docs/04-standard-library/06-ex/api-reference.md b/docs/docs/04-standard-library/06-ex/api-reference.md index 3e9fee172e6..98d2cfb27fe 100644 --- a/docs/docs/04-standard-library/06-ex/api-reference.md +++ b/docs/docs/04-standard-library/06-ex/api-reference.md @@ -46,7 +46,8 @@ new ex.DynamodbTable(props: DynamodbTableProps); | deleteItem | Delete an item from the table. | | getItem | Get an item from the table. | | putItem | Put an item into the table. | -| scan | Get the table. | +| query | Return all items with a given partition key value. | +| scan | Return one or more items and item attributes by accessing every item in a table or a secondary index. | | transactWriteItems | Perform a synchronous write operation that groups up to 100 action requests. | | updateItem | Get an item from the table. | @@ -76,6 +77,8 @@ inflight getItem(key: Json): Json Get an item from the table. +> [https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html) + ###### `key`Required - *Type:* Json @@ -108,13 +111,39 @@ dynamodb PutItem props. --- +##### `query` + +```wing +inflight query(props: DynamodbTableQueryProps): DynamodbTableQueryResult +``` + +Return all items with a given partition key value. + +> [https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html) + +###### `props`Required + +- *Type:* DynamodbTableQueryProps + +properties for the query operation. + +--- + ##### `scan` ```wing -inflight scan(): MutArray +inflight scan(props?: DynamodbTableScanProps): DynamodbTableScanResult ``` -Get the table. +Return one or more items and item attributes by accessing every item in a table or a secondary index. + +###### `props`Optional + +- *Type:* DynamodbTableScanProps + +properties for the scan operation. + +--- ##### `transactWriteItems` @@ -708,7 +737,8 @@ the table name. | deleteItem | Delete an item from the table. | | getItem | Get an item from the table. | | putItem | Put an item into the table. | -| scan | Get the table. | +| query | Return all items with a given partition key value. | +| scan | Return one or more items and item attributes by accessing every item in a table or a secondary index. | | transactWriteItems | Perform a synchronous write operation that groups up to 100 action requests. | | updateItem | Get an item from the table. | @@ -762,13 +792,33 @@ Put an item into the table. --- +##### `query` + +```wing +query(props: DynamodbTableQueryProps): DynamodbTableQueryResult +``` + +Return all items with a given partition key value. + +###### `props`Required + +- *Type:* DynamodbTableQueryProps + +--- + ##### `scan` ```wing -scan(): MutArray +scan(props?: DynamodbTableScanProps): DynamodbTableScanResult ``` -Get the table. +Return one or more items and item attributes by accessing every item in a table or a secondary index. + +###### `props`Optional + +- *Type:* DynamodbTableScanProps + +--- ##### `transactWriteItems` @@ -1098,6 +1148,603 @@ A condition that must be satisfied in order for an operation to succeed. --- +### DynamodbTableQueryProps + +Properties for `DynamodbTable.query`. + +#### Initializer + +```wing +bring ex; + +let DynamodbTableQueryProps = ex.DynamodbTableQueryProps{ ... }; +``` + +#### Properties + +| **Name** | **Type** | **Description** | +| --- | --- | --- | +| keyConditionExpression | str | The condition that specifies the key values for items to be retrieved by the Query action. | +| consistentRead | bool | Determines the read consistency model: If set to true, then the operation uses strongly consistent reads; | +| exclusiveStartKey | Json | The primary key of the first item that this operation will evaluate. | +| expressionAttributeNames | Json | One or more substitution tokens for attribute names in an expression. | +| expressionAttributeValues | Json | One or more values that can be substituted in an expression. | +| filterExpression | str | A string that contains conditions that DynamoDB applies after the Query operation, but before the data is returned to you. | +| indexName | str | The name of an index to query. | +| limit | num | The maximum number of items to evaluate (not necessarily the number of matching items). | +| projectionExpression | str | A string that identifies one or more attributes to retrieve from the table. | +| returnConsumedCapacity | str | Determines the level of detail about either provisioned or on-demand throughput consumption. | +| scanIndexForward | bool | Specifies the order for index traversal. | +| select | str | The attributes to be returned in the result. | + +--- + +##### `keyConditionExpression`Required + +```wing +keyConditionExpression: str; +``` + +- *Type:* str + +The condition that specifies the key values for items to be retrieved by the Query action. + +> [https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html#DDB-Query-request-KeyConditionExpression](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html#DDB-Query-request-KeyConditionExpression) + +--- + +##### `consistentRead`Optional + +```wing +consistentRead: bool; +``` + +- *Type:* bool +- *Default:* false + +Determines the read consistency model: If set to true, then the operation uses strongly consistent reads; + +otherwise, the operation uses eventually consistent reads. + +> [https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html#DDB-Query-request-ConsistentRead](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html#DDB-Query-request-ConsistentRead) + +--- + +##### `exclusiveStartKey`Optional + +```wing +exclusiveStartKey: Json; +``` + +- *Type:* Json +- *Default:* undefined + +The primary key of the first item that this operation will evaluate. + +> [https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html#DDB-Query-request-ExclusiveStartKey](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html#DDB-Query-request-ExclusiveStartKey) + +--- + +##### `expressionAttributeNames`Optional + +```wing +expressionAttributeNames: Json; +``` + +- *Type:* Json +- *Default:* undefined + +One or more substitution tokens for attribute names in an expression. + +> [https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html#DDB-Query-request-ExpressionAttributeNames](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html#DDB-Query-request-ExpressionAttributeNames) + +--- + +##### `expressionAttributeValues`Optional + +```wing +expressionAttributeValues: Json; +``` + +- *Type:* Json +- *Default:* undefined + +One or more values that can be substituted in an expression. + +> [https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html#DDB-Query-request-ExpressionAttributeValues](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html#DDB-Query-request-ExpressionAttributeValues) + +--- + +##### `filterExpression`Optional + +```wing +filterExpression: str; +``` + +- *Type:* str +- *Default:* undefined + +A string that contains conditions that DynamoDB applies after the Query operation, but before the data is returned to you. + +> [https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html#DDB-Query-request-FilterExpression](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html#DDB-Query-request-FilterExpression) + +--- + +##### `indexName`Optional + +```wing +indexName: str; +``` + +- *Type:* str +- *Default:* undefined + +The name of an index to query. + +> [https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html#DDB-Query-request-IndexName](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html#DDB-Query-request-IndexName) + +--- + +##### `limit`Optional + +```wing +limit: num; +``` + +- *Type:* num +- *Default:* undefined + +The maximum number of items to evaluate (not necessarily the number of matching items). + +> [https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html#DDB-Query-request-Limit](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html#DDB-Query-request-Limit) + +--- + +##### `projectionExpression`Optional + +```wing +projectionExpression: str; +``` + +- *Type:* str +- *Default:* undefined + +A string that identifies one or more attributes to retrieve from the table. + +> [https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html#DDB-Query-request-ProjectionExpression](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html#DDB-Query-request-ProjectionExpression) + +--- + +##### `returnConsumedCapacity`Optional + +```wing +returnConsumedCapacity: str; +``` + +- *Type:* str +- *Default:* "NONE" + +Determines the level of detail about either provisioned or on-demand throughput consumption. + +> [https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html#DDB-Query-request-ReturnConsumedCapacity](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html#DDB-Query-request-ReturnConsumedCapacity) + +--- + +##### `scanIndexForward`Optional + +```wing +scanIndexForward: bool; +``` + +- *Type:* bool +- *Default:* true + +Specifies the order for index traversal. + +> [https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html#DDB-Query-request-ScanIndexForward](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html#DDB-Query-request-ScanIndexForward) + +--- + +##### `select`Optional + +```wing +select: str; +``` + +- *Type:* str +- *Default:* undefined + +The attributes to be returned in the result. + +> [https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html#DDB-Query-request-Select](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html#DDB-Query-request-Select) + +--- + +### DynamodbTableQueryResult + +Result for `DynamodbTable.query`. + +> [https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html#API_Scan_ResponseSyntax](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html#API_Scan_ResponseSyntax) + +#### Initializer + +```wing +bring ex; + +let DynamodbTableQueryResult = ex.DynamodbTableQueryResult{ ... }; +``` + +#### Properties + +| **Name** | **Type** | **Description** | +| --- | --- | --- | +| count | num | The number of items in the response. | +| items | MutArray<Json> | An array of item attributes that match the scan criteria. | +| scannedCount | num | The number of items evaluated, before any QueryFilter is applied. | +| consumedCapacity | Json | The capacity units consumed by the Query operation. | +| lastEvaluatedKey | Json | The primary key of the item where the operation stopped, inclusive of the previous result set. | + +--- + +##### `count`Required + +```wing +count: num; +``` + +- *Type:* num + +The number of items in the response. + +--- + +##### `items`Required + +```wing +items: MutArray; +``` + +- *Type:* MutArray<Json> + +An array of item attributes that match the scan criteria. + +--- + +##### `scannedCount`Required + +```wing +scannedCount: num; +``` + +- *Type:* num + +The number of items evaluated, before any QueryFilter is applied. + +--- + +##### `consumedCapacity`Optional + +```wing +consumedCapacity: Json; +``` + +- *Type:* Json + +The capacity units consumed by the Query operation. + +--- + +##### `lastEvaluatedKey`Optional + +```wing +lastEvaluatedKey: Json; +``` + +- *Type:* Json + +The primary key of the item where the operation stopped, inclusive of the previous result set. + +--- + +### DynamodbTableScanProps + +Properties for `DynamodbTable.scan`. + +#### Initializer + +```wing +bring ex; + +let DynamodbTableScanProps = ex.DynamodbTableScanProps{ ... }; +``` + +#### Properties + +| **Name** | **Type** | **Description** | +| --- | --- | --- | +| consistentRead | bool | Determines the read consistency model: If set to true, then the operation uses strongly consistent reads; | +| exclusiveStartKey | Json | The primary key of the first item that this operation will evaluate. | +| expressionAttributeNames | Json | One or more substitution tokens for attribute names in an expression. | +| expressionAttributeValues | Json | One or more values that can be substituted in an expression. | +| filterExpression | str | A string that contains conditions that DynamoDB applies after the Query operation, but before the data is returned to you. | +| indexName | str | The name of an index to query. | +| limit | num | The maximum number of items to evaluate (not necessarily the number of matching items). | +| projectionExpression | str | A string that identifies one or more attributes to retrieve from the table. | +| returnConsumedCapacity | str | Determines the level of detail about either provisioned or on-demand throughput consumption. | +| segment | num | For a parallel Scan request, Segment identifies an individual segment to be scanned by an application worker. | +| select | str | The attributes to be returned in the result. | +| totalSegments | num | For a parallel Scan request, TotalSegments represents the total number of segments into which the Scan operation will be divided. | + +--- + +##### `consistentRead`Optional + +```wing +consistentRead: bool; +``` + +- *Type:* bool +- *Default:* false + +Determines the read consistency model: If set to true, then the operation uses strongly consistent reads; + +otherwise, the operation uses eventually consistent reads. + +> [https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html#DDB-Scan-request-ConsistentRead](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html#DDB-Scan-request-ConsistentRead) + +--- + +##### `exclusiveStartKey`Optional + +```wing +exclusiveStartKey: Json; +``` + +- *Type:* Json +- *Default:* undefined + +The primary key of the first item that this operation will evaluate. + +> [https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html#DDB-Scan-request-ExclusiveStartKey](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html#DDB-Scan-request-ExclusiveStartKey) + +--- + +##### `expressionAttributeNames`Optional + +```wing +expressionAttributeNames: Json; +``` + +- *Type:* Json +- *Default:* undefined + +One or more substitution tokens for attribute names in an expression. + +> [https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html#DDB-Scan-request-ExpressionAttributeNames](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html#DDB-Scan-request-ExpressionAttributeNames) + +--- + +##### `expressionAttributeValues`Optional + +```wing +expressionAttributeValues: Json; +``` + +- *Type:* Json +- *Default:* undefined + +One or more values that can be substituted in an expression. + +> [https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html#DDB-Scan-request-ExpressionAttributeValues](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html#DDB-Scan-request-ExpressionAttributeValues) + +--- + +##### `filterExpression`Optional + +```wing +filterExpression: str; +``` + +- *Type:* str +- *Default:* undefined + +A string that contains conditions that DynamoDB applies after the Query operation, but before the data is returned to you. + +> [https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html#DDB-Scan-request-FilterExpression](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html#DDB-Scan-request-FilterExpression) + +--- + +##### `indexName`Optional + +```wing +indexName: str; +``` + +- *Type:* str +- *Default:* undefined + +The name of an index to query. + +> [https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html#DDB-Scan-request-IndexName](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html#DDB-Scan-request-IndexName) + +--- + +##### `limit`Optional + +```wing +limit: num; +``` + +- *Type:* num +- *Default:* undefined + +The maximum number of items to evaluate (not necessarily the number of matching items). + +> [https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html#DDB-Scan-request-Limit](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html#DDB-Scan-request-Limit) + +--- + +##### `projectionExpression`Optional + +```wing +projectionExpression: str; +``` + +- *Type:* str +- *Default:* undefined + +A string that identifies one or more attributes to retrieve from the table. + +> [https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html#DDB-Scan-request-ProjectionExpression](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html#DDB-Scan-request-ProjectionExpression) + +--- + +##### `returnConsumedCapacity`Optional + +```wing +returnConsumedCapacity: str; +``` + +- *Type:* str +- *Default:* "NONE" + +Determines the level of detail about either provisioned or on-demand throughput consumption. + +> [https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html#DDB-Scan-request-ReturnConsumedCapacity](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html#DDB-Scan-request-ReturnConsumedCapacity) + +--- + +##### `segment`Optional + +```wing +segment: num; +``` + +- *Type:* num +- *Default:* 0 + +For a parallel Scan request, Segment identifies an individual segment to be scanned by an application worker. + +> [https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html#DDB-Scan-request-Segment](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html#DDB-Scan-request-Segment) + +--- + +##### `select`Optional + +```wing +select: str; +``` + +- *Type:* str +- *Default:* undefined + +The attributes to be returned in the result. + +> [https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html#DDB-Scan-request-Select](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html#DDB-Scan-request-Select) + +--- + +##### `totalSegments`Optional + +```wing +totalSegments: num; +``` + +- *Type:* num +- *Default:* 1 + +For a parallel Scan request, TotalSegments represents the total number of segments into which the Scan operation will be divided. + +> [https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html#DDB-Scan-request-TotalSegments](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html#DDB-Scan-request-TotalSegments) + +--- + +### DynamodbTableScanResult + +Result for `DynamodbTable.scan`. + +> [https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html#API_Scan_ResponseSyntax](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html#API_Scan_ResponseSyntax) + +#### Initializer + +```wing +bring ex; + +let DynamodbTableScanResult = ex.DynamodbTableScanResult{ ... }; +``` + +#### Properties + +| **Name** | **Type** | **Description** | +| --- | --- | --- | +| count | num | The number of items in the response. | +| items | MutArray<Json> | An array of item attributes that match the scan criteria. | +| scannedCount | num | The number of items evaluated, before any ScanFilter is applied. | +| consumedCapacity | Json | The capacity units consumed by the Scan operation. | +| lastEvaluatedKey | Json | The primary key of the item where the operation stopped, inclusive of the previous result set. | + +--- + +##### `count`Required + +```wing +count: num; +``` + +- *Type:* num + +The number of items in the response. + +--- + +##### `items`Required + +```wing +items: MutArray; +``` + +- *Type:* MutArray<Json> + +An array of item attributes that match the scan criteria. + +--- + +##### `scannedCount`Required + +```wing +scannedCount: num; +``` + +- *Type:* num + +The number of items evaluated, before any ScanFilter is applied. + +--- + +##### `consumedCapacity`Optional + +```wing +consumedCapacity: Json; +``` + +- *Type:* Json + +The capacity units consumed by the Scan operation. + +--- + +##### `lastEvaluatedKey`Optional + +```wing +lastEvaluatedKey: Json; +``` + +- *Type:* Json + +The primary key of the item where the operation stopped, inclusive of the previous result set. + +--- + ### DynamodbTableUpdateItemProps Properties for `DynamodbTable.updateItem`. diff --git a/examples/tests/sdk_tests/dynamodb-table/query.main.w b/examples/tests/sdk_tests/dynamodb-table/query.main.w new file mode 100644 index 00000000000..9b3a6ee0bf3 --- /dev/null +++ b/examples/tests/sdk_tests/dynamodb-table/query.main.w @@ -0,0 +1,37 @@ +/*\ +skipPlatforms: + - win32 + - darwin +\*/ + +bring ex; + +let t1 = new ex.DynamodbTable(name: "test1", attributeDefinitions: { "k1": "S", "k2": "S" }, hashKey: "k1", rangeKey: "k2"); + +test "query" { + t1.putItem({ + "k1": "key1", + "k2": "value1", + "k3": "other-value1" + }); + t1.putItem({ + "k1": "key1", + "k2": "value2", + "k3": "other-value2" + }); + + let result = t1.query( + keyConditionExpression: "k1 = :k1", + expressionAttributeValues: { + ":k1": "key1", + }, + ); + + assert(result.count == 2); + assert(result.items.at(0).get("k1") == "key1"); + assert(result.items.at(0).get("k2") == "value1"); + assert(result.items.at(0).get("k3") == "other-value1"); + assert(result.items.at(1).get("k1") == "key1"); + assert(result.items.at(1).get("k2") == "value2"); + assert(result.items.at(1).get("k3") == "other-value2"); +} diff --git a/libs/wingsdk/src/ex/dynamodb-table.ts b/libs/wingsdk/src/ex/dynamodb-table.ts index dca23dca2a6..c2fe42a14ee 100644 --- a/libs/wingsdk/src/ex/dynamodb-table.ts +++ b/libs/wingsdk/src/ex/dynamodb-table.ts @@ -3,6 +3,7 @@ import { DynamoDBClient, GetItemCommand, PutItemCommand, + QueryCommand, ScanCommand, TransactWriteItemsCommand, UpdateItemCommand, @@ -84,6 +85,7 @@ export abstract class DynamodbTable extends Resource { DynamodbTableInflightMethods.DELETE_ITEM, DynamodbTableInflightMethods.GET_ITEM, DynamodbTableInflightMethods.SCAN, + DynamodbTableInflightMethods.QUERY, DynamodbTableInflightMethods.TRANSACT_WRITE_ITEMS, ]; } @@ -117,6 +119,284 @@ export interface DynamodbTableUpdateItemProps { readonly expressionAttributeValues?: Json; } +/** + * Properties for `DynamodbTable.scan`. + */ +export interface DynamodbTableScanProps { + /** + * Determines the read consistency model: If set to true, then the operation uses strongly consistent reads; otherwise, the operation uses eventually consistent reads. + * + * @default false + * @see https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html#DDB-Scan-request-ConsistentRead + */ + readonly consistentRead?: boolean; + + /** + * The primary key of the first item that this operation will evaluate. + * + * @default undefined + * @see https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html#DDB-Scan-request-ExclusiveStartKey + */ + readonly exclusiveStartKey?: Json; + + /** + * One or more substitution tokens for attribute names in an expression. + * + * @default undefined + * @see https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html#DDB-Scan-request-ExpressionAttributeNames + */ + readonly expressionAttributeNames?: Json; + + /** + * One or more values that can be substituted in an expression. + * + * @default undefined + * @see https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html#DDB-Scan-request-ExpressionAttributeValues + */ + readonly expressionAttributeValues?: Json; + + /** + * A string that contains conditions that DynamoDB applies after the Query operation, but before the data is returned to you. + * + * @default undefined + * @see https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html#DDB-Scan-request-FilterExpression + */ + readonly filterExpression?: string; + + /** + * The name of an index to query. + * + * @default undefined + * @see https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html#DDB-Scan-request-IndexName + */ + readonly indexName?: string; + + /** + * The maximum number of items to evaluate (not necessarily the number of matching items). + * + * @minimum 1 + * @default undefined + * @see https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html#DDB-Scan-request-Limit + */ + readonly limit?: number; + + /** + * A string that identifies one or more attributes to retrieve from the table. + * + * @default undefined + * @see https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html#DDB-Scan-request-ProjectionExpression + */ + readonly projectionExpression?: string; + + /** + * Determines the level of detail about either provisioned or on-demand throughput consumption. + * + * @default "NONE" + * @see https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html#DDB-Scan-request-ReturnConsumedCapacity + */ + readonly returnConsumedCapacity?: "INDEXES" | "TOTAL" | "NONE"; + + /** + * For a parallel Scan request, Segment identifies an individual segment to be scanned by an application worker. + * + * @minimum 0 + * @maximum 999999 + * @default 0 + * @see https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html#DDB-Scan-request-Segment + */ + readonly segment?: number; + + /** + * The attributes to be returned in the result. + * + * @default undefined + * @see https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html#DDB-Scan-request-Select + */ + readonly select?: + | "ALL_ATTRIBUTES" + | "ALL_PROJECTED_ATTRIBUTES" + | "SPECIFIC_ATTRIBUTES" + | "COUNT"; + + /** + * For a parallel Scan request, TotalSegments represents the total number of segments into which the Scan operation will be divided. + * + * @minimum 1 + * @maximum 1000000 + * @default 1 + * @see https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html#DDB-Scan-request-TotalSegments + */ + readonly totalSegments?: number; +} + +/** + * Result for `DynamodbTable.scan`. + * @see https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html#API_Scan_ResponseSyntax + */ +export interface DynamodbTableScanResult { + /** + * The capacity units consumed by the Scan operation. + */ + readonly consumedCapacity?: Json; + + /** + * The number of items in the response. + */ + readonly count: number; + + /** + * An array of item attributes that match the scan criteria. + */ + readonly items: Array; + + /** + * The primary key of the item where the operation stopped, inclusive of the previous result set. + */ + readonly lastEvaluatedKey?: Json; + + /** + * The number of items evaluated, before any ScanFilter is applied. + */ + readonly scannedCount: number; +} + +/** + * Properties for `DynamodbTable.query`. + */ +export interface DynamodbTableQueryProps { + /** + * Determines the read consistency model: If set to true, then the operation uses strongly consistent reads; otherwise, the operation uses eventually consistent reads. + * + * @default false + * @see https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html#DDB-Query-request-ConsistentRead + */ + readonly consistentRead?: boolean; + + /** + * The primary key of the first item that this operation will evaluate. + * + * @default undefined + * @see https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html#DDB-Query-request-ExclusiveStartKey + */ + readonly exclusiveStartKey?: Json; + + /** + * One or more substitution tokens for attribute names in an expression. + * + * @default undefined + * @see https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html#DDB-Query-request-ExpressionAttributeNames + */ + readonly expressionAttributeNames?: Json; + + /** + * One or more values that can be substituted in an expression. + * + * @default undefined + * @see https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html#DDB-Query-request-ExpressionAttributeValues + */ + readonly expressionAttributeValues?: Json; + + /** + * A string that contains conditions that DynamoDB applies after the Query operation, but before the data is returned to you. + * + * @default undefined + * @see https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html#DDB-Query-request-FilterExpression + */ + readonly filterExpression?: string; + + /** + * The name of an index to query. + * + * @default undefined + * @see https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html#DDB-Query-request-IndexName + */ + readonly indexName?: string; + + /** + * The condition that specifies the key values for items to be retrieved by the Query action. + * + * @see https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html#DDB-Query-request-KeyConditionExpression + */ + readonly keyConditionExpression: string; + + /** + * The maximum number of items to evaluate (not necessarily the number of matching items). + * + * @minimum 1 + * @default undefined + * @see https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html#DDB-Query-request-Limit + */ + readonly limit?: number; + + /** + * A string that identifies one or more attributes to retrieve from the table. + * + * @default undefined + * @see https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html#DDB-Query-request-ProjectionExpression + */ + readonly projectionExpression?: string; + + /** + * Determines the level of detail about either provisioned or on-demand throughput consumption. + * + * @default "NONE" + * @see https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html#DDB-Query-request-ReturnConsumedCapacity + */ + readonly returnConsumedCapacity?: "INDEXES" | "TOTAL" | "NONE"; + + /** + * Specifies the order for index traversal. + * + * @default true + * @see https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html#DDB-Query-request-ScanIndexForward + */ + readonly scanIndexForward?: boolean; + + /** + * The attributes to be returned in the result. + * + * @default undefined + * @see https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html#DDB-Query-request-Select + */ + readonly select?: + | "ALL_ATTRIBUTES" + | "ALL_PROJECTED_ATTRIBUTES" + | "SPECIFIC_ATTRIBUTES" + | "COUNT"; +} + +/** + * Result for `DynamodbTable.query`. + * + * @see https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html#API_Scan_ResponseSyntax + */ +export interface DynamodbTableQueryResult { + /** + * The capacity units consumed by the Query operation. + */ + readonly consumedCapacity?: Json; + + /** + * The number of items in the response. + */ + readonly count: number; + + /** + * An array of item attributes that match the scan criteria. + */ + readonly items: Array; + + /** + * The primary key of the item where the operation stopped, inclusive of the previous result set. + */ + readonly lastEvaluatedKey?: Json; + + /** + * The number of items evaluated, before any QueryFilter is applied. + */ + readonly scannedCount: number; +} + /** * Properties for transact write item's update operation. */ @@ -221,14 +501,24 @@ export interface IDynamodbTableClient { * Get an item from the table. * @param key key of the item. * @inflight + * @see https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html */ getItem(key: Json): Promise; /** - * Get the table. + * Return one or more items and item attributes by accessing every item in a table or a secondary index. + * @param props properties for the scan operation. + * @inflight + */ + scan(props?: DynamodbTableScanProps): Promise; + + /** + * Return all items with a given partition key value. + * @param props properties for the query operation. * @inflight + * @see https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html */ - scan(): Promise>; + query(props: DynamodbTableQueryProps): Promise; /** * Perform a synchronous write operation that groups up to 100 action requests. @@ -253,6 +543,8 @@ export enum DynamodbTableInflightMethods { GET_ITEM = "getItem", /** `DynamodbTable.scan` */ SCAN = "scan", + /** `DynamodbTable.query` */ + QUERY = "query", /** `DynamodbTable.transactWriteItems` */ TRANSACT_WRITE_ITEMS = "transactWriteItems", } @@ -331,20 +623,76 @@ export abstract class DynamodbTableClientBase implements IDynamodbTableClient { return {} as Json; } - public async scan(): Promise> { + public async scan( + props?: DynamodbTableScanProps + ): Promise { const client = await this._rawClient(); const result = await client.send( new ScanCommand({ TableName: this.tableName, + ConsistentRead: props?.consistentRead, + ExclusiveStartKey: props?.exclusiveStartKey + ? marshall(props.exclusiveStartKey) + : undefined, + ExpressionAttributeNames: props?.expressionAttributeNames as any, + ExpressionAttributeValues: props?.expressionAttributeValues + ? marshall(props.expressionAttributeValues) + : undefined, + FilterExpression: props?.filterExpression, + IndexName: props?.indexName, + Limit: props?.limit, + ProjectionExpression: props?.projectionExpression, + ReturnConsumedCapacity: props?.returnConsumedCapacity, + Segment: props?.segment, + Select: props?.select, + TotalSegments: props?.totalSegments, }) ); - const response = []; - if (result.Items) { - for (const item of result.Items) { - response.push(unmarshall(item) as Json); - } - } - return response; + return { + consumedCapacity: result.ConsumedCapacity as Json | undefined, + count: result.Count!, + items: result.Items!.map((item) => unmarshall(item) as Json), + lastEvaluatedKey: result.LastEvaluatedKey + ? (unmarshall(result.LastEvaluatedKey) as Json) + : undefined, + scannedCount: result.ScannedCount!, + }; + } + + public async query( + props: DynamodbTableQueryProps + ): Promise { + const client = await this._rawClient(); + const result = await client.send( + new QueryCommand({ + TableName: this.tableName, + ConsistentRead: props.consistentRead, + ExclusiveStartKey: props.exclusiveStartKey + ? marshall(props.exclusiveStartKey) + : undefined, + ExpressionAttributeNames: props.expressionAttributeNames as any, + ExpressionAttributeValues: props.expressionAttributeValues + ? marshall(props.expressionAttributeValues) + : undefined, + FilterExpression: props.filterExpression, + IndexName: props.indexName, + KeyConditionExpression: props.keyConditionExpression, + Limit: props.limit, + ProjectionExpression: props.projectionExpression, + ReturnConsumedCapacity: props.returnConsumedCapacity, + ScanIndexForward: props.scanIndexForward, + Select: props.select, + }) + ); + return { + consumedCapacity: result.ConsumedCapacity as Json | undefined, + count: result.Count!, + items: result.Items!.map((item) => unmarshall(item) as Json), + lastEvaluatedKey: result.LastEvaluatedKey + ? (unmarshall(result.LastEvaluatedKey) as Json) + : undefined, + scannedCount: result.ScannedCount!, + }; } public async transactWriteItems( diff --git a/libs/wingsdk/src/target-tf-aws/dynamodb-table.ts b/libs/wingsdk/src/target-tf-aws/dynamodb-table.ts index 8e5260c26f9..d858b947dce 100644 --- a/libs/wingsdk/src/target-tf-aws/dynamodb-table.ts +++ b/libs/wingsdk/src/target-tf-aws/dynamodb-table.ts @@ -78,6 +78,12 @@ export class DynamodbTable extends ex.DynamodbTable { resources: [this.table.arn], }); } + if (ops.includes(ex.DynamodbTableInflightMethods.QUERY)) { + host.addPolicyStatements({ + actions: ["dynamodb:Query"], + resources: [this.table.arn], + }); + } if (ops.includes(ex.DynamodbTableInflightMethods.TRANSACT_WRITE_ITEMS)) { host.addPolicyStatements({ actions: ["dynamodb:PutItem"], diff --git a/libs/wingsdk/test/target-sim/dynamodb-table.test.ts b/libs/wingsdk/test/target-sim/dynamodb-table.test.ts index f4b21076a3a..f88e898023d 100644 --- a/libs/wingsdk/test/target-sim/dynamodb-table.test.ts +++ b/libs/wingsdk/test/target-sim/dynamodb-table.test.ts @@ -236,8 +236,8 @@ test("scan", async () => { await client.putItem({ id: "1", age: 50 } as any); await client.putItem({ id: "2", loc: "US" } as any); - const items = await client.scan(); - expect(items).toEqual([ + const result = await client.scan(); + expect(result.items).toEqual([ { id: "1", age: 50 }, { id: "2", loc: "US" }, ]); @@ -245,6 +245,46 @@ test("scan", async () => { await s.stop(); }); +test("query", async () => { + // GIVEN + const app = new SimApp(); + const t = ex.DynamodbTable._newDynamodbTable(app, "query_table", { + name: "query_table", + attributeDefinitions: { id: "S", age: "N" } as any, + hashKey: "id", + rangeKey: "age", + }); + const s = await app.startSimulator(); + const client = s.getResource("/query_table") as ex.IDynamodbTableClient; + + await client.putItem({ id: "1", age: 2 } as any); + await client.putItem({ id: "1", age: 1 } as any); + await client.putItem({ id: "2", age: 3 } as any); + await client.putItem({ id: "2", age: 1 } as any); + { + const result = await client.query({ + keyConditionExpression: "id = :id", + expressionAttributeValues: { ":id": "1" } as any, + }); + expect(result.items).toEqual([ + { id: "1", age: 1 }, + { id: "1", age: 2 }, + ]); + } + { + const result = await client.query({ + keyConditionExpression: "id = :id", + expressionAttributeValues: { ":id": "2" } as any, + }); + expect(result.items).toEqual([ + { id: "2", age: 1 }, + { id: "2", age: 3 }, + ]); + } + + await s.stop(); +}); + test("write transaction", async () => { // GIVEN const app = new SimApp(); diff --git a/tools/hangar/__snapshots__/test_corpus/sdk_tests/dynamodb-table/query.main.w_compile_tf-aws.md b/tools/hangar/__snapshots__/test_corpus/sdk_tests/dynamodb-table/query.main.w_compile_tf-aws.md new file mode 100644 index 00000000000..633f2214a81 --- /dev/null +++ b/tools/hangar/__snapshots__/test_corpus/sdk_tests/dynamodb-table/query.main.w_compile_tf-aws.md @@ -0,0 +1,140 @@ +# [query.main.w](../../../../../../examples/tests/sdk_tests/dynamodb-table/query.main.w) | compile | tf-aws + +## inflight.$Closure1-1.js +```js +module.exports = function({ $t1 }) { + class $Closure1 { + constructor({ }) { + const $obj = (...args) => this.handle(...args); + Object.setPrototypeOf($obj, this); + return $obj; + } + async handle() { + (await $t1.putItem(({"k1": "key1","k2": "value1","k3": "other-value1"}))); + (await $t1.putItem(({"k1": "key1","k2": "value2","k3": "other-value2"}))); + const result = (await $t1.query({ keyConditionExpression: "k1 = :k1", expressionAttributeValues: ({":k1": "key1"}) })); + {((cond) => {if (!cond) throw new Error("assertion failed: result.count == 2")})((((a,b) => { try { return require('assert').deepStrictEqual(a,b) === undefined; } catch { return false; } })(result.count,2)))}; + {((cond) => {if (!cond) throw new Error("assertion failed: result.items.at(0).get(\"k1\") == \"key1\"")})((((a,b) => { try { return require('assert').deepStrictEqual(a,b) === undefined; } catch { return false; } })(((obj, args) => { if (obj[args] === undefined) throw new Error(`Json property "${args}" does not exist`); return obj[args] })((await result.items.at(0)), "k1"),"key1")))}; + {((cond) => {if (!cond) throw new Error("assertion failed: result.items.at(0).get(\"k2\") == \"value1\"")})((((a,b) => { try { return require('assert').deepStrictEqual(a,b) === undefined; } catch { return false; } })(((obj, args) => { if (obj[args] === undefined) throw new Error(`Json property "${args}" does not exist`); return obj[args] })((await result.items.at(0)), "k2"),"value1")))}; + {((cond) => {if (!cond) throw new Error("assertion failed: result.items.at(0).get(\"k3\") == \"other-value1\"")})((((a,b) => { try { return require('assert').deepStrictEqual(a,b) === undefined; } catch { return false; } })(((obj, args) => { if (obj[args] === undefined) throw new Error(`Json property "${args}" does not exist`); return obj[args] })((await result.items.at(0)), "k3"),"other-value1")))}; + {((cond) => {if (!cond) throw new Error("assertion failed: result.items.at(1).get(\"k1\") == \"key1\"")})((((a,b) => { try { return require('assert').deepStrictEqual(a,b) === undefined; } catch { return false; } })(((obj, args) => { if (obj[args] === undefined) throw new Error(`Json property "${args}" does not exist`); return obj[args] })((await result.items.at(1)), "k1"),"key1")))}; + {((cond) => {if (!cond) throw new Error("assertion failed: result.items.at(1).get(\"k2\") == \"value2\"")})((((a,b) => { try { return require('assert').deepStrictEqual(a,b) === undefined; } catch { return false; } })(((obj, args) => { if (obj[args] === undefined) throw new Error(`Json property "${args}" does not exist`); return obj[args] })((await result.items.at(1)), "k2"),"value2")))}; + {((cond) => {if (!cond) throw new Error("assertion failed: result.items.at(1).get(\"k3\") == \"other-value2\"")})((((a,b) => { try { return require('assert').deepStrictEqual(a,b) === undefined; } catch { return false; } })(((obj, args) => { if (obj[args] === undefined) throw new Error(`Json property "${args}" does not exist`); return obj[args] })((await result.items.at(1)), "k3"),"other-value2")))}; + } + } + return $Closure1; +} + +``` + +## main.tf.json +```json +{ + "//": { + "metadata": { + "backend": "local", + "stackName": "root", + "version": "0.17.0" + }, + "outputs": { + "root": { + "Default": { + "cloud.TestRunner": { + "TestFunctionArns": "WING_TEST_RUNNER_FUNCTION_ARNS" + } + } + } + } + }, + "output": { + "WING_TEST_RUNNER_FUNCTION_ARNS": { + "value": "[]" + } + }, + "provider": { + "aws": [ + {} + ] + }, + "resource": { + "aws_dynamodb_table": { + "exDynamodbTable": { + "//": { + "metadata": { + "path": "root/Default/Default/ex.DynamodbTable/Default", + "uniqueId": "exDynamodbTable" + } + }, + "attribute": [ + { + "name": "k1", + "type": "S" + }, + { + "name": "k2", + "type": "S" + } + ], + "billing_mode": "PAY_PER_REQUEST", + "hash_key": "k1", + "name": "test1ex.DynamodbTable-c8d9b5e7", + "range_key": "k2" + } + } + } +} +``` + +## preflight.js +```js +const $stdlib = require('@winglang/sdk'); +const $plugins = ((s) => !s ? [] : s.split(';'))(process.env.WING_PLUGIN_PATHS); +const $outdir = process.env.WING_SYNTH_DIR ?? "."; +const $wing_is_test = process.env.WING_IS_TEST === "true"; +const std = $stdlib.std; +const ex = $stdlib.ex; +class $Root extends $stdlib.std.Resource { + constructor(scope, id) { + super(scope, id); + class $Closure1 extends $stdlib.std.Resource { + constructor(scope, id, ) { + super(scope, id); + (std.Node.of(this)).hidden = true; + } + static _toInflightType(context) { + return ` + require("./inflight.$Closure1-1.js")({ + $t1: ${context._lift(t1)}, + }) + `; + } + _toInflight() { + return ` + (await (async () => { + const $Closure1Client = ${$Closure1._toInflightType(this)}; + const client = new $Closure1Client({ + }); + if (client.$inflight_init) { await client.$inflight_init(); } + return client; + })()) + `; + } + _getInflightOps() { + return ["handle", "$inflight_init"]; + } + _registerBind(host, ops) { + if (ops.includes("handle")) { + $Closure1._registerBindObject(t1, host, ["putItem", "query"]); + } + super._registerBind(host, ops); + } + } + const t1 = this.node.root.newAbstract("@winglang/sdk.ex.DynamodbTable",this,"ex.DynamodbTable",{ name: "test1", attributeDefinitions: ({"k1": "S","k2": "S"}), hashKey: "k1", rangeKey: "k2" }); + this.node.root.new("@winglang/sdk.std.Test",std.Test,this,"test:query",new $Closure1(this,"$Closure1")); + } +} +const $App = $stdlib.core.App.for(process.env.WING_TARGET); +new $App({ outdir: $outdir, name: "query.main", rootConstruct: $Root, plugins: $plugins, isTestEnvironment: $wing_is_test, entrypointDir: process.env['WING_SOURCE_DIR'], rootId: process.env['WING_ROOT_ID'] }).synth(); + +``` + diff --git a/tools/hangar/__snapshots__/test_corpus/sdk_tests/dynamodb-table/query.main.w_test_sim.md b/tools/hangar/__snapshots__/test_corpus/sdk_tests/dynamodb-table/query.main.w_test_sim.md new file mode 100644 index 00000000000..ed44751fee0 --- /dev/null +++ b/tools/hangar/__snapshots__/test_corpus/sdk_tests/dynamodb-table/query.main.w_test_sim.md @@ -0,0 +1,12 @@ +# [query.main.w](../../../../../../examples/tests/sdk_tests/dynamodb-table/query.main.w) | test | sim + +## stdout.log +```log +pass ─ query.main.wsim » root/env0/test:query + + +Tests 1 passed (1) +Test Files 1 passed (1) +Duration +``` +