1use crate::actions::helpers;
2use crate::errors::{DynoxideError, Result};
3use crate::expressions;
4use crate::storage_backend::StorageBackend;
5use crate::types::{AttributeValue, Item};
6use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8
9const MAX_RESPONSE_SIZE: usize = 1_048_576;
11
12#[derive(Debug, Default, Deserialize)]
14struct ScanRequestRaw {
15 #[serde(rename = "TableName", default)]
16 table_name: Option<String>,
17 #[serde(rename = "FilterExpression", default)]
18 filter_expression: Option<String>,
19 #[serde(rename = "ProjectionExpression", default)]
20 projection_expression: Option<String>,
21 #[serde(rename = "ExpressionAttributeNames", default)]
22 expression_attribute_names: Option<HashMap<String, String>>,
23 #[serde(
24 rename = "ExpressionAttributeValues",
25 default,
26 deserialize_with = "crate::types::deserialize_older_expression_values"
27 )]
28 expression_attribute_values: Option<HashMap<String, AttributeValue>>,
29 #[serde(rename = "Limit", default)]
30 limit: Option<usize>,
31 #[serde(rename = "ExclusiveStartKey", default)]
32 exclusive_start_key: Option<serde_json::Value>,
33 #[serde(rename = "Select", default)]
34 select: Option<String>,
35 #[serde(
36 rename = "ConsistentRead",
37 default,
38 deserialize_with = "crate::types::deserialize_lenient_flag"
39 )]
40 consistent_read: Option<bool>,
41 #[serde(rename = "IndexName", default)]
42 index_name: Option<String>,
43 #[serde(rename = "Segment", default)]
47 segment: Option<i64>,
48 #[serde(rename = "TotalSegments", default)]
49 total_segments: Option<u32>,
50 #[serde(rename = "ReturnConsumedCapacity", default)]
51 return_consumed_capacity: Option<String>,
52 #[serde(rename = "AttributesToGet", default)]
53 attributes_to_get: Option<Vec<String>>,
54 #[serde(rename = "ScanFilter", default)]
55 scan_filter: Option<serde_json::Value>,
56 #[serde(rename = "ConditionalOperator", default)]
57 conditional_operator: Option<String>,
58}
59
60#[derive(Debug, Default)]
61pub struct ScanRequest {
62 pub table_name: String,
63 pub filter_expression: Option<String>,
64 pub projection_expression: Option<String>,
65 pub expression_attribute_names: Option<HashMap<String, String>>,
66 pub expression_attribute_values: Option<HashMap<String, AttributeValue>>,
67 pub limit: Option<usize>,
68 pub exclusive_start_key: Option<HashMap<String, AttributeValue>>,
69 pub select: Option<String>,
70 pub consistent_read: Option<bool>,
71 pub index_name: Option<String>,
72 pub segment: Option<u32>,
73 pub total_segments: Option<u32>,
74 pub return_consumed_capacity: Option<String>,
75 pub attributes_to_get: Option<Vec<String>>,
76 pub scan_filter: Option<serde_json::Value>,
77 pub conditional_operator: Option<String>,
78 pub exclusive_start_key_raw: Option<serde_json::Value>,
81}
82
83impl<'de> serde::Deserialize<'de> for ScanRequest {
84 fn deserialize<D: serde::Deserializer<'de>>(
85 deserializer: D,
86 ) -> std::result::Result<Self, D::Error> {
87 let raw = ScanRequestRaw::deserialize(deserializer)?;
88 use crate::validation::format_validation_errors;
89
90 if let Some(message) = crate::types::malformed_condition_boolean(raw.scan_filter.as_ref()) {
94 return Err(serde::de::Error::custom(message));
95 }
96
97 let mut errors = Vec::new();
103
104 if let Some(ref sel) = raw.select
106 && ![
107 "ALL_ATTRIBUTES",
108 "ALL_PROJECTED_ATTRIBUTES",
109 "COUNT",
110 "SPECIFIC_ATTRIBUTES",
111 ]
112 .contains(&sel.as_str())
113 {
114 errors.push(format!(
115 "Value '{}' at 'select' failed to satisfy constraint: \
116 Member must satisfy enum value set: [SPECIFIC_ATTRIBUTES, COUNT, ALL_ATTRIBUTES, ALL_PROJECTED_ATTRIBUTES]",
117 sel
118 ));
119 }
120
121 if let Some(msg) = crate::validation::return_consumed_capacity_rejection(
123 raw.return_consumed_capacity.as_deref(),
124 ) {
125 errors.push(msg);
126 }
127
128 if let Some(msg) =
130 crate::validation::conditional_operator_rejection(raw.conditional_operator.as_deref())
131 {
132 errors.push(msg);
133 }
134
135 if let Some(limit) = raw.limit
140 && limit == 0
141 {
142 errors.push(
143 "Value '0' at 'limit' failed to satisfy constraint: \
144 Member must have value greater than or equal to 1"
145 .to_string(),
146 );
147 }
148
149 if let Some(segment) = raw.segment
152 && segment < 0
153 {
154 errors.push(format!(
155 "Value '{}' at 'segment' failed to satisfy constraint: \
156 Member must have value greater than or equal to 0",
157 segment
158 ));
159 }
160
161 errors.extend(helpers::missing_comparison_operators(
165 raw.scan_filter.as_ref(),
166 "scanFilter",
167 ));
168
169 match raw.table_name.as_deref() {
170 None | Some("") => errors.extend(crate::validation::table_name_constraint_errors(
171 raw.table_name.as_deref(),
172 crate::validation::TableNameContext::ReadWrite,
173 )),
174 Some(_) => {}
175 }
176
177 if let Some(msg) = format_validation_errors(&errors) {
178 return Err(serde::de::Error::custom(format!("VALIDATION:{}", msg)));
179 }
180
181 if let Some(name) = raw.table_name.as_deref()
182 && let Some(msg) =
183 format_validation_errors(&crate::validation::scan_table_name_errors(name))
184 {
185 return Err(serde::de::Error::custom(format!("VALIDATION:{}", msg)));
186 }
187 let table_name = raw.table_name.unwrap_or_default();
188
189 Ok(ScanRequest {
190 table_name,
191 filter_expression: raw.filter_expression,
192 projection_expression: raw.projection_expression,
193 expression_attribute_names: raw.expression_attribute_names,
194 expression_attribute_values: raw.expression_attribute_values,
195 limit: raw.limit,
196 exclusive_start_key: None,
197 select: raw.select,
198 consistent_read: raw.consistent_read,
199 index_name: raw.index_name,
200 segment: raw.segment.map(|s| s as u32),
201 total_segments: raw.total_segments,
202 return_consumed_capacity: raw.return_consumed_capacity,
203 attributes_to_get: raw.attributes_to_get,
204 scan_filter: raw.scan_filter,
205 conditional_operator: raw.conditional_operator,
206 exclusive_start_key_raw: raw.exclusive_start_key,
207 })
208 }
209}
210
211#[derive(Debug, Default, Serialize)]
212pub struct ScanResponse {
213 #[serde(
214 rename = "Items",
215 skip_serializing_if = "Option::is_none",
216 serialize_with = "crate::set_order::items_in_hash_order"
217 )]
218 pub items: Option<Vec<Item>>,
221 #[serde(rename = "Count")]
222 pub count: usize,
223 #[serde(rename = "ScannedCount")]
224 pub scanned_count: usize,
225 #[serde(rename = "LastEvaluatedKey", skip_serializing_if = "Option::is_none")]
226 pub last_evaluated_key: Option<HashMap<String, AttributeValue>>,
227 #[serde(rename = "ConsumedCapacity", skip_serializing_if = "Option::is_none")]
228 pub consumed_capacity: Option<crate::types::ConsumedCapacity>,
229}
230
231pub async fn execute<S: StorageBackend>(
232 storage: &S,
233 mut request: ScanRequest,
234) -> Result<ScanResponse> {
235 crate::validation::validate_scan_table_name(&request.table_name)?;
237
238 if let Some(msg) =
243 crate::validation::conditional_operator_rejection(request.conditional_operator.as_deref())
244 {
245 return Err(DynoxideError::ValidationException(
246 crate::validation::envelope_message(&msg),
247 ));
248 }
249
250 if crate::empty_names::names_include_empty(request.attributes_to_get.as_ref())
256 || crate::empty_names::filter_values_have_empty_name(request.scan_filter.as_ref())
257 {
258 return Err(crate::empty_names::bare());
259 }
260 if request
261 .exclusive_start_key
262 .as_ref()
263 .is_some_and(crate::empty_names::map_has_empty_name)
264 || request
265 .exclusive_start_key_raw
266 .as_ref()
267 .is_some_and(crate::empty_names::json_map_has_empty_name)
268 {
269 return Err(DynoxideError::ValidationException(format!(
270 "The provided starting key is invalid: {}",
271 crate::empty_names::MESSAGE
272 )));
273 }
274 crate::empty_names::check_names(request.expression_attribute_names.as_ref())?;
275 if let Some(key) =
276 crate::empty_names::value_placeholder(request.expression_attribute_values.as_ref(), None)
277 {
278 return Err(DynoxideError::ValidationException(
279 crate::empty_names::value_placeholder_message(key),
280 ));
281 }
282
283 {
285 let mut non_expr = Vec::new();
286 let mut expr = Vec::new();
287 if request.attributes_to_get.is_some() {
288 non_expr.push("AttributesToGet");
289 }
290 if request.scan_filter.is_some()
291 && request.scan_filter.as_ref().is_some_and(|v| !v.is_null())
292 {
293 non_expr.push("ScanFilter");
294 }
295 if request.conditional_operator.is_some() {
296 non_expr.push("ConditionalOperator");
297 }
298 if request.projection_expression.is_some() {
299 expr.push("ProjectionExpression");
300 }
301 if request.filter_expression.is_some() {
302 expr.push("FilterExpression");
303 }
304 let no_raw_eav: Option<serde_json::Value> = None;
305 let ctx = helpers::ExpressionParamContext {
306 non_expression_params: non_expr,
307 expression_params: expr,
308 all_expression_param_names: vec!["FilterExpression"],
309 expression_attribute_names: &request.expression_attribute_names,
310 expression_attribute_values: &request.expression_attribute_values,
311 expression_attribute_values_raw: &no_raw_eav,
312 layer: crate::validation::ValueLayer::Older,
313 };
314 helpers::validate_expression_params(&ctx)?;
315 }
316
317 if let Some(msg) = crate::validation::conditional_operator_misuse(
321 request.conditional_operator.as_deref(),
322 crate::validation::legacy_condition_count(request.scan_filter.as_ref()),
323 ) {
324 return Err(DynoxideError::ValidationException(msg.to_string()));
325 }
326 crate::value_nesting::check_scan_filter(request.scan_filter.as_ref())?;
328
329 helpers::validate_filter_conditions_raw(
331 request.scan_filter.as_ref(),
332 "ScanFilter",
333 crate::validation::ValueLayer::Older,
334 )?;
335
336 helpers::validate_filter_condition_args(request.scan_filter.as_ref(), "scanFilter")?;
338
339 if let Some(ref attrs) = request.attributes_to_get {
341 helpers::validate_attributes_to_get_no_duplicates(attrs)?;
342 }
343
344 let exclusive_start_key = if let Some(ref esk_val) = request.exclusive_start_key_raw {
346 Some(helpers::parse_exclusive_start_key(
347 esk_val,
348 crate::validation::ValueLayer::Older,
349 )?)
350 } else {
351 request.exclusive_start_key.clone()
352 };
353 crate::value_nesting::check_scan_start_key(exclusive_start_key.as_ref())?;
354
355 let scan_filter: Option<HashMap<String, helpers::FilterCondition>> =
357 match request.scan_filter.as_ref() {
358 Some(sf_val) if request.filter_expression.is_none() && !sf_val.is_null() => Some(
359 helpers::read_legacy_conditions(sf_val, crate::validation::ValueLayer::Older)?,
360 ),
361 _ => None,
362 };
363 if let Some(sf) = scan_filter
364 && !sf.is_empty()
365 {
366 let converted =
367 helpers::convert_filter_conditions(&sf, request.conditional_operator.as_deref())?;
368 if !converted.expression.is_empty() {
369 request.filter_expression = Some(converted.expression);
370 let expr_values = request
371 .expression_attribute_values
372 .get_or_insert_with(HashMap::new);
373 expr_values.extend(converted.attribute_values);
374 let expr_names = request
375 .expression_attribute_names
376 .get_or_insert_with(HashMap::new);
377 expr_names.extend(converted.attribute_names);
378 }
379 }
380
381 match (request.segment, request.total_segments) {
383 (Some(segment), Some(total)) => {
384 if !(1..=1_000_000).contains(&total) {
385 return Err(DynoxideError::ValidationException(
386 "1 validation error detected: Value at 'totalSegments' failed to satisfy constraint: \
387 Member must have value between 1 and 1000000".to_string(),
388 ));
389 }
390 if segment >= total {
391 return Err(DynoxideError::ValidationException(format!(
392 "The Segment parameter is zero-based and must be less than parameter TotalSegments: Segment: {} is not less than TotalSegments: {}",
393 segment, total
394 )));
395 }
396 }
397 (Some(_), None) => {
398 return Err(DynoxideError::ValidationException(
399 "The TotalSegments parameter is required but was not present in the request when Segment parameter is present".to_string(),
400 ));
401 }
402 (None, Some(_)) => {
403 return Err(DynoxideError::ValidationException(
404 "The Segment parameter is required but was not present in the request when parameter TotalSegments is present".to_string(),
405 ));
406 }
407 (None, None) => {}
408 }
409
410 if let Some(ref filter_expr_str) = request.filter_expression {
413 if filter_expr_str.is_empty() {
414 if request.scan_filter.is_none() || request.filter_expression.as_deref() == Some("") {
417 return Err(DynoxideError::ValidationException(
418 "Invalid FilterExpression: The expression can not be empty;".to_string(),
419 ));
420 }
421 } else {
422 expressions::check_expression_size_with_figure(filter_expr_str).map_err(|e| {
425 DynoxideError::ValidationException(format!("Invalid FilterExpression: {e}"))
426 })?;
427 let parsed_fe = expressions::condition::parse(filter_expr_str).map_err(|e| {
429 DynoxideError::ValidationException(format!("Invalid FilterExpression: {e}"))
430 })?;
431 if let Err(e) = expressions::condition::validate_name_refs(
433 &parsed_fe,
434 &request.expression_attribute_names,
435 ) {
436 return Err(DynoxideError::ValidationException(format!(
437 "Invalid FilterExpression: {e}"
438 )));
439 }
440 if let Err(e) = expressions::condition::validate_operand_semantics(
441 &parsed_fe,
442 &request.expression_attribute_names,
443 &request.expression_attribute_values,
444 ) {
445 return Err(DynoxideError::ValidationException(format!(
446 "Invalid FilterExpression: {e}"
447 )));
448 }
449 }
450 }
451 if let Some(ref proj_expr_str) = request.projection_expression
452 && proj_expr_str.is_empty()
453 {
454 return Err(DynoxideError::ValidationException(
455 "Invalid ProjectionExpression: The expression can not be empty;".to_string(),
456 ));
457 }
458
459 if request.select.as_deref() == Some("SPECIFIC_ATTRIBUTES")
461 && request.projection_expression.is_none()
462 && request.attributes_to_get.is_none()
463 {
464 return Err(DynoxideError::ValidationException(
465 "Must specify the AttributesToGet or ProjectionExpression when choosing to get SPECIFIC_ATTRIBUTES"
466 .to_string(),
467 ));
468 }
469
470 if request.projection_expression.is_some()
472 && let Some(select) = request.select.as_deref()
473 && select != "SPECIFIC_ATTRIBUTES"
474 {
475 let target = if select == "COUNT" {
477 "only the Count"
478 } else {
479 select
480 };
481 return Err(DynoxideError::ValidationException(format!(
482 "Cannot specify the ProjectionExpression when choosing to get {target}"
483 )));
484 }
485
486 if request.select.as_deref() == Some("ALL_PROJECTED_ATTRIBUTES") && request.index_name.is_none()
489 {
490 return Err(DynoxideError::ValidationException(
491 "ALL_PROJECTED_ATTRIBUTES can be used only when Querying using an IndexName"
492 .to_string(),
493 ));
494 }
495
496 let meta = helpers::require_table_for_item_op(storage, &request.table_name).await?;
497 let table_key_schema = helpers::parse_key_schema(&meta)?;
498
499 let legacy_projection = if request.projection_expression.is_none() {
502 request
503 .attributes_to_get
504 .as_ref()
505 .map(|attrs| helpers::attributes_to_get_to_projection(attrs))
506 } else {
507 None
508 };
509
510 let lsi_keys = request
512 .index_name
513 .as_ref()
514 .and_then(|idx| super::lsi::parse_lsi_key_schema(&meta, idx).ok());
515 let is_lsi = lsi_keys.is_some();
516
517 if request.consistent_read.unwrap_or(false) && request.index_name.is_some() && !is_lsi {
519 return Err(DynoxideError::ValidationException(
520 "Consistent reads are not supported on global secondary indexes".to_string(),
521 ));
522 }
523
524 let (effective_pk, effective_sk) = if let Some(ref index_name) = request.index_name {
525 if let Some(keys) = lsi_keys {
526 keys
527 } else {
528 reject_vector_index(&meta, index_name)?;
534 super::gsi::parse_gsi_key_schema(&meta, index_name)?
535 }
536 } else {
537 (
538 table_key_schema.partition_key.clone(),
539 table_key_schema.sort_key.clone(),
540 )
541 };
542
543 let (key_hash, key_range): (Vec<String>, Vec<String>) = match request.index_name {
546 Some(ref index_name) if !is_lsi => super::gsi::parse_gsi_key_attrs(&meta, index_name)?,
547 _ => (
548 vec![effective_pk.clone()],
549 effective_sk.iter().cloned().collect(),
550 ),
551 };
552
553 if let Some(ref esk) = exclusive_start_key {
556 let count_msg = if request.index_name.is_some() {
557 "The provided starting key is invalid"
558 } else {
559 "The provided starting key is invalid: The provided key element does not match the schema"
560 };
561 helpers::validate_esk_count_and_index_keys(
562 esk,
563 &meta,
564 request.index_name.as_deref(),
565 count_msg,
566 )?;
567 }
568
569 if let Some(ref index_name) = request.index_name
571 && !is_lsi
572 && let Some(ref select) = request.select
573 && select == "ALL_ATTRIBUTES"
574 {
575 let gsi_defs = super::gsi::parse_gsi_defs(&meta)?;
577 if let Some(gsi) = gsi_defs.iter().find(|g| g.index_name == *index_name)
578 && gsi.projection_type != crate::types::ProjectionType::ALL
579 {
580 return Err(DynoxideError::ValidationException(format!(
581 "One or more parameter values were invalid: \
582 Select type ALL_ATTRIBUTES is not supported for global secondary index {} \
583 because its projection type is not ALL",
584 index_name
585 )));
586 }
587 }
588
589 if let Some(ref esk) = exclusive_start_key {
591 helpers::validate_esk_table_keys(esk, &meta)?;
592 }
593
594 let (start_pk, start_sk) = if let Some(ref esk) = exclusive_start_key {
596 let key_string = |attrs: &[String]| {
597 let values: Option<Vec<&AttributeValue>> = attrs.iter().map(|a| esk.get(a)).collect();
598 values.and_then(|v| super::gsi::composite_key_string(&v))
599 };
600 let pk = key_string(&key_hash);
601 let sk = if key_range.is_empty() {
602 Some(String::new())
603 } else {
604 key_string(&key_range)
605 };
606 (pk, sk)
607 } else {
608 (None, None)
609 };
610
611 let (start_base_pk, start_base_sk) = if is_lsi || request.index_name.is_some() {
617 if let Some(ref esk) = exclusive_start_key {
618 let base_pk = esk
619 .get(&table_key_schema.partition_key)
620 .and_then(|v| v.to_key_string());
621 let base_sk = if let Some(sk_name) = table_key_schema.sort_key.as_ref() {
629 esk.get(sk_name).and_then(|v| v.to_key_string())
630 } else {
631 Some(String::new())
632 };
633 (base_pk, base_sk)
634 } else {
635 (None, None)
636 }
637 } else {
638 (None, None)
639 };
640
641 let scan_params = crate::storage::ScanParams {
643 limit: request.limit,
644 exclusive_start_pk: start_pk.as_deref(),
645 exclusive_start_sk: start_sk.as_deref(),
646 segment: request.segment,
647 total_segments: request.total_segments,
648 exclusive_start_base_pk: start_base_pk.as_deref(),
649 exclusive_start_base_sk: start_base_sk.as_deref(),
650 };
651 let rows = if let Some(ref index_name) = request.index_name {
652 if is_lsi {
653 storage
654 .scan_lsi_items(&request.table_name, index_name, &scan_params)
655 .await?
656 } else {
657 storage
658 .scan_gsi_items(&request.table_name, index_name, &scan_params)
659 .await?
660 }
661 } else {
662 storage
663 .scan_items(&request.table_name, &scan_params)
664 .await?
665 };
666
667 let tracker = crate::expressions::TrackedExpressionAttributes::new(
669 &request.expression_attribute_names,
670 &request.expression_attribute_values,
671 );
672
673 let filter_expr = request
675 .filter_expression
676 .as_ref()
677 .map(|expr| expressions::condition::parse(expr))
678 .transpose()
679 .map_err(DynoxideError::ValidationException)?;
680
681 if let Some(ref filter) = filter_expr {
683 let mut base_key_attrs = vec![table_key_schema.partition_key.clone()];
685 if let Some(ref sk) = table_key_schema.sort_key {
686 base_key_attrs.push(sk.clone());
687 }
688 let mut index_key_attrs = Vec::new();
689 if request.index_name.is_some() {
690 for attr in key_hash.iter().chain(&key_range) {
691 if !base_key_attrs.contains(attr) {
692 index_key_attrs.push(attr.clone());
693 }
694 }
695 }
696 if let Some((attr, is_index)) = expressions::condition::check_non_scalar_key_access(
697 filter,
698 &request.expression_attribute_names,
699 &base_key_attrs,
700 &index_key_attrs,
701 ) {
702 let prefix = if is_index { "IndexKey" } else { "Key" };
703 return Err(DynoxideError::ValidationException(format!(
704 "Key attributes must be scalars; \
705 list random access '[]' and map lookup '.' are not allowed: {prefix}: {attr}"
706 )));
707 }
708 }
709
710 let projection = if let Some(ref proj_expr) = request.projection_expression {
712 Some(
713 expressions::projection::parse(proj_expr)
714 .map_err(DynoxideError::ValidationException)?,
715 )
716 } else {
717 legacy_projection.clone()
718 };
719
720 if let Some(ref filter) = filter_expr {
722 tracker.track_condition_expr(filter);
723 }
724 if let Some(ref proj) = projection {
725 tracker.track_projection_expr(proj);
726 expressions::projection::validate(proj, &tracker)
729 .map_err(DynoxideError::ValidationException)?;
730 }
731
732 let loop_tracker = crate::expressions::TrackedExpressionAttributes::without_tracking(
734 &request.expression_attribute_names,
735 &request.expression_attribute_values,
736 );
737
738 let is_count = request
740 .select
741 .as_deref()
742 .map(|s| s.eq_ignore_ascii_case("COUNT"))
743 .unwrap_or(false);
744
745 let mut key_attrs = vec![effective_pk.clone()];
747 if let Some(ref sk) = effective_sk {
748 key_attrs.push(sk.clone());
749 }
750 if request.index_name.is_some() {
751 if !key_attrs.contains(&table_key_schema.partition_key) {
752 key_attrs.push(table_key_schema.partition_key.clone());
753 }
754 if let Some(ref sk) = table_key_schema.sort_key
755 && !key_attrs.contains(sk)
756 {
757 key_attrs.push(sk.clone());
758 }
759 }
760
761 let mut items = Vec::new();
762 let mut scanned_count = 0;
763 let mut filtered_count = 0;
764 let mut cumulative_size = 0;
765 let mut last_evaluated_item: Option<Item> = None;
766 let mut truncated_by_size = false;
767
768 for (_pk, _sk, item_json) in &rows {
769 let item: Item = serde_json::from_str(item_json).map_err(|e| {
770 DynoxideError::InternalServerError(format!("Bad item JSON in storage: {e}"))
771 })?;
772
773 scanned_count += 1;
774
775 let item_size = crate::types::item_size(&item);
778 if cumulative_size + item_size > MAX_RESPONSE_SIZE && scanned_count > 1 {
779 truncated_by_size = true;
780 break;
781 }
782 cumulative_size += item_size;
783
784 if let Some(ref filter) = filter_expr {
786 let passes = expressions::condition::evaluate(filter, &item, &loop_tracker)
787 .map_err(DynoxideError::ValidationException)?;
788 if !passes {
789 last_evaluated_item = Some(item);
790 continue;
791 }
792 }
793
794 filtered_count += 1;
795
796 let result_item = if let Some(ref proj) = projection {
799 let no_keys: &[String] = &[];
800 expressions::projection::apply(&item, proj, &loop_tracker, no_keys)
801 .map_err(DynoxideError::ValidationException)?
802 } else {
803 item.clone()
804 };
805
806 last_evaluated_item = Some(item);
807 if !is_count {
808 items.push(result_item);
809 }
810 }
811
812 tracker.check_unused()?;
814
815 let count = if is_count {
816 filtered_count
817 } else {
818 items.len()
819 };
820
821 let has_more = truncated_by_size
823 || (request.limit.is_some() && scanned_count >= request.limit.unwrap_or(usize::MAX));
824
825 let is_gsi_scan = request.index_name.is_some() && !is_lsi;
830 let last_evaluated_key = if has_more {
831 last_evaluated_item.map(|item| {
832 let mut key: HashMap<String, AttributeValue> = key_hash
833 .iter()
834 .chain(&key_range)
835 .filter_map(|name| item.get(name).map(|v| (name.clone(), v.clone())))
836 .collect();
837 if is_lsi
839 && let Some(tsk) = table_key_schema.sort_key.as_deref()
840 && !key.contains_key(tsk)
841 && let Some(v) = item.get(tsk)
842 {
843 key.insert(tsk.to_string(), v.clone());
844 }
845 if is_gsi_scan {
847 if !key.contains_key(&table_key_schema.partition_key)
848 && let Some(v) = item.get(&table_key_schema.partition_key)
849 {
850 key.insert(table_key_schema.partition_key.clone(), v.clone());
851 }
852 if let Some(ref tsk) = table_key_schema.sort_key
853 && !key.contains_key(tsk)
854 && let Some(v) = item.get(tsk)
855 {
856 key.insert(tsk.clone(), v.clone());
857 }
858 }
859 key
860 })
861 } else {
862 None
863 };
864
865 let is_gsi = is_gsi_scan;
867 let consistent = request.consistent_read.unwrap_or(false);
868 let consumed_capacity = if is_gsi {
869 let mut gsi_units = std::collections::HashMap::new();
870 gsi_units.insert(
871 request.index_name.as_ref().unwrap().clone(),
872 crate::types::read_capacity_units_with_consistency(cumulative_size, consistent),
873 );
874 crate::types::consumed_capacity_with_indexes(
875 &request.table_name,
876 0.0,
877 &gsi_units,
878 &request.return_consumed_capacity,
879 )
880 } else {
881 crate::types::consumed_capacity(
882 &request.table_name,
883 crate::types::read_capacity_units_with_consistency(cumulative_size, consistent),
884 &request.return_consumed_capacity,
885 )
886 };
887
888 Ok(ScanResponse {
889 items: if is_count { None } else { Some(items) },
890 count,
891 scanned_count,
892 last_evaluated_key,
893 consumed_capacity,
894 })
895}
896
897fn reject_vector_index(
903 meta: &crate::storage::TableMetadata,
904 index_name: &str,
905) -> crate::errors::Result<()> {
906 if super::vector_index::parse_vector_defs(meta)?
907 .iter()
908 .any(|v| v.index_name == index_name)
909 {
910 return Err(DynoxideError::ValidationException(
911 "Scan operation not supported on this index type".to_string(),
912 ));
913 }
914 Ok(())
915}