Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add IncludeItemInCondCheckFail and friends
Browse files Browse the repository at this point in the history
guregu committed Aug 23, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
1 parent 1519c9c commit 973bed6
Showing 7 changed files with 92 additions and 14 deletions.
4 changes: 2 additions & 2 deletions db.go
Original file line number Diff line number Diff line change
@@ -213,7 +213,7 @@ func UnmarshalItemFromCondCheckFailed(condCheckErr error, out any) (match bool,
var cfe *types.ConditionalCheckFailedException
if errors.As(condCheckErr, &cfe) {
if cfe.Item == nil {
return true, fmt.Errorf("dynamo: ConditionalCheckFailedException does not contain item")
return true, fmt.Errorf("dynamo: ConditionalCheckFailedException does not contain item (is IncludeItemInCondCheckFail disabled?): %w", condCheckErr)
}
return true, UnmarshalItem(cfe.Item, out)
}
@@ -233,7 +233,7 @@ func UnmarshalItemsFromTxCondCheckFailed(txCancelErr error, out any) (match bool
for _, cr := range txe.CancellationReasons {
if cr.Code != nil && *cr.Code == "ConditionalCheckFailed" {
if cr.Item == nil {
return true, fmt.Errorf("dynamo: TransactionCanceledException.CancellationReasons does not contain item")
return true, fmt.Errorf("dynamo: TransactionCanceledException.CancellationReasons does not contain item (is IncludeItemInCondCheckFail disabled?): %w", txCancelErr)
}
if err = unmarshal(cr.Item, out); err != nil {
return true, err
18 changes: 16 additions & 2 deletions delete.go
Original file line number Diff line number Diff line change
@@ -11,8 +11,10 @@ import (
// Delete is a request to delete an item.
// See: http://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_DeleteItem.html
type Delete struct {
table Table
table Table

returnType types.ReturnValue
onCondFail types.ReturnValuesOnConditionCheckFailure

hashKey string
hashValue types.AttributeValue
@@ -108,6 +110,7 @@ func (d *Delete) OldValue(ctx context.Context, out interface{}) error {
// See also: [UnmarshalItemFromCondCheckFailed].
func (d *Delete) CurrentValue(ctx context.Context, out interface{}) (wrote bool, err error) {
d.returnType = types.ReturnValueNone
d.onCondFail = types.ReturnValuesOnConditionCheckFailureAllOld
_, err = d.run(ctx)
if err != nil {
if ok, err := UnmarshalItemFromCondCheckFailed(err, out); ok {
@@ -118,6 +121,17 @@ func (d *Delete) CurrentValue(ctx context.Context, out interface{}) (wrote bool,
return true, nil
}

// IncludeAllItemsInCondCheckFail specifies whether an item delete that fails its condition check should include the item itself in the error.
// Such items can be extracted using [UnmarshalItemFromCondCheckFailed] for single deletes, or [UnmarshalItemsFromTxCondCheckFailed] for write transactions.
func (d *Delete) IncludeItemInCondCheckFail(enabled bool) *Delete {
if enabled {
d.onCondFail = types.ReturnValuesOnConditionCheckFailureAllOld
} else {
d.onCondFail = types.ReturnValuesOnConditionCheckFailureNone
}
return d
}

func (d *Delete) run(ctx context.Context) (*dynamodb.DeleteItemOutput, error) {
if d.err != nil {
return nil, d.err
@@ -147,7 +161,7 @@ func (d *Delete) deleteInput() *dynamodb.DeleteItemInput {
}
if d.condition != "" {
input.ConditionExpression = &d.condition
input.ReturnValuesOnConditionCheckFailure = types.ReturnValuesOnConditionCheckFailureAllOld
input.ReturnValuesOnConditionCheckFailure = d.onCondFail
}
if d.cc != nil {
input.ReturnConsumedCapacity = types.ReturnConsumedCapacityIndexes
18 changes: 16 additions & 2 deletions put.go
Original file line number Diff line number Diff line change
@@ -10,8 +10,10 @@ import (
// Put is a request to create or replace an item.
// See: http://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_PutItem.html
type Put struct {
table Table
table Table

returnType types.ReturnValue
onCondFail types.ReturnValuesOnConditionCheckFailure

item Item
subber
@@ -83,6 +85,7 @@ func (p *Put) OldValue(ctx context.Context, out interface{}) error {
// See also: [UnmarshalItemFromCondCheckFailed].
func (p *Put) CurrentValue(ctx context.Context, out interface{}) (wrote bool, err error) {
p.returnType = types.ReturnValueNone
p.onCondFail = types.ReturnValuesOnConditionCheckFailureAllOld
item, _, err := p.run(ctx)
wrote = err == nil
if err != nil {
@@ -93,6 +96,17 @@ func (p *Put) CurrentValue(ctx context.Context, out interface{}) (wrote bool, er
return
}

// IncludeAllItemsInCondCheckFail specifies whether an item put that fails its condition check should include the item itself in the error.
// Such items can be extracted using [UnmarshalItemFromCondCheckFailed] for single puts, or [UnmarshalItemsFromTxCondCheckFailed] for write transactions.
func (p *Put) IncludeItemInCondCheckFail(enabled bool) *Put {
if enabled {
p.onCondFail = types.ReturnValuesOnConditionCheckFailureAllOld
} else {
p.onCondFail = types.ReturnValuesOnConditionCheckFailureNone
}
return p
}

func (p *Put) run(ctx context.Context) (item Item, output *dynamodb.PutItemOutput, err error) {
if p.err != nil {
return nil, nil, p.err
@@ -121,7 +135,7 @@ func (p *Put) input() *dynamodb.PutItemInput {
}
if p.condition != "" {
input.ConditionExpression = &p.condition
input.ReturnValuesOnConditionCheckFailure = types.ReturnValuesOnConditionCheckFailureAllOld
input.ReturnValuesOnConditionCheckFailure = p.onCondFail
}
if p.cc != nil {
input.ReturnConsumedCapacity = types.ReturnConsumedCapacityIndexes
5 changes: 4 additions & 1 deletion put_test.go
Original file line number Diff line number Diff line change
@@ -70,7 +70,10 @@ func TestPut(t *testing.T) {

// putting the same item: this should fail
t.Run("UnmarshalItemFromCondCheckFailed", func(t *testing.T) {
err := table.Put(newItem).If("attribute_not_exists(UserID)").If("attribute_not_exists('Time')").Run(ctx)
err := table.Put(newItem).
If("attribute_not_exists(UserID)").
If("attribute_not_exists('Time')").
IncludeItemInCondCheckFail(true).Run(ctx)
if !IsCondCheckFailed(err) {
t.Error("expected ConditionalCheckFailedException, not", err)
}
42 changes: 37 additions & 5 deletions tx.go
Original file line number Diff line number Diff line change
@@ -168,11 +168,12 @@ type writeTxOp interface {
// WriteTx is analogous to TransactWriteItems in DynamoDB's API.
// See: https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_TransactWriteItems.html
type WriteTx struct {
db *DB
items []writeTxOp
token string
cc *ConsumedCapacity
err error
db *DB
items []writeTxOp
token string
onCondFail types.ReturnValuesOnConditionCheckFailure
cc *ConsumedCapacity
err error
}

// WriteTx begins a new write transaction.
@@ -206,6 +207,20 @@ func (tx *WriteTx) Check(check *ConditionCheck) *WriteTx {
return tx
}

// IncludeAllItemsInCondCheckFail specifies whether an item write that fails its condition check should include the item itself in the error.
// Such items can be extracted using [UnmarshalItemsFromTxCondCheckFailed].
//
// By default, the individual settings for each item are respected.
// Calling this will override all individual settings.
func (tx *WriteTx) IncludeAllItemsInCondCheckFail(enabled bool) *WriteTx {
if enabled {
tx.onCondFail = types.ReturnValuesOnConditionCheckFailureAllOld
} else {
tx.onCondFail = types.ReturnValuesOnConditionCheckFailureNone
}
return tx
}

// Idempotent marks this transaction as idempotent when enabled is true.
// This automatically generates a unique idempotency token for you.
// An idempotent transaction ran multiple times will have the same effect as being run once.
@@ -279,6 +294,7 @@ func (tx *WriteTx) input() (*dynamodb.TransactWriteItemsInput, error) {
if err != nil {
return nil, err
}
setTWIReturnType(wti, tx.onCondFail)
input.TransactItems = append(input.TransactItems, *wti)
}
if tx.token != "" {
@@ -290,6 +306,22 @@ func (tx *WriteTx) input() (*dynamodb.TransactWriteItemsInput, error) {
return input, nil
}

func setTWIReturnType(wti *types.TransactWriteItem, ret types.ReturnValuesOnConditionCheckFailure) {
if ret == "" {
return
}
switch {
case wti.ConditionCheck != nil:
wti.ConditionCheck.ReturnValuesOnConditionCheckFailure = ret
case wti.Delete != nil:
wti.Delete.ReturnValuesOnConditionCheckFailure = ret
case wti.Put != nil:
wti.Put.ReturnValuesOnConditionCheckFailure = ret
case wti.Update != nil:
wti.Update.ReturnValuesOnConditionCheckFailure = ret
}
}

func (tx *WriteTx) setError(err error) {
if tx.err == nil {
tx.err = err
1 change: 1 addition & 0 deletions tx_test.go
Original file line number Diff line number Diff line change
@@ -143,6 +143,7 @@ func TestTx(t *testing.T) {
tx := testDB.WriteTx()
tx.Put(table.Put(widget{UserID: 69, Time: date1}).If("'BadField' = ?", "should not exist"))
tx.Put(table.Put(widget{UserID: 69, Time: date2}).If("'BadField' = ?", "should not exist"))
tx.IncludeAllItemsInCondCheckFail(true)
err := tx.Run(ctx)
if err == nil {
t.Fatal("expected error")
18 changes: 16 additions & 2 deletions update.go
Original file line number Diff line number Diff line change
@@ -13,8 +13,10 @@ import (
// It uses the UpdateItem API.
// See: http://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_UpdateItem.html
type Update struct {
table Table
table Table

returnType types.ReturnValue
onCondFail types.ReturnValuesOnConditionCheckFailure

hashKey string
hashValue types.AttributeValue
@@ -347,6 +349,7 @@ func (u *Update) OnlyUpdatedOldValue(ctx context.Context, out interface{}) error
// See also: [UnmarshalItemFromCondCheckFailed].
func (u *Update) CurrentValue(ctx context.Context, out interface{}) (wrote bool, err error) {
u.returnType = types.ReturnValueAllNew
u.onCondFail = types.ReturnValuesOnConditionCheckFailureAllOld
output, err := u.run(ctx)
if err != nil {
if ok, err := UnmarshalItemFromCondCheckFailed(err, out); ok {
@@ -357,6 +360,17 @@ func (u *Update) CurrentValue(ctx context.Context, out interface{}) (wrote bool,
return true, unmarshalItem(output.Attributes, out)
}

// IncludeAllItemsInCondCheckFail specifies whether an item update that fails its condition check should include the item itself in the error.
// Such items can be extracted using [UnmarshalItemFromCondCheckFailed] for single updates, or [UnmarshalItemsFromTxCondCheckFailed] for write transactions.
func (u *Update) IncludeItemInCondCheckFail(enabled bool) *Update {
if enabled {
u.onCondFail = types.ReturnValuesOnConditionCheckFailureAllOld
} else {
u.onCondFail = types.ReturnValuesOnConditionCheckFailureNone
}
return u
}

func (u *Update) run(ctx context.Context) (*dynamodb.UpdateItemOutput, error) {
if u.err != nil {
return nil, u.err
@@ -387,7 +401,7 @@ func (u *Update) updateInput() *dynamodb.UpdateItemInput {
}
if u.condition != "" {
input.ConditionExpression = &u.condition
input.ReturnValuesOnConditionCheckFailure = types.ReturnValuesOnConditionCheckFailureAllOld
input.ReturnValuesOnConditionCheckFailure = u.onCondFail
}
if u.cc != nil {
input.ReturnConsumedCapacity = types.ReturnConsumedCapacityIndexes

0 comments on commit 973bed6

Please sign in to comment.