1use crate::actions::helpers;
2use crate::actions::index_capacity::{
3 WriteCapacity, aggregate_by_table, per_table_capacity, transactional_read_units,
4};
5use crate::errors::{CancellationReason, DynoxideError, Result};
6use crate::storage_backend::StorageBackend;
7use crate::types::{self, AttributeValue, Item};
8use serde::{Deserialize, Serialize};
9use std::collections::{HashMap, HashSet};
10
11#[derive(Debug, Clone, Default, Deserialize, Serialize)]
12pub struct TransactWriteItemsRequest {
13 #[serde(rename = "TransactItems")]
14 pub transact_items: Vec<TransactWriteItem>,
15 #[serde(rename = "ClientRequestToken", default)]
16 pub client_request_token: Option<String>,
17 #[serde(rename = "ReturnConsumedCapacity", default)]
18 pub return_consumed_capacity: Option<String>,
19 #[serde(rename = "ReturnItemCollectionMetrics", default)]
20 pub return_item_collection_metrics: Option<String>,
21}
22
23#[derive(Debug, Clone, Default, Deserialize, Serialize)]
24pub struct TransactWriteItem {
25 #[serde(rename = "Put", default)]
26 pub put: Option<TransactPut>,
27 #[serde(rename = "Update", default)]
28 pub update: Option<TransactUpdate>,
29 #[serde(rename = "Delete", default)]
30 pub delete: Option<TransactDelete>,
31 #[serde(rename = "ConditionCheck", default)]
32 pub condition_check: Option<TransactConditionCheck>,
33}
34
35#[derive(Debug, Clone, Default, Deserialize, Serialize)]
36pub struct TransactPut {
37 #[serde(rename = "TableName")]
38 pub table_name: String,
39 #[serde(rename = "Item")]
40 pub item: Item,
41 #[serde(rename = "ConditionExpression", default)]
42 pub condition_expression: Option<String>,
43 #[serde(rename = "ExpressionAttributeNames", default)]
44 pub expression_attribute_names: Option<HashMap<String, String>>,
45 #[serde(rename = "ExpressionAttributeValues", default)]
46 pub expression_attribute_values: Option<HashMap<String, AttributeValue>>,
47 #[serde(rename = "ReturnValuesOnConditionCheckFailure", default)]
48 pub return_values_on_condition_check_failure: Option<String>,
49}
50
51#[derive(Debug, Clone, Default, Deserialize, Serialize)]
52pub struct TransactUpdate {
53 #[serde(rename = "TableName")]
54 pub table_name: String,
55 #[serde(rename = "Key")]
56 pub key: HashMap<String, AttributeValue>,
57 #[serde(rename = "UpdateExpression")]
58 pub update_expression: String,
59 #[serde(rename = "ConditionExpression", default)]
60 pub condition_expression: Option<String>,
61 #[serde(rename = "ExpressionAttributeNames", default)]
62 pub expression_attribute_names: Option<HashMap<String, String>>,
63 #[serde(rename = "ExpressionAttributeValues", default)]
64 pub expression_attribute_values: Option<HashMap<String, AttributeValue>>,
65 #[serde(rename = "ReturnValuesOnConditionCheckFailure", default)]
66 pub return_values_on_condition_check_failure: Option<String>,
67}
68
69#[derive(Debug, Clone, Default, Deserialize, Serialize)]
70pub struct TransactDelete {
71 #[serde(rename = "TableName")]
72 pub table_name: String,
73 #[serde(rename = "Key")]
74 pub key: HashMap<String, AttributeValue>,
75 #[serde(rename = "ConditionExpression", default)]
76 pub condition_expression: Option<String>,
77 #[serde(rename = "ExpressionAttributeNames", default)]
78 pub expression_attribute_names: Option<HashMap<String, String>>,
79 #[serde(rename = "ExpressionAttributeValues", default)]
80 pub expression_attribute_values: Option<HashMap<String, AttributeValue>>,
81 #[serde(rename = "ReturnValuesOnConditionCheckFailure", default)]
82 pub return_values_on_condition_check_failure: Option<String>,
83}
84
85#[derive(Debug, Clone, Default, Deserialize, Serialize)]
86pub struct TransactConditionCheck {
87 #[serde(rename = "TableName")]
88 pub table_name: String,
89 #[serde(rename = "Key")]
90 pub key: HashMap<String, AttributeValue>,
91 #[serde(rename = "ConditionExpression")]
92 pub condition_expression: String,
93 #[serde(rename = "ExpressionAttributeNames", default)]
94 pub expression_attribute_names: Option<HashMap<String, String>>,
95 #[serde(rename = "ExpressionAttributeValues", default)]
96 pub expression_attribute_values: Option<HashMap<String, AttributeValue>>,
97 #[serde(rename = "ReturnValuesOnConditionCheckFailure", default)]
98 pub return_values_on_condition_check_failure: Option<String>,
99}
100
101#[derive(Debug, Clone, Default, Serialize)]
102pub struct TransactWriteItemsResponse {
103 #[serde(rename = "ConsumedCapacity", skip_serializing_if = "Option::is_none")]
104 pub consumed_capacity: Option<Vec<crate::types::ConsumedCapacity>>,
105 #[serde(
108 rename = "ItemCollectionMetrics",
109 skip_serializing_if = "Option::is_none"
110 )]
111 pub item_collection_metrics: Option<HashMap<String, Vec<crate::types::ItemCollectionMetrics>>>,
112}
113
114#[derive(Debug, Clone, Default)]
121pub(crate) struct CachedWrite {
122 pub(crate) response: TransactWriteItemsResponse,
123 pub(crate) replay_sizes: Vec<(String, usize)>,
125}
126
127pub async fn execute<S: StorageBackend>(
132 storage: &S,
133 request: TransactWriteItemsRequest,
134) -> Result<TransactWriteItemsResponse> {
135 Ok(execute_cached(storage, request).await?.response)
136}
137
138pub(crate) async fn execute_cached<S: StorageBackend>(
139 storage: &S,
140 request: TransactWriteItemsRequest,
141) -> Result<CachedWrite> {
142 let items = &request.transact_items;
143
144 if items.is_empty() {
146 return Err(DynoxideError::ValidationException(
147 "1 validation error detected: Value '[]' at 'transactItems' failed to satisfy constraint: Member must have length greater than or equal to 1".to_string(),
148 ));
149 }
150
151 if items.len() > 100 {
157 let dump = format!("{items:?}");
158 return Err(DynoxideError::ValidationException(format!(
159 "1 validation error detected: Value '[{dump}]' at 'transactItems' failed to satisfy constraint: Member must have length less than or equal to 100"
160 )));
161 }
162
163 let mut seen_targets = HashSet::new();
166 for item in items {
167 if let Some(target) = get_item_target(storage, item).await?
168 && !seen_targets.insert(target)
169 {
170 return Err(DynoxideError::ValidationException(
171 "Transaction request cannot include multiple operations on one item".to_string(),
172 ));
173 }
174 }
175
176 for item in items {
182 if let Some(ref put) = item.put
183 && types::item_size(&put.item) > types::MAX_ITEM_SIZE
184 {
185 return Err(DynoxideError::ValidationException(
186 "Item size has exceeded the maximum allowed size".to_string(),
187 ));
188 }
189 }
190
191 let total_size: usize = items.iter().map(|i| get_action_table_and_size(i).1).sum();
193 if total_size > 4 * 1024 * 1024 {
194 return Err(DynoxideError::ValidationException(
195 "Collection size of items exceeded, which can also be caused by the aggregate size of the items in the transaction exceeding the 4MB limit".to_string(),
196 ));
197 }
198
199 let capacity_mode = request.return_consumed_capacity.as_deref();
201 let capacity = helpers::with_write_transaction(
202 storage,
203 execute_within_transaction(storage, items, capacity_mode),
204 )
205 .await?;
206
207 Ok(CachedWrite {
208 response: TransactWriteItemsResponse {
209 consumed_capacity: build_write_capacity(&capacity, &request.return_consumed_capacity),
210 item_collection_metrics: None,
211 },
212 replay_sizes: replay_sizes(&capacity),
213 })
214}
215
216fn replay_sizes(capacity: &[WriteCapacity]) -> Vec<(String, usize)> {
222 capacity
223 .iter()
224 .map(|record| {
225 let larger = record
226 .old_size
227 .unwrap_or(0)
228 .max(record.new_size.unwrap_or(0));
229 (record.table_name.clone(), larger)
230 })
231 .collect()
232}
233
234fn build_write_capacity(
241 capacity: &[WriteCapacity],
242 mode: &Option<String>,
243) -> Option<Vec<crate::types::ConsumedCapacity>> {
244 if !matches!(mode.as_deref(), Some("TOTAL") | Some("INDEXES")) {
248 return None;
249 }
250
251 let by_table = aggregate_by_table(capacity, crate::types::TRANSACTIONAL_CAPACITY_FACTOR);
252 per_table_capacity(
253 &by_table,
254 mode,
255 crate::types::transactional_write_capacity_with_indexes,
256 )
257}
258
259pub(crate) fn replay_response(cached: &CachedWrite, mode: &Option<String>) -> CachedWrite {
268 CachedWrite {
269 response: TransactWriteItemsResponse {
270 consumed_capacity: crate::types::build_transactional_capacity(
271 &transactional_read_units(&cached.replay_sizes),
272 mode,
273 crate::types::transactional_read_capacity,
274 ),
275 item_collection_metrics: cached.response.item_collection_metrics.clone(),
276 },
277 replay_sizes: cached.replay_sizes.clone(),
278 }
279}
280
281async fn execute_within_transaction<S: StorageBackend>(
286 storage: &S,
287 items: &[TransactWriteItem],
288 capacity_mode: Option<&str>,
289) -> Result<Vec<WriteCapacity>> {
290 let mut cancellation_reasons: Vec<CancellationReason> = Vec::with_capacity(items.len());
291 let mut capacity: Vec<WriteCapacity> = Vec::with_capacity(items.len());
292 let mut has_failure = false;
293
294 for item in items {
295 let reason = execute_single_action(storage, item, capacity_mode).await;
296 match reason {
297 Ok(action_capacity) => {
298 capacity.push(action_capacity);
299 cancellation_reasons.push(CancellationReason {
300 code: "None".to_string(),
301 message: None,
302 item: None,
303 });
304 }
305 Err(e) => {
306 if matches!(e, DynoxideError::KeyEmptyValueValidation(_)) {
310 return Err(e);
311 }
312 has_failure = true;
313 let message = Some(e.to_string());
314 let (code, item) = match e {
315 DynoxideError::ConditionalCheckFailedException(_, item) => {
316 ("ConditionalCheckFailed".to_string(), item)
317 }
318 DynoxideError::ValidationException(_) => ("ValidationError".to_string(), None),
319 _ => ("InternalError".to_string(), None),
320 };
321 cancellation_reasons.push(CancellationReason {
322 code,
323 message,
324 item,
325 });
326 }
327 }
328 }
329
330 if has_failure {
331 let codes: Vec<&str> = cancellation_reasons
332 .iter()
333 .map(|r| r.code.as_str())
334 .collect();
335 let message = format!(
336 "Transaction cancelled, please refer cancellation reasons for specific reasons [{}]",
337 codes.join(", ")
338 );
339 return Err(DynoxideError::TransactionCanceledException(
340 message,
341 cancellation_reasons,
342 ));
343 }
344
345 Ok(capacity)
346}
347
348async fn execute_single_action<S: StorageBackend>(
349 storage: &S,
350 item: &TransactWriteItem,
351 capacity_mode: Option<&str>,
352) -> Result<WriteCapacity> {
353 if let Some(ref put) = item.put {
354 execute_put(storage, put, capacity_mode).await
355 } else if let Some(ref update) = item.update {
356 execute_update(storage, update, capacity_mode).await
357 } else if let Some(ref delete) = item.delete {
358 execute_delete(storage, delete, capacity_mode).await
359 } else if let Some(ref check) = item.condition_check {
360 execute_condition_check(storage, check).await
361 } else {
362 Err(DynoxideError::ValidationException(
363 "TransactItem must contain exactly one of Put, Update, Delete, or ConditionCheck"
364 .to_string(),
365 ))
366 }
367}
368
369fn validate_eav_nesting(values: &Option<HashMap<String, AttributeValue>>) -> Result<()> {
373 if let Some(map) = values {
374 for value in map.values() {
375 crate::validation::validate_nesting_depth(value)?;
376 }
377 }
378 Ok(())
379}
380
381async fn execute_put<S: StorageBackend>(
382 storage: &S,
383 put: &TransactPut,
384 capacity_mode: Option<&str>,
385) -> Result<WriteCapacity> {
386 crate::validation::validate_table_name(&put.table_name)?;
387 let meta = helpers::require_table_for_item_op(storage, &put.table_name).await?;
388 let key_schema = helpers::parse_key_schema(&meta)?;
389
390 helpers::validate_item_keys(&put.item, &key_schema, &meta)?;
391 crate::validation::validate_item_attribute_values(&put.item)?;
392
393 let mut item = put.item.clone();
395 crate::validation::normalize_item_sets(&mut item);
396
397 let size = types::item_size(&item);
400
401 let (pk, sk) = helpers::extract_key_strings(&item, &key_schema)?;
403
404 validate_eav_nesting(&put.expression_attribute_values)?;
405
406 let tracker = crate::expressions::TrackedExpressionAttributes::new(
407 &put.expression_attribute_names,
408 &put.expression_attribute_values,
409 );
410
411 if let Some(ref cond_expr) = put.condition_expression
413 && let Ok(parsed) = crate::expressions::condition::parse(cond_expr)
414 {
415 tracker.track_condition_expr(&parsed);
416 }
417
418 if let Some(ref cond_expr) = put.condition_expression {
420 let existing_json = storage.get_item(&put.table_name, &pk, &sk).await?;
421 let existing_item: Item = existing_json
422 .as_ref()
423 .and_then(|j| serde_json::from_str(j).ok())
424 .unwrap_or_default();
425
426 let return_item = if put.return_values_on_condition_check_failure.as_deref()
427 == Some("ALL_OLD")
428 && !existing_item.is_empty()
429 {
430 Some(existing_item.clone())
431 } else {
432 None
433 };
434 check_condition_tracked(cond_expr, &existing_item, &tracker, return_item)?;
435 }
436
437 tracker.check_unused()?;
438
439 let item_json = serde_json::to_string(&item)
440 .map_err(|e| DynoxideError::InternalServerError(e.to_string()))?;
441 let hash_prefix = item
442 .get(&key_schema.partition_key)
443 .map(crate::storage::compute_hash_prefix)
444 .unwrap_or_default();
445 let old_json = storage
446 .put_item_with_hash(&put.table_name, &pk, &sk, &item_json, size, &hash_prefix)
447 .await?;
448
449 let old_item: Option<Item> = old_json.and_then(|j| serde_json::from_str(&j).ok());
450
451 let target = super::gsi::IndexWrite {
452 table_name: &put.table_name,
453 pk: &pk,
454 sk: &sk,
455 pk_attr: &key_schema.partition_key,
456 sk_attr: key_schema.sort_key.as_deref(),
457 old_item: old_item.as_ref(),
458 capacity_mode,
459 };
460
461 let gsi_units = super::gsi::maintain_gsis_after_write(storage, &meta, &target, &item).await?;
462
463 let lsi_units = super::lsi::maintain_lsis_after_write(storage, &meta, &target, &item).await?;
464
465 let vector_bytes =
466 super::vector_index::maintain_vector_indexes_after_write(storage, &meta, &target, &item)
467 .await?;
468
469 crate::streams::record_stream_event(storage, &meta, old_item.as_ref(), Some(&item)).await?;
471
472 Ok(WriteCapacity::new(
473 &put.table_name,
474 old_item.as_ref().map(types::item_size),
475 Some(size),
476 gsi_units,
477 lsi_units,
478 )
479 .with_vector_bytes(vector_bytes))
480}
481
482async fn execute_update<S: StorageBackend>(
483 storage: &S,
484 update: &TransactUpdate,
485 capacity_mode: Option<&str>,
486) -> Result<WriteCapacity> {
487 crate::validation::validate_table_name(&update.table_name)?;
488 let meta = helpers::require_table_for_item_op(storage, &update.table_name).await?;
489 let key_schema = helpers::parse_key_schema(&meta)?;
490
491 helpers::validate_key_only(&update.key, &key_schema)?;
492 let (pk, sk) = helpers::extract_key_strings(&update.key, &key_schema)?;
494
495 let existing_json = storage.get_item(&update.table_name, &pk, &sk).await?;
496 let existing_item: Item = existing_json
497 .as_ref()
498 .and_then(|j| serde_json::from_str(j).ok())
499 .unwrap_or_default();
500
501 validate_eav_nesting(&update.expression_attribute_values)?;
502
503 let tracker = crate::expressions::TrackedExpressionAttributes::new(
504 &update.expression_attribute_names,
505 &update.expression_attribute_values,
506 );
507
508 if let Some(ref cond_expr) = update.condition_expression
510 && let Ok(parsed) = crate::expressions::condition::parse(cond_expr)
511 {
512 tracker.track_condition_expr(&parsed);
513 }
514 if let Ok(parsed) = crate::expressions::update::parse(&update.update_expression) {
515 tracker.track_update_expr(&parsed);
516 }
517
518 if let Some(ref cond_expr) = update.condition_expression {
522 let return_item = if update.return_values_on_condition_check_failure.as_deref()
523 == Some("ALL_OLD")
524 && existing_json.is_some()
525 {
526 Some(existing_item.clone())
527 } else {
528 None
529 };
530 check_condition_tracked(cond_expr, &existing_item, &tracker, return_item)?;
531 }
532
533 let mut item = existing_item;
536 if existing_json.is_none() {
537 for (k, v) in &update.key {
538 item.insert(k.clone(), v.clone());
539 }
540 }
541 let before_item = item.clone();
542
543 let parsed = crate::expressions::update::parse(&update.update_expression)
545 .map_err(DynoxideError::ValidationException)?;
546 crate::expressions::update::apply(&mut item, &parsed, &tracker)
547 .map_err(DynoxideError::ValidationException)?;
548
549 tracker.check_unused()?;
550
551 crate::validation::validate_item_attribute_values(&item)?;
553 crate::validation::normalize_item_sets(&mut item);
554
555 helpers::validate_updated_index_keys(&before_item, &item, &meta)?;
557
558 let size = types::item_size(&item);
562 if size > types::MAX_ITEM_SIZE {
563 return Err(DynoxideError::ValidationException(
564 "Item size to update has exceeded the maximum allowed size".to_string(),
565 ));
566 }
567
568 let old_for_stream = existing_json.clone();
570
571 let item_json = serde_json::to_string(&item)
572 .map_err(|e| DynoxideError::InternalServerError(e.to_string()))?;
573 let hash_prefix = update
574 .key
575 .get(&key_schema.partition_key)
576 .map(crate::storage::compute_hash_prefix)
577 .unwrap_or_default();
578 storage
579 .put_item_with_hash(&update.table_name, &pk, &sk, &item_json, size, &hash_prefix)
580 .await?;
581
582 let old_item: Option<Item> = old_for_stream.and_then(|j| serde_json::from_str(&j).ok());
583
584 let target = super::gsi::IndexWrite {
585 table_name: &update.table_name,
586 pk: &pk,
587 sk: &sk,
588 pk_attr: &key_schema.partition_key,
589 sk_attr: key_schema.sort_key.as_deref(),
590 old_item: old_item.as_ref(),
591 capacity_mode,
592 };
593
594 let gsi_units = super::gsi::maintain_gsis_after_write(storage, &meta, &target, &item).await?;
595
596 let lsi_units = super::lsi::maintain_lsis_after_write(storage, &meta, &target, &item).await?;
597
598 let vector_bytes =
599 super::vector_index::maintain_vector_indexes_after_write(storage, &meta, &target, &item)
600 .await?;
601
602 crate::streams::record_stream_event(storage, &meta, old_item.as_ref(), Some(&item)).await?;
604
605 Ok(WriteCapacity::new(
610 &update.table_name,
611 old_item.as_ref().map(types::item_size),
612 Some(size),
613 gsi_units,
614 lsi_units,
615 )
616 .with_vector_bytes(vector_bytes))
617}
618
619async fn execute_delete<S: StorageBackend>(
620 storage: &S,
621 delete: &TransactDelete,
622 capacity_mode: Option<&str>,
623) -> Result<WriteCapacity> {
624 crate::validation::validate_table_name(&delete.table_name)?;
625 let meta = helpers::require_table_for_item_op(storage, &delete.table_name).await?;
626 let key_schema = helpers::parse_key_schema(&meta)?;
627
628 helpers::validate_key_only(&delete.key, &key_schema)?;
629 let (pk, sk) = helpers::extract_key_strings(&delete.key, &key_schema)?;
631
632 validate_eav_nesting(&delete.expression_attribute_values)?;
633
634 let tracker = crate::expressions::TrackedExpressionAttributes::new(
635 &delete.expression_attribute_names,
636 &delete.expression_attribute_values,
637 );
638
639 if let Some(ref cond_expr) = delete.condition_expression
641 && let Ok(parsed) = crate::expressions::condition::parse(cond_expr)
642 {
643 tracker.track_condition_expr(&parsed);
644 }
645
646 if let Some(ref cond_expr) = delete.condition_expression {
648 let existing_json = storage.get_item(&delete.table_name, &pk, &sk).await?;
649 let existing_item: Item = existing_json
650 .as_ref()
651 .and_then(|j| serde_json::from_str(j).ok())
652 .unwrap_or_default();
653
654 let return_item = if delete.return_values_on_condition_check_failure.as_deref()
655 == Some("ALL_OLD")
656 && !existing_item.is_empty()
657 {
658 Some(existing_item.clone())
659 } else {
660 None
661 };
662 check_condition_tracked(cond_expr, &existing_item, &tracker, return_item)?;
663 }
664
665 tracker.check_unused()?;
666
667 let old_json = storage.delete_item(&delete.table_name, &pk, &sk).await?;
668 let old_item: Option<Item> = old_json.and_then(|j| serde_json::from_str(&j).ok());
669
670 let target = super::gsi::IndexWrite {
671 table_name: &delete.table_name,
672 pk: &pk,
673 sk: &sk,
674 pk_attr: &key_schema.partition_key,
675 sk_attr: key_schema.sort_key.as_deref(),
676 old_item: old_item.as_ref(),
677 capacity_mode,
678 };
679
680 let gsi_units = super::gsi::maintain_gsis_after_delete(storage, &meta, &target).await?;
681 let lsi_units = super::lsi::maintain_lsis_after_delete(storage, &meta, &target).await?;
682 let vector_bytes =
683 super::vector_index::maintain_vector_indexes_after_delete(storage, &meta, &target).await?;
684
685 if old_item.is_some() {
687 crate::streams::record_stream_event(storage, &meta, old_item.as_ref(), None).await?;
688 }
689
690 Ok(WriteCapacity::from_items(
691 &delete.table_name,
692 old_item.as_ref(),
693 None,
694 gsi_units,
695 lsi_units,
696 )
697 .with_vector_bytes(vector_bytes))
698}
699
700async fn execute_condition_check<S: StorageBackend>(
701 storage: &S,
702 check: &TransactConditionCheck,
703) -> Result<WriteCapacity> {
704 crate::validation::validate_table_name(&check.table_name)?;
705 let meta = helpers::require_table_for_item_op(storage, &check.table_name).await?;
706 let key_schema = helpers::parse_key_schema(&meta)?;
707
708 helpers::validate_key_only(&check.key, &key_schema)?;
709 let (pk, sk) = helpers::extract_key_strings(&check.key, &key_schema)?;
711
712 let existing_json = storage.get_item(&check.table_name, &pk, &sk).await?;
713 let existing_item: Item = existing_json
714 .as_ref()
715 .and_then(|j| serde_json::from_str(j).ok())
716 .unwrap_or_default();
717
718 validate_eav_nesting(&check.expression_attribute_values)?;
719
720 let tracker = crate::expressions::TrackedExpressionAttributes::new(
721 &check.expression_attribute_names,
722 &check.expression_attribute_values,
723 );
724
725 if let Ok(parsed) = crate::expressions::condition::parse(&check.condition_expression) {
727 tracker.track_condition_expr(&parsed);
728 }
729
730 let return_item = if check.return_values_on_condition_check_failure.as_deref()
731 == Some("ALL_OLD")
732 && !existing_item.is_empty()
733 {
734 Some(existing_item.clone())
735 } else {
736 None
737 };
738 check_condition_tracked(
739 &check.condition_expression,
740 &existing_item,
741 &tracker,
742 return_item,
743 )?;
744
745 tracker.check_unused()?;
746 Ok(WriteCapacity::condition_check(
749 &check.table_name,
750 existing_json.is_some().then_some(&existing_item),
751 ))
752}
753
754fn check_condition_tracked(
755 expression: &str,
756 item: &Item,
757 tracker: &crate::expressions::TrackedExpressionAttributes,
758 return_item_on_failure: Option<Item>,
759) -> Result<()> {
760 let parsed = crate::expressions::condition::parse(expression)
761 .map_err(DynoxideError::ValidationException)?;
762 let result = crate::expressions::condition::evaluate(&parsed, item, tracker)
763 .map_err(DynoxideError::ValidationException)?;
764 if !result {
765 return Err(DynoxideError::ConditionalCheckFailedException(
766 "The conditional request failed".to_string(),
767 return_item_on_failure,
768 ));
769 }
770 Ok(())
771}
772
773fn get_action_table_and_size(item: &TransactWriteItem) -> (String, usize) {
779 if let Some(ref put) = item.put {
780 (put.table_name.clone(), types::item_size(&put.item))
781 } else if let Some(ref update) = item.update {
782 let key_size = types::item_size(&update.key);
783 let eav_size = update
784 .expression_attribute_values
785 .as_ref()
786 .map(|vals| vals.values().map(|v| v.size()).sum::<usize>())
787 .unwrap_or(0);
788 (update.table_name.clone(), key_size + eav_size)
789 } else if let Some(ref delete) = item.delete {
790 (delete.table_name.clone(), types::item_size(&delete.key))
791 } else if let Some(ref check) = item.condition_check {
792 (check.table_name.clone(), types::item_size(&check.key))
793 } else {
794 (String::new(), 0)
795 }
796}
797
798async fn target_for<S: StorageBackend>(
802 storage: &S,
803 table_name: &str,
804 key_source: &HashMap<String, AttributeValue>,
805) -> Result<Option<String>> {
806 crate::validation::validate_table_name(table_name)?;
807 let meta = helpers::require_table_for_item_op(storage, table_name).await?;
808 let key_schema = helpers::parse_key_schema(&meta)?;
809 match helpers::extract_key_strings(key_source, &key_schema) {
810 Ok((pk, sk)) => Ok(Some(format!("{table_name}#{pk}#{sk}"))),
811 Err(_) => Ok(None),
812 }
813}
814
815async fn get_item_target<S: StorageBackend>(
818 storage: &S,
819 item: &TransactWriteItem,
820) -> Result<Option<String>> {
821 if let Some(ref put) = item.put {
822 target_for(storage, &put.table_name, &put.item).await
823 } else if let Some(ref update) = item.update {
824 target_for(storage, &update.table_name, &update.key).await
825 } else if let Some(ref delete) = item.delete {
826 target_for(storage, &delete.table_name, &delete.key).await
827 } else if let Some(ref check) = item.condition_check {
828 target_for(storage, &check.table_name, &check.key).await
829 } else {
830 Err(DynoxideError::ValidationException(
831 "TransactItem must contain exactly one action".to_string(),
832 ))
833 }
834}