Skip to content

Commit 4d4d8d1

Browse files
committed
Add TransactWriteItems
1 parent 6f8896d commit 4d4d8d1

File tree

4 files changed

+95
-2
lines changed

4 files changed

+95
-2
lines changed

RELEASE_NOTES.md

+4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
### 0.11.0-beta
2+
* Added `Precondition.CheckFailed`
3+
* Added `TableContext.TransactWriteItems`, `TransactWrite` DU, `TransactWriteItemsRequest.TransactionCanceledConditionalCheckFailed`
4+
15
### 0.10.1-beta
26
* Fixed accidentally removed/renamed legacy factory methods (`TableContext.Create`/`TableContext.CreateAsync`)
37

global.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"sdk": {
3-
"version": "6.0.201",
3+
"version": "6.0.202",
44
"rollForward": "latestMajor"
55
}
66
}

src/FSharp.AWS.DynamoDB/TableContext.fs

+72-1
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ module internal Provisioning =
183183
run (client, tableName, template) None
184184

185185
/// Represents the operation performed on the table, for metrics collection purposes
186-
type Operation = GetItem | PutItem | UpdateItem | DeleteItem | BatchGetItems | BatchWriteItems | Scan | Query
186+
type Operation = GetItem | PutItem | UpdateItem | DeleteItem | BatchGetItems | BatchWriteItems | TransactWriteItems | Scan | Query
187187

188188
/// Represents metrics returned by the table operation, for plugging in to an observability framework
189189
type RequestMetrics =
@@ -209,6 +209,57 @@ type private LimitType = All | Default | Count of int
209209
static member AllOrCount (l : int option) = l |> Option.map Count |> Option.defaultValue All
210210
static member DefaultOrCount (l : int option) = l |> Option.map Count |> Option.defaultValue Default
211211

212+
/// <summary>Represents an individual request that can be included in the <c>TransactItems</c> of a <c>TransactWriteItems</c> call.</summary>
213+
[<RequireQualifiedAccess>]
214+
type TransactWrite<'TRecord> =
215+
/// Specify a Check to be run on a specified item.
216+
/// If the condition does not hold, the overall TransactWriteItems request will be Canceled.
217+
| Check of key : TableKey * condition : ConditionExpression<'TRecord>
218+
/// Specify a PutItem operation to be performed, inserting or replacing an item in the Table.
219+
/// If the (optional) precondition does not hold, the overall TransactWriteItems request will be Canceled.
220+
| Put of item : 'TRecord * precondition : ConditionExpression<'TRecord> option
221+
/// Specify an UpdateItem operation to be performed, applying an updater expression on the item identified by the specified `key`, if it exists.
222+
/// If the item exists and the (optional) precondition does not hold, the overall TransactWriteItems request will be Canceled.
223+
| Update of key : TableKey * precondition : ConditionExpression<'TRecord> option * updater : UpdateExpression<'TRecord>
224+
/// Specify a DeleteItem operation to be performed, removing the item identified by the specified `key` if it exists.
225+
/// If the item exists and the (optional) precondition does not hold, the overall TransactWriteItems request will be Canceled.
226+
| Delete of key : TableKey * precondition : ConditionExpression<'TRecord> option
227+
228+
/// Helpers for building a <c>TransactWriteItemsRequest</c> to supply to <c>TransactWriteItems</c>
229+
module TransactWriteItemsRequest =
230+
231+
let private toTransactWriteItem<'TRecord> tableName (template : RecordTemplate<'TRecord>) : TransactWrite<'TRecord> -> TransactWriteItem = function
232+
| TransactWrite.Check (key, cond) ->
233+
let req = ConditionCheck(TableName = tableName, Key = template.ToAttributeValues key)
234+
let writer = AttributeWriter(req.ExpressionAttributeNames, req.ExpressionAttributeValues)
235+
req.ConditionExpression <- cond.Conditional.Write writer
236+
TransactWriteItem(ConditionCheck = req)
237+
| TransactWrite.Put (item, maybeCond) ->
238+
let req = Put(TableName = tableName, Item = template.ToAttributeValues item)
239+
maybeCond |> Option.iter (fun cond ->
240+
let writer = AttributeWriter(req.ExpressionAttributeNames, req.ExpressionAttributeValues)
241+
req.ConditionExpression <- cond.Conditional.Write writer)
242+
TransactWriteItem(Put = req)
243+
| TransactWrite.Update (key, maybeCond, updater) ->
244+
let req = Update(TableName = tableName, Key = template.ToAttributeValues key)
245+
let writer = AttributeWriter(req.ExpressionAttributeNames, req.ExpressionAttributeValues)
246+
req.UpdateExpression <- updater.UpdateOps.Write(writer)
247+
maybeCond |> Option.iter (fun cond -> req.ConditionExpression <- cond.Conditional.Write writer)
248+
TransactWriteItem(Update = req)
249+
| TransactWrite.Delete (key, maybeCond) ->
250+
let req = Delete(TableName = tableName, Key = template.ToAttributeValues key)
251+
maybeCond |> Option.iter (fun cond ->
252+
let writer = AttributeWriter(req.ExpressionAttributeNames, req.ExpressionAttributeValues)
253+
req.ConditionExpression <- cond.Conditional.Write writer)
254+
TransactWriteItem(Delete = req)
255+
let internal toTransactItems<'TRecord> tableName template items = Seq.map (toTransactWriteItem<'TRecord> tableName template) items |> rlist
256+
257+
/// <summary>Exception filter to identify whether a <c>TransactWriteItems</c> call has failed due to
258+
/// one or more of the supplied <c>precondition</c> checks failing.</summary>
259+
let (|TransactionCanceledConditionalCheckFailed|_|) : exn -> unit option = function
260+
| :? TransactionCanceledException as e when e.CancellationReasons.Exists(fun x -> x.Code = "ConditionalCheckFailed") -> Some ()
261+
| _ -> None
262+
212263
/// DynamoDB client object for performing table operations in the context of given F# record representations
213264
[<Sealed; AutoSerializable(false)>]
214265
type TableContext<'TRecord> internal
@@ -744,6 +795,26 @@ type TableContext<'TRecord> internal
744795
}
745796

746797

798+
/// <summary>
799+
/// Atomically applies a set of 1-25 write operations to the table.<br/>
800+
/// NOTE requests are charged at twice the normal rate in Write Capacity Units.
801+
/// See the DynamoDB <a href="https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_TransactWriteItems.html"><c>TransactWriteItems</c> API documentation</a> for full details of semantics and charges.<br/>
802+
/// </summary>
803+
/// <param name="items">Operations to be performed.<br/>
804+
/// Use <c>TransactWriteItemsRequest.TransactionCanceledConditionalCheckFailed</c> to identify any Precondition Check failures.</param>
805+
/// <param name="clientRequestToken">The <c>ClientRequestToken</c> to supply as an idempotency key (10 minute window).</param>
806+
member _.TransactWriteItems(items : seq<TransactWrite<'TRecord>>, ?clientRequestToken) : Async<unit> = async {
807+
let reqs = TransactWriteItemsRequest.toTransactItems tableName template items
808+
let req = TransactWriteItemsRequest(ReturnConsumedCapacity = returnConsumedCapacity, TransactItems = reqs)
809+
clientRequestToken |> Option.iter (fun x -> req.ClientRequestToken <- x)
810+
let! ct = Async.CancellationToken
811+
let! response = client.TransactWriteItemsAsync(req, ct) |> Async.AwaitTaskCorrect
812+
maybeReport |> Option.iter (fun r -> r TransactWriteItems (Seq.toList response.ConsumedCapacity) reqs.Count)
813+
if response.HttpStatusCode <> HttpStatusCode.OK then
814+
failwithf "TransactWriteItems request returned error %O" response.HttpStatusCode
815+
}
816+
817+
747818
/// <summary>
748819
/// Asynchronously queries table with given condition expressions.
749820
/// </summary>

tests/FSharp.AWS.DynamoDB.Tests/SimpleTableOperationTests.fs

+18
Original file line numberDiff line numberDiff line change
@@ -125,4 +125,22 @@ type ``Simple Table Operation Tests`` (fixture : TableFixture) =
125125
test <@ None = deletedItem @>
126126
test <@ not (table.ContainsKey key) @>
127127

128+
let [<Theory; InlineData true; InlineData false>]
129+
``Condition Check outcome should affect sibling TransactWrite`` shouldFail = async {
130+
let item, item2 = mkItem (), mkItem ()
131+
let key = table.PutItem item
132+
133+
let requests = [
134+
if shouldFail then TransactWrite.Check (key, table.Template.PrecomputeConditionalExpr <@ fun t -> NOT_EXISTS t.Value @>)
135+
else TransactWrite.Check (key, table.Template.PrecomputeConditionalExpr <@ fun t -> EXISTS t.Value @>)
136+
TransactWrite.Put (item2, None) ]
137+
let mutable failed = false
138+
try do! table.TransactWriteItems requests
139+
with TransactWriteItemsRequest.TransactionCanceledConditionalCheckFailed -> failed <- true
140+
141+
let failed = failed
142+
test <@ failed = shouldFail @>
143+
let item2Found = table.ContainsKey (table.Template.ExtractKey item2)
144+
test <@ not shouldFail = item2Found @> }
145+
128146
interface IClassFixture<TableFixture>

0 commit comments

Comments
 (0)