vendor: update all dependencies to latest versions

This commit is contained in:
Nick Craig-Wood 2017-09-30 15:27:27 +01:00
parent 911d121bb9
commit b017fcfe9a
3048 changed files with 537057 additions and 189681 deletions

View file

@ -17,19 +17,18 @@ const opBatchGetItem = "BatchGetItem"
// BatchGetItemRequest generates a "aws/request.Request" representing the
// client's request for the BatchGetItem operation. The "output" return
// value can be used to capture response data after the request's "Send" method
// is called.
// value will be populated with the request's response once the request complets
// successfuly.
//
// See BatchGetItem for usage and error information.
// Use "Send" method on the returned Request to send the API call to the service.
// the "output" return value is not valid until after Send returns without error.
//
// Creating a request object using this method should be used when you want to inject
// custom logic into the request's lifecycle using a custom handler, or if you want to
// access properties on the request object before or after sending the request. If
// you just want the service response, call the BatchGetItem method directly
// instead.
// See BatchGetItem for more information on using the BatchGetItem
// API call, and error handling.
//
// This method is useful when you want to inject custom logic or configuration
// into the SDK's request lifecycle. Such as custom headers, or retry logic.
//
// Note: You must call the "Send" method on the returned request object in order
// to execute the request.
//
// // Example sending a request using the BatchGetItemRequest method.
// req, resp := client.BatchGetItemRequest(params)
@ -213,19 +212,18 @@ const opBatchWriteItem = "BatchWriteItem"
// BatchWriteItemRequest generates a "aws/request.Request" representing the
// client's request for the BatchWriteItem operation. The "output" return
// value can be used to capture response data after the request's "Send" method
// is called.
// value will be populated with the request's response once the request complets
// successfuly.
//
// See BatchWriteItem for usage and error information.
// Use "Send" method on the returned Request to send the API call to the service.
// the "output" return value is not valid until after Send returns without error.
//
// Creating a request object using this method should be used when you want to inject
// custom logic into the request's lifecycle using a custom handler, or if you want to
// access properties on the request object before or after sending the request. If
// you just want the service response, call the BatchWriteItem method directly
// instead.
// See BatchWriteItem for more information on using the BatchWriteItem
// API call, and error handling.
//
// This method is useful when you want to inject custom logic or configuration
// into the SDK's request lifecycle. Such as custom headers, or retry logic.
//
// Note: You must call the "Send" method on the returned request object in order
// to execute the request.
//
// // Example sending a request using the BatchWriteItemRequest method.
// req, resp := client.BatchWriteItemRequest(params)
@ -377,19 +375,18 @@ const opCreateTable = "CreateTable"
// CreateTableRequest generates a "aws/request.Request" representing the
// client's request for the CreateTable operation. The "output" return
// value can be used to capture response data after the request's "Send" method
// is called.
// value will be populated with the request's response once the request complets
// successfuly.
//
// See CreateTable for usage and error information.
// Use "Send" method on the returned Request to send the API call to the service.
// the "output" return value is not valid until after Send returns without error.
//
// Creating a request object using this method should be used when you want to inject
// custom logic into the request's lifecycle using a custom handler, or if you want to
// access properties on the request object before or after sending the request. If
// you just want the service response, call the CreateTable method directly
// instead.
// See CreateTable for more information on using the CreateTable
// API call, and error handling.
//
// This method is useful when you want to inject custom logic or configuration
// into the SDK's request lifecycle. Such as custom headers, or retry logic.
//
// Note: You must call the "Send" method on the returned request object in order
// to execute the request.
//
// // Example sending a request using the CreateTableRequest method.
// req, resp := client.CreateTableRequest(params)
@ -486,19 +483,18 @@ const opDeleteItem = "DeleteItem"
// DeleteItemRequest generates a "aws/request.Request" representing the
// client's request for the DeleteItem operation. The "output" return
// value can be used to capture response data after the request's "Send" method
// is called.
// value will be populated with the request's response once the request complets
// successfuly.
//
// See DeleteItem for usage and error information.
// Use "Send" method on the returned Request to send the API call to the service.
// the "output" return value is not valid until after Send returns without error.
//
// Creating a request object using this method should be used when you want to inject
// custom logic into the request's lifecycle using a custom handler, or if you want to
// access properties on the request object before or after sending the request. If
// you just want the service response, call the DeleteItem method directly
// instead.
// See DeleteItem for more information on using the DeleteItem
// API call, and error handling.
//
// This method is useful when you want to inject custom logic or configuration
// into the SDK's request lifecycle. Such as custom headers, or retry logic.
//
// Note: You must call the "Send" method on the returned request object in order
// to execute the request.
//
// // Example sending a request using the DeleteItemRequest method.
// req, resp := client.DeleteItemRequest(params)
@ -598,19 +594,18 @@ const opDeleteTable = "DeleteTable"
// DeleteTableRequest generates a "aws/request.Request" representing the
// client's request for the DeleteTable operation. The "output" return
// value can be used to capture response data after the request's "Send" method
// is called.
// value will be populated with the request's response once the request complets
// successfuly.
//
// See DeleteTable for usage and error information.
// Use "Send" method on the returned Request to send the API call to the service.
// the "output" return value is not valid until after Send returns without error.
//
// Creating a request object using this method should be used when you want to inject
// custom logic into the request's lifecycle using a custom handler, or if you want to
// access properties on the request object before or after sending the request. If
// you just want the service response, call the DeleteTable method directly
// instead.
// See DeleteTable for more information on using the DeleteTable
// API call, and error handling.
//
// This method is useful when you want to inject custom logic or configuration
// into the SDK's request lifecycle. Such as custom headers, or retry logic.
//
// Note: You must call the "Send" method on the returned request object in order
// to execute the request.
//
// // Example sending a request using the DeleteTableRequest method.
// req, resp := client.DeleteTableRequest(params)
@ -714,19 +709,18 @@ const opDescribeLimits = "DescribeLimits"
// DescribeLimitsRequest generates a "aws/request.Request" representing the
// client's request for the DescribeLimits operation. The "output" return
// value can be used to capture response data after the request's "Send" method
// is called.
// value will be populated with the request's response once the request complets
// successfuly.
//
// See DescribeLimits for usage and error information.
// Use "Send" method on the returned Request to send the API call to the service.
// the "output" return value is not valid until after Send returns without error.
//
// Creating a request object using this method should be used when you want to inject
// custom logic into the request's lifecycle using a custom handler, or if you want to
// access properties on the request object before or after sending the request. If
// you just want the service response, call the DescribeLimits method directly
// instead.
// See DescribeLimits for more information on using the DescribeLimits
// API call, and error handling.
//
// This method is useful when you want to inject custom logic or configuration
// into the SDK's request lifecycle. Such as custom headers, or retry logic.
//
// Note: You must call the "Send" method on the returned request object in order
// to execute the request.
//
// // Example sending a request using the DescribeLimitsRequest method.
// req, resp := client.DescribeLimitsRequest(params)
@ -850,19 +844,18 @@ const opDescribeTable = "DescribeTable"
// DescribeTableRequest generates a "aws/request.Request" representing the
// client's request for the DescribeTable operation. The "output" return
// value can be used to capture response data after the request's "Send" method
// is called.
// value will be populated with the request's response once the request complets
// successfuly.
//
// See DescribeTable for usage and error information.
// Use "Send" method on the returned Request to send the API call to the service.
// the "output" return value is not valid until after Send returns without error.
//
// Creating a request object using this method should be used when you want to inject
// custom logic into the request's lifecycle using a custom handler, or if you want to
// access properties on the request object before or after sending the request. If
// you just want the service response, call the DescribeTable method directly
// instead.
// See DescribeTable for more information on using the DescribeTable
// API call, and error handling.
//
// This method is useful when you want to inject custom logic or configuration
// into the SDK's request lifecycle. Such as custom headers, or retry logic.
//
// Note: You must call the "Send" method on the returned request object in order
// to execute the request.
//
// // Example sending a request using the DescribeTableRequest method.
// req, resp := client.DescribeTableRequest(params)
@ -942,19 +935,18 @@ const opDescribeTimeToLive = "DescribeTimeToLive"
// DescribeTimeToLiveRequest generates a "aws/request.Request" representing the
// client's request for the DescribeTimeToLive operation. The "output" return
// value can be used to capture response data after the request's "Send" method
// is called.
// value will be populated with the request's response once the request complets
// successfuly.
//
// See DescribeTimeToLive for usage and error information.
// Use "Send" method on the returned Request to send the API call to the service.
// the "output" return value is not valid until after Send returns without error.
//
// Creating a request object using this method should be used when you want to inject
// custom logic into the request's lifecycle using a custom handler, or if you want to
// access properties on the request object before or after sending the request. If
// you just want the service response, call the DescribeTimeToLive method directly
// instead.
// See DescribeTimeToLive for more information on using the DescribeTimeToLive
// API call, and error handling.
//
// This method is useful when you want to inject custom logic or configuration
// into the SDK's request lifecycle. Such as custom headers, or retry logic.
//
// Note: You must call the "Send" method on the returned request object in order
// to execute the request.
//
// // Example sending a request using the DescribeTimeToLiveRequest method.
// req, resp := client.DescribeTimeToLiveRequest(params)
@ -1026,19 +1018,18 @@ const opGetItem = "GetItem"
// GetItemRequest generates a "aws/request.Request" representing the
// client's request for the GetItem operation. The "output" return
// value can be used to capture response data after the request's "Send" method
// is called.
// value will be populated with the request's response once the request complets
// successfuly.
//
// See GetItem for usage and error information.
// Use "Send" method on the returned Request to send the API call to the service.
// the "output" return value is not valid until after Send returns without error.
//
// Creating a request object using this method should be used when you want to inject
// custom logic into the request's lifecycle using a custom handler, or if you want to
// access properties on the request object before or after sending the request. If
// you just want the service response, call the GetItem method directly
// instead.
// See GetItem for more information on using the GetItem
// API call, and error handling.
//
// This method is useful when you want to inject custom logic or configuration
// into the SDK's request lifecycle. Such as custom headers, or retry logic.
//
// Note: You must call the "Send" method on the returned request object in order
// to execute the request.
//
// // Example sending a request using the GetItemRequest method.
// req, resp := client.GetItemRequest(params)
@ -1125,19 +1116,18 @@ const opListTables = "ListTables"
// ListTablesRequest generates a "aws/request.Request" representing the
// client's request for the ListTables operation. The "output" return
// value can be used to capture response data after the request's "Send" method
// is called.
// value will be populated with the request's response once the request complets
// successfuly.
//
// See ListTables for usage and error information.
// Use "Send" method on the returned Request to send the API call to the service.
// the "output" return value is not valid until after Send returns without error.
//
// Creating a request object using this method should be used when you want to inject
// custom logic into the request's lifecycle using a custom handler, or if you want to
// access properties on the request object before or after sending the request. If
// you just want the service response, call the ListTables method directly
// instead.
// See ListTables for more information on using the ListTables
// API call, and error handling.
//
// This method is useful when you want to inject custom logic or configuration
// into the SDK's request lifecycle. Such as custom headers, or retry logic.
//
// Note: You must call the "Send" method on the returned request object in order
// to execute the request.
//
// // Example sending a request using the ListTablesRequest method.
// req, resp := client.ListTablesRequest(params)
@ -1263,19 +1253,18 @@ const opListTagsOfResource = "ListTagsOfResource"
// ListTagsOfResourceRequest generates a "aws/request.Request" representing the
// client's request for the ListTagsOfResource operation. The "output" return
// value can be used to capture response data after the request's "Send" method
// is called.
// value will be populated with the request's response once the request complets
// successfuly.
//
// See ListTagsOfResource for usage and error information.
// Use "Send" method on the returned Request to send the API call to the service.
// the "output" return value is not valid until after Send returns without error.
//
// Creating a request object using this method should be used when you want to inject
// custom logic into the request's lifecycle using a custom handler, or if you want to
// access properties on the request object before or after sending the request. If
// you just want the service response, call the ListTagsOfResource method directly
// instead.
// See ListTagsOfResource for more information on using the ListTagsOfResource
// API call, and error handling.
//
// This method is useful when you want to inject custom logic or configuration
// into the SDK's request lifecycle. Such as custom headers, or retry logic.
//
// Note: You must call the "Send" method on the returned request object in order
// to execute the request.
//
// // Example sending a request using the ListTagsOfResourceRequest method.
// req, resp := client.ListTagsOfResourceRequest(params)
@ -1351,19 +1340,18 @@ const opPutItem = "PutItem"
// PutItemRequest generates a "aws/request.Request" representing the
// client's request for the PutItem operation. The "output" return
// value can be used to capture response data after the request's "Send" method
// is called.
// value will be populated with the request's response once the request complets
// successfuly.
//
// See PutItem for usage and error information.
// Use "Send" method on the returned Request to send the API call to the service.
// the "output" return value is not valid until after Send returns without error.
//
// Creating a request object using this method should be used when you want to inject
// custom logic into the request's lifecycle using a custom handler, or if you want to
// access properties on the request object before or after sending the request. If
// you just want the service response, call the PutItem method directly
// instead.
// See PutItem for more information on using the PutItem
// API call, and error handling.
//
// This method is useful when you want to inject custom logic or configuration
// into the SDK's request lifecycle. Such as custom headers, or retry logic.
//
// Note: You must call the "Send" method on the returned request object in order
// to execute the request.
//
// // Example sending a request using the PutItemRequest method.
// req, resp := client.PutItemRequest(params)
@ -1397,10 +1385,31 @@ func (c *DynamoDB) PutItemRequest(input *PutItemInput) (req *request.Request, ou
// table, the new item completely replaces the existing item. You can perform
// a conditional put operation (add a new item if one with the specified primary
// key doesn't exist), or replace an existing item if it has certain attribute
// values.
// values. You can return the item's attribute values in the same operation,
// using the ReturnValues parameter.
//
// In addition to putting an item, you can also return the item's attribute
// values in the same operation, using the ReturnValues parameter.
// This topic provides general information about the PutItem API.
//
// For information on how to call the PutItem API using the AWS SDK in specific
// languages, see the following:
//
// PutItem in the AWS Command Line Interface (http://docs.aws.amazon.com/goto/aws-cli/dynamodb-2012-08-10/PutItem)
//
// PutItem in the AWS SDK for .NET (http://docs.aws.amazon.com/goto/DotNetSDKV3/dynamodb-2012-08-10/PutItem)
//
// PutItem in the AWS SDK for C++ (http://docs.aws.amazon.com/goto/SdkForCpp/dynamodb-2012-08-10/PutItem)
//
// PutItem in the AWS SDK for Go (http://docs.aws.amazon.com/goto/SdkForGoV1/dynamodb-2012-08-10/PutItem)
//
// PutItem in the AWS SDK for Java (http://docs.aws.amazon.com/goto/SdkForJava/dynamodb-2012-08-10/PutItem)
//
// PutItem in the AWS SDK for JavaScript (http://docs.aws.amazon.com/goto/AWSJavaScriptSDK/dynamodb-2012-08-10/PutItem)
//
// PutItem in the AWS SDK for PHP V3 (http://docs.aws.amazon.com/goto/SdkForPHPV3/dynamodb-2012-08-10/PutItem)
//
// PutItem in the AWS SDK for Python (http://docs.aws.amazon.com/goto/boto3/dynamodb-2012-08-10/PutItem)
//
// PutItem in the AWS SDK for Ruby V2 (http://docs.aws.amazon.com/goto/SdkForRubyV2/dynamodb-2012-08-10/PutItem)
//
// When you add an item, the primary key attribute(s) are the only required
// attributes. Attribute values cannot be null. String and Binary type attributes
@ -1472,19 +1481,18 @@ const opQuery = "Query"
// QueryRequest generates a "aws/request.Request" representing the
// client's request for the Query operation. The "output" return
// value can be used to capture response data after the request's "Send" method
// is called.
// value will be populated with the request's response once the request complets
// successfuly.
//
// See Query for usage and error information.
// Use "Send" method on the returned Request to send the API call to the service.
// the "output" return value is not valid until after Send returns without error.
//
// Creating a request object using this method should be used when you want to inject
// custom logic into the request's lifecycle using a custom handler, or if you want to
// access properties on the request object before or after sending the request. If
// you just want the service response, call the Query method directly
// instead.
// See Query for more information on using the Query
// API call, and error handling.
//
// This method is useful when you want to inject custom logic or configuration
// into the SDK's request lifecycle. Such as custom headers, or retry logic.
//
// Note: You must call the "Send" method on the returned request object in order
// to execute the request.
//
// // Example sending a request using the QueryRequest method.
// req, resp := client.QueryRequest(params)
@ -1519,26 +1527,48 @@ func (c *DynamoDB) QueryRequest(input *QueryInput) (req *request.Request, output
// Query API operation for Amazon DynamoDB.
//
// A Query operation uses the primary key of a table or a secondary index to
// directly access items from that table or index.
// The Query operation finds items based on primary key values. You can query
// any table or secondary index that has a composite primary key (a partition
// key and a sort key).
//
// Use the KeyConditionExpression parameter to provide a specific value for
// the partition key. The Query operation will return all of the items from
// the table or index with that partition key value. You can optionally narrow
// the scope of the Query operation by specifying a sort key value and a comparison
// operator in KeyConditionExpression. You can use the ScanIndexForward parameter
// to get results in forward or reverse order, by sort key.
// operator in KeyConditionExpression. To further refine the Query results,
// you can optionally provide a FilterExpression. A FilterExpression determines
// which items within the results should be returned to you. All of the other
// results are discarded.
//
// Queries that do not return results consume the minimum number of read capacity
// units for that type of read operation.
// A Query operation always returns a result set. If no matching items are found,
// the result set will be empty. 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 the LastEvaluatedKey element 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 value. LastEvaluatedKey is only provided if you
// have used the Limit parameter, or if the result set exceeds 1 MB (prior to
// applying a filter).
// DynamoDB calculates the number of read capacity units consumed based on item
// size, not on the amount of data that is returned to an application. The number
// of capacity units consumed will be the same whether you request all of the
// attributes (the default behavior) or just some of them (using a projection
// expression). The number will also be the same whether or not you use a FilterExpression.
//
// Query results are always sorted by the sort key value. If the data type of
// the sort key is Number, the results are returned in numeric order; otherwise,
// the results are returned in order of UTF-8 bytes. By default, the sort order
// is ascending. To reverse the order, set the ScanIndexForward parameter to
// false.
//
// A single Query operation will read up to the maximum number of items set
// (if using the Limit parameter) or a maximum of 1 MB of data and then apply
// any filtering to the results using FilterExpression. If LastEvaluatedKey
// is present in the response, you will need to paginate the result set. For
// more information, see Paginating the Results (http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Query.html#Query.Pagination)
// in the Amazon DynamoDB Developer Guide.
//
// FilterExpression is applied after a Query finishes, but before the results
// are returned. A FilterExpression cannot contain partition key or sort key
// attributes. You need to specify those attributes in the KeyConditionExpression.
//
// A Query operation can return an empty result set and a LastEvaluatedKey if
// all the items read for the page of results are filtered out.
//
// 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 the ConsistentRead
@ -1645,19 +1675,18 @@ const opScan = "Scan"
// ScanRequest generates a "aws/request.Request" representing the
// client's request for the Scan operation. The "output" return
// value can be used to capture response data after the request's "Send" method
// is called.
// value will be populated with the request's response once the request complets
// successfuly.
//
// See Scan for usage and error information.
// Use "Send" method on the returned Request to send the API call to the service.
// the "output" return value is not valid until after Send returns without error.
//
// Creating a request object using this method should be used when you want to inject
// custom logic into the request's lifecycle using a custom handler, or if you want to
// access properties on the request object before or after sending the request. If
// you just want the service response, call the Scan method directly
// instead.
// See Scan for more information on using the Scan
// API call, and error handling.
//
// This method is useful when you want to inject custom logic or configuration
// into the SDK's request lifecycle. Such as custom headers, or retry logic.
//
// Note: You must call the "Send" method on the returned request object in order
// to execute the request.
//
// // Example sending a request using the ScanRequest method.
// req, resp := client.ScanRequest(params)
@ -1702,16 +1731,23 @@ func (c *DynamoDB) ScanRequest(input *ScanInput) (req *request.Request, output *
// the number of items exceeding the limit. A scan can result in no table data
// meeting the filter criteria.
//
// By default, Scan operations proceed sequentially; however, for faster performance
// on a large table or secondary index, applications can request a parallel
// Scan operation by providing the Segment and TotalSegments parameters. For
// more information, see Parallel Scan (http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/QueryAndScan.html#QueryAndScanParallelScan)
// A single Scan operation will read up to the maximum number of items set (if
// using the Limit parameter) or a maximum of 1 MB of data and then apply any
// filtering to the results using FilterExpression. If LastEvaluatedKey is present
// in the response, you will need to paginate the result set. For more information,
// see Paginating the Results (http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Scan.html#Scan.Pagination)
// in the Amazon DynamoDB Developer Guide.
//
// By default, Scan uses eventually consistent reads when accessing the data
// in a table; therefore, the result set might not include the changes to data
// in the table immediately before the operation began. If you need a consistent
// copy of the data, as of the time that the Scan begins, you can set the ConsistentRead
// Scan operations proceed sequentially; however, for faster performance on
// a large table or secondary index, applications can request a parallel Scan
// operation by providing the Segment and TotalSegments parameters. For more
// information, see Parallel Scan (http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Scan.html#Scan.ParallelScan)
// in the Amazon DynamoDB Developer Guide.
//
// Scan uses eventually consistent reads when accessing the data in a table;
// therefore, the result set might not include the changes to data in the table
// immediately before the operation began. If you need a consistent copy of
// the data, as of the time that the Scan begins, you can set the ConsistentRead
// parameter to true.
//
// Returns awserr.Error for service API and SDK errors. Use runtime type assertions
@ -1813,19 +1849,18 @@ const opTagResource = "TagResource"
// TagResourceRequest generates a "aws/request.Request" representing the
// client's request for the TagResource operation. The "output" return
// value can be used to capture response data after the request's "Send" method
// is called.
// value will be populated with the request's response once the request complets
// successfuly.
//
// See TagResource for usage and error information.
// Use "Send" method on the returned Request to send the API call to the service.
// the "output" return value is not valid until after Send returns without error.
//
// Creating a request object using this method should be used when you want to inject
// custom logic into the request's lifecycle using a custom handler, or if you want to
// access properties on the request object before or after sending the request. If
// you just want the service response, call the TagResource method directly
// instead.
// See TagResource for more information on using the TagResource
// API call, and error handling.
//
// This method is useful when you want to inject custom logic or configuration
// into the SDK's request lifecycle. Such as custom headers, or retry logic.
//
// Note: You must call the "Send" method on the returned request object in order
// to execute the request.
//
// // Example sending a request using the TagResourceRequest method.
// req, resp := client.TagResourceRequest(params)
@ -1920,19 +1955,18 @@ const opUntagResource = "UntagResource"
// UntagResourceRequest generates a "aws/request.Request" representing the
// client's request for the UntagResource operation. The "output" return
// value can be used to capture response data after the request's "Send" method
// is called.
// value will be populated with the request's response once the request complets
// successfuly.
//
// See UntagResource for usage and error information.
// Use "Send" method on the returned Request to send the API call to the service.
// the "output" return value is not valid until after Send returns without error.
//
// Creating a request object using this method should be used when you want to inject
// custom logic into the request's lifecycle using a custom handler, or if you want to
// access properties on the request object before or after sending the request. If
// you just want the service response, call the UntagResource method directly
// instead.
// See UntagResource for more information on using the UntagResource
// API call, and error handling.
//
// This method is useful when you want to inject custom logic or configuration
// into the SDK's request lifecycle. Such as custom headers, or retry logic.
//
// Note: You must call the "Send" method on the returned request object in order
// to execute the request.
//
// // Example sending a request using the UntagResourceRequest method.
// req, resp := client.UntagResourceRequest(params)
@ -2025,19 +2059,18 @@ const opUpdateItem = "UpdateItem"
// UpdateItemRequest generates a "aws/request.Request" representing the
// client's request for the UpdateItem operation. The "output" return
// value can be used to capture response data after the request's "Send" method
// is called.
// value will be populated with the request's response once the request complets
// successfuly.
//
// See UpdateItem for usage and error information.
// Use "Send" method on the returned Request to send the API call to the service.
// the "output" return value is not valid until after Send returns without error.
//
// Creating a request object using this method should be used when you want to inject
// custom logic into the request's lifecycle using a custom handler, or if you want to
// access properties on the request object before or after sending the request. If
// you just want the service response, call the UpdateItem method directly
// instead.
// See UpdateItem for more information on using the UpdateItem
// API call, and error handling.
//
// This method is useful when you want to inject custom logic or configuration
// into the SDK's request lifecycle. Such as custom headers, or retry logic.
//
// Note: You must call the "Send" method on the returned request object in order
// to execute the request.
//
// // Example sending a request using the UpdateItemRequest method.
// req, resp := client.UpdateItemRequest(params)
@ -2131,19 +2164,18 @@ const opUpdateTable = "UpdateTable"
// UpdateTableRequest generates a "aws/request.Request" representing the
// client's request for the UpdateTable operation. The "output" return
// value can be used to capture response data after the request's "Send" method
// is called.
// value will be populated with the request's response once the request complets
// successfuly.
//
// See UpdateTable for usage and error information.
// Use "Send" method on the returned Request to send the API call to the service.
// the "output" return value is not valid until after Send returns without error.
//
// Creating a request object using this method should be used when you want to inject
// custom logic into the request's lifecycle using a custom handler, or if you want to
// access properties on the request object before or after sending the request. If
// you just want the service response, call the UpdateTable method directly
// instead.
// See UpdateTable for more information on using the UpdateTable
// API call, and error handling.
//
// This method is useful when you want to inject custom logic or configuration
// into the SDK's request lifecycle. Such as custom headers, or retry logic.
//
// Note: You must call the "Send" method on the returned request object in order
// to execute the request.
//
// // Example sending a request using the UpdateTableRequest method.
// req, resp := client.UpdateTableRequest(params)
@ -2247,19 +2279,18 @@ const opUpdateTimeToLive = "UpdateTimeToLive"
// UpdateTimeToLiveRequest generates a "aws/request.Request" representing the
// client's request for the UpdateTimeToLive operation. The "output" return
// value can be used to capture response data after the request's "Send" method
// is called.
// value will be populated with the request's response once the request complets
// successfuly.
//
// See UpdateTimeToLive for usage and error information.
// Use "Send" method on the returned Request to send the API call to the service.
// the "output" return value is not valid until after Send returns without error.
//
// Creating a request object using this method should be used when you want to inject
// custom logic into the request's lifecycle using a custom handler, or if you want to
// access properties on the request object before or after sending the request. If
// you just want the service response, call the UpdateTimeToLive method directly
// instead.
// See UpdateTimeToLive for more information on using the UpdateTimeToLive
// API call, and error handling.
//
// This method is useful when you want to inject custom logic or configuration
// into the SDK's request lifecycle. Such as custom headers, or retry logic.
//
// Note: You must call the "Send" method on the returned request object in order
// to execute the request.
//
// // Example sending a request using the UpdateTimeToLiveRequest method.
// req, resp := client.UpdateTimeToLiveRequest(params)
@ -2288,11 +2319,11 @@ func (c *DynamoDB) UpdateTimeToLiveRequest(input *UpdateTimeToLiveInput) (req *r
// UpdateTimeToLive API operation for Amazon DynamoDB.
//
// Specify the lifetime of individual table items. The database automatically
// removes the item at the expiration of the item. The UpdateTimeToLive method
// will enable or disable TTL for the specified table. A successful UpdateTimeToLive
// call returns the current TimeToLiveSpecification; it may take up to one hour
// for the change to fully process.
// The UpdateTimeToLive method will enable or disable TTL for the specified
// table. A successful UpdateTimeToLive call returns the current TimeToLiveSpecification;
// it may take up to one hour for the change to fully process. Any additional
// UpdateTimeToLive calls for the same table during this one hour duration result
// in a ValidationException.
//
// TTL compares the current time in epoch time format to the time stored in
// the TTL attribute of an item. If the epoch time value stored in the attribute
@ -2908,8 +2939,8 @@ type BatchWriteItemInput struct {
// * DeleteRequest - Perform a DeleteItem operation on the specified item.
// The item to be deleted is identified by a Key subelement:
//
// Key - A map of primary key attribute values that uniquely identify the !
// item. Each entry in this map consists of an attribute name and an attribute
// Key - A map of primary key attribute values that uniquely identify the item.
// Each entry in this map consists of an attribute name and an attribute
// value. For each primary key, you must provide all of the key attributes.
// For example, with a simple primary key, you only need to provide a value
// for the partition key. For a composite primary key, you must provide values
@ -3786,7 +3817,7 @@ type DeleteItemInput struct {
// in the Amazon DynamoDB Developer Guide.
ConditionalOperator *string `type:"string" enum:"ConditionalOperator"`
// This is a legacy parameter. Use ConditionExpresssion instead. For more information,
// This is a legacy parameter. Use ConditionExpression instead. For more information,
// see Expected (http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/LegacyConditionalParameters.Expected.html)
// in the Amazon DynamoDB Developer Guide.
Expected map[string]*ExpectedAttributeValue `type:"map"`
@ -5953,7 +5984,7 @@ type PutItemInput struct {
// in the Amazon DynamoDB Developer Guide.
ConditionalOperator *string `type:"string" enum:"ConditionalOperator"`
// This is a legacy parameter. Use ConditionExpresssion instead. For more information,
// This is a legacy parameter. Use ConditionExpression instead. For more information,
// see Expected (http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/LegacyConditionalParameters.Expected.html)
// in the Amazon DynamoDB Developer Guide.
Expected map[string]*ExpectedAttributeValue `type:"map"`
@ -8050,7 +8081,7 @@ type UpdateItemInput struct {
// in the Amazon DynamoDB Developer Guide.
ConditionalOperator *string `type:"string" enum:"ConditionalOperator"`
// This is a legacy parameter. Use ConditionExpresssion instead. For more information,
// This is a legacy parameter. Use ConditionExpression instead. For more information,
// see Expected (http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/LegacyConditionalParameters.Expected.html)
// in the Amazon DynamoDB Developer Guide.
Expected map[string]*ExpectedAttributeValue `type:"map"`
@ -8148,9 +8179,8 @@ type UpdateItemInput struct {
// (the default), no statistics are returned.
ReturnItemCollectionMetrics *string `type:"string" enum:"ReturnItemCollectionMetrics"`
// Use ReturnValues if you want to get the item attributes as they appeared
// either before or after they were updated. For UpdateItem, the valid values
// are:
// Use ReturnValues if you want to get the item attributes as they appear before
// or after they are updated. For UpdateItem, the valid values are:
//
// * NONE - If ReturnValues is not specified, or if its value is NONE, then
// nothing is returned. (This setting is the default for ReturnValues.)
@ -8169,9 +8199,9 @@ type UpdateItemInput struct {
//
// There is no additional cost associated with requesting a return value aside
// from the small network and processing overhead of receiving a larger response.
// No Read Capacity Units are consumed.
// No read capacity units are consumed.
//
// Values returned are strongly consistent
// The values returned are strongly consistent.
ReturnValues *string `type:"string" enum:"ReturnValue"`
// The name of the table containing the item to update.
@ -8361,9 +8391,11 @@ func (s *UpdateItemInput) SetUpdateExpression(v string) *UpdateItemInput {
type UpdateItemOutput struct {
_ struct{} `type:"structure"`
// A map of attribute values as they appeared before the UpdateItem operation.
// This map only appears if ReturnValues was specified as something other than
// NONE in the request. Each element represents one attribute.
// A map of attribute values as they appear before or after the UpdateItem operation,
// as determined by the ReturnValues parameter.
//
// The Attributes map is only present if ReturnValues was specified as something
// other than NONE in the request. Each element represents one attribute.
Attributes map[string]*AttributeValue `type:"map"`
// The capacity units consumed by the UpdateItem operation. The data returned

View file

@ -29,69 +29,17 @@
//
// Using the Client
//
// To use the client for Amazon DynamoDB you will first need
// to create a new instance of it.
// To Amazon DynamoDB with the SDK use the New function to create
// a new service client. With that client you can make API requests to the service.
// These clients are safe to use concurrently.
//
// When creating a client for an AWS service you'll first need to have a Session
// already created. The Session provides configuration that can be shared
// between multiple service clients. Additional configuration can be applied to
// the Session and service's client when they are constructed. The aws package's
// Config type contains several fields such as Region for the AWS Region the
// client should make API requests too. The optional Config value can be provided
// as the variadic argument for Sessions and client creation.
//
// Once the service's client is created you can use it to make API requests the
// AWS service. These clients are safe to use concurrently.
//
// // Create a session to share configuration, and load external configuration.
// sess := session.Must(session.NewSession())
//
// // Create the service's client with the session.
// svc := dynamodb.New(sess)
//
// See the SDK's documentation for more information on how to use service clients.
// See the SDK's documentation for more information on how to use the SDK.
// https://docs.aws.amazon.com/sdk-for-go/api/
//
// See aws package's Config type for more information on configuration options.
// See aws.Config documentation for more information on configuring SDK clients.
// https://docs.aws.amazon.com/sdk-for-go/api/aws/#Config
//
// See the Amazon DynamoDB client DynamoDB for more
// information on creating the service's client.
// information on creating client for this service.
// https://docs.aws.amazon.com/sdk-for-go/api/service/dynamodb/#New
//
// Once the client is created you can make an API request to the service.
// Each API method takes a input parameter, and returns the service response
// and an error.
//
// The API method will document which error codes the service can be returned
// by the operation if the service models the API operation's errors. These
// errors will also be available as const strings prefixed with "ErrCode".
//
// result, err := svc.BatchGetItem(params)
// if err != nil {
// // Cast err to awserr.Error to handle specific error codes.
// aerr, ok := err.(awserr.Error)
// if ok && aerr.Code() == <error code to check for> {
// // Specific error code handling
// }
// return err
// }
//
// fmt.Println("BatchGetItem result:")
// fmt.Println(result)
//
// Using the Client with Context
//
// The service's client also provides methods to make API requests with a Context
// value. This allows you to control the timeout, and cancellation of pending
// requests. These methods also take request Option as variadic parameter to apply
// additional configuration to the API request.
//
// ctx := context.Background()
//
// result, err := svc.BatchGetItemWithContext(ctx, params)
//
// See the request package documentation for more information on using Context pattern
// with the SDK.
// https://docs.aws.amazon.com/sdk-for-go/api/aws/request/
package dynamodb

View file

@ -1,84 +1,27 @@
// AttributeValue Marshaling and Unmarshaling Helpers
//
// Utility helpers to marshal and unmarshal AttributeValue to and
// from Go types can be found in the dynamodbattribute sub package. This package
// provides has specialized functions for the common ways of working with
// AttributeValues. Such as map[string]*AttributeValue, []*AttributeValue, and
// directly with *AttributeValue. This is helpful for marshaling Go types for API
// operations such as PutItem, and unmarshaling Query and Scan APIs' responses.
//
// See the dynamodbattribute package documentation for more information.
// https://docs.aws.amazon.com/sdk-for-go/api/service/dynamodb/dynamodbattribute/
//
// AttributeValue Marshaling
//
// To marshal a Go type to an AttributeValue you can use the Marshal
// functions in the dynamodbattribute package. There are specialized versions
// of these functions for collections of AttributeValue, such as maps and lists.
//
// The following example uses MarshalMap to convert the Record Go type to a
// dynamodb.AttributeValue type and use the value to make a PutItem API request.
//
// type Record struct {
// ID string
// URLs []string
// }
//
// //...
//
// r := Record{
// ID: "ABC123",
// URLs: []string{
// "https://example.com/first/link",
// "https://example.com/second/url",
// },
// }
// av, err := dynamodbattribute.MarshalMap(r)
// if err != nil {
// panic(fmt.Sprintf("failed to DynamoDB marshal Record, %v", err))
// }
//
// _, err = svc.PutItem(&dynamodb.PutItemInput{
// TableName: aws.String(myTableName),
// Item: av,
// })
// if err != nil {
// panic(fmt.Sprintf("failed to put Record to DynamoDB, %v", err))
// }
//
// AttributeValue Unmarshaling
//
// To unmarshal a dynamodb.AttributeValue to a Go type you can use the Unmarshal
// functions in the dynamodbattribute package. There are specialized versions
// of these functions for collections of AttributeValue, such as maps and lists.
//
// The following example will unmarshal the DynamoDB's Scan API operation. The
// Items returned by the operation will be unmarshaled into the slice of Records
// Go type.
//
// type Record struct {
// ID string
// URLs []string
// }
//
// //...
//
// var records []Record
//
// // Use the ScanPages method to perform the scan with pagination. Use
// // just Scan method to make the API call without pagination.
// err := svc.ScanPages(&dynamodb.ScanInput{
// TableName: aws.String(myTableName),
// }, func(page *dynamodb.ScanOutput, last bool) bool {
// recs := []Record{}
//
// err := dynamodbattribute.UnmarshalListOfMaps(page.Items, &recs)
// if err != nil {
// panic(fmt.Sprintf("failed to unmarshal Dynamodb Scan Items, %v", err))
// }
//
// records = append(records, recs...)
//
// return true // keep paging
// })
/*
AttributeValue Marshaling and Unmarshaling Helpers
Utility helpers to marshal and unmarshal AttributeValue to and
from Go types can be found in the dynamodbattribute sub package. This package
provides has specialized functions for the common ways of working with
AttributeValues. Such as map[string]*AttributeValue, []*AttributeValue, and
directly with *AttributeValue. This is helpful for marshaling Go types for API
operations such as PutItem, and unmarshaling Query and Scan APIs' responses.
See the dynamodbattribute package documentation for more information.
https://docs.aws.amazon.com/sdk-for-go/api/service/dynamodb/dynamodbattribute/
Expression Builders
The expression package provides utility types and functions to build DynamoDB
expression for type safe construction of API ExpressionAttributeNames, and
ExpressionAttribute Values.
The package represents the various DynamoDB Expressions as structs named
accordingly. For example, ConditionBuilder represents a DynamoDB Condition
Expression, an UpdateBuilder represents a DynamoDB Update Expression, and so on.
See the expression package documentation for more information.
https://docs.aws.amazon.com/sdk-for-go/api/service/dynamodb/expression/
*/
package dynamodb

View file

@ -153,6 +153,7 @@ var stringInterfaceMapType = reflect.TypeOf(map[string]interface{}(nil))
var byteSliceType = reflect.TypeOf([]byte(nil))
var byteSliceSlicetype = reflect.TypeOf([][]byte(nil))
var numberType = reflect.TypeOf(Number(""))
var timeType = reflect.TypeOf(time.Time{})
func (d *Decoder) decode(av *dynamodb.AttributeValue, v reflect.Value, fieldTag tag) error {
var u Unmarshaler
@ -338,12 +339,12 @@ func (d *Decoder) decodeNumber(n *string, v reflect.Value, fieldTag tag) error {
}
v.SetFloat(i)
default:
if _, ok := v.Interface().(time.Time); ok && fieldTag.AsUnixTime {
if v.Type().ConvertibleTo(timeType) && fieldTag.AsUnixTime {
t, err := decodeUnixTime(*n)
if err != nil {
return err
}
v.Set(reflect.ValueOf(t))
v.Set(reflect.ValueOf(t).Convert(v.Type()))
return nil
}
return &UnmarshalTypeError{Value: "number", Type: v.Type()}
@ -502,12 +503,12 @@ func (d *Decoder) decodeString(s *string, v reflect.Value, fieldTag tag) error {
// To maintain backwards compatibility with ConvertFrom family of methods which
// converted strings to time.Time structs
if _, ok := v.Interface().(time.Time); ok {
if v.Type().ConvertibleTo(timeType) {
t, err := time.Parse(time.RFC3339, *s)
if err != nil {
return err
}
v.Set(reflect.ValueOf(t))
v.Set(reflect.ValueOf(t).Convert(v.Type()))
return nil
}
@ -564,7 +565,7 @@ func decodeUnixTime(n string) (time.Time, error) {
v, err := strconv.ParseInt(n, 10, 64)
if err != nil {
return time.Time{}, &UnmarshalError{
Err: err, Value: n, Type: reflect.TypeOf(time.Time{}),
Err: err, Value: n, Type: timeType,
}
}

View file

@ -10,7 +10,6 @@ import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/dynamodb"
"github.com/stretchr/testify/assert"
)
func TestUnmarshalErrorTypes(t *testing.T) {
@ -390,12 +389,22 @@ func TestUnmarshalUnmashaler(t *testing.T) {
}
err := Unmarshal(av, u)
assert.NoError(t, err)
if err != nil {
t.Errorf("expect no error, got %v", err)
}
assert.Equal(t, "value", u.Value)
assert.Equal(t, 123, u.Value2)
assert.Equal(t, true, u.Value3)
assert.Equal(t, testDate, u.Value4)
if e, a := "value", u.Value; e != a {
t.Errorf("expect %v, got %v", e, a)
}
if e, a := 123, u.Value2; e != a {
t.Errorf("expect %v, got %v", e, a)
}
if e, a := true, u.Value3; e != a {
t.Errorf("expect %v, got %v", e, a)
}
if e, a := testDate, u.Value4; e != a {
t.Errorf("expect %v, got %v", e, a)
}
}
func TestDecodeUseNumber(t *testing.T) {
@ -412,13 +421,20 @@ func TestDecodeUseNumber(t *testing.T) {
d.UseNumber = true
})
err := decoder.Decode(av, &u)
assert.NoError(t, err)
if err != nil {
t.Errorf("expect no error, got %v", err)
}
assert.Equal(t, "value", u["abc"])
n, ok := u["def"].(Number)
assert.True(t, ok)
assert.Equal(t, "123", n.String())
assert.Equal(t, true, u["ghi"])
if e, a := "value", u["abc"]; e != a {
t.Errorf("expect %v, got %v", e, a)
}
n := u["def"].(Number)
if e, a := "123", n.String(); e != a {
t.Errorf("expect %v, got %v", e, a)
}
if e, a := true, u["ghi"]; e != a {
t.Errorf("expect %v, got %v", e, a)
}
}
func TestDecodeUseNumberNumberSet(t *testing.T) {
@ -437,13 +453,18 @@ func TestDecodeUseNumberNumberSet(t *testing.T) {
d.UseNumber = true
})
err := decoder.Decode(av, &u)
assert.NoError(t, err)
if err != nil {
t.Errorf("expect no error, got %v", err)
}
ns, ok := u["ns"].([]Number)
assert.True(t, ok)
ns := u["ns"].([]Number)
assert.Equal(t, "123", ns[0].String())
assert.Equal(t, "321", ns[1].String())
if e, a := "123", ns[0].String(); e != a {
t.Errorf("expect %v, got %v", e, a)
}
if e, a := "321", ns[1].String(); e != a {
t.Errorf("expect %v, got %v", e, a)
}
}
func TestDecodeEmbeddedPointerStruct(t *testing.T) {
@ -471,12 +492,20 @@ func TestDecodeEmbeddedPointerStruct(t *testing.T) {
decoder := NewDecoder()
a := A{}
err := decoder.Decode(av, &a)
assert.NoError(t, err)
assert.Equal(t, 321, a.Aint)
if err != nil {
t.Errorf("expect no error, got %v", err)
}
if e, a := 321, a.Aint; e != a {
t.Errorf("expect %v, got %v", e, a)
}
// Embedded pointer struct can be created automatically.
assert.Equal(t, 123, a.Bint)
if e, a := 123, a.Bint; e != a {
t.Errorf("expect %v, got %v", e, a)
}
// But not for absent fields.
assert.Nil(t, a.C)
if a.C != nil {
t.Errorf("expect nil, got %v", a.C)
}
}
func TestDecodeBooleanOverlay(t *testing.T) {
@ -491,8 +520,12 @@ func TestDecodeBooleanOverlay(t *testing.T) {
var v BooleanOverlay
err := decoder.Decode(av, &v)
assert.NoError(t, err)
assert.Equal(t, BooleanOverlay(true), v)
if err != nil {
t.Errorf("expect no error, got %v", err)
}
if e, a := BooleanOverlay(true), v; e != a {
t.Errorf("expect %v, got %v", e, a)
}
}
func TestDecodeUnixTime(t *testing.T) {
@ -524,6 +557,42 @@ func TestDecodeUnixTime(t *testing.T) {
actual := A{}
err := Unmarshal(input, &actual)
assert.NoError(t, err)
assert.Equal(t, expect, actual)
if err != nil {
t.Errorf("expect no error, got %v", err)
}
if e, a := expect, actual; e != a {
t.Errorf("expect %v, got %v", e, a)
}
}
func TestDecodeAliasedUnixTime(t *testing.T) {
type A struct {
Normal AliasedTime
Tagged AliasedTime `dynamodbav:",unixtime"`
}
expect := A{
Normal: AliasedTime(time.Unix(123, 0).UTC()),
Tagged: AliasedTime(time.Unix(456, 0)),
}
input := &dynamodb.AttributeValue{
M: map[string]*dynamodb.AttributeValue{
"Normal": {
S: aws.String("1970-01-01T00:02:03Z"),
},
"Tagged": {
N: aws.String("456"),
},
},
}
actual := A{}
err := Unmarshal(input, &actual)
if err != nil {
t.Errorf("expect no error, got %v", err)
}
if expect != actual {
t.Errorf("expect %v, got %v", expect, actual)
}
}

View file

@ -34,7 +34,7 @@
// panic(fmt.Sprintf("failed to DynamoDB marshal Record, %v", err))
// }
//
// _, err := r.svc.PutItem(&dynamodb.PutItemInput{
// _, err = svc.PutItem(&dynamodb.PutItemInput{
// TableName: aws.String(myTableName),
// Item: av,
// })

View file

@ -285,7 +285,9 @@ func (e *Encoder) encode(av *dynamodb.AttributeValue, v reflect.Value, fieldTag
func (e *Encoder) encodeStruct(av *dynamodb.AttributeValue, v reflect.Value, fieldTag tag) error {
// To maintain backwards compatibility with ConvertTo family of methods which
// converted time.Time structs to strings
if t, ok := v.Interface().(time.Time); ok {
if v.Type().ConvertibleTo(timeType) {
var t time.Time
t = v.Convert(timeType).Interface().(time.Time)
if fieldTag.AsUnixTime {
return UnixTime(t).MarshalDynamoDBAttributeValue(av)
}

View file

@ -2,13 +2,13 @@ package dynamodbattribute
import (
"fmt"
"reflect"
"testing"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/dynamodb"
"github.com/stretchr/testify/assert"
)
func TestMarshalErrorTypes(t *testing.T) {
@ -73,9 +73,13 @@ func TestMarshalMashaler(t *testing.T) {
}
actual, err := Marshal(m)
assert.NoError(t, err)
if err != nil {
t.Errorf("expect nil, got %v", err)
}
assert.Equal(t, expect, actual)
if e, a := expect, actual; !reflect.DeepEqual(e, a) {
t.Errorf("expect %v, got %v", e, a)
}
}
type testOmitEmptyElemListStruct struct {
@ -99,8 +103,12 @@ func TestMarshalListOmitEmptyElem(t *testing.T) {
m := testOmitEmptyElemListStruct{Values: []string{"abc", "", "123"}}
actual, err := Marshal(m)
assert.NoError(t, err)
assert.Equal(t, expect, actual)
if err != nil {
t.Errorf("expect nil, got %v", err)
}
if e, a := expect, actual; !reflect.DeepEqual(e, a) {
t.Errorf("expect %v, got %v", e, a)
}
}
func TestMarshalMapOmitEmptyElem(t *testing.T) {
@ -121,8 +129,12 @@ func TestMarshalMapOmitEmptyElem(t *testing.T) {
}}
actual, err := Marshal(m)
assert.NoError(t, err)
assert.Equal(t, expect, actual)
if err != nil {
t.Errorf("expect nil, got %v", err)
}
if e, a := expect, actual; !reflect.DeepEqual(e, a) {
t.Errorf("expect %v, got %v", e, a)
}
}
type testOmitEmptyScalar struct {
@ -141,8 +153,12 @@ func TestMarshalOmitEmpty(t *testing.T) {
m := testOmitEmptyScalar{IntPtrSetZero: aws.Int(0)}
actual, err := Marshal(m)
assert.NoError(t, err)
assert.Equal(t, expect, actual)
if err != nil {
t.Errorf("expect nil, got %v", err)
}
if e, a := expect, actual; !reflect.DeepEqual(e, a) {
t.Errorf("expect %v, got %v", e, a)
}
}
func TestEncodeEmbeddedPointerStruct(t *testing.T) {
@ -158,12 +174,20 @@ func TestEncodeEmbeddedPointerStruct(t *testing.T) {
*C
}
a := A{Aint: 321, B: &B{123}}
assert.Equal(t, 321, a.Aint)
assert.Equal(t, 123, a.Bint)
assert.Nil(t, a.C)
if e, a := 321, a.Aint; e != a {
t.Errorf("expect %v, got %v", e, a)
}
if e, a := 123, a.Bint; e != a {
t.Errorf("expect %v, got %v", e, a)
}
if a.C != nil {
t.Errorf("expect nil, got %v", a.C)
}
actual, err := Marshal(a)
assert.NoError(t, err)
if err != nil {
t.Errorf("expect nil, got %v", err)
}
expect := &dynamodb.AttributeValue{
M: map[string]*dynamodb.AttributeValue{
"Aint": {
@ -174,7 +198,9 @@ func TestEncodeEmbeddedPointerStruct(t *testing.T) {
},
},
}
assert.Equal(t, expect, actual)
if e, a := expect, actual; !reflect.DeepEqual(e, a) {
t.Errorf("expect %v, got %v", e, a)
}
}
func TestEncodeUnixTime(t *testing.T) {
@ -191,7 +217,9 @@ func TestEncodeUnixTime(t *testing.T) {
}
actual, err := Marshal(a)
assert.NoError(t, err)
if err != nil {
t.Errorf("expect nil, got %v", err)
}
expect := &dynamodb.AttributeValue{
M: map[string]*dynamodb.AttributeValue{
"Normal": {
@ -205,5 +233,39 @@ func TestEncodeUnixTime(t *testing.T) {
},
},
}
assert.Equal(t, expect, actual)
if e, a := expect, actual; !reflect.DeepEqual(e, a) {
t.Errorf("expect %v, got %v", e, a)
}
}
type AliasedTime time.Time
func TestEncodeAliasedUnixTime(t *testing.T) {
type A struct {
Normal AliasedTime
Tagged AliasedTime `dynamodbav:",unixtime"`
}
a := A{
Normal: AliasedTime(time.Unix(123, 0).UTC()),
Tagged: AliasedTime(time.Unix(456, 0)),
}
actual, err := Marshal(a)
if err != nil {
t.Errorf("expect no err, got %v", err)
}
expect := &dynamodb.AttributeValue{
M: map[string]*dynamodb.AttributeValue{
"Normal": {
S: aws.String("1970-01-01T00:02:03Z"),
},
"Tagged": {
N: aws.String("456"),
},
},
}
if e, a := expect, actual; !reflect.DeepEqual(e, a) {
t.Errorf("expect %v, got %v", e, a)
}
}

View file

@ -3,8 +3,6 @@ package dynamodbattribute
import (
"reflect"
"testing"
"github.com/stretchr/testify/assert"
)
type testUnionValues struct {
@ -77,9 +75,13 @@ func TestUnionStructFields(t *testing.T) {
fields := unionStructFields(v.Type(), MarshalOptions{SupportJSONTags: true})
for j, f := range fields {
expected := c.expect[j]
assert.Equal(t, expected.Name, f.Name, "case %d, field %d", i, j)
if e, a := expected.Name, f.Name; e != a {
t.Errorf("%d:%d expect %v, got %v", i, j, e, f)
}
actual := v.FieldByIndex(f.Index).Interface()
assert.EqualValues(t, expected.Value, actual, "case %d, field %d", i, j)
if e, a := expected.Value, actual; !reflect.DeepEqual(e, a) {
t.Errorf("%d:%d expect %v, got %v", i, j, e, f)
}
}
}
}
@ -102,9 +104,13 @@ func TestFieldByName(t *testing.T) {
for _, c := range cases {
f, ok := fieldByName(fields, c.Name)
assert.Equal(t, c.Found, ok)
if e, a := c.Found, ok; e != a {
t.Errorf("expect %v, got %v", e, a)
}
if ok {
assert.Equal(t, c.FieldName, f.Name)
if e, a := c.FieldName, f.Name; e != a {
t.Errorf("expect %v, got %v", e, a)
}
}
}
}

View file

@ -7,7 +7,6 @@ import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/dynamodb"
"github.com/stretchr/testify/assert"
)
type testBinarySetStruct struct {
@ -376,14 +375,18 @@ func assertConvertTest(t *testing.T, i int, actual, expected interface{}, err, e
i++
if expectedErr != nil {
if err != nil {
assert.Equal(t, expectedErr, err, "case %d", i)
if e, a := expectedErr, err; !reflect.DeepEqual(e, a) {
t.Errorf("case %d expect %v, got %v", i, e, a)
}
} else {
assert.Fail(t, "", "case %d, expected error, %v", i)
t.Fatalf("case %d, expected error, %v", i, expectedErr)
}
} else if err != nil {
assert.Fail(t, "", "case %d, expect no error, got %v", i, err)
t.Fatalf("case %d, expect no error, got %v", i, err)
} else {
assert.Equal(t, ptrToValue(expected), ptrToValue(actual), "case %d", i)
if e, a := ptrToValue(expected), ptrToValue(actual); !reflect.DeepEqual(e, a) {
t.Errorf("case %d, expect %v, got %v", i, e, a)
}
}
}

View file

@ -3,8 +3,6 @@ package dynamodbattribute
import (
"reflect"
"testing"
"github.com/stretchr/testify/assert"
)
func TestTagParse(t *testing.T) {
@ -42,6 +40,8 @@ func TestTagParse(t *testing.T) {
if c.av {
actual.parseAVTag(c.in)
}
assert.Equal(t, c.expect, actual, "case %d", i+1)
if e, a := c.expect, actual; !reflect.DeepEqual(e, a) {
t.Errorf("case %d, expect %v, got %v", i, e, a)
}
}
}

View file

@ -21,7 +21,7 @@ import (
//
// The best way to use this interface is so the SDK's service client's calls
// can be stubbed out for unit testing your code with the SDK without needing
// to inject custom request handlers into the the SDK's request pipeline.
// to inject custom request handlers into the SDK's request pipeline.
//
// // myFunc uses an SDK service client to make a request to
// // Amazon DynamoDB.

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,48 @@
/*
Package expression provides types and functions to create Amazon DynamoDB
Expression strings, ExpressionAttributeNames maps, and ExpressionAttributeValues
maps.
Using the Package
The package represents the various DynamoDB Expressions as structs named
accordingly. For example, ConditionBuilder represents a DynamoDB Condition
Expression, an UpdateBuilder represents a DynamoDB Update Expression, and so on.
The following example shows a sample ConditionExpression and how to build an
equilvalent ConditionBuilder
// Let :a be an ExpressionAttributeValue representing the string "No One You
// Know"
condExpr := "Artist = :a"
condBuilder := expression.Name("Artist").Equal(expression.Value("No One You Know"))
In order to retrieve the formatted DynamoDB Expression strings, call the getter
methods on the Expression struct. To create the Expression struct, call the
Build() method on the Builder struct. Because some input structs, such as
QueryInput, can have multiple DynamoDB Expressions, multiple structs
representing various DynamoDB Expressions can be added to the Builder struct.
The following example shows a generic usage of the whole package.
filt := expression.Name("Artist").Equal(expression.Value("No One You Know"))
proj := expression.NamesList(expression.Name("SongTitle"), expression.Name("AlbumTitle"))
expr, err := expression.NewBuilder().WithFilter(filt).WithProjection(proj).Build()
if err != nil {
fmt.Println(err)
}
input := &dynamodb.ScanInput{
ExpressionAttributeNames: expr.Names(),
ExpressionAttributeValues: expr.Values(),
FilterExpression: expr.Filter(),
ProjectionExpression: expr.Projection(),
TableName: aws.String("Music"),
}
The ExpressionAttributeNames and ExpressionAttributeValues member of the input
struct must always be assigned when using the Expression struct because all item
attribute names and values are aliased. That means that if the
ExpressionAttributeNames and ExpressionAttributeValues member is not assigned
with the corresponding Names() and Values() methods, the DynamoDB operation will
run into a logic error.
*/
package expression

View file

@ -0,0 +1,59 @@
package expression
import (
"fmt"
)
// InvalidParameterError is returned if invalid parameters are encountered. This
// error specifically refers to situations where parameters are non-empty but
// have an invalid syntax/format. The error message includes the function
// that returned the error originally and the parameter type that was deemed
// invalid.
//
// Example:
//
// // err is of type InvalidParameterError
// _, err := expression.Name("foo..bar").BuildOperand()
type InvalidParameterError struct {
parameterType string
functionName string
}
func (ipe InvalidParameterError) Error() string {
return fmt.Sprintf("%s error: invalid parameter: %s", ipe.functionName, ipe.parameterType)
}
func newInvalidParameterError(funcName, paramType string) InvalidParameterError {
return InvalidParameterError{
parameterType: paramType,
functionName: funcName,
}
}
// UnsetParameterError is returned if parameters are empty and uninitialized.
// This error is returned if opaque structs (ConditionBuilder, NameBuilder,
// Builder, etc) are initialized outside of functions in the package, since all
// structs in the package are designed to be initialized with functions.
//
// Example:
//
// // err is of type UnsetParameterError
// _, err := expression.Builder{}.Build()
// _, err := expression.NewBuilder().
// WithCondition(expression.ConditionBuilder{}).
// Build()
type UnsetParameterError struct {
parameterType string
functionName string
}
func (upe UnsetParameterError) Error() string {
return fmt.Sprintf("%s error: unset parameter: %s", upe.functionName, upe.parameterType)
}
func newUnsetParameterError(funcName, paramType string) UnsetParameterError {
return UnsetParameterError{
parameterType: paramType,
functionName: funcName,
}
}

View file

@ -0,0 +1,51 @@
// +build go1.7
package expression
import (
"testing"
)
func TestInvalidParameterError(t *testing.T) {
cases := []struct {
name string
input InvalidParameterError
expected string
}{
{
name: "invalid error",
input: newInvalidParameterError("func", "param"),
expected: "func error: invalid parameter: param",
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
actual := c.input.Error()
if e, a := c.expected, actual; e != a {
t.Errorf("expect %v, got %v", e, a)
}
})
}
}
func TestUnsetParameterError(t *testing.T) {
cases := []struct {
name string
input UnsetParameterError
expected string
}{
{
name: "unset error",
input: newUnsetParameterError("func", "param"),
expected: "func error: unset parameter: param",
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
actual := c.input.Error()
if e, a := c.expected, actual; e != a {
t.Errorf("expect %v, got %v", e, a)
}
})
}
}

View file

@ -0,0 +1,315 @@
package expression_test
import (
"fmt"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/dynamodb"
"github.com/aws/aws-sdk-go/service/dynamodb/expression"
)
// Using Projection Expression
//
// This example queries items in the Music table. The table has a partition key and
// sort key (Artist and SongTitle), but this query only specifies the partition key
// value. It returns song titles by the artist named "No One You Know".
func ExampleBuilder_WithProjection() {
svc := dynamodb.New(session.New())
// Construct the Key condition builder
keyCond := expression.Key("Artist").Equal(expression.Value("No One You Know"))
// Create the project expression builder with a names list.
proj := expression.NamesList(expression.Name("SongTitle"))
// Combine the key condition, and projection together as a DynamoDB expression
// builder.
expr, err := expression.NewBuilder().
WithKeyCondition(keyCond).
WithProjection(proj).
Build()
if err != nil {
fmt.Println(err)
}
// Use the built expression to populate the DynamoDB Query's API input
// parameters.
input := &dynamodb.QueryInput{
ExpressionAttributeValues: expr.Values(),
KeyConditionExpression: expr.KeyCondition(),
ProjectionExpression: expr.Projection(),
TableName: aws.String("Music"),
}
result, err := svc.Query(input)
if err != nil {
if aerr, ok := err.(awserr.Error); ok {
switch aerr.Code() {
case dynamodb.ErrCodeProvisionedThroughputExceededException:
fmt.Println(dynamodb.ErrCodeProvisionedThroughputExceededException, aerr.Error())
case dynamodb.ErrCodeResourceNotFoundException:
fmt.Println(dynamodb.ErrCodeResourceNotFoundException, aerr.Error())
case dynamodb.ErrCodeInternalServerError:
fmt.Println(dynamodb.ErrCodeInternalServerError, aerr.Error())
default:
fmt.Println(aerr.Error())
}
} else {
// Print the error, cast err to awserr.Error to get the Code and
// Message from an error.
fmt.Println(err.Error())
}
return
}
fmt.Println(result)
}
// Using Key Condition Expression
//
// This example queries items in the Music table. The table has a partition key and
// sort key (Artist and SongTitle), but this query only specifies the partition key
// value. It returns song titles by the artist named "No One You Know".
func ExampleBuilder_WithKeyCondition() {
svc := dynamodb.New(session.New())
// Construct the Key condition builder
keyCond := expression.Key("Artist").Equal(expression.Value("No One You Know"))
// Create the project expression builder with a names list.
proj := expression.NamesList(expression.Name("SongTitle"))
// Combine the key condition, and projection together as a DynamoDB expression
// builder.
expr, err := expression.NewBuilder().
WithKeyCondition(keyCond).
WithProjection(proj).
Build()
if err != nil {
fmt.Println(err)
}
// Use the built expression to populate the DynamoDB Query's API input
// parameters.
input := &dynamodb.QueryInput{
ExpressionAttributeValues: expr.Values(),
KeyConditionExpression: expr.KeyCondition(),
ProjectionExpression: expr.Projection(),
TableName: aws.String("Music"),
}
result, err := svc.Query(input)
if err != nil {
if aerr, ok := err.(awserr.Error); ok {
switch aerr.Code() {
case dynamodb.ErrCodeProvisionedThroughputExceededException:
fmt.Println(dynamodb.ErrCodeProvisionedThroughputExceededException, aerr.Error())
case dynamodb.ErrCodeResourceNotFoundException:
fmt.Println(dynamodb.ErrCodeResourceNotFoundException, aerr.Error())
case dynamodb.ErrCodeInternalServerError:
fmt.Println(dynamodb.ErrCodeInternalServerError, aerr.Error())
default:
fmt.Println(aerr.Error())
}
} else {
// Print the error, cast err to awserr.Error to get the Code and
// Message from an error.
fmt.Println(err.Error())
}
return
}
fmt.Println(result)
}
// Using Filter Expression
//
// This example scans the entire Music table, and then narrows the results to songs
// by the artist "No One You Know". For each item, only the album title and song title
// are returned.
func ExampleBuilder_WithFilter() {
svc := dynamodb.New(session.New())
// Construct the filter builder with a name and value.
filt := expression.Name("Artist").Equal(expression.Value("No One You Know"))
// Create the names list projection of names to project.
proj := expression.NamesList(
expression.Name("AlbumTitle"),
expression.Name("SongTitle"),
)
// Using the filter and projections create a DynamoDB expression from the two.
expr, err := expression.NewBuilder().
WithFilter(filt).
WithProjection(proj).
Build()
if err != nil {
fmt.Println(err)
}
// Use the built expression to populate the DynamoDB Scan API input parameters.
input := &dynamodb.ScanInput{
ExpressionAttributeNames: expr.Names(),
ExpressionAttributeValues: expr.Values(),
FilterExpression: expr.Filter(),
ProjectionExpression: expr.Projection(),
TableName: aws.String("Music"),
}
result, err := svc.Scan(input)
if err != nil {
if aerr, ok := err.(awserr.Error); ok {
switch aerr.Code() {
case dynamodb.ErrCodeProvisionedThroughputExceededException:
fmt.Println(dynamodb.ErrCodeProvisionedThroughputExceededException, aerr.Error())
case dynamodb.ErrCodeResourceNotFoundException:
fmt.Println(dynamodb.ErrCodeResourceNotFoundException, aerr.Error())
case dynamodb.ErrCodeInternalServerError:
fmt.Println(dynamodb.ErrCodeInternalServerError, aerr.Error())
default:
fmt.Println(aerr.Error())
}
} else {
// Print the error, cast err to awserr.Error to get the Code and
// Message from an error.
fmt.Println(err.Error())
}
return
}
fmt.Println(result)
}
// Using Update Expression
//
// This example updates an item in the Music table. It adds a new attribute (Year) and
// modifies the AlbumTitle attribute. All of the attributes in the item, as they appear
// after the update, are returned in the response.
func ExampleBuilder_WithUpdate() {
svc := dynamodb.New(session.New())
// Create an update to set two fields in the table.
update := expression.Set(
expression.Name("Year"),
expression.Value(2015),
).Set(
expression.Name("AlbumTitle"),
expression.Value("Louder Than Ever"),
)
// Create the DynamoDB expression from the Update.
expr, err := expression.NewBuilder().
WithUpdate(update).
Build()
// Use the built expression to populate the DynamoDB UpdateItem API
// input parameters.
input := &dynamodb.UpdateItemInput{
ExpressionAttributeNames: expr.Names(),
ExpressionAttributeValues: expr.Values(),
Key: map[string]*dynamodb.AttributeValue{
"Artist": {
S: aws.String("Acme Band"),
},
"SongTitle": {
S: aws.String("Happy Day"),
},
},
ReturnValues: aws.String("ALL_NEW"),
TableName: aws.String("Music"),
UpdateExpression: expr.Update(),
}
result, err := svc.UpdateItem(input)
if err != nil {
if aerr, ok := err.(awserr.Error); ok {
switch aerr.Code() {
case dynamodb.ErrCodeConditionalCheckFailedException:
fmt.Println(dynamodb.ErrCodeConditionalCheckFailedException, aerr.Error())
case dynamodb.ErrCodeProvisionedThroughputExceededException:
fmt.Println(dynamodb.ErrCodeProvisionedThroughputExceededException, aerr.Error())
case dynamodb.ErrCodeResourceNotFoundException:
fmt.Println(dynamodb.ErrCodeResourceNotFoundException, aerr.Error())
case dynamodb.ErrCodeItemCollectionSizeLimitExceededException:
fmt.Println(dynamodb.ErrCodeItemCollectionSizeLimitExceededException, aerr.Error())
case dynamodb.ErrCodeInternalServerError:
fmt.Println(dynamodb.ErrCodeInternalServerError, aerr.Error())
default:
fmt.Println(aerr.Error())
}
} else {
// Print the error, cast err to awserr.Error to get the Code and
// Message from an error.
fmt.Println(err.Error())
}
return
}
fmt.Println(result)
}
// Using Condition Expression
//
// This example deletes an item from the Music table if the rating is lower than
// 7.
func ExampleBuilder_WithCondition() {
svc := dynamodb.New(session.New())
// Create a condition where the Rating field must be less than 7.
cond := expression.Name("Rating").LessThan(expression.Value(7))
// Create a DynamoDB expression from the condition.
expr, err := expression.NewBuilder().
WithCondition(cond).
Build()
if err != nil {
fmt.Println(err)
}
// Use the built expression to populate the DeleteItem API operation with the
// condition expression.
input := &dynamodb.DeleteItemInput{
Key: map[string]*dynamodb.AttributeValue{
"Artist": {
S: aws.String("No One You Know"),
},
"SongTitle": {
S: aws.String("Scared of My Shadow"),
},
},
ExpressionAttributeNames: expr.Names(),
ExpressionAttributeValues: expr.Values(),
ConditionExpression: expr.Condition(),
TableName: aws.String("Music"),
}
result, err := svc.DeleteItem(input)
if err != nil {
if aerr, ok := err.(awserr.Error); ok {
switch aerr.Code() {
case dynamodb.ErrCodeConditionalCheckFailedException:
fmt.Println(dynamodb.ErrCodeConditionalCheckFailedException, aerr.Error())
case dynamodb.ErrCodeProvisionedThroughputExceededException:
fmt.Println(dynamodb.ErrCodeProvisionedThroughputExceededException, aerr.Error())
case dynamodb.ErrCodeResourceNotFoundException:
fmt.Println(dynamodb.ErrCodeResourceNotFoundException, aerr.Error())
case dynamodb.ErrCodeItemCollectionSizeLimitExceededException:
fmt.Println(dynamodb.ErrCodeItemCollectionSizeLimitExceededException, aerr.Error())
case dynamodb.ErrCodeInternalServerError:
fmt.Println(dynamodb.ErrCodeInternalServerError, aerr.Error())
default:
fmt.Println(aerr.Error())
}
} else {
// Print the error, cast err to awserr.Error to get the Code and
// Message from an error.
fmt.Println(err.Error())
}
return
}
fmt.Println(result)
}

View file

@ -0,0 +1,635 @@
package expression
import (
"fmt"
"sort"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/dynamodb"
)
// expressionType specifies the type of Expression. Declaring this type is used
// to eliminate magic strings
type expressionType string
const (
projection expressionType = "projection"
keyCondition = "keyCondition"
condition = "condition"
filter = "filter"
update = "update"
)
// Implement the Sort interface
type typeList []expressionType
func (l typeList) Len() int {
return len(l)
}
func (l typeList) Less(i, j int) bool {
return string(l[i]) < string(l[j])
}
func (l typeList) Swap(i, j int) {
l[i], l[j] = l[j], l[i]
}
// Builder represents the struct that builds the Expression struct. Methods such
// as WithProjection() and WithCondition() can add different kinds of DynamoDB
// Expressions to the Builder. The method Build() creates an Expression struct
// with the specified types of DynamoDB Expressions.
//
// Example:
//
// keyCond := expression.Key("someKey").Equal(expression.Value("someValue"))
// proj := expression.NamesList(expression.Name("aName"), expression.Name("anotherName"), expression.Name("oneOtherName"))
//
// builder := expression.NewBuilder().WithKeyCondition(keyCond).WithProjection(proj)
// expression := builder.Build()
//
// queryInput := dynamodb.QueryInput{
// KeyConditionExpression: expression.KeyCondition(),
// ProjectionExpression: expression.Projection(),
// ExpressionAttributeNames: expression.Names(),
// ExpressionAttributeValues: expression.Values(),
// TableName: aws.String("SomeTable"),
// }
type Builder struct {
expressionMap map[expressionType]treeBuilder
}
// NewBuilder returns an empty Builder struct. Methods such as WithProjection()
// and WithCondition() can add different kinds of DynamoDB Expressions to the
// Builder. The method Build() creates an Expression struct with the specified
// types of DynamoDB Expressions.
//
// Example:
//
// keyCond := expression.Key("someKey").Equal(expression.Value("someValue"))
// proj := expression.NamesList(expression.Name("aName"), expression.Name("anotherName"), expression.Name("oneOtherName"))
// builder := expression.NewBuilder().WithKeyCondition(keyCond).WithProjection(proj)
func NewBuilder() Builder {
return Builder{}
}
// Build builds an Expression struct representing multiple types of DynamoDB
// Expressions. Getter methods on the resulting Expression struct returns the
// DynamoDB Expression strings as well as the maps that correspond to
// ExpressionAttributeNames and ExpressionAttributeValues. Calling Build() on an
// empty Builder returns the typed error EmptyParameterError.
//
// Example:
//
// // keyCond represents the Key Condition Expression
// keyCond := expression.Key("someKey").Equal(expression.Value("someValue"))
// // proj represents the Projection Expression
// proj := expression.NamesList(expression.Name("aName"), expression.Name("anotherName"), expression.Name("oneOtherName"))
//
// // Add keyCond and proj to builder as a Key Condition and Projection
// // respectively
// builder := expression.NewBuilder().WithKeyCondition(keyCond).WithProjection(proj)
// expression := builder.Build()
//
// queryInput := dynamodb.QueryInput{
// KeyConditionExpression: expression.KeyCondition(),
// ProjectionExpression: expression.Projection(),
// ExpressionAttributeNames: expression.Names(),
// ExpressionAttributeValues: expression.Values(),
// TableName: aws.String("SomeTable"),
// }
func (b Builder) Build() (Expression, error) {
if b.expressionMap == nil {
return Expression{}, newUnsetParameterError("Build", "Builder")
}
aliasList, expressionMap, err := b.buildChildTrees()
if err != nil {
return Expression{}, err
}
expression := Expression{
expressionMap: expressionMap,
}
if len(aliasList.namesList) != 0 {
namesMap := map[string]*string{}
for ind, val := range aliasList.namesList {
namesMap[fmt.Sprintf("#%v", ind)] = aws.String(val)
}
expression.namesMap = namesMap
}
if len(aliasList.valuesList) != 0 {
valuesMap := map[string]*dynamodb.AttributeValue{}
for i := 0; i < len(aliasList.valuesList); i++ {
valuesMap[fmt.Sprintf(":%v", i)] = &aliasList.valuesList[i]
}
expression.valuesMap = valuesMap
}
return expression, nil
}
// buildChildTrees compiles the list of treeBuilders that are the children of
// the argument Builder. The returned aliasList represents all the alias tokens
// used in the expression strings. The returned map[string]string maps the type
// of expression (i.e. "condition", "update") to the appropriate expression
// string.
func (b Builder) buildChildTrees() (aliasList, map[expressionType]string, error) {
aList := aliasList{}
formattedExpressions := map[expressionType]string{}
keys := typeList{}
for expressionType := range b.expressionMap {
keys = append(keys, expressionType)
}
sort.Sort(keys)
for _, key := range keys {
node, err := b.expressionMap[key].buildTree()
if err != nil {
return aliasList{}, nil, err
}
formattedExpression, err := node.buildExpressionString(&aList)
if err != nil {
return aliasList{}, nil, err
}
formattedExpressions[key] = formattedExpression
}
return aList, formattedExpressions, nil
}
// WithCondition method adds the argument ConditionBuilder as a Condition
// Expression to the argument Builder. If the argument Builder already has a
// ConditionBuilder representing a Condition Expression, WithCondition()
// overwrites the existing ConditionBuilder.
//
// Example:
//
// // let builder be an existing Builder{} and cond be an existing
// // ConditionBuilder{}
// builder = builder.WithCondition(cond)
//
// // add other DynamoDB Expressions to the builder. let proj be an already
// // existing ProjectionBuilder
// builder = builder.WithProjection(proj)
// // create an Expression struct
// expression := builder.Build()
func (b Builder) WithCondition(conditionBuilder ConditionBuilder) Builder {
if b.expressionMap == nil {
b.expressionMap = map[expressionType]treeBuilder{}
}
b.expressionMap[condition] = conditionBuilder
return b
}
// WithProjection method adds the argument ProjectionBuilder as a Projection
// Expression to the argument Builder. If the argument Builder already has a
// ProjectionBuilder representing a Projection Expression, WithProjection()
// overwrites the existing ProjectionBuilder.
//
// Example:
//
// // let builder be an existing Builder{} and proj be an existing
// // ProjectionBuilder{}
// builder = builder.WithProjection(proj)
//
// // add other DynamoDB Expressions to the builder. let cond be an already
// // existing ConditionBuilder
// builder = builder.WithCondition(cond)
// // create an Expression struct
// expression := builder.Build()
func (b Builder) WithProjection(projectionBuilder ProjectionBuilder) Builder {
if b.expressionMap == nil {
b.expressionMap = map[expressionType]treeBuilder{}
}
b.expressionMap[projection] = projectionBuilder
return b
}
// WithKeyCondition method adds the argument KeyConditionBuilder as a Key
// Condition Expression to the argument Builder. If the argument Builder already
// has a KeyConditionBuilder representing a Key Condition Expression,
// WithKeyCondition() overwrites the existing KeyConditionBuilder.
//
// Example:
//
// // let builder be an existing Builder{} and keyCond be an existing
// // KeyConditionBuilder{}
// builder = builder.WithKeyCondition(keyCond)
//
// // add other DynamoDB Expressions to the builder. let cond be an already
// // existing ConditionBuilder
// builder = builder.WithCondition(cond)
// // create an Expression struct
// expression := builder.Build()
func (b Builder) WithKeyCondition(keyConditionBuilder KeyConditionBuilder) Builder {
if b.expressionMap == nil {
b.expressionMap = map[expressionType]treeBuilder{}
}
b.expressionMap[keyCondition] = keyConditionBuilder
return b
}
// WithFilter method adds the argument ConditionBuilder as a Filter Expression
// to the argument Builder. If the argument Builder already has a
// ConditionBuilder representing a Filter Expression, WithFilter()
// overwrites the existing ConditionBuilder.
//
// Example:
//
// // let builder be an existing Builder{} and filt be an existing
// // ConditionBuilder{}
// builder = builder.WithFilter(filt)
//
// // add other DynamoDB Expressions to the builder. let cond be an already
// // existing ConditionBuilder
// builder = builder.WithCondition(cond)
// // create an Expression struct
// expression := builder.Build()
func (b Builder) WithFilter(filterBuilder ConditionBuilder) Builder {
if b.expressionMap == nil {
b.expressionMap = map[expressionType]treeBuilder{}
}
b.expressionMap[filter] = filterBuilder
return b
}
// WithUpdate method adds the argument UpdateBuilder as an Update Expression
// to the argument Builder. If the argument Builder already has a UpdateBuilder
// representing a Update Expression, WithUpdate() overwrites the existing
// UpdateBuilder.
//
// Example:
//
// // let builder be an existing Builder{} and update be an existing
// // UpdateBuilder{}
// builder = builder.WithUpdate(update)
//
// // add other DynamoDB Expressions to the builder. let cond be an already
// // existing ConditionBuilder
// builder = builder.WithCondition(cond)
// // create an Expression struct
// expression := builder.Build()
func (b Builder) WithUpdate(updateBuilder UpdateBuilder) Builder {
if b.expressionMap == nil {
b.expressionMap = map[expressionType]treeBuilder{}
}
b.expressionMap[update] = updateBuilder
return b
}
// Expression represents a collection of DynamoDB Expressions. The getter
// methods of the Expression struct retrieves the formatted DynamoDB
// Expressions, ExpressionAttributeNames, and ExpressionAttributeValues.
//
// Example:
//
// // keyCond represents the Key Condition Expression
// keyCond := expression.Key("someKey").Equal(expression.Value("someValue"))
// // proj represents the Projection Expression
// proj := expression.NamesList(expression.Name("aName"), expression.Name("anotherName"), expression.Name("oneOtherName"))
//
// // Add keyCond and proj to builder as a Key Condition and Projection
// // respectively
// builder := expression.NewBuilder().WithKeyCondition(keyCond).WithProjection(proj)
// expression := builder.Build()
//
// queryInput := dynamodb.QueryInput{
// KeyConditionExpression: expression.KeyCondition(),
// ProjectionExpression: expression.Projection(),
// ExpressionAttributeNames: expression.Names(),
// ExpressionAttributeValues: expression.Values(),
// TableName: aws.String("SomeTable"),
// }
type Expression struct {
expressionMap map[expressionType]string
namesMap map[string]*string
valuesMap map[string]*dynamodb.AttributeValue
}
// treeBuilder interface is fulfilled by builder structs that represent
// different types of Expressions.
type treeBuilder interface {
// buildTree creates the tree structure of exprNodes. The tree structure
// of exprNodes are traversed in order to build the string representing
// different types of Expressions as well as the maps that represent
// ExpressionAttributeNames and ExpressionAttributeValues.
buildTree() (exprNode, error)
}
// Condition returns the *string corresponding to the Condition Expression
// of the argument Expression. This method is used to satisfy the members of
// DynamoDB input structs. If the Expression does not have a condition
// expression this method returns nil.
//
// Example:
//
// // let expression be an instance of Expression{}
//
// deleteInput := dynamodb.DeleteItemInput{
// ConditionExpression: expression.Condition(),
// ExpressionAttributeNames: expression.Names(),
// ExpressionAttributeValues: expression.Values(),
// Key: map[string]*dynamodb.AttributeValue{
// "PartitionKey": &dynamodb.AttributeValue{
// S: aws.String("SomeKey"),
// },
// },
// TableName: aws.String("SomeTable"),
// }
func (e Expression) Condition() *string {
return e.returnExpression(condition)
}
// Filter returns the *string corresponding to the Filter Expression of the
// argument Expression. This method is used to satisfy the members of DynamoDB
// input structs. If the Expression does not have a filter expression this
// method returns nil.
//
// Example:
//
// // let expression be an instance of Expression{}
//
// queryInput := dynamodb.QueryInput{
// KeyConditionExpression: expression.KeyCondition(),
// FilterExpression: expression.Filter(),
// ExpressionAttributeNames: expression.Names(),
// ExpressionAttributeValues: expression.Values(),
// TableName: aws.String("SomeTable"),
// }
func (e Expression) Filter() *string {
return e.returnExpression(filter)
}
// Projection returns the *string corresponding to the Projection Expression
// of the argument Expression. This method is used to satisfy the members of
// DynamoDB input structs. If the Expression does not have a projection
// expression this method returns nil.
//
// Example:
//
// // let expression be an instance of Expression{}
//
// queryInput := dynamodb.QueryInput{
// KeyConditionExpression: expression.KeyCondition(),
// ProjectionExpression: expression.Projection(),
// ExpressionAttributeNames: expression.Names(),
// ExpressionAttributeValues: expression.Values(),
// TableName: aws.String("SomeTable"),
// }
func (e Expression) Projection() *string {
return e.returnExpression(projection)
}
// KeyCondition returns the *string corresponding to the Key Condition
// Expression of the argument Expression. This method is used to satisfy the
// members of DynamoDB input structs. If the argument Expression does not have a
// KeyConditionExpression, KeyCondition() returns nil.
//
// Example:
//
// // let expression be an instance of Expression{}
//
// queryInput := dynamodb.QueryInput{
// KeyConditionExpression: expression.KeyCondition(),
// ProjectionExpression: expression.Projection(),
// ExpressionAttributeNames: expression.Names(),
// ExpressionAttributeValues: expression.Values(),
// TableName: aws.String("SomeTable"),
// }
func (e Expression) KeyCondition() *string {
return e.returnExpression(keyCondition)
}
// Update returns the *string corresponding to the Update Expression of the
// argument Expression. This method is used to satisfy the members of DynamoDB
// input structs. If the argument Expression does not have a UpdateExpression,
// Update() returns nil.
//
// Example:
//
// // let expression be an instance of Expression{}
//
// updateInput := dynamodb.UpdateInput{
// Key: map[string]*dynamodb.AttributeValue{
// "PartitionKey": {
// S: aws.String("someKey"),
// },
// },
// UpdateExpression: expression.Update(),
// ExpressionAttributeNames: expression.Names(),
// ExpressionAttributeValues: expression.Values(),
// TableName: aws.String("SomeTable"),
// }
func (e Expression) Update() *string {
return e.returnExpression(update)
}
// Names returns the map[string]*string corresponding to the
// ExpressionAttributeNames of the argument Expression. This method is used to
// satisfy the members of DynamoDB input structs. If Expression does not use
// ExpressionAttributeNames, this method returns nil. The
// ExpressionAttributeNames and ExpressionAttributeValues member of the input
// struct must always be assigned when using the Expression struct since all
// item attribute names and values are aliased. That means that if the
// ExpressionAttributeNames and ExpressionAttributeValues member is not assigned
// with the corresponding Names() and Values() methods, the DynamoDB operation
// will run into a logic error.
//
// Example:
//
// // let expression be an instance of Expression{}
//
// queryInput := dynamodb.QueryInput{
// KeyConditionExpression: expression.KeyCondition(),
// ProjectionExpression: expression.Projection(),
// ExpressionAttributeNames: expression.Names(),
// ExpressionAttributeValues: expression.Values(),
// TableName: aws.String("SomeTable"),
// }
func (e Expression) Names() map[string]*string {
return e.namesMap
}
// Values returns the map[string]*dynamodb.AttributeValue corresponding to
// the ExpressionAttributeValues of the argument Expression. This method is used
// to satisfy the members of DynamoDB input structs. If Expression does not use
// ExpressionAttributeValues, this method returns nil. The
// ExpressionAttributeNames and ExpressionAttributeValues member of the input
// struct must always be assigned when using the Expression struct since all
// item attribute names and values are aliased. That means that if the
// ExpressionAttributeNames and ExpressionAttributeValues member is not assigned
// with the corresponding Names() and Values() methods, the DynamoDB operation
// will run into a logic error.
//
// Example:
//
// // let expression be an instance of Expression{}
//
// queryInput := dynamodb.QueryInput{
// KeyConditionExpression: expression.KeyCondition(),
// ProjectionExpression: expression.Projection(),
// ExpressionAttributeNames: expression.Names(),
// ExpressionAttributeValues: expression.Values(),
// TableName: aws.String("SomeTable"),
// }
func (e Expression) Values() map[string]*dynamodb.AttributeValue {
return e.valuesMap
}
// returnExpression returns *string corresponding to the type of Expression
// string specified by the expressionType. If there is no corresponding
// expression available in Expression, the method returns nil
func (e Expression) returnExpression(expressionType expressionType) *string {
if e.expressionMap == nil {
return nil
}
return aws.String(e.expressionMap[expressionType])
}
// exprNode are the generic nodes that represents both Operands and
// Conditions. The purpose of exprNode is to be able to call an generic
// recursive function on the top level exprNode to be able to determine a root
// node in order to deduplicate name aliases.
// fmtExpr is a string that has escaped characters to refer to
// names/values/children which needs to be aliased at runtime in order to avoid
// duplicate values. The rules are as follows:
// $n: Indicates that an alias of a name needs to be inserted. The
// corresponding name to be alias is in the []names slice.
// $v: Indicates that an alias of a value needs to be inserted. The
// corresponding value to be alias is in the []values slice.
// $c: Indicates that the fmtExpr of a child exprNode needs to be inserted.
// The corresponding child node is in the []children slice.
type exprNode struct {
names []string
values []dynamodb.AttributeValue
children []exprNode
fmtExpr string
}
// aliasList keeps track of all the names we need to alias in the nested
// struct of conditions and operands. This allows each alias to be unique.
// aliasList is passed in as a pointer when buildChildTrees is called in
// order to deduplicate all names within the tree strcuture of the exprNodes.
type aliasList struct {
namesList []string
valuesList []dynamodb.AttributeValue
}
// buildExpressionString returns a string with aliasing for names/values
// specified by aliasList. The string corresponds to the expression that the
// exprNode tree represents.
func (en exprNode) buildExpressionString(aliasList *aliasList) (string, error) {
// Since each exprNode contains a slice of names, values, and children that
// correspond to the escaped characters, we an index to traverse the slices
index := struct {
name, value, children int
}{}
formattedExpression := en.fmtExpr
for i := 0; i < len(formattedExpression); {
if formattedExpression[i] != '$' {
i++
continue
}
if i == len(formattedExpression)-1 {
return "", fmt.Errorf("buildexprNode error: invalid escape character")
}
var alias string
var err error
// if an escaped character is found, substitute it with the proper alias
// TODO consider AST instead of string in the future
switch formattedExpression[i+1] {
case 'n':
alias, err = substitutePath(index.name, en, aliasList)
if err != nil {
return "", err
}
index.name++
case 'v':
alias, err = substituteValue(index.value, en, aliasList)
if err != nil {
return "", err
}
index.value++
case 'c':
alias, err = substituteChild(index.children, en, aliasList)
if err != nil {
return "", err
}
index.children++
default:
return "", fmt.Errorf("buildexprNode error: invalid escape rune %#v", formattedExpression[i+1])
}
formattedExpression = formattedExpression[:i] + alias + formattedExpression[i+2:]
i += len(alias)
}
return formattedExpression, nil
}
// substitutePath substitutes the escaped character $n with the appropriate
// alias.
func substitutePath(index int, node exprNode, aliasList *aliasList) (string, error) {
if index >= len(node.names) {
return "", fmt.Errorf("substitutePath error: exprNode []names out of range")
}
str, err := aliasList.aliasPath(node.names[index])
if err != nil {
return "", err
}
return str, nil
}
// substituteValue substitutes the escaped character $v with the appropriate
// alias.
func substituteValue(index int, node exprNode, aliasList *aliasList) (string, error) {
if index >= len(node.values) {
return "", fmt.Errorf("substituteValue error: exprNode []values out of range")
}
str, err := aliasList.aliasValue(node.values[index])
if err != nil {
return "", err
}
return str, nil
}
// substituteChild substitutes the escaped character $c with the appropriate
// alias.
func substituteChild(index int, node exprNode, aliasList *aliasList) (string, error) {
if index >= len(node.children) {
return "", fmt.Errorf("substituteChild error: exprNode []children out of range")
}
return node.children[index].buildExpressionString(aliasList)
}
// aliasValue returns the corresponding alias to the dav value argument. Since
// values are not deduplicated as of now, all values are just appended to the
// aliasList and given the index as the alias.
func (al *aliasList) aliasValue(dav dynamodb.AttributeValue) (string, error) {
al.valuesList = append(al.valuesList, dav)
return fmt.Sprintf(":%d", len(al.valuesList)-1), nil
}
// aliasPath returns the corresponding alias to the argument string. The
// argument is checked against all existing aliasList names in order to avoid
// duplicate strings getting two different aliases.
func (al *aliasList) aliasPath(nm string) (string, error) {
for ind, name := range al.namesList {
if nm == name {
return fmt.Sprintf("#%d", ind), nil
}
}
al.namesList = append(al.namesList, nm)
return fmt.Sprintf("#%d", len(al.namesList)-1), nil
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,557 @@
package expression
import (
"fmt"
)
// keyConditionMode specifies the types of the struct KeyConditionBuilder,
// representing the different types of KeyConditions (i.e. And, Or, Between, ...)
type keyConditionMode int
const (
// unsetKeyCond catches errors for unset KeyConditionBuilder structs
unsetKeyCond keyConditionMode = iota
// equalKeyCond represents the Equals KeyCondition
equalKeyCond
// lessThanKeyCond represents the Less Than KeyCondition
lessThanKeyCond
// lessThanEqualKeyCond represents the Less Than Or Equal To KeyCondition
lessThanEqualKeyCond
// greaterThanKeyCond represents the Greater Than KeyCondition
greaterThanKeyCond
// greaterThanEqualKeyCond represents the Greater Than Or Equal To KeyCondition
greaterThanEqualKeyCond
// andKeyCond represents the Logical And KeyCondition
andKeyCond
// betweenKeyCond represents the Between KeyCondition
betweenKeyCond
// beginsWithKeyCond represents the Begins With KeyCondition
beginsWithKeyCond
)
// KeyConditionBuilder represents Key Condition Expressions in DynamoDB.
// KeyConditionBuilders are the building blocks of Expressions.
// More Information at: http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Query.html#Query.KeyConditionExpressions
type KeyConditionBuilder struct {
operandList []OperandBuilder
keyConditionList []KeyConditionBuilder
mode keyConditionMode
}
// KeyEqual returns a KeyConditionBuilder representing the equality clause
// of the two argument OperandBuilders. The resulting KeyConditionBuilder can be
// used as a part of other Key Condition Expressions or as an argument to the
// WithKeyCondition() method for the Builder struct.
//
// Example:
//
// // keyCondition represents the equal clause of the key "foo" and the
// // value 5
// keyCondition := expression.KeyEqual(expression.Key("foo"), expression.Value(5))
//
// // Used in another Key Condition Expression
// anotherKeyCondition := expression.Key("partitionKey").Equal(expression.Value("aValue")).And(keyCondition)
// // Used to make an Builder
// builder := expression.NewBuilder().WithKeyCondition(keyCondition)
//
// Expression Equivalent:
//
// expression.KeyEqual(expression.Key("foo"), expression.Value(5))
// // Let :five be an ExpressionAttributeValue representing the value 5
// "foo = :five"
func KeyEqual(keyBuilder KeyBuilder, valueBuilder ValueBuilder) KeyConditionBuilder {
return KeyConditionBuilder{
operandList: []OperandBuilder{keyBuilder, valueBuilder},
mode: equalKeyCond,
}
}
// Equal returns a KeyConditionBuilder representing the equality clause of
// the two argument OperandBuilders. The resulting KeyConditionBuilder can be
// used as a part of other Key Condition Expressions or as an argument to the
// WithKeyCondition() method for the Builder struct.
//
// Example:
//
// // keyCondition represents the equal clause of the key "foo" and the
// // value 5
// keyCondition := expression.Key("foo").Equal(expression.Value(5))
//
// // Used in another Key Condition Expression
// anotherKeyCondition := expression.Key("partitionKey").Equal(expression.Value("aValue")).And(keyCondition)
// // Used to make an Builder
// builder := expression.NewBuilder().WithKeyCondition(keyCondition)
//
// Expression Equivalent:
//
// expression.Key("foo").Equal(expression.Value(5))
// // Let :five be an ExpressionAttributeValue representing the value 5
// "foo = :five"
func (kb KeyBuilder) Equal(valueBuilder ValueBuilder) KeyConditionBuilder {
return KeyEqual(kb, valueBuilder)
}
// KeyLessThan returns a KeyConditionBuilder representing the less than
// clause of the two argument OperandBuilders. The resulting KeyConditionBuilder
// can be used as a part of other Key Condition Expressions.
//
// Example:
//
// // keyCondition represents the less than clause of the key "foo" and the
// // value 5
// keyCondition := expression.KeyLessThan(expression.Key("foo"), expression.Value(5))
//
// // Used in another Key Condition Expression
// anotherKeyCondition := expression.Key("partitionKey").Equal(expression.Value("aValue")).And(keyCondition)
//
// Expression Equivalent:
//
// expression.KeyLessThan(expression.Key("foo"), expression.Value(5))
// // Let :five be an ExpressionAttributeValue representing the value 5
// "foo < :five"
func KeyLessThan(keyBuilder KeyBuilder, valueBuilder ValueBuilder) KeyConditionBuilder {
return KeyConditionBuilder{
operandList: []OperandBuilder{keyBuilder, valueBuilder},
mode: lessThanKeyCond,
}
}
// LessThan returns a KeyConditionBuilder representing the less than clause
// of the two argument OperandBuilders. The resulting KeyConditionBuilder can be
// used as a part of other Key Condition Expressions.
//
// Example:
//
// // keyCondition represents the less than clause of the key "foo" and the
// // value 5
// keyCondition := expression.Key("foo").LessThan(expression.Value(5))
//
// // Used in another Key Condition Expression
// anotherKeyCondition := expression.Key("partitionKey").Equal(expression.Value("aValue")).And(keyCondition)
//
// Expression Equivalent:
//
// expression.Key("foo").LessThan(expression.Value(5))
// // Let :five be an ExpressionAttributeValue representing the value 5
// "foo < :five"
func (kb KeyBuilder) LessThan(valueBuilder ValueBuilder) KeyConditionBuilder {
return KeyLessThan(kb, valueBuilder)
}
// KeyLessThanEqual returns a KeyConditionBuilder representing the less than
// equal to clause of the two argument OperandBuilders. The resulting
// KeyConditionBuilder can be used as a part of other Key Condition Expressions.
//
// Example:
//
// // keyCondition represents the less than equal to clause of the key
// // "foo" and the value 5
// keyCondition := expression.KeyLessThanEqual(expression.Key("foo"), expression.Value(5))
//
// // Used in another Key Condition Expression
// anotherKeyCondition := expression.Key("partitionKey").Equal(expression.Value("aValue")).And(keyCondition)
//
// Expression Equivalent:
//
// expression.KeyLessThanEqual(expression.Key("foo"), expression.Value(5))
// // Let :five be an ExpressionAttributeValue representing the value 5
// "foo <= :five"
func KeyLessThanEqual(keyBuilder KeyBuilder, valueBuilder ValueBuilder) KeyConditionBuilder {
return KeyConditionBuilder{
operandList: []OperandBuilder{keyBuilder, valueBuilder},
mode: lessThanEqualKeyCond,
}
}
// LessThanEqual returns a KeyConditionBuilder representing the less than
// equal to clause of the two argument OperandBuilders. The resulting
// KeyConditionBuilder can be used as a part of other Key Condition Expressions.
//
// Example:
//
// // keyCondition represents the less than equal to clause of the key
// // "foo" and the value 5
// keyCondition := expression.Key("foo").LessThanEqual(expression.Value(5))
//
// // Used in another Key Condition Expression
// anotherKeyCondition := expression.Key("partitionKey").Equal(expression.Value("aValue")).And(keyCondition)
//
// Expression Equivalent:
//
// expression.Key("foo").LessThanEqual(expression.Value(5))
// // Let :five be an ExpressionAttributeValue representing the value 5
// "foo <= :five"
func (kb KeyBuilder) LessThanEqual(valueBuilder ValueBuilder) KeyConditionBuilder {
return KeyLessThanEqual(kb, valueBuilder)
}
// KeyGreaterThan returns a KeyConditionBuilder representing the greater
// than clause of the two argument OperandBuilders. The resulting
// KeyConditionBuilder can be used as a part of other Key Condition Expressions.
//
// Example:
//
// // keyCondition represents the greater than clause of the key "foo" and
// // the value 5
// keyCondition := expression.KeyGreaterThan(expression.Key("foo"), expression.Value(5))
//
// // Used in another Key Condition Expression
// anotherKeyCondition := expression.Key("partitionKey").Equal(expression.Value("aValue")).And(keyCondition)
//
// Expression Equivalent:
//
// expression.KeyGreaterThan(expression.Key("foo"), expression.Value(5))
// // Let :five be an ExpressionAttributeValue representing the value 5
// "foo > :five"
func KeyGreaterThan(keyBuilder KeyBuilder, valueBuilder ValueBuilder) KeyConditionBuilder {
return KeyConditionBuilder{
operandList: []OperandBuilder{keyBuilder, valueBuilder},
mode: greaterThanKeyCond,
}
}
// GreaterThan returns a KeyConditionBuilder representing the greater than
// clause of the two argument OperandBuilders. The resulting KeyConditionBuilder
// can be used as a part of other Key Condition Expressions.
//
// Example:
//
// // key condition represents the greater than clause of the key "foo" and
// // the value 5
// keyCondition := expression.Key("foo").GreaterThan(expression.Value(5))
//
// // Used in another Key Condition Expression
// anotherKeyCondition := expression.Key("partitionKey").Equal(expression.Value("aValue")).And(keyCondition)
//
// Expression Equivalent:
//
// expression.Key("foo").GreaterThan(expression.Value(5))
// // Let :five be an ExpressionAttributeValue representing the value 5
// "foo > :five"
func (kb KeyBuilder) GreaterThan(valueBuilder ValueBuilder) KeyConditionBuilder {
return KeyGreaterThan(kb, valueBuilder)
}
// KeyGreaterThanEqual returns a KeyConditionBuilder representing the
// greater than equal to clause of the two argument OperandBuilders. The
// resulting KeyConditionBuilder can be used as a part of other Key Condition
// Expressions.
//
// Example:
//
// // keyCondition represents the greater than equal to clause of the key
// // "foo" and the value 5
// keyCondition := expression.KeyGreaterThanEqual(expression.Key("foo"), expression.Value(5))
//
// // Used in another Key Condition Expression
// anotherKeyCondition := expression.Key("partitionKey").Equal(expression.Value("aValue")).And(keyCondition)
//
// Expression Equivalent:
//
// expression.KeyGreaterThanEqual(expression.Key("foo"), expression.Value(5))
// // Let :five be an ExpressionAttributeValue representing the value 5
// "foo >= :five"
func KeyGreaterThanEqual(keyBuilder KeyBuilder, valueBuilder ValueBuilder) KeyConditionBuilder {
return KeyConditionBuilder{
operandList: []OperandBuilder{keyBuilder, valueBuilder},
mode: greaterThanEqualKeyCond,
}
}
// GreaterThanEqual returns a KeyConditionBuilder representing the greater
// than equal to clause of the two argument OperandBuilders. The resulting
// KeyConditionBuilder can be used as a part of other Key Condition Expressions.
//
// Example:
//
// // keyCondition represents the greater than equal to clause of the key
// // "foo" and the value 5
// keyCondition := expression.Key("foo").GreaterThanEqual(expression.Value(5))
//
// // Used in another Key Condition Expression
// anotherKeyCondition := expression.Key("partitionKey").Equal(expression.Value("aValue")).And(keyCondition)
//
// Expression Equivalent:
//
// expression.Key("foo").GreaterThanEqual(expression.Value(5))
// // Let :five be an ExpressionAttributeValue representing the value 5
// "foo >= :five"
func (kb KeyBuilder) GreaterThanEqual(valueBuilder ValueBuilder) KeyConditionBuilder {
return KeyGreaterThanEqual(kb, valueBuilder)
}
// KeyAnd returns a KeyConditionBuilder representing the logical AND clause
// of the two argument KeyConditionBuilders. The resulting KeyConditionBuilder
// can be used as an argument to the WithKeyCondition() method for the Builder
// struct.
//
// Example:
//
// // keyCondition represents the key condition where the partition key
// // "TeamName" is equal to value "Wildcats" and sort key "Number" is equal
// // to value 1
// keyCondition := expression.KeyAnd(expression.Key("TeamName").Equal(expression.Value("Wildcats")), expression.Key("Number").Equal(expression.Value(1)))
//
// // Used to make an Builder
// builder := expression.NewBuilder().WithKeyCondition(keyCondition)
//
// Expression Equivalent:
//
// expression.KeyAnd(expression.Key("TeamName").Equal(expression.Value("Wildcats")), expression.Key("Number").Equal(expression.Value(1)))
// // Let #NUMBER, :teamName, and :one be ExpressionAttributeName and
// // ExpressionAttributeValues representing the item attribute "Number",
// // the value "Wildcats", and the value 1
// "(TeamName = :teamName) AND (#NUMBER = :one)"
func KeyAnd(left, right KeyConditionBuilder) KeyConditionBuilder {
if left.mode != equalKeyCond {
return KeyConditionBuilder{
mode: andKeyCond,
}
}
if right.mode == andKeyCond {
return KeyConditionBuilder{
mode: andKeyCond,
}
}
return KeyConditionBuilder{
keyConditionList: []KeyConditionBuilder{left, right},
mode: andKeyCond,
}
}
// And returns a KeyConditionBuilder representing the logical AND clause of
// the two argument KeyConditionBuilders. The resulting KeyConditionBuilder can
// be used as an argument to the WithKeyCondition() method for the Builder
// struct.
//
// Example:
//
// // keyCondition represents the key condition where the partition key
// // "TeamName" is equal to value "Wildcats" and sort key "Number" is equal
// // to value 1
// keyCondition := expression.Key("TeamName").Equal(expression.Value("Wildcats")).And(expression.Key("Number").Equal(expression.Value(1)))
//
// // Used to make an Builder
// builder := expression.NewBuilder().WithKeyCondition(keyCondition)
//
// Expression Equivalent:
//
// expression.Key("TeamName").Equal(expression.Value("Wildcats")).And(expression.Key("Number").Equal(expression.Value(1)))
// // Let #NUMBER, :teamName, and :one be ExpressionAttributeName and
// // ExpressionAttributeValues representing the item attribute "Number",
// // the value "Wildcats", and the value 1
// "(TeamName = :teamName) AND (#NUMBER = :one)"
func (kcb KeyConditionBuilder) And(right KeyConditionBuilder) KeyConditionBuilder {
return KeyAnd(kcb, right)
}
// KeyBetween returns a KeyConditionBuilder representing the result of the
// BETWEEN function in DynamoDB Key Condition Expressions. The resulting
// KeyConditionBuilder can be used as a part of other Key Condition Expressions.
//
// Example:
//
// // keyCondition represents the boolean key condition of whether the value
// // of the key "foo" is between values 5 and 10
// keyCondition := expression.KeyBetween(expression.Key("foo"), expression.Value(5), expression.Value(10))
//
// // Used in another Key Condition Expression
// anotherKeyCondition := expression.Key("partitionKey").Equal(expression.Value("aValue")).And(keyCondition)
//
// Expression Equivalent:
//
// expression.KeyBetween(expression.Key("foo"), expression.Value(5), expression.Value(10))
// // Let :five and :ten be ExpressionAttributeValues representing the
// // values 5 and 10 respectively
// "foo BETWEEN :five AND :ten"
func KeyBetween(keyBuilder KeyBuilder, lower, upper ValueBuilder) KeyConditionBuilder {
return KeyConditionBuilder{
operandList: []OperandBuilder{keyBuilder, lower, upper},
mode: betweenKeyCond,
}
}
// Between returns a KeyConditionBuilder representing the result of the
// BETWEEN function in DynamoDB Key Condition Expressions. The resulting
// KeyConditionBuilder can be used as a part of other Key Condition Expressions.
//
// Example:
//
// // keyCondition represents the boolean key condition of whether the value
// // of the key "foo" is between values 5 and 10
// keyCondition := expression.Key("foo").Between(expression.Value(5), expression.Value(10))
//
// // Used in another Key Condition Expression
// anotherKeyCondition := expression.Key("partitionKey").Equal(expression.Value("aValue")).And(keyCondition)
//
// Expression Equivalent:
//
// expression.Key("foo").Between(expression.Value(5), expression.Value(10))
// // Let :five and :ten be ExpressionAttributeValues representing the
// // values 5 and 10 respectively
// "foo BETWEEN :five AND :ten"
func (kb KeyBuilder) Between(lower, upper ValueBuilder) KeyConditionBuilder {
return KeyBetween(kb, lower, upper)
}
// KeyBeginsWith returns a KeyConditionBuilder representing the result of
// the begins_with function in DynamoDB Key Condition Expressions. The resulting
// KeyConditionBuilder can be used as a part of other Key Condition Expressions.
//
// Example:
//
// // keyCondition represents the boolean key condition of whether the value
// // of the key "foo" is begins with the prefix "bar"
// keyCondition := expression.KeyBeginsWith(expression.Key("foo"), "bar")
//
// // Used in another Key Condition Expression
// anotherKeyCondition := expression.Key("partitionKey").Equal(expression.Value("aValue")).And(keyCondition)
//
// Expression Equivalent:
//
// expression.KeyBeginsWith(expression.Key("foo"), "bar")
// // Let :bar be an ExpressionAttributeValue representing the value "bar"
// "begins_with(foo, :bar)"
func KeyBeginsWith(keyBuilder KeyBuilder, prefix string) KeyConditionBuilder {
valueBuilder := ValueBuilder{
value: prefix,
}
return KeyConditionBuilder{
operandList: []OperandBuilder{keyBuilder, valueBuilder},
mode: beginsWithKeyCond,
}
}
// BeginsWith returns a KeyConditionBuilder representing the result of the
// begins_with function in DynamoDB Key Condition Expressions. The resulting
// KeyConditionBuilder can be used as a part of other Key Condition Expressions.
//
// Example:
//
// // keyCondition represents the boolean key condition of whether the value
// // of the key "foo" is begins with the prefix "bar"
// keyCondition := expression.Key("foo").BeginsWith("bar")
//
// // Used in another Key Condition Expression
// anotherKeyCondition := expression.Key("partitionKey").Equal(expression.Value("aValue")).And(keyCondition)
//
// Expression Equivalent:
//
// expression.Key("foo").BeginsWith("bar")
// // Let :bar be an ExpressionAttributeValue representing the value "bar"
// "begins_with(foo, :bar)"
func (kb KeyBuilder) BeginsWith(prefix string) KeyConditionBuilder {
return KeyBeginsWith(kb, prefix)
}
// buildTree builds a tree structure of exprNodes based on the tree
// structure of the input KeyConditionBuilder's child KeyConditions/Operands.
// buildTree() satisfies the treeBuilder interface so KeyConditionBuilder can be
// a part of Expression struct.
func (kcb KeyConditionBuilder) buildTree() (exprNode, error) {
childNodes, err := kcb.buildChildNodes()
if err != nil {
return exprNode{}, err
}
ret := exprNode{
children: childNodes,
}
switch kcb.mode {
case equalKeyCond, lessThanKeyCond, lessThanEqualKeyCond, greaterThanKeyCond, greaterThanEqualKeyCond:
return compareBuildKeyCondition(kcb.mode, ret)
case andKeyCond:
return andBuildKeyCondition(kcb, ret)
case betweenKeyCond:
return betweenBuildKeyCondition(ret)
case beginsWithKeyCond:
return beginsWithBuildKeyCondition(ret)
case unsetKeyCond:
return exprNode{}, newUnsetParameterError("buildTree", "KeyConditionBuilder")
default:
return exprNode{}, fmt.Errorf("buildKeyCondition error: unsupported mode: %v", kcb.mode)
}
}
// compareBuildKeyCondition is the function to make exprNodes from Compare
// KeyConditionBuilders. compareBuildKeyCondition is only called by the
// buildKeyCondition method. This function assumes that the argument
// KeyConditionBuilder has the right format.
func compareBuildKeyCondition(keyConditionMode keyConditionMode, node exprNode) (exprNode, error) {
// Create a string with special characters that can be substituted later: $c
switch keyConditionMode {
case equalKeyCond:
node.fmtExpr = "$c = $c"
case lessThanKeyCond:
node.fmtExpr = "$c < $c"
case lessThanEqualKeyCond:
node.fmtExpr = "$c <= $c"
case greaterThanKeyCond:
node.fmtExpr = "$c > $c"
case greaterThanEqualKeyCond:
node.fmtExpr = "$c >= $c"
default:
return exprNode{}, fmt.Errorf("build compare key condition error: unsupported mode: %v", keyConditionMode)
}
return node, nil
}
// andBuildKeyCondition is the function to make exprNodes from And
// KeyConditionBuilders. andBuildKeyCondition is only called by the
// buildKeyCondition method. This function assumes that the argument
// KeyConditionBuilder has the right format.
func andBuildKeyCondition(keyConditionBuilder KeyConditionBuilder, node exprNode) (exprNode, error) {
if len(keyConditionBuilder.keyConditionList) == 0 && len(keyConditionBuilder.operandList) == 0 {
return exprNode{}, newInvalidParameterError("andBuildKeyCondition", "KeyConditionBuilder")
}
// create a string with escaped characters to substitute them with proper
// aliases during runtime
node.fmtExpr = "($c) AND ($c)"
return node, nil
}
// betweenBuildKeyCondition is the function to make exprNodes from Between
// KeyConditionBuilders. betweenBuildKeyCondition is only called by the
// buildKeyCondition method. This function assumes that the argument
// KeyConditionBuilder has the right format.
func betweenBuildKeyCondition(node exprNode) (exprNode, error) {
// Create a string with special characters that can be substituted later: $c
node.fmtExpr = "$c BETWEEN $c AND $c"
return node, nil
}
// beginsWithBuildKeyCondition is the function to make exprNodes from
// BeginsWith KeyConditionBuilders. beginsWithBuildKeyCondition is only
// called by the buildKeyCondition method. This function assumes that the argument
// KeyConditionBuilder has the right format.
func beginsWithBuildKeyCondition(node exprNode) (exprNode, error) {
// Create a string with special characters that can be substituted later: $c
node.fmtExpr = "begins_with ($c, $c)"
return node, nil
}
// buildChildNodes creates the list of the child exprNodes. This avoids
// duplication of code amongst the various buildConditions.
func (kcb KeyConditionBuilder) buildChildNodes() ([]exprNode, error) {
childNodes := make([]exprNode, 0, len(kcb.keyConditionList)+len(kcb.operandList))
for _, keyCondition := range kcb.keyConditionList {
node, err := keyCondition.buildTree()
if err != nil {
return []exprNode{}, err
}
childNodes = append(childNodes, node)
}
for _, operand := range kcb.operandList {
ope, err := operand.BuildOperand()
if err != nil {
return []exprNode{}, err
}
childNodes = append(childNodes, ope.exprNode)
}
return childNodes, nil
}

View file

@ -0,0 +1,446 @@
// +build go1.7
package expression
import (
"reflect"
"strings"
"testing"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/dynamodb"
)
// keyCondErrorMode will help with error cases and checking error types
type keyCondErrorMode string
const (
noKeyConditionError keyCondErrorMode = ""
// unsetKeyCondition error will occur when buildTree() is called on an empty
// KeyConditionBuilder
unsetKeyCondition = "unset parameter: KeyConditionBuilder"
// invalidKeyConditionOperand error will occur when an invalid OperandBuilder is used as
// an argument
invalidKeyConditionOperand = "BuildOperand error"
// invalidAndFormat error will occur when the first key condition is not an equal
// clause or if the second key condition is an and condition.
invalidAndFormat = "invalid parameter: KeyConditionBuilder"
)
func TestKeyCompare(t *testing.T) {
cases := []struct {
name string
input KeyConditionBuilder
expectedNode exprNode
err keyCondErrorMode
}{
{
name: "key equal",
input: Key("foo").Equal(Value(5)),
expectedNode: exprNode{
children: []exprNode{
{
names: []string{"foo"},
fmtExpr: "$n",
},
{
values: []dynamodb.AttributeValue{
{
N: aws.String("5"),
},
},
fmtExpr: "$v",
},
},
fmtExpr: "$c = $c",
},
},
{
name: "key less than",
input: Key("foo").LessThan(Value(5)),
expectedNode: exprNode{
children: []exprNode{
{
names: []string{"foo"},
fmtExpr: "$n",
},
{
values: []dynamodb.AttributeValue{
{
N: aws.String("5"),
},
},
fmtExpr: "$v",
},
},
fmtExpr: "$c < $c",
},
},
{
name: "key less than equal",
input: Key("foo").LessThanEqual(Value(5)),
expectedNode: exprNode{
children: []exprNode{
{
names: []string{"foo"},
fmtExpr: "$n",
},
{
values: []dynamodb.AttributeValue{
{
N: aws.String("5"),
},
},
fmtExpr: "$v",
},
},
fmtExpr: "$c <= $c",
},
},
{
name: "key greater than",
input: Key("foo").GreaterThan(Value(5)),
expectedNode: exprNode{
children: []exprNode{
{
names: []string{"foo"},
fmtExpr: "$n",
},
{
values: []dynamodb.AttributeValue{
{
N: aws.String("5"),
},
},
fmtExpr: "$v",
},
},
fmtExpr: "$c > $c",
},
},
{
name: "key greater than equal",
input: Key("foo").GreaterThanEqual(Value(5)),
expectedNode: exprNode{
children: []exprNode{
{
names: []string{"foo"},
fmtExpr: "$n",
},
{
values: []dynamodb.AttributeValue{
{
N: aws.String("5"),
},
},
fmtExpr: "$v",
},
},
fmtExpr: "$c >= $c",
},
},
{
name: "unset KeyConditionBuilder",
input: KeyConditionBuilder{},
err: unsetKeyCondition,
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
actual, err := c.input.buildTree()
if c.err != noKeyConditionError {
if err == nil {
t.Errorf("expect error %q, got no error", c.err)
} else {
if e, a := string(c.err), err.Error(); !strings.Contains(a, e) {
t.Errorf("expect %q error message to be in %q", e, a)
}
}
} else {
if err != nil {
t.Errorf("expect no error, got unexpected Error %q", err)
}
if e, a := c.expectedNode, actual; !reflect.DeepEqual(a, e) {
t.Errorf("expect %v, got %v", e, a)
}
}
})
}
}
func TestKeyBetween(t *testing.T) {
cases := []struct {
name string
input KeyConditionBuilder
expectedNode exprNode
err keyCondErrorMode
}{
{
name: "key between",
input: Key("foo").Between(Value(5), Value(10)),
expectedNode: exprNode{
children: []exprNode{
{
names: []string{"foo"},
fmtExpr: "$n",
},
{
values: []dynamodb.AttributeValue{
{
N: aws.String("5"),
},
},
fmtExpr: "$v",
},
{
values: []dynamodb.AttributeValue{
{
N: aws.String("10"),
},
},
fmtExpr: "$v",
},
},
fmtExpr: "$c BETWEEN $c AND $c",
},
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
actual, err := c.input.buildTree()
if c.err != noKeyConditionError {
if err == nil {
t.Errorf("expect error %q, got no error", c.err)
} else {
if e, a := string(c.err), err.Error(); !strings.Contains(a, e) {
t.Errorf("expect %q error message to be in %q", e, a)
}
}
} else {
if err != nil {
t.Errorf("expect no error, got unexpected Error %q", err)
}
if e, a := c.expectedNode, actual; !reflect.DeepEqual(a, e) {
t.Errorf("expect %v, got %v", e, a)
}
}
})
}
}
func TestKeyBeginsWith(t *testing.T) {
cases := []struct {
name string
input KeyConditionBuilder
expectedNode exprNode
err keyCondErrorMode
}{
{
name: "key begins with",
input: Key("foo").BeginsWith("bar"),
expectedNode: exprNode{
children: []exprNode{
{
names: []string{"foo"},
fmtExpr: "$n",
},
{
values: []dynamodb.AttributeValue{
{
S: aws.String("bar"),
},
},
fmtExpr: "$v",
},
},
fmtExpr: "begins_with ($c, $c)",
},
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
actual, err := c.input.buildTree()
if c.err != noKeyConditionError {
if err == nil {
t.Errorf("expect error %q, got no error", c.err)
} else {
if e, a := string(c.err), err.Error(); !strings.Contains(a, e) {
t.Errorf("expect %q error message to be in %q", e, a)
}
}
} else {
if err != nil {
t.Errorf("expect no error, got unexpected Error %q", err)
}
if e, a := c.expectedNode, actual; !reflect.DeepEqual(a, e) {
t.Errorf("expect %v, got %v", e, a)
}
}
})
}
}
func TestKeyAnd(t *testing.T) {
cases := []struct {
name string
input KeyConditionBuilder
expectedNode exprNode
err keyCondErrorMode
}{
{
name: "key and",
input: Key("foo").Equal(Value(5)).And(Key("bar").BeginsWith("baz")),
expectedNode: exprNode{
children: []exprNode{
{
children: []exprNode{
{
names: []string{"foo"},
fmtExpr: "$n",
},
{
values: []dynamodb.AttributeValue{
{
N: aws.String("5"),
},
},
fmtExpr: "$v",
},
},
fmtExpr: "$c = $c",
},
{
children: []exprNode{
{
names: []string{"bar"},
fmtExpr: "$n",
},
{
values: []dynamodb.AttributeValue{
{
S: aws.String("baz"),
},
},
fmtExpr: "$v",
},
},
fmtExpr: "begins_with ($c, $c)",
},
},
fmtExpr: "($c) AND ($c)",
},
},
{
name: "first condition is not equal",
input: Key("foo").LessThan(Value(5)).And(Key("bar").BeginsWith("baz")),
err: invalidAndFormat,
},
{
name: "second condition is and",
input: Key("foo").Equal(Value(5)).And(Key("bar").Equal(Value(1)).And(Key("baz").BeginsWith("yar"))),
err: invalidAndFormat,
},
{
name: "operand error",
input: Key("").Equal(Value("yikes")),
err: invalidKeyConditionOperand,
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
actual, err := c.input.buildTree()
if c.err != noKeyConditionError {
if err == nil {
t.Errorf("expect error %q, got no error", c.err)
} else {
if e, a := string(c.err), err.Error(); !strings.Contains(a, e) {
t.Errorf("expect %q error message to be in %q", e, a)
}
}
} else {
if err != nil {
t.Errorf("expect no error, got unexpected Error %q", err)
}
if e, a := c.expectedNode, actual; !reflect.DeepEqual(a, e) {
t.Errorf("expect %v, got %v", e, a)
}
}
})
}
}
func TestKeyConditionBuildChildNodes(t *testing.T) {
cases := []struct {
name string
input KeyConditionBuilder
expected []exprNode
err keyCondErrorMode
}{
{
name: "build child nodes",
input: Key("foo").Equal(Value("bar")).And(Key("baz").LessThan(Value(10))),
expected: []exprNode{
{
children: []exprNode{
{
names: []string{"foo"},
fmtExpr: "$n",
},
{
values: []dynamodb.AttributeValue{
{
S: aws.String("bar"),
},
},
fmtExpr: "$v",
},
},
fmtExpr: "$c = $c",
},
{
children: []exprNode{
{
names: []string{"baz"},
fmtExpr: "$n",
},
{
values: []dynamodb.AttributeValue{
{
N: aws.String("10"),
},
},
fmtExpr: "$v",
},
},
fmtExpr: "$c < $c",
},
},
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
actual, err := c.input.buildChildNodes()
if c.err != noKeyConditionError {
if err == nil {
t.Errorf("expect error %q, got no error", c.err)
} else {
if e, a := string(c.err), err.Error(); !strings.Contains(a, e) {
t.Errorf("expect %q error message to be in %q", e, a)
}
}
} else {
if err != nil {
t.Errorf("expect no error, got unexpected Error %q", err)
}
if e, a := c.expected, actual; !reflect.DeepEqual(a, e) {
t.Errorf("expect %#v, got %#v", e, a)
}
}
})
}
}

View file

@ -0,0 +1,620 @@
package expression
import (
"fmt"
"strings"
"github.com/aws/aws-sdk-go/service/dynamodb"
"github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute"
)
// ValueBuilder represents an item attribute value operand and implements the
// OperandBuilder interface. Methods and functions in the package take
// ValueBuilder as an argument and establishes relationships between operands.
// ValueBuilder should only be initialized using the function Value().
//
// Example:
//
// // Create a ValueBuilder representing the string "aValue"
// valueBuilder := expression.Value("aValue")
type ValueBuilder struct {
value interface{}
}
// NameBuilder represents a name of a top level item attribute or a nested
// attribute. Since NameBuilder represents a DynamoDB Operand, it implements the
// OperandBuilder interface. Methods and functions in the package take
// NameBuilder as an argument and establishes relationships between operands.
// NameBuilder should only be initialized using the function Name().
//
// Example:
//
// // Create a NameBuilder representing the item attribute "aName"
// nameBuilder := expression.Name("aName")
type NameBuilder struct {
name string
}
// SizeBuilder represents the output of the function size ("someName"), which
// evaluates to the size of the item attribute defined by "someName". Since
// SizeBuilder represents an operand, SizeBuilder implements the OperandBuilder
// interface. Methods and functions in the package take SizeBuilder as an
// argument and establishes relationships between operands. SizeBuilder should
// only be initialized using the function Size().
//
// Example:
//
// // Create a SizeBuilder representing the size of the item attribute
// // "aName"
// sizeBuilder := expression.Name("aName").Size()
type SizeBuilder struct {
nameBuilder NameBuilder
}
// KeyBuilder represents either the partition key or the sort key, both of which
// are top level attributes to some item in DynamoDB. Since KeyBuilder
// represents an operand, KeyBuilder implements the OperandBuilder interface.
// Methods and functions in the package take KeyBuilder as an argument and
// establishes relationships between operands. However, KeyBuilder should only
// be used to describe Key Condition Expressions. KeyBuilder should only be
// initialized using the function Key().
//
// Example:
//
// // Create a KeyBuilder representing the item key "aKey"
// keyBuilder := expression.Key("aKey")
type KeyBuilder struct {
key string
}
// setValueMode specifies the type of SetValueBuilder. The default value is
// unsetValue so that an UnsetParameterError when BuildOperand() is called on an
// empty SetValueBuilder.
type setValueMode int
const (
unsetValue setValueMode = iota
plusValueMode
minusValueMode
listAppendValueMode
ifNotExistsValueMode
)
// SetValueBuilder represents the outcome of operator functions supported by the
// DynamoDB Set operation. The operator functions are the following:
// Plus() // Represents the "+" operator
// Minus() // Represents the "-" operator
// ListAppend()
// IfNotExists()
// For documentation on the above functions,
// see: http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.UpdateExpressions.html#Expressions.UpdateExpressions.SET
// Since SetValueBuilder represents an operand, it implements the OperandBuilder
// interface. SetValueBuilder structs are used as arguments to the Set()
// function. SetValueBuilders should only initialize a SetValueBuilder using the
// functions listed above.
type SetValueBuilder struct {
leftOperand OperandBuilder
rightOperand OperandBuilder
mode setValueMode
}
// Operand represents an item attribute name or value in DynamoDB. The
// relationship between Operands specified by various builders such as
// ConditionBuilders and UpdateBuilders for example is processed internally to
// write Condition Expressions and Update Expressions respectively.
type Operand struct {
exprNode exprNode
}
// OperandBuilder represents the idea of Operand which are building blocks to
// DynamoDB Expressions. Package methods and functions can establish
// relationships between operands, representing DynamoDB Expressions. The method
// BuildOperand() is called recursively when the Build() method on the type
// Builder is called. BuildOperand() should never be called externally.
// OperandBuilder and BuildOperand() are exported to allow package functions to
// take an interface as an argument.
type OperandBuilder interface {
BuildOperand() (Operand, error)
}
// Name creates a NameBuilder. The argument should represent the desired item
// attribute. It is possible to reference nested item attributes by using
// square brackets for lists and dots for maps. For documentation on specifying
// item attributes,
// see: http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.Attributes.html
//
// Example:
//
// // Specify a top-level attribute
// name := expression.Name("TopLevel")
// // Specify a nested attribute
// nested := expression.Name("Record[6].SongList")
// // Use Name() to create a condition expression
// condition := expression.Name("foo").Equal(expression.Name("bar"))
func Name(name string) NameBuilder {
return NameBuilder{
name: name,
}
}
// Value creates a ValueBuilder. The argument should represent the desired item
// attribute. The value is marshalled using the dynamodbattribute package by the
// Build() method for type Builder.
//
// Example:
//
// // Use Value() to create a condition expression
// condition := expression.Name("foo").Equal(expression.Value(10))
func Value(value interface{}) ValueBuilder {
return ValueBuilder{
value: value,
}
}
// Size creates a SizeBuilder representing the size of the item attribute
// specified by the argument NameBuilder. Size() is only valid for certain types
// of item attributes. For documentation,
// see: http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.OperatorsAndFunctions.html
// SizeBuilder is only a valid operand in Condition Expressions and Filter
// Expressions.
//
// Example:
//
// // Use Size() to create a condition expression
// condition := expression.Name("foo").Size().Equal(expression.Value(10))
//
// Expression Equivalent:
//
// expression.Name("aName").Size()
// "size (aName)"
func (nb NameBuilder) Size() SizeBuilder {
return SizeBuilder{
nameBuilder: nb,
}
}
// Size creates a SizeBuilder representing the size of the item attribute
// specified by the argument NameBuilder. Size() is only valid for certain types
// of item attributes. For documentation,
// see: http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.OperatorsAndFunctions.html
// SizeBuilder is only a valid operand in Condition Expressions and Filter
// Expressions.
//
// Example:
//
// // Use Size() to create a condition expression
// condition := expression.Size(expression.Name("foo")).Equal(expression.Value(10))
//
// Expression Equivalent:
//
// expression.Size(expression.Name("aName"))
// "size (aName)"
func Size(nameBuilder NameBuilder) SizeBuilder {
return nameBuilder.Size()
}
// Key creates a KeyBuilder. The argument should represent the desired partition
// key or sort key value. KeyBuilders should only be used to specify
// relationships for Key Condition Expressions. When referring to the partition
// key or sort key in any other Expression, use Name().
//
// Example:
//
// // Use Key() to create a key condition expression
// keyCondition := expression.Key("foo").Equal(expression.Value("bar"))
func Key(key string) KeyBuilder {
return KeyBuilder{
key: key,
}
}
// Plus creates a SetValueBuilder to be used in as an argument to Set(). The
// arguments can either be NameBuilders or ValueBuilders. Plus() only supports
// DynamoDB Number types, so the ValueBuilder must be a Number and the
// NameBuilder must specify an item attribute of type Number.
// More information: http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.UpdateExpressions.html#Expressions.UpdateExpressions.SET.IncrementAndDecrement
//
// Example:
//
// // Use Plus() to set the value of the item attribute "someName" to 5 + 10
// update, err := expression.Set(expression.Name("someName"), expression.Plus(expression.Value(5), expression.Value(10)))
//
// Expression Equivalent:
//
// expression.Plus(expression.Value(5), expression.Value(10))
// // let :five and :ten be ExpressionAttributeValues for the values 5 and
// // 10 respectively.
// ":five + :ten"
func Plus(leftOperand, rightOperand OperandBuilder) SetValueBuilder {
return SetValueBuilder{
leftOperand: leftOperand,
rightOperand: rightOperand,
mode: plusValueMode,
}
}
// Plus creates a SetValueBuilder to be used in as an argument to Set(). The
// arguments can either be NameBuilders or ValueBuilders. Plus() only supports
// DynamoDB Number types, so the ValueBuilder must be a Number and the
// NameBuilder must specify an item attribute of type Number.
// More information: http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.UpdateExpressions.html#Expressions.UpdateExpressions.SET.IncrementAndDecrement
//
// Example:
//
// // Use Plus() to set the value of the item attribute "someName" to the
// // numeric value of item attribute "aName" incremented by 10
// update, err := expression.Set(expression.Name("someName"), expression.Name("aName").Plus(expression.Value(10)))
//
// Expression Equivalent:
//
// expression.Name("aName").Plus(expression.Value(10))
// // let :ten be ExpressionAttributeValues representing the value 10
// "aName + :ten"
func (nb NameBuilder) Plus(rightOperand OperandBuilder) SetValueBuilder {
return Plus(nb, rightOperand)
}
// Plus creates a SetValueBuilder to be used in as an argument to Set(). The
// arguments can either be NameBuilders or ValueBuilders. Plus() only supports
// DynamoDB Number types, so the ValueBuilder must be a Number and the
// NameBuilder must specify an item attribute of type Number.
// More information: http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.UpdateExpressions.html#Expressions.UpdateExpressions.SET.IncrementAndDecrement
//
// Example:
//
// // Use Plus() to set the value of the item attribute "someName" to 5 + 10
// update, err := expression.Set(expression.Name("someName"), expression.Value(5).Plus(expression.Value(10)))
//
// Expression Equivalent:
//
// expression.Value(5).Plus(expression.Value(10))
// // let :five and :ten be ExpressionAttributeValues representing the value
// // 5 and 10 respectively
// ":five + :ten"
func (vb ValueBuilder) Plus(rightOperand OperandBuilder) SetValueBuilder {
return Plus(vb, rightOperand)
}
// Minus creates a SetValueBuilder to be used in as an argument to Set(). The
// arguments can either be NameBuilders or ValueBuilders. Minus() only supports
// DynamoDB Number types, so the ValueBuilder must be a Number and the
// NameBuilder must specify an item attribute of type Number.
// More information: http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.UpdateExpressions.html#Expressions.UpdateExpressions.SET.IncrementAndDecrement
//
// Example:
//
// // Use Minus() to set the value of item attribute "someName" to 5 - 10
// update, err := expression.Set(expression.Name("someName"), expression.Minus(expression.Value(5), expression.Value(10)))
//
// Expression Equivalent:
//
// expression.Minus(expression.Value(5), expression.Value(10))
// // let :five and :ten be ExpressionAttributeValues for the values 5 and
// // 10 respectively.
// ":five - :ten"
func Minus(leftOperand, rightOperand OperandBuilder) SetValueBuilder {
return SetValueBuilder{
leftOperand: leftOperand,
rightOperand: rightOperand,
mode: minusValueMode,
}
}
// Minus creates a SetValueBuilder to be used in as an argument to Set(). The
// arguments can either be NameBuilders or ValueBuilders. Minus() only supports
// DynamoDB Number types, so the ValueBuilder must be a Number and the
// NameBuilder must specify an item attribute of type Number.
// More information: http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.UpdateExpressions.html#Expressions.UpdateExpressions.SET.IncrementAndDecrement
//
// Example:
//
// // Use Minus() to set the value of item attribute "someName" to the
// // numeric value of "aName" decremented by 10
// update, err := expression.Set(expression.Name("someName"), expression.Name("aName").Minus(expression.Value(10)))
//
// Expression Equivalent:
//
// expression.Name("aName").Minus(expression.Value(10)))
// // let :ten be ExpressionAttributeValues represent the value 10
// "aName - :ten"
func (nb NameBuilder) Minus(rightOperand OperandBuilder) SetValueBuilder {
return Minus(nb, rightOperand)
}
// Minus creates a SetValueBuilder to be used in as an argument to Set(). The
// arguments can either be NameBuilders or ValueBuilders. Minus() only supports
// DynamoDB Number types, so the ValueBuilder must be a Number and the
// NameBuilder must specify an item attribute of type Number.
// More information: http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.UpdateExpressions.html#Expressions.UpdateExpressions.SET.IncrementAndDecrement
//
// Example:
//
// // Use Minus() to set the value of item attribute "someName" to 5 - 10
// update, err := expression.Set(expression.Name("someName"), expression.Value(5).Minus(expression.Value(10)))
//
// Expression Equivalent:
//
// expression.Value(5).Minus(expression.Value(10))
// // let :five and :ten be ExpressionAttributeValues for the values 5 and
// // 10 respectively.
// ":five - :ten"
func (vb ValueBuilder) Minus(rightOperand OperandBuilder) SetValueBuilder {
return Minus(vb, rightOperand)
}
// ListAppend creates a SetValueBuilder to be used in as an argument to Set().
// The arguments can either be NameBuilders or ValueBuilders. ListAppend() only
// supports DynamoDB List types, so the ValueBuilder must be a List and the
// NameBuilder must specify an item attribute of type List.
// More information: http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.UpdateExpressions.html#Expressions.UpdateExpressions.SET.UpdatingListElements
//
// Example:
//
// // Use ListAppend() to set item attribute "someName" to the item
// // attribute "nameOfList" with "some" and "list" appended to it
// update, err := expression.Set(expression.Name("someName"), expression.ListAppend(expression.Name("nameOfList"), expression.Value([]string{"some", "list"})))
//
// Expression Equivalent:
//
// expression.ListAppend(expression.Name("nameOfList"), expression.Value([]string{"some", "list"})
// // let :list be a ExpressionAttributeValue representing the list
// // containing "some" and "list".
// "list_append (nameOfList, :list)"
func ListAppend(leftOperand, rightOperand OperandBuilder) SetValueBuilder {
return SetValueBuilder{
leftOperand: leftOperand,
rightOperand: rightOperand,
mode: listAppendValueMode,
}
}
// ListAppend creates a SetValueBuilder to be used in as an argument to Set().
// The arguments can either be NameBuilders or ValueBuilders. ListAppend() only
// supports DynamoDB List types, so the ValueBuilder must be a List and the
// NameBuilder must specify an item attribute of type List.
// More information: http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.UpdateExpressions.html#Expressions.UpdateExpressions.SET.UpdatingListElements
//
// Example:
//
// // Use ListAppend() to set item attribute "someName" to the item
// // attribute "nameOfList" with "some" and "list" appended to it
// update, err := expression.Set(expression.Name("someName"), expression.Name("nameOfList").ListAppend(expression.Value([]string{"some", "list"})))
//
// Expression Equivalent:
//
// expression.Name("nameOfList").ListAppend(expression.Value([]string{"some", "list"})
// // let :list be a ExpressionAttributeValue representing the list
// // containing "some" and "list".
// "list_append (nameOfList, :list)"
func (nb NameBuilder) ListAppend(rightOperand OperandBuilder) SetValueBuilder {
return ListAppend(nb, rightOperand)
}
// ListAppend creates a SetValueBuilder to be used in as an argument to Set().
// The arguments can either be NameBuilders or ValueBuilders. ListAppend() only
// supports DynamoDB List types, so the ValueBuilder must be a List and the
// NameBuilder must specify an item attribute of type List.
// More information: http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.UpdateExpressions.html#Expressions.UpdateExpressions.SET.UpdatingListElements
//
// Example:
//
// // Use ListAppend() to set item attribute "someName" to a string list
// // equal to {"a", "list", "some", "list"}
// update, err := expression.Set(expression.Name("someName"), expression.Value([]string{"a", "list"}).ListAppend(expression.Value([]string{"some", "list"})))
//
// Expression Equivalent:
//
// expression.Name([]string{"a", "list"}).ListAppend(expression.Value([]string{"some", "list"})
// // let :list1 and :list2 be a ExpressionAttributeValue representing the
// // list {"a", "list"} and {"some", "list"} respectively
// "list_append (:list1, :list2)"
func (vb ValueBuilder) ListAppend(rightOperand OperandBuilder) SetValueBuilder {
return ListAppend(vb, rightOperand)
}
// IfNotExists creates a SetValueBuilder to be used in as an argument to Set().
// The first argument must be a NameBuilder representing the name where the new
// item attribute is created. The second argument can either be a NameBuilder or
// a ValueBuilder. In the case that it is a NameBuilder, the value of the item
// attribute at the name specified becomes the value of the new item attribute.
// More information: http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.UpdateExpressions.html#Expressions.UpdateExpressions.SET.PreventingAttributeOverwrites
//
// Example:
//
// // Use IfNotExists() to set item attribute "someName" to value 5 if
// // "someName" does not exist yet. (Prevents overwrite)
// update, err := expression.Set(expression.Name("someName"), expression.IfNotExists(expression.Name("someName"), expression.Value(5)))
//
// Expression Equivalent:
//
// expression.IfNotExists(expression.Name("someName"), expression.Value(5))
// // let :five be a ExpressionAttributeValue representing the value 5
// "if_not_exists (someName, :five)"
func IfNotExists(name NameBuilder, setValue OperandBuilder) SetValueBuilder {
return SetValueBuilder{
leftOperand: name,
rightOperand: setValue,
mode: ifNotExistsValueMode,
}
}
// IfNotExists creates a SetValueBuilder to be used in as an argument to Set().
// The first argument must be a NameBuilder representing the name where the new
// item attribute is created. The second argument can either be a NameBuilder or
// a ValueBuilder. In the case that it is a NameBuilder, the value of the item
// attribute at the name specified becomes the value of the new item attribute.
// More information: http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.UpdateExpressions.html#Expressions.UpdateExpressions.SET.PreventingAttributeOverwrites
//
// Example:
//
// // Use IfNotExists() to set item attribute "someName" to value 5 if
// // "someName" does not exist yet. (Prevents overwrite)
// update, err := expression.Set(expression.Name("someName"), expression.Name("someName").IfNotExists(expression.Value(5)))
//
// Expression Equivalent:
//
// expression.Name("someName").IfNotExists(expression.Value(5))
// // let :five be a ExpressionAttributeValue representing the value 5
// "if_not_exists (someName, :five)"
func (nb NameBuilder) IfNotExists(rightOperand OperandBuilder) SetValueBuilder {
return IfNotExists(nb, rightOperand)
}
// BuildOperand creates an Operand struct which are building blocks to DynamoDB
// Expressions. Package methods and functions can establish relationships
// between operands, representing DynamoDB Expressions. The method
// BuildOperand() is called recursively when the Build() method on the type
// Builder is called. BuildOperand() should never be called externally.
// BuildOperand() aliases all strings to avoid stepping over DynamoDB's reserved
// words.
// More information on reserved words at http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/ReservedWords.html
func (nb NameBuilder) BuildOperand() (Operand, error) {
if nb.name == "" {
return Operand{}, newUnsetParameterError("BuildOperand", "NameBuilder")
}
node := exprNode{
names: []string{},
}
nameSplit := strings.Split(nb.name, ".")
fmtNames := make([]string, 0, len(nameSplit))
for _, word := range nameSplit {
var substr string
if word == "" {
return Operand{}, newInvalidParameterError("BuildOperand", "NameBuilder")
}
if word[len(word)-1] == ']' {
for j, char := range word {
if char == '[' {
substr = word[j:]
word = word[:j]
break
}
}
}
if word == "" {
return Operand{}, newInvalidParameterError("BuildOperand", "NameBuilder")
}
// Create a string with special characters that can be substituted later: $p
node.names = append(node.names, word)
fmtNames = append(fmtNames, "$n"+substr)
}
node.fmtExpr = strings.Join(fmtNames, ".")
return Operand{
exprNode: node,
}, nil
}
// BuildOperand creates an Operand struct which are building blocks to DynamoDB
// Expressions. Package methods and functions can establish relationships
// between operands, representing DynamoDB Expressions. The method
// BuildOperand() is called recursively when the Build() method on the type
// Builder is called. BuildOperand() should never be called externally.
// BuildOperand() aliases all strings to avoid stepping over DynamoDB's reserved
// words.
// More information on reserved words at http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/ReservedWords.html
func (vb ValueBuilder) BuildOperand() (Operand, error) {
expr, err := dynamodbattribute.Marshal(vb.value)
if err != nil {
return Operand{}, newInvalidParameterError("BuildOperand", "ValueBuilder")
}
// Create a string with special characters that can be substituted later: $v
operand := Operand{
exprNode: exprNode{
values: []dynamodb.AttributeValue{*expr},
fmtExpr: "$v",
},
}
return operand, nil
}
// BuildOperand creates an Operand struct which are building blocks to DynamoDB
// Expressions. Package methods and functions can establish relationships
// between operands, representing DynamoDB Expressions. The method
// BuildOperand() is called recursively when the Build() method on the type
// Builder is called. BuildOperand() should never be called externally.
// BuildOperand() aliases all strings to avoid stepping over DynamoDB's reserved
// words.
// More information on reserved words at http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/ReservedWords.html
func (sb SizeBuilder) BuildOperand() (Operand, error) {
operand, err := sb.nameBuilder.BuildOperand()
operand.exprNode.fmtExpr = "size (" + operand.exprNode.fmtExpr + ")"
return operand, err
}
// BuildOperand creates an Operand struct which are building blocks to DynamoDB
// Expressions. Package methods and functions can establish relationships
// between operands, representing DynamoDB Expressions. The method
// BuildOperand() is called recursively when the Build() method on the type
// Builder is called. BuildOperand() should never be called externally.
// BuildOperand() aliases all strings to avoid stepping over DynamoDB's reserved
// words.
// More information on reserved words at http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/ReservedWords.html
func (kb KeyBuilder) BuildOperand() (Operand, error) {
if kb.key == "" {
return Operand{}, newUnsetParameterError("BuildOperand", "KeyBuilder")
}
ret := Operand{
exprNode: exprNode{
names: []string{kb.key},
fmtExpr: "$n",
},
}
return ret, nil
}
// BuildOperand creates an Operand struct which are building blocks to DynamoDB
// Expressions. Package methods and functions can establish relationships
// between operands, representing DynamoDB Expressions. The method
// BuildOperand() is called recursively when the Build() method on the type
// Builder is called. BuildOperand() should never be called externally.
// BuildOperand() aliases all strings to avoid stepping over DynamoDB's reserved
// words.
// More information on reserved words at http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/ReservedWords.html
func (svb SetValueBuilder) BuildOperand() (Operand, error) {
if svb.mode == unsetValue {
return Operand{}, newUnsetParameterError("BuildOperand", "SetValueBuilder")
}
left, err := svb.leftOperand.BuildOperand()
if err != nil {
return Operand{}, err
}
leftNode := left.exprNode
right, err := svb.rightOperand.BuildOperand()
if err != nil {
return Operand{}, err
}
rightNode := right.exprNode
node := exprNode{
children: []exprNode{leftNode, rightNode},
}
switch svb.mode {
case plusValueMode:
node.fmtExpr = "$c + $c"
case minusValueMode:
node.fmtExpr = "$c - $c"
case listAppendValueMode:
node.fmtExpr = "list_append($c, $c)"
case ifNotExistsValueMode:
node.fmtExpr = "if_not_exists($c, $c)"
default:
return Operand{}, fmt.Errorf("build operand error: unsupported mode: %v", svb.mode)
}
return Operand{
exprNode: node,
}, nil
}

View file

@ -0,0 +1,144 @@
// +build go1.7
package expression
import (
"reflect"
"strings"
"testing"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/dynamodb"
)
// opeErrorMode will help with error cases and checking error types
type opeErrorMode string
const (
noOperandError opeErrorMode = ""
// unsetName error will occur if an empty string is passed into NameBuilder
unsetName = "unset parameter: NameBuilder"
// invalidName error will occur if a nested name has an empty intermediary
// attribute name (i.e. foo.bar..baz)
invalidName = "invalid parameter: NameBuilder"
// unsetKey error will occur if an empty string is passed into KeyBuilder
unsetKey = "unset parameter: KeyBuilder"
)
func TestBuildOperand(t *testing.T) {
cases := []struct {
name string
input OperandBuilder
expected exprNode
err opeErrorMode
}{
{
name: "basic name",
input: Name("foo"),
expected: exprNode{
names: []string{"foo"},
fmtExpr: "$n",
},
},
{
name: "duplicate name name",
input: Name("foo.foo"),
expected: exprNode{
names: []string{"foo", "foo"},
fmtExpr: "$n.$n",
},
},
{
name: "basic value",
input: Value(5),
expected: exprNode{
values: []dynamodb.AttributeValue{
{
N: aws.String("5"),
},
},
fmtExpr: "$v",
},
},
{
name: "nested name",
input: Name("foo.bar"),
expected: exprNode{
names: []string{"foo", "bar"},
fmtExpr: "$n.$n",
},
},
{
name: "nested name with index",
input: Name("foo.bar[0].baz"),
expected: exprNode{
names: []string{"foo", "bar", "baz"},
fmtExpr: "$n.$n[0].$n",
},
},
{
name: "basic size",
input: Name("foo").Size(),
expected: exprNode{
names: []string{"foo"},
fmtExpr: "size ($n)",
},
},
{
name: "key",
input: Key("foo"),
expected: exprNode{
names: []string{"foo"},
fmtExpr: "$n",
},
},
{
name: "unset key error",
input: Key(""),
expected: exprNode{},
err: unsetKey,
},
{
name: "empty name error",
input: Name(""),
expected: exprNode{},
err: unsetName,
},
{
name: "invalid name",
input: Name("foo..bar"),
expected: exprNode{},
err: invalidName,
},
{
name: "invalid index",
input: Name("[foo]"),
expected: exprNode{},
err: invalidName,
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
operand, err := c.input.BuildOperand()
if c.err != noOperandError {
if err == nil {
t.Errorf("expect error %q, got no error", c.err)
} else {
if e, a := string(c.err), err.Error(); !strings.Contains(a, e) {
t.Errorf("expect %q error message to be in %q", e, a)
}
}
} else {
if err != nil {
t.Errorf("expect no error, got unexpected Error %q", err)
}
if e, a := c.expected, operand.exprNode; !reflect.DeepEqual(a, e) {
t.Errorf("expect %v, got %v", e, a)
}
}
})
}
}

View file

@ -0,0 +1,148 @@
package expression
import (
"strings"
)
// ProjectionBuilder represents Projection Expressions in DynamoDB.
// ProjectionBuilders are the building blocks of Builders.
// More Information at: http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.ProjectionExpressions.html
type ProjectionBuilder struct {
names []NameBuilder
}
// NamesList returns a ProjectionBuilder representing the list of item
// attribute names specified by the argument NameBuilders. The resulting
// ProjectionBuilder can be used as a part of other ProjectionBuilders or as an
// argument to the WithProjection() method for the Builder struct.
//
// Example:
//
// // projection represents the list of names {"foo", "bar"}
// projection := expression.NamesList(expression.Name("foo"), expression.Name("bar"))
//
// // Used in another Projection Expression
// anotherProjection := expression.AddNames(projection, expression.Name("baz"))
// // Used to make an Builder
// builder := expression.NewBuilder().WithProjection(newProjection)
//
// Expression Equivalent:
//
// expression.NamesList(expression.Name("foo"), expression.Name("bar"))
// "foo, bar"
func NamesList(nameBuilder NameBuilder, namesList ...NameBuilder) ProjectionBuilder {
namesList = append([]NameBuilder{nameBuilder}, namesList...)
return ProjectionBuilder{
names: namesList,
}
}
// NamesList returns a ProjectionBuilder representing the list of item
// attribute names specified by the argument NameBuilders. The resulting
// ProjectionBuilder can be used as a part of other ProjectionBuilders or as an
// argument to the WithProjection() method for the Builder struct.
//
// Example:
//
// // projection represents the list of names {"foo", "bar"}
// projection := expression.Name("foo").NamesList(expression.Name("bar"))
//
// // Used in another Projection Expression
// anotherProjection := expression.AddNames(projection, expression.Name("baz"))
// // Used to make an Builder
// builder := expression.NewBuilder().WithProjection(newProjection)
//
// Expression Equivalent:
//
// expression.Name("foo").NamesList(expression.Name("bar"))
// "foo, bar"
func (nb NameBuilder) NamesList(namesList ...NameBuilder) ProjectionBuilder {
return NamesList(nb, namesList...)
}
// AddNames returns a ProjectionBuilder representing the list of item
// attribute names equivalent to appending all of the argument item attribute
// names to the argument ProjectionBuilder. The resulting ProjectionBuilder can
// be used as a part of other ProjectionBuilders or as an argument to the
// WithProjection() method for the Builder struct.
//
// Example:
//
// // projection represents the list of names {"foo", "bar", "baz", "qux"}
// oldProj := expression.NamesList(expression.Name("foo"), expression.Name("bar"))
// projection := expression.AddNames(oldProj, expression.Name("baz"), expression.Name("qux"))
//
// // Used in another Projection Expression
// anotherProjection := expression.AddNames(projection, expression.Name("quux"))
// // Used to make an Builder
// builder := expression.NewBuilder().WithProjection(newProjection)
//
// Expression Equivalent:
//
// expression.AddNames(expression.NamesList(expression.Name("foo"), expression.Name("bar")), expression.Name("baz"), expression.Name("qux"))
// "foo, bar, baz, qux"
func AddNames(projectionBuilder ProjectionBuilder, namesList ...NameBuilder) ProjectionBuilder {
projectionBuilder.names = append(projectionBuilder.names, namesList...)
return projectionBuilder
}
// AddNames returns a ProjectionBuilder representing the list of item
// attribute names equivalent to appending all of the argument item attribute
// names to the argument ProjectionBuilder. The resulting ProjectionBuilder can
// be used as a part of other ProjectionBuilders or as an argument to the
// WithProjection() method for the Builder struct.
//
// Example:
//
// // projection represents the list of names {"foo", "bar", "baz", "qux"}
// oldProj := expression.NamesList(expression.Name("foo"), expression.Name("bar"))
// projection := oldProj.AddNames(expression.Name("baz"), expression.Name("qux"))
//
// // Used in another Projection Expression
// anotherProjection := expression.AddNames(projection, expression.Name("quux"))
// // Used to make an Builder
// builder := expression.NewBuilder().WithProjection(newProjection)
//
// Expression Equivalent:
//
// expression.NamesList(expression.Name("foo"), expression.Name("bar")).AddNames(expression.Name("baz"), expression.Name("qux"))
// "foo, bar, baz, qux"
func (pb ProjectionBuilder) AddNames(namesList ...NameBuilder) ProjectionBuilder {
return AddNames(pb, namesList...)
}
// buildTree builds a tree structure of exprNodes based on the tree
// structure of the input ProjectionBuilder's child NameBuilders. buildTree()
// satisfies the treeBuilder interface so ProjectionBuilder can be a part of
// Builder and Expression struct.
func (pb ProjectionBuilder) buildTree() (exprNode, error) {
if len(pb.names) == 0 {
return exprNode{}, newUnsetParameterError("buildTree", "ProjectionBuilder")
}
childNodes, err := pb.buildChildNodes()
if err != nil {
return exprNode{}, err
}
ret := exprNode{
children: childNodes,
}
ret.fmtExpr = "$c" + strings.Repeat(", $c", len(pb.names)-1)
return ret, nil
}
// buildChildNodes creates the list of the child exprNodes.
func (pb ProjectionBuilder) buildChildNodes() ([]exprNode, error) {
childNodes := make([]exprNode, 0, len(pb.names))
for _, name := range pb.names {
operand, err := name.BuildOperand()
if err != nil {
return []exprNode{}, err
}
childNodes = append(childNodes, operand.exprNode)
}
return childNodes, nil
}

View file

@ -0,0 +1,215 @@
// +build go1.7
package expression
import (
"reflect"
"strings"
"testing"
)
// projErrorMode will help with error cases and checking error types
type projErrorMode string
const (
noProjError projErrorMode = ""
// invalidProjectionOperand error will occur when an invalid OperandBuilder is
// used as an argument
invalidProjectionOperand = "BuildOperand error"
// unsetProjection error will occur if the argument ProjectionBuilder is unset
unsetProjection = "unset parameter: ProjectionBuilder"
)
func TestProjectionBuilder(t *testing.T) {
cases := []struct {
name string
input ProjectionBuilder
expectedNode exprNode
err projErrorMode
}{
{
name: "names list function call",
input: NamesList(Name("foo"), Name("bar")),
expectedNode: exprNode{
children: []exprNode{
{
names: []string{"foo"},
fmtExpr: "$n",
},
{
names: []string{"bar"},
fmtExpr: "$n",
},
},
fmtExpr: "$c, $c",
},
},
{
name: "names list method call",
input: Name("foo").NamesList(Name("bar")),
expectedNode: exprNode{
children: []exprNode{
{
names: []string{"foo"},
fmtExpr: "$n",
},
{
names: []string{"bar"},
fmtExpr: "$n",
},
},
fmtExpr: "$c, $c",
},
},
{
name: "add name",
input: Name("foo").NamesList(Name("bar")).AddNames(Name("baz"), Name("qux")),
expectedNode: exprNode{
children: []exprNode{
{
names: []string{"foo"},
fmtExpr: "$n",
},
{
names: []string{"bar"},
fmtExpr: "$n",
},
{
names: []string{"baz"},
fmtExpr: "$n",
}, {
names: []string{"qux"},
fmtExpr: "$n",
},
},
fmtExpr: "$c, $c, $c, $c",
},
},
{
name: "invalid operand",
input: NamesList(Name("")),
err: invalidProjectionOperand,
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
actual, err := c.input.buildTree()
if c.err != noProjError {
if err == nil {
t.Errorf("expect error %q, got no error", c.err)
} else {
if e, a := string(c.err), err.Error(); !strings.Contains(a, e) {
t.Errorf("expect %q error message to be in %q", e, a)
}
}
} else {
if err != nil {
t.Errorf("expect no error, got unexpected Error %q", err)
}
if e, a := c.expectedNode, actual; !reflect.DeepEqual(a, e) {
t.Errorf("expect %v, got %v", e, a)
}
}
})
}
}
func TestBuildProjection(t *testing.T) {
cases := []struct {
name string
input ProjectionBuilder
expected string
err projErrorMode
}{
{
name: "build projection 3",
input: NamesList(Name("foo"), Name("bar"), Name("baz")),
expected: "$c, $c, $c",
},
{
name: "build projection 5",
input: NamesList(Name("foo"), Name("bar"), Name("baz")).AddNames(Name("qux"), Name("quux")),
expected: "$c, $c, $c, $c, $c",
},
{
name: "empty ProjectionBuilder",
input: ProjectionBuilder{},
err: unsetProjection,
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
actual, err := c.input.buildTree()
if c.err != noProjError {
if err == nil {
t.Errorf("expect error %q, got no error", c.err)
} else {
if e, a := string(c.err), err.Error(); !strings.Contains(a, e) {
t.Errorf("expect %q error message to be in %q", e, a)
}
}
} else {
if err != nil {
t.Errorf("expect no error, got unexpected Error %q", err)
}
if e, a := c.expected, actual.fmtExpr; !reflect.DeepEqual(a, e) {
t.Errorf("expect %v, got %v", e, a)
}
}
})
}
}
func TestBuildProjectionChildNodes(t *testing.T) {
cases := []struct {
name string
input ProjectionBuilder
expected []exprNode
err projErrorMode
}{
{
name: "build child nodes",
input: NamesList(Name("foo"), Name("bar"), Name("baz")),
expected: []exprNode{
{
names: []string{"foo"},
fmtExpr: "$n",
},
{
names: []string{"bar"},
fmtExpr: "$n",
},
{
names: []string{"baz"},
fmtExpr: "$n",
},
},
},
{
name: "operand error",
input: NamesList(Name("")),
err: invalidProjectionOperand,
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
actual, err := c.input.buildTree()
if c.err != noProjError {
if err == nil {
t.Errorf("expect error %q, got no error", c.err)
} else {
if e, a := string(c.err), err.Error(); !strings.Contains(a, e) {
t.Errorf("expect %q error message to be in %q", e, a)
}
}
} else {
if err != nil {
t.Errorf("expect no error, got unexpected Error %q", err)
}
if e, a := c.expected, actual.children; !reflect.DeepEqual(a, e) {
t.Errorf("expect %v, got %v", e, a)
}
}
})
}
}

View file

@ -0,0 +1,391 @@
package expression
import (
"fmt"
"sort"
"strings"
)
// operationMode specifies the types of update operations that the
// updateBuilder is going to represent. The const is in a string to use the
// const value as a map key and as a string when creating the formatted
// expression for the exprNodes.
type operationMode string
const (
setOperation operationMode = "SET"
removeOperation = "REMOVE"
addOperation = "ADD"
deleteOperation = "DELETE"
)
// Implementing the Sort interface
type modeList []operationMode
func (ml modeList) Len() int {
return len(ml)
}
func (ml modeList) Less(i, j int) bool {
return string(ml[i]) < string(ml[j])
}
func (ml modeList) Swap(i, j int) {
ml[i], ml[j] = ml[j], ml[i]
}
// UpdateBuilder represents Update Expressions in DynamoDB. UpdateBuilders
// are the building blocks of the Builder struct. Note that there are different
// update operations in DynamoDB and an UpdateBuilder can represent multiple
// update operations.
// More Information at: http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.UpdateExpressions.html
type UpdateBuilder struct {
operationList map[operationMode][]operationBuilder
}
// operationBuilder represents specific update actions (SET, REMOVE, ADD,
// DELETE). The mode specifies what type of update action the
// operationBuilder represents.
type operationBuilder struct {
name NameBuilder
value OperandBuilder
mode operationMode
}
// buildOperation builds an exprNode from an operationBuilder. buildOperation
// is called recursively by buildTree in order to create a tree structure
// of exprNodes representing the parent/child relationships between
// UpdateBuilders and operationBuilders.
func (ob operationBuilder) buildOperation() (exprNode, error) {
pathChild, err := ob.name.BuildOperand()
if err != nil {
return exprNode{}, err
}
node := exprNode{
children: []exprNode{pathChild.exprNode},
fmtExpr: "$c",
}
if ob.mode == removeOperation {
return node, nil
}
valueChild, err := ob.value.BuildOperand()
if err != nil {
return exprNode{}, err
}
node.children = append(node.children, valueChild.exprNode)
switch ob.mode {
case setOperation:
node.fmtExpr += " = $c"
case addOperation, deleteOperation:
node.fmtExpr += " $c"
default:
return exprNode{}, fmt.Errorf("build update error: build operation error: unsupported mode: %v", ob.mode)
}
return node, nil
}
// Delete returns an UpdateBuilder representing one Delete operation for
// DynamoDB Update Expressions. The argument name should specify the item
// attribute and the argument value should specify the value to be deleted. The
// resulting UpdateBuilder can be used as an argument to the WithUpdate() method
// for the Builder struct.
//
// Example:
//
// // update represents the delete operation to delete the string value
// // "subsetToDelete" from the item attribute "pathToList"
// update := expression.Delete(expression.Name("pathToList"), expression.Value("subsetToDelete"))
//
// // Adding more update methods
// anotherUpdate := update.Remove(expression.Name("someName"))
// // Creating a Builder
// builder := Update(update)
//
// Expression Equivalent:
//
// expression.Delete(expression.Name("pathToList"), expression.Value("subsetToDelete"))
// // let :del be an ExpressionAttributeValue representing the value
// // "subsetToDelete"
// "DELETE pathToList :del"
func Delete(name NameBuilder, value ValueBuilder) UpdateBuilder {
emptyUpdateBuilder := UpdateBuilder{}
return emptyUpdateBuilder.Delete(name, value)
}
// Delete adds a Delete operation to the argument UpdateBuilder. The
// argument name should specify the item attribute and the argument value should
// specify the value to be deleted. The resulting UpdateBuilder can be used as
// an argument to the WithUpdate() method for the Builder struct.
//
// Example:
//
// // Let update represent an already existing update expression. Delete()
// // adds the operation to delete the value "subsetToDelete" from the item
// // attribute "pathToList"
// update := update.Delete(expression.Name("pathToList"), expression.Value("subsetToDelete"))
//
// // Adding more update methods
// anotherUpdate := update.Remove(expression.Name("someName"))
// // Creating a Builder
// builder := Update(update)
//
// Expression Equivalent:
//
// Delete(expression.Name("pathToList"), expression.Value("subsetToDelete"))
// // let :del be an ExpressionAttributeValue representing the value
// // "subsetToDelete"
// "DELETE pathToList :del"
func (ub UpdateBuilder) Delete(name NameBuilder, value ValueBuilder) UpdateBuilder {
if ub.operationList == nil {
ub.operationList = map[operationMode][]operationBuilder{}
}
ub.operationList[deleteOperation] = append(ub.operationList[deleteOperation], operationBuilder{
name: name,
value: value,
mode: deleteOperation,
})
return ub
}
// Add returns an UpdateBuilder representing the Add operation for DynamoDB
// Update Expressions. The argument name should specify the item attribute and
// the argument value should specify the value to be added. The resulting
// UpdateBuilder can be used as an argument to the WithUpdate() method for the
// Builder struct.
//
// Example:
//
// // update represents the add operation to add the value 5 to the item
// // attribute "aPath"
// update := expression.Add(expression.Name("aPath"), expression.Value(5))
//
// // Adding more update methods
// anotherUpdate := update.Remove(expression.Name("someName"))
// // Creating a Builder
// builder := Update(update)
//
// Expression Equivalent:
//
// expression.Add(expression.Name("aPath"), expression.Value(5))
// // Let :five be an ExpressionAttributeValue representing the value 5
// "ADD aPath :5"
func Add(name NameBuilder, value ValueBuilder) UpdateBuilder {
emptyUpdateBuilder := UpdateBuilder{}
return emptyUpdateBuilder.Add(name, value)
}
// Add adds an Add operation to the argument UpdateBuilder. The argument
// name should specify the item attribute and the argument value should specify
// the value to be added. The resulting UpdateBuilder can be used as an argument
// to the WithUpdate() method for the Builder struct.
//
// Example:
//
// // Let update represent an already existing update expression. Add() adds
// // the operation to add the value 5 to the item attribute "aPath"
// update := update.Add(expression.Name("aPath"), expression.Value(5))
//
// // Adding more update methods
// anotherUpdate := update.Remove(expression.Name("someName"))
// // Creating a Builder
// builder := Update(update)
//
// Expression Equivalent:
//
// Add(expression.Name("aPath"), expression.Value(5))
// // Let :five be an ExpressionAttributeValue representing the value 5
// "ADD aPath :5"
func (ub UpdateBuilder) Add(name NameBuilder, value ValueBuilder) UpdateBuilder {
if ub.operationList == nil {
ub.operationList = map[operationMode][]operationBuilder{}
}
ub.operationList[addOperation] = append(ub.operationList[addOperation], operationBuilder{
name: name,
value: value,
mode: addOperation,
})
return ub
}
// Remove returns an UpdateBuilder representing the Remove operation for
// DynamoDB Update Expressions. The argument name should specify the item
// attribute to delete. The resulting UpdateBuilder can be used as an argument
// to the WithUpdate() method for the Builder struct.
//
// Example:
//
// // update represents the remove operation to remove the item attribute
// // "itemToRemove"
// update := expression.Remove(expression.Name("itemToRemove"))
//
// // Adding more update methods
// anotherUpdate := update.Remove(expression.Name("someName"))
// // Creating a Builder
// builder := Update(update)
//
// Expression Equivalent:
//
// expression.Remove(expression.Name("itemToRemove"))
// "REMOVE itemToRemove"
func Remove(name NameBuilder) UpdateBuilder {
emptyUpdateBuilder := UpdateBuilder{}
return emptyUpdateBuilder.Remove(name)
}
// Remove adds a Remove operation to the argument UpdateBuilder. The
// argument name should specify the item attribute to delete. The resulting
// UpdateBuilder can be used as an argument to the WithUpdate() method for the
// Builder struct.
//
// Example:
//
// // Let update represent an already existing update expression. Remove()
// // adds the operation to remove the item attribute "itemToRemove"
// update := update.Remove(expression.Name("itemToRemove"))
//
// // Adding more update methods
// anotherUpdate := update.Remove(expression.Name("someName"))
// // Creating a Builder
// builder := Update(update)
//
// Expression Equivalent:
//
// Remove(expression.Name("itemToRemove"))
// "REMOVE itemToRemove"
func (ub UpdateBuilder) Remove(name NameBuilder) UpdateBuilder {
if ub.operationList == nil {
ub.operationList = map[operationMode][]operationBuilder{}
}
ub.operationList[removeOperation] = append(ub.operationList[removeOperation], operationBuilder{
name: name,
mode: removeOperation,
})
return ub
}
// Set returns an UpdateBuilder representing the Set operation for DynamoDB
// Update Expressions. The argument name should specify the item attribute to
// modify. The argument OperandBuilder should specify the value to modify the
// the item attribute to. The resulting UpdateBuilder can be used as an argument
// to the WithUpdate() method for the Builder struct.
//
// Example:
//
// // update represents the set operation to set the item attribute
// // "itemToSet" to the value "setValue" if the item attribute does not
// // exist yet. (conditional write)
// update := expression.Set(expression.Name("itemToSet"), expression.IfNotExists(expression.Name("itemToSet"), expression.Value("setValue")))
//
// // Adding more update methods
// anotherUpdate := update.Remove(expression.Name("someName"))
// // Creating a Builder
// builder := Update(update)
//
// Expression Equivalent:
//
// expression.Set(expression.Name("itemToSet"), expression.IfNotExists(expression.Name("itemToSet"), expression.Value("setValue")))
// // Let :val be an ExpressionAttributeValue representing the value
// // "setValue"
// "SET itemToSet = :val"
func Set(name NameBuilder, operandBuilder OperandBuilder) UpdateBuilder {
emptyUpdateBuilder := UpdateBuilder{}
return emptyUpdateBuilder.Set(name, operandBuilder)
}
// Set adds a Set operation to the argument UpdateBuilder. The argument name
// should specify the item attribute to modify. The argument OperandBuilder
// should specify the value to modify the the item attribute to. The resulting
// UpdateBuilder can be used as an argument to the WithUpdate() method for the
// Builder struct.
//
// Example:
//
// // Let update represent an already existing update expression. Set() adds
// // the operation to to set the item attribute "itemToSet" to the value
// // "setValue" if the item attribute does not exist yet. (conditional
// // write)
// update := update.Set(expression.Name("itemToSet"), expression.IfNotExists(expression.Name("itemToSet"), expression.Value("setValue")))
//
// // Adding more update methods
// anotherUpdate := update.Remove(expression.Name("someName"))
// // Creating a Builder
// builder := Update(update)
//
// Expression Equivalent:
//
// Set(expression.Name("itemToSet"), expression.IfNotExists(expression.Name("itemToSet"), expression.Value("setValue")))
// // Let :val be an ExpressionAttributeValue representing the value
// // "setValue"
// "SET itemToSet = :val"
func (ub UpdateBuilder) Set(name NameBuilder, operandBuilder OperandBuilder) UpdateBuilder {
if ub.operationList == nil {
ub.operationList = map[operationMode][]operationBuilder{}
}
ub.operationList[setOperation] = append(ub.operationList[setOperation], operationBuilder{
name: name,
value: operandBuilder,
mode: setOperation,
})
return ub
}
// buildTree builds a tree structure of exprNodes based on the tree
// structure of the input UpdateBuilder's child UpdateBuilders/Operands.
// buildTree() satisfies the TreeBuilder interface so ProjectionBuilder can be a
// part of Expression struct.
func (ub UpdateBuilder) buildTree() (exprNode, error) {
if ub.operationList == nil {
return exprNode{}, newUnsetParameterError("buildTree", "UpdateBuilder")
}
ret := exprNode{
children: []exprNode{},
}
modes := modeList{}
for mode := range ub.operationList {
modes = append(modes, mode)
}
sort.Sort(modes)
for _, key := range modes {
ret.fmtExpr += string(key) + " $c\n"
childNode, err := buildChildNodes(ub.operationList[key])
if err != nil {
return exprNode{}, err
}
ret.children = append(ret.children, childNode)
}
return ret, nil
}
// buildChildNodes creates the list of the child exprNodes.
func buildChildNodes(operationBuilderList []operationBuilder) (exprNode, error) {
if len(operationBuilderList) == 0 {
return exprNode{}, fmt.Errorf("buildChildNodes error: operationBuilder list is empty")
}
node := exprNode{
children: make([]exprNode, 0, len(operationBuilderList)),
fmtExpr: "$c" + strings.Repeat(", $c", len(operationBuilderList)-1),
}
for _, val := range operationBuilderList {
valNode, err := val.buildOperation()
if err != nil {
return exprNode{}, err
}
node.children = append(node.children, valNode)
}
return node, nil
}

View file

@ -0,0 +1,771 @@
// +build go1.7
package expression
import (
"reflect"
"strings"
"testing"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/dynamodb"
)
// updateErrorMode will help with error cases and checking error types
type updateErrorMode string
const (
noUpdateError updateErrorMode = ""
invalidUpdateOperand = "BuildOperand error"
unsetSetValue = "unset parameter: SetValueBuilder"
unsetUpdate = "unset parameter: UpdateBuilder"
emptyOperationBuilderList = "operationBuilder list is empty"
)
func TestBuildOperation(t *testing.T) {
cases := []struct {
name string
input operationBuilder
expected exprNode
err updateErrorMode
}{
{
name: "set operation",
input: operationBuilder{
name: Name("foo"),
value: Value(5),
mode: setOperation,
},
expected: exprNode{
children: []exprNode{
{
names: []string{"foo"},
fmtExpr: "$n",
},
{
values: []dynamodb.AttributeValue{
{
N: aws.String("5"),
},
},
fmtExpr: "$v",
},
},
fmtExpr: "$c = $c",
},
},
{
name: "add operation",
input: operationBuilder{
name: Name("foo"),
value: Value(5),
mode: addOperation,
},
expected: exprNode{
children: []exprNode{
{
names: []string{"foo"},
fmtExpr: "$n",
},
{
values: []dynamodb.AttributeValue{
{
N: aws.String("5"),
},
},
fmtExpr: "$v",
},
},
fmtExpr: "$c $c",
},
},
{
name: "remove operation",
input: operationBuilder{
name: Name("foo"),
mode: removeOperation,
},
expected: exprNode{
children: []exprNode{
{
names: []string{"foo"},
fmtExpr: "$n",
},
},
fmtExpr: "$c",
},
},
{
name: "invalid operand",
input: operationBuilder{
name: Name(""),
mode: removeOperation,
},
err: invalidUpdateOperand,
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
actual, err := c.input.buildOperation()
if c.err != noUpdateError {
if err == nil {
t.Errorf("expect error %q, got no error", c.err)
} else {
if e, a := string(c.err), err.Error(); !strings.Contains(a, e) {
t.Errorf("expect %q error message to be in %q", e, a)
}
}
} else {
if err != nil {
t.Errorf("expect no error, got unexpected Error %q", err)
}
if e, a := c.expected, actual; !reflect.DeepEqual(a, e) {
t.Errorf("expect %v, got %v", e, a)
}
}
})
}
}
func TestUpdateTree(t *testing.T) {
cases := []struct {
name string
input UpdateBuilder
expectedNode exprNode
err updateErrorMode
}{
{
name: "set update",
input: Set(Name("foo"), Value(5)),
expectedNode: exprNode{
children: []exprNode{
{
children: []exprNode{
{
children: []exprNode{
{
names: []string{"foo"},
fmtExpr: "$n",
},
{
values: []dynamodb.AttributeValue{
{
N: aws.String("5"),
},
},
fmtExpr: "$v",
},
},
fmtExpr: "$c = $c",
},
},
fmtExpr: "$c",
},
},
fmtExpr: "SET $c\n",
},
},
{
name: "remove update",
input: Remove(Name("foo")),
expectedNode: exprNode{
children: []exprNode{
{
children: []exprNode{
{
children: []exprNode{
{
names: []string{"foo"},
fmtExpr: "$n",
},
},
fmtExpr: "$c",
},
},
fmtExpr: "$c",
},
},
fmtExpr: "REMOVE $c\n",
},
},
{
name: "add update",
input: Add(Name("foo"), Value(5)),
expectedNode: exprNode{
children: []exprNode{
{
children: []exprNode{
{
children: []exprNode{
{
names: []string{"foo"},
fmtExpr: "$n",
},
{
values: []dynamodb.AttributeValue{
{
N: aws.String("5"),
},
},
fmtExpr: "$v",
},
},
fmtExpr: "$c $c",
},
},
fmtExpr: "$c",
},
},
fmtExpr: "ADD $c\n",
},
},
{
name: "delete update",
input: Delete(Name("foo"), Value(5)),
expectedNode: exprNode{
children: []exprNode{
{
children: []exprNode{
{
children: []exprNode{
{
names: []string{"foo"},
fmtExpr: "$n",
},
{
values: []dynamodb.AttributeValue{
{
N: aws.String("5"),
},
},
fmtExpr: "$v",
},
},
fmtExpr: "$c $c",
},
},
fmtExpr: "$c",
},
},
fmtExpr: "DELETE $c\n",
},
},
{
name: "multiple sets",
input: Set(Name("foo"), Value(5)).Set(Name("bar"), Value(6)).Set(Name("baz"), Name("qux")),
expectedNode: exprNode{
fmtExpr: "SET $c\n",
children: []exprNode{
{
fmtExpr: "$c, $c, $c",
children: []exprNode{
{
fmtExpr: "$c = $c",
children: []exprNode{
{
fmtExpr: "$n",
names: []string{"foo"},
},
{
fmtExpr: "$v",
values: []dynamodb.AttributeValue{
{
N: aws.String("5"),
},
},
},
},
},
{
fmtExpr: "$c = $c",
children: []exprNode{
{
fmtExpr: "$n",
names: []string{"bar"},
},
{
fmtExpr: "$v",
values: []dynamodb.AttributeValue{
{
N: aws.String("6"),
},
},
},
},
},
{
fmtExpr: "$c = $c",
children: []exprNode{
{
fmtExpr: "$n",
names: []string{"baz"},
},
{
fmtExpr: "$n",
names: []string{"qux"},
},
},
},
},
},
},
},
},
{
name: "compound update",
input: Add(Name("foo"), Value(5)).Set(Name("foo"), Value(5)).Delete(Name("foo"), Value(5)).Remove(Name("foo")),
expectedNode: exprNode{
children: []exprNode{
{
children: []exprNode{
{
children: []exprNode{
{
names: []string{"foo"},
fmtExpr: "$n",
},
{
values: []dynamodb.AttributeValue{
{
N: aws.String("5"),
},
},
fmtExpr: "$v",
},
},
fmtExpr: "$c $c",
},
},
fmtExpr: "$c",
},
{
children: []exprNode{
{
children: []exprNode{
{
names: []string{"foo"},
fmtExpr: "$n",
},
{
values: []dynamodb.AttributeValue{
{
N: aws.String("5"),
},
},
fmtExpr: "$v",
},
},
fmtExpr: "$c $c",
},
},
fmtExpr: "$c",
},
{
children: []exprNode{
{
children: []exprNode{
{
names: []string{"foo"},
fmtExpr: "$n",
},
},
fmtExpr: "$c",
},
},
fmtExpr: "$c",
},
{
children: []exprNode{
{
children: []exprNode{
{
names: []string{"foo"},
fmtExpr: "$n",
},
{
values: []dynamodb.AttributeValue{
{
N: aws.String("5"),
},
},
fmtExpr: "$v",
},
},
fmtExpr: "$c = $c",
},
},
fmtExpr: "$c",
},
},
fmtExpr: "ADD $c\nDELETE $c\nREMOVE $c\nSET $c\n",
},
},
{
name: "empty UpdateBuilder",
input: UpdateBuilder{},
err: unsetUpdate,
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
actual, err := c.input.buildTree()
if c.err != noUpdateError {
if err == nil {
t.Errorf("expect error %q, got no error", c.err)
} else {
if e, a := string(c.err), err.Error(); !strings.Contains(a, e) {
t.Errorf("expect %q error message to be in %q", e, a)
}
}
} else {
if err != nil {
t.Errorf("expect no error, got unexpected Error %q", err)
}
if e, a := c.expectedNode, actual; !reflect.DeepEqual(a, e) {
t.Errorf("expect %v, got %v", e, a)
}
}
})
}
}
func TestSetValueBuilder(t *testing.T) {
cases := []struct {
name string
input SetValueBuilder
expected exprNode
err updateErrorMode
}{
{
name: "name plus name",
input: Name("foo").Plus(Name("bar")),
expected: exprNode{
children: []exprNode{
{
names: []string{"foo"},
fmtExpr: "$n",
},
{
names: []string{"bar"},
fmtExpr: "$n",
},
},
fmtExpr: "$c + $c",
},
},
{
name: "name minus name",
input: Name("foo").Minus(Name("bar")),
expected: exprNode{
children: []exprNode{
{
names: []string{"foo"},
fmtExpr: "$n",
},
{
names: []string{"bar"},
fmtExpr: "$n",
},
},
fmtExpr: "$c - $c",
},
},
{
name: "list append name and name",
input: Name("foo").ListAppend(Name("bar")),
expected: exprNode{
children: []exprNode{
{
names: []string{"foo"},
fmtExpr: "$n",
},
{
names: []string{"bar"},
fmtExpr: "$n",
},
},
fmtExpr: "list_append($c, $c)",
},
},
{
name: "if not exists name and name",
input: Name("foo").IfNotExists(Name("bar")),
expected: exprNode{
children: []exprNode{
{
names: []string{"foo"},
fmtExpr: "$n",
},
{
names: []string{"bar"},
fmtExpr: "$n",
},
},
fmtExpr: "if_not_exists($c, $c)",
},
},
{
name: "value plus name",
input: Value(5).Plus(Name("bar")),
expected: exprNode{
children: []exprNode{
{
values: []dynamodb.AttributeValue{
{
N: aws.String("5"),
},
},
fmtExpr: "$v",
},
{
names: []string{"bar"},
fmtExpr: "$n",
},
},
fmtExpr: "$c + $c",
},
},
{
name: "value minus name",
input: Value(5).Minus(Name("bar")),
expected: exprNode{
children: []exprNode{
{
values: []dynamodb.AttributeValue{
{
N: aws.String("5"),
},
},
fmtExpr: "$v",
},
{
names: []string{"bar"},
fmtExpr: "$n",
},
},
fmtExpr: "$c - $c",
},
},
{
name: "list append list and name",
input: Value([]int{1, 2, 3}).ListAppend(Name("bar")),
expected: exprNode{
children: []exprNode{
{
values: []dynamodb.AttributeValue{
{
L: []*dynamodb.AttributeValue{
{
N: aws.String("1"),
},
{
N: aws.String("2"),
},
{
N: aws.String("3"),
},
},
},
},
fmtExpr: "$v",
},
{
names: []string{"bar"},
fmtExpr: "$n",
},
},
fmtExpr: "list_append($c, $c)",
},
},
{
name: "unset SetValueBuilder",
input: SetValueBuilder{},
err: unsetSetValue,
},
{
name: "invalid operand error",
input: Name("").Plus(Name("foo")),
err: invalidUpdateOperand,
},
{
name: "invalid operand error",
input: Name("foo").Plus(Name("")),
err: invalidUpdateOperand,
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
actual, err := c.input.BuildOperand()
if c.err != noUpdateError {
if err == nil {
t.Errorf("expect error %q, got no error", c.err)
} else {
if e, a := string(c.err), err.Error(); !strings.Contains(a, e) {
t.Errorf("expect %q error message to be in %q", e, a)
}
}
} else {
if err != nil {
t.Errorf("expect no error, got unexpected Error %q", err)
}
if e, a := c.expected, actual.exprNode; !reflect.DeepEqual(a, e) {
t.Errorf("expect %v, got %v", e, a)
}
}
})
}
}
func TestUpdateBuildChildNodes(t *testing.T) {
cases := []struct {
name string
input []operationBuilder
expected exprNode
err updateErrorMode
}{
{
name: "set operand builder",
input: []operationBuilder{
{
mode: setOperation,
name: NameBuilder{
name: "foo",
},
value: ValueBuilder{
value: 5,
},
},
{
mode: setOperation,
name: NameBuilder{
name: "bar",
},
value: ValueBuilder{
value: 6,
},
},
{
mode: setOperation,
name: NameBuilder{
name: "baz",
},
value: ValueBuilder{
value: 7,
},
},
{
mode: setOperation,
name: NameBuilder{
name: "qux",
},
value: ValueBuilder{
value: 8,
},
},
},
expected: exprNode{
fmtExpr: "$c, $c, $c, $c",
children: []exprNode{
{
fmtExpr: "$c = $c",
children: []exprNode{
{
fmtExpr: "$n",
names: []string{"foo"},
},
{
fmtExpr: "$v",
values: []dynamodb.AttributeValue{
{
N: aws.String("5"),
},
},
},
},
},
{
fmtExpr: "$c = $c",
children: []exprNode{
{
fmtExpr: "$n",
names: []string{"bar"},
},
{
fmtExpr: "$v",
values: []dynamodb.AttributeValue{
{
N: aws.String("6"),
},
},
},
},
},
{
fmtExpr: "$c = $c",
children: []exprNode{
{
fmtExpr: "$n",
names: []string{"baz"},
},
{
fmtExpr: "$v",
values: []dynamodb.AttributeValue{
{
N: aws.String("7"),
},
},
},
},
},
{
fmtExpr: "$c = $c",
children: []exprNode{
{
fmtExpr: "$n",
names: []string{"qux"},
},
{
fmtExpr: "$v",
values: []dynamodb.AttributeValue{
{
N: aws.String("8"),
},
},
},
},
},
},
},
},
{
name: "empty operationBuilder list",
input: []operationBuilder{},
err: emptyOperationBuilderList,
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
actual, err := buildChildNodes(c.input)
if c.err != noUpdateError {
if err == nil {
t.Errorf("expect error %q, got no error", c.err)
} else {
if e, a := string(c.err), err.Error(); !strings.Contains(a, e) {
t.Errorf("expect %q error message to be in %q", e, a)
}
}
} else {
if err != nil {
t.Errorf("expect no error, got unexpected Error %q", err)
}
if e, a := c.expected, actual; !reflect.DeepEqual(a, e) {
t.Errorf("expect %v, got %v", e, a)
}
}
})
}
}

View file

@ -11,7 +11,7 @@ import (
// WaitUntilTableExists uses the DynamoDB API operation
// DescribeTable to wait for a condition to be met before returning.
// If the condition is not meet within the max attempt window an error will
// If the condition is not met within the max attempt window, an error will
// be returned.
func (c *DynamoDB) WaitUntilTableExists(input *DescribeTableInput) error {
return c.WaitUntilTableExistsWithContext(aws.BackgroundContext(), input)
@ -62,7 +62,7 @@ func (c *DynamoDB) WaitUntilTableExistsWithContext(ctx aws.Context, input *Descr
// WaitUntilTableNotExists uses the DynamoDB API operation
// DescribeTable to wait for a condition to be met before returning.
// If the condition is not meet within the max attempt window an error will
// If the condition is not met within the max attempt window, an error will
// be returned.
func (c *DynamoDB) WaitUntilTableNotExists(input *DescribeTableInput) error {
return c.WaitUntilTableNotExistsWithContext(aws.BackgroundContext(), input)