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(rename = "ExpressionAttributeValues", default)]
24 expression_attribute_values: Option<HashMap<String, AttributeValue>>,
25 #[serde(
26 rename = "Limit",
27 default,
28 deserialize_with = "helpers::deserialize_int32"
29 )]
30 limit: Option<helpers::Int32>,
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(
44 rename = "Segment",
45 default,
46 deserialize_with = "helpers::deserialize_int32"
47 )]
48 segment: Option<helpers::Int32>,
49 #[serde(
50 rename = "TotalSegments",
51 default,
52 deserialize_with = "helpers::deserialize_int32"
53 )]
54 total_segments: Option<helpers::Int32>,
55 #[serde(rename = "ReturnConsumedCapacity", default)]
56 return_consumed_capacity: Option<String>,
57 #[serde(rename = "AttributesToGet", default)]
58 attributes_to_get: Option<Vec<String>>,
59 #[serde(rename = "ScanFilter", default)]
60 scan_filter: Option<serde_json::Value>,
61 #[serde(rename = "ConditionalOperator", default)]
62 conditional_operator: Option<String>,
63}
64
65#[derive(Debug, Default)]
66pub struct ScanRequest {
67 pub table_name: String,
68 pub filter_expression: Option<String>,
69 pub projection_expression: Option<String>,
70 pub expression_attribute_names: Option<HashMap<String, String>>,
71 pub expression_attribute_values: Option<HashMap<String, AttributeValue>>,
72 pub limit: Option<usize>,
73 pub exclusive_start_key: Option<HashMap<String, AttributeValue>>,
74 pub select: Option<String>,
75 pub consistent_read: Option<bool>,
76 pub index_name: Option<String>,
77 pub segment: Option<u32>,
78 pub total_segments: Option<u32>,
79 pub return_consumed_capacity: Option<String>,
80 pub attributes_to_get: Option<Vec<String>>,
81 pub scan_filter: Option<serde_json::Value>,
82 pub conditional_operator: Option<String>,
83 pub exclusive_start_key_raw: Option<serde_json::Value>,
86}
87
88pub(crate) struct ScanMembers<'a> {
93 pub table_name: Option<&'a str>,
94 pub select: Option<&'a str>,
95 pub return_consumed_capacity: Option<&'a str>,
96 pub conditional_operator: Option<&'a str>,
97 pub scan_filter: Option<&'a serde_json::Value>,
98 pub limit: Option<i64>,
99 pub segment: Option<i64>,
100 pub total_segments: Option<i64>,
101 pub coerced: bool,
104}
105
106const TOTAL_SEGMENTS_MAX: i64 = 1_000_000;
107const SEGMENT_MAX: i64 = 999_999;
108
109impl ScanMembers<'_> {
110 pub(crate) fn rejection(&self) -> Option<String> {
133 use crate::validation::{
134 EMPTY_TABLE_NAME_REJECTION, TableNameContext, conditional_operator_rejection,
135 first_table_name_error, format_validation_errors, return_consumed_capacity_rejection,
136 table_name_constraint_errors,
137 };
138
139 let missing_operators =
140 helpers::missing_comparison_operators(self.scan_filter, "scanFilter");
141 if self.table_name.is_none() || !missing_operators.is_empty() || self.coerced {
142 let mut errors = Vec::new();
143 errors.extend(self.select.and_then(super::query::select_rejection));
144 if let Some(total) = self.total_segments {
145 if total < 1 {
146 errors.push(format!(
147 "Value '{total}' at 'totalSegments' failed to satisfy constraint: \
148 Member must have value greater than or equal to 1"
149 ));
150 } else if total > TOTAL_SEGMENTS_MAX {
151 errors.push(format!(
152 "Value '{total}' at 'totalSegments' failed to satisfy constraint: \
153 Member must have value less than or equal to {TOTAL_SEGMENTS_MAX}"
154 ));
155 }
156 }
157 errors.extend(return_consumed_capacity_rejection(
158 self.return_consumed_capacity,
159 ));
160 if matches!(self.table_name, None | Some("")) {
161 errors.extend(table_name_constraint_errors(
162 self.table_name,
163 TableNameContext::ReadWrite,
164 ));
165 }
166 errors.extend(conditional_operator_rejection(self.conditional_operator));
167 errors.extend(missing_operators);
168 if let Some(segment) = self.segment {
169 if segment < 0 {
170 errors.push(format!(
171 "Value '{segment}' at 'segment' failed to satisfy constraint: \
172 Member must have value greater than or equal to 0"
173 ));
174 } else if segment > SEGMENT_MAX {
175 errors.push(format!(
176 "Value '{segment}' at 'segment' failed to satisfy constraint: \
177 Member must have value less than or equal to {SEGMENT_MAX}"
178 ));
179 }
180 }
181 if let Some(limit) = self.limit.filter(|l| *l < 1) {
182 errors.push(format!(
183 "Value '{limit}' at 'limit' failed to satisfy constraint: \
184 Member must have value greater than or equal to 1"
185 ));
186 }
187 return format_validation_errors(&errors);
188 }
189
190 let mut errors = Vec::new();
191 if self.limit.is_some_and(|l| l < 1) {
192 errors.push(
193 "Value at 'Limit' failed to satisfy constraint: \
194 Member must have value greater than or equal to 1"
195 .to_string(),
196 );
197 }
198 if let Some(segment) = self.segment {
199 if segment < 0 {
200 errors.push(
201 "Value at 'Segment' failed to satisfy constraint: \
202 Member must have value greater than or equal to 0"
203 .to_string(),
204 );
205 } else if segment > SEGMENT_MAX {
206 errors.push(format!(
207 "Value at 'Segment' failed to satisfy constraint: \
208 Member must have value less than or equal to {SEGMENT_MAX}"
209 ));
210 }
211 }
212 if let Some(total) = self.total_segments {
213 if total < 1 {
214 errors.push(
215 "Value at 'TotalSegments' failed to satisfy constraint: \
216 Member must have value greater than or equal to 1"
217 .to_string(),
218 );
219 } else if total > TOTAL_SEGMENTS_MAX {
220 errors.push(format!(
221 "Value at 'TotalSegments' failed to satisfy constraint: \
222 Member must have value less than or equal to {TOTAL_SEGMENTS_MAX}"
223 ));
224 }
225 }
226 if self.table_name == Some("") {
227 errors.push(EMPTY_TABLE_NAME_REJECTION.to_string());
228 }
229 if let Some(msg) = format_validation_errors(&errors) {
230 return Some(msg);
231 }
232
233 let mut errors = Vec::new();
234 errors.extend(self.table_name.and_then(first_table_name_error));
235 errors.extend(return_consumed_capacity_rejection(
236 self.return_consumed_capacity,
237 ));
238 format_validation_errors(&errors)
239 }
240}
241
242impl<'de> serde::Deserialize<'de> for ScanRequest {
243 fn deserialize<D: serde::Deserializer<'de>>(
244 deserializer: D,
245 ) -> std::result::Result<Self, D::Error> {
246 let raw = ScanRequestRaw::deserialize(deserializer)?;
247 if let Some(message) = crate::types::malformed_condition_boolean(raw.scan_filter.as_ref()) {
251 return Err(serde::de::Error::custom(message));
252 }
253
254 if let Some(msg) = (ScanMembers {
257 table_name: raw.table_name.as_deref(),
258 select: raw.select.as_deref(),
259 return_consumed_capacity: raw.return_consumed_capacity.as_deref(),
260 conditional_operator: raw.conditional_operator.as_deref(),
261 scan_filter: raw.scan_filter.as_ref(),
262 limit: raw.limit.map(|n| n.value),
263 segment: raw.segment.map(|n| n.value),
264 total_segments: raw.total_segments.map(|n| n.value),
265 coerced: [raw.limit, raw.segment, raw.total_segments]
266 .iter()
267 .flatten()
268 .any(|n| n.coerced),
269 })
270 .rejection()
271 {
272 return Err(serde::de::Error::custom(format!("VALIDATION:{}", msg)));
273 }
274 let table_name = raw.table_name.unwrap_or_default();
275
276 Ok(ScanRequest {
277 table_name,
278 filter_expression: raw.filter_expression,
279 projection_expression: raw.projection_expression,
280 expression_attribute_names: raw.expression_attribute_names,
281 expression_attribute_values: raw.expression_attribute_values,
282 limit: raw.limit.map(|n| n.value as usize),
283 exclusive_start_key: None,
284 select: raw.select,
285 consistent_read: raw.consistent_read,
286 index_name: raw.index_name,
287 segment: raw.segment.map(|n| n.value as u32),
288 total_segments: raw.total_segments.map(|n| n.value as u32),
289 return_consumed_capacity: raw.return_consumed_capacity,
290 attributes_to_get: raw.attributes_to_get,
291 scan_filter: raw.scan_filter,
292 conditional_operator: raw.conditional_operator,
293 exclusive_start_key_raw: raw.exclusive_start_key,
294 })
295 }
296}
297
298#[derive(Debug, Default, Serialize)]
299pub struct ScanResponse {
300 #[serde(
301 rename = "Items",
302 skip_serializing_if = "Option::is_none",
303 serialize_with = "crate::set_order::items_in_hash_order"
304 )]
305 pub items: Option<Vec<Item>>,
308 #[serde(rename = "Count")]
309 pub count: usize,
310 #[serde(rename = "ScannedCount")]
311 pub scanned_count: usize,
312 #[serde(rename = "LastEvaluatedKey", skip_serializing_if = "Option::is_none")]
313 pub last_evaluated_key: Option<HashMap<String, AttributeValue>>,
314 #[serde(rename = "ConsumedCapacity", skip_serializing_if = "Option::is_none")]
315 pub consumed_capacity: Option<crate::types::ConsumedCapacity>,
316}
317
318pub async fn execute<S: StorageBackend>(
319 storage: &S,
320 mut request: ScanRequest,
321) -> Result<ScanResponse> {
322 if let Some(msg) = (ScanMembers {
325 table_name: Some(&request.table_name),
326 select: request.select.as_deref(),
327 return_consumed_capacity: request.return_consumed_capacity.as_deref(),
328 conditional_operator: request.conditional_operator.as_deref(),
329 scan_filter: request.scan_filter.as_ref(),
330 limit: request.limit.map(|l| i64::try_from(l).unwrap_or(i64::MAX)),
331 segment: request.segment.map(i64::from),
332 total_segments: request.total_segments.map(i64::from),
333 coerced: false,
334 })
335 .rejection()
336 {
337 return Err(DynoxideError::ValidationException(msg));
338 }
339
340 request.limit = request.limit.map(|l| l.min(i32::MAX as usize));
342
343 if crate::empty_names::names_include_empty(request.attributes_to_get.as_ref())
349 || crate::empty_names::filter_values_have_empty_name(request.scan_filter.as_ref())
350 || request
351 .exclusive_start_key
352 .as_ref()
353 .is_some_and(crate::empty_names::map_has_empty_name)
354 || request
355 .exclusive_start_key_raw
356 .as_ref()
357 .is_some_and(crate::empty_names::json_map_has_empty_name)
358 {
359 return Err(crate::empty_names::bare());
360 }
361 crate::empty_names::check_names(request.expression_attribute_names.as_ref())?;
362 if crate::empty_names::value_placeholder(request.expression_attribute_values.as_ref(), None)
363 .is_some()
364 {
365 return Err(crate::empty_names::bare());
366 }
367
368 {
370 let mut non_expr = Vec::new();
371 let mut expr = Vec::new();
372 if request.attributes_to_get.is_some() {
373 non_expr.push("AttributesToGet");
374 }
375 if request.scan_filter.is_some()
376 && request.scan_filter.as_ref().is_some_and(|v| !v.is_null())
377 {
378 non_expr.push("ScanFilter");
379 }
380 if request.conditional_operator.is_some() {
381 non_expr.push("ConditionalOperator");
382 }
383 if request.projection_expression.is_some() {
384 expr.push("ProjectionExpression");
385 }
386 if request.filter_expression.is_some() {
387 expr.push("FilterExpression");
388 }
389 let no_raw_eav: Option<serde_json::Value> = None;
390 let ctx = helpers::ExpressionParamContext {
391 non_expression_params: non_expr,
392 expression_params: expr,
393 all_expression_param_names: vec!["FilterExpression"],
394 expression_attribute_names: &request.expression_attribute_names,
395 expression_attribute_values: &request.expression_attribute_values,
396 expression_attribute_values_raw: &no_raw_eav,
397 layer: crate::validation::ValueLayer::Current,
398 };
399 helpers::validate_expression_params(&ctx)?;
400 }
401
402 if let Some(msg) = crate::validation::conditional_operator_misuse(
406 request.conditional_operator.as_deref(),
407 crate::validation::legacy_condition_count(request.scan_filter.as_ref()),
408 ) {
409 return Err(DynoxideError::ValidationException(msg.to_string()));
410 }
411 crate::value_nesting::check_scan_filter(request.scan_filter.as_ref())?;
413
414 helpers::validate_filter_conditions_raw(
416 request.scan_filter.as_ref(),
417 "ScanFilter",
418 crate::validation::ValueLayer::Current,
419 )?;
420
421 helpers::validate_filter_condition_args(request.scan_filter.as_ref(), "scanFilter")?;
423
424 if let Some(ref attrs) = request.attributes_to_get {
426 helpers::validate_attributes_to_get_no_duplicates(attrs)?;
427 }
428
429 let exclusive_start_key = if let Some(ref esk_val) = request.exclusive_start_key_raw {
431 Some(helpers::parse_exclusive_start_key(
432 esk_val,
433 crate::validation::ValueLayer::Current,
434 )?)
435 } else {
436 request.exclusive_start_key.clone()
437 };
438 crate::value_nesting::check_scan_start_key(exclusive_start_key.as_ref())?;
439
440 let scan_filter: Option<HashMap<String, helpers::FilterCondition>> =
442 match request.scan_filter.as_ref() {
443 Some(sf_val) if request.filter_expression.is_none() && !sf_val.is_null() => Some(
444 helpers::read_legacy_conditions(sf_val, crate::validation::ValueLayer::Current)?,
445 ),
446 _ => None,
447 };
448 if let Some(sf) = scan_filter
449 && !sf.is_empty()
450 {
451 let converted =
452 helpers::convert_filter_conditions(&sf, request.conditional_operator.as_deref())?;
453 if !converted.expression.is_empty() {
454 request.filter_expression = Some(converted.expression);
455 let expr_values = request
456 .expression_attribute_values
457 .get_or_insert_with(HashMap::new);
458 expr_values.extend(converted.attribute_values);
459 let expr_names = request
460 .expression_attribute_names
461 .get_or_insert_with(HashMap::new);
462 expr_names.extend(converted.attribute_names);
463 }
464 }
465
466 if let Some(ref filter_expr_str) = request.filter_expression {
469 if filter_expr_str.is_empty() {
470 if request.scan_filter.is_none() || request.filter_expression.as_deref() == Some("") {
473 return Err(DynoxideError::ValidationException(
474 "Invalid FilterExpression: The expression can not be empty;".to_string(),
475 ));
476 }
477 } else {
478 expressions::check_expression_size_with_figure(filter_expr_str).map_err(|e| {
481 DynoxideError::ValidationException(format!("Invalid FilterExpression: {e}"))
482 })?;
483 let parsed_fe = expressions::condition::parse(filter_expr_str).map_err(|e| {
485 DynoxideError::ValidationException(format!("Invalid FilterExpression: {e}"))
486 })?;
487 if let Err(e) = expressions::condition::validate_name_refs(
489 &parsed_fe,
490 &request.expression_attribute_names,
491 ) {
492 return Err(DynoxideError::ValidationException(format!(
493 "Invalid FilterExpression: {e}"
494 )));
495 }
496 if let Err(e) = expressions::condition::validate_operand_semantics(
497 &parsed_fe,
498 &request.expression_attribute_names,
499 &request.expression_attribute_values,
500 ) {
501 return Err(DynoxideError::ValidationException(format!(
502 "Invalid FilterExpression: {e}"
503 )));
504 }
505 }
506 }
507 if let Some(ref proj_expr_str) = request.projection_expression
508 && proj_expr_str.is_empty()
509 {
510 return Err(DynoxideError::ValidationException(
511 "Invalid ProjectionExpression: The expression can not be empty;".to_string(),
512 ));
513 }
514
515 if let Some(msg) = request
519 .select
520 .as_deref()
521 .and_then(super::query::select_rejection)
522 {
523 return Err(DynoxideError::ValidationException(
524 crate::validation::envelope_message(&msg),
525 ));
526 }
527 if let Some(msg) = helpers::select_projection_rejection(
528 request.select.as_deref(),
529 request.attributes_to_get.is_some(),
530 request.projection_expression.is_some(),
531 request.index_name.is_some(),
532 ) {
533 return Err(DynoxideError::ValidationException(msg));
534 }
535
536 match (request.segment, request.total_segments) {
537 (Some(segment), Some(total)) if segment >= total => {
538 return Err(DynoxideError::ValidationException(
539 "The Segment parameter is zero-based and must be less than parameter TotalSegments"
540 .to_string(),
541 ));
542 }
543 (Some(_), None) => {
544 return Err(DynoxideError::ValidationException(
545 "The TotalSegments parameter is required but was not present in the request when Segment parameter is present".to_string(),
546 ));
547 }
548 (None, Some(_)) => {
549 return Err(DynoxideError::ValidationException(
550 "The Segment parameter is required but was not present in the request when parameter TotalSegments is present".to_string(),
551 ));
552 }
553 _ => {}
554 }
555
556 let meta = helpers::require_table_for_item_op(storage, &request.table_name).await?;
557 let table_key_schema = helpers::parse_key_schema(&meta)?;
558
559 let legacy_projection = if request.projection_expression.is_none() {
562 request
563 .attributes_to_get
564 .as_ref()
565 .map(|attrs| helpers::attributes_to_get_to_projection(attrs))
566 } else {
567 None
568 };
569
570 let lsi_keys = request
572 .index_name
573 .as_ref()
574 .and_then(|idx| super::lsi::parse_lsi_key_schema(&meta, idx).ok());
575 let is_lsi = lsi_keys.is_some();
576
577 if request.consistent_read.unwrap_or(false) && request.index_name.is_some() && !is_lsi {
579 return Err(DynoxideError::ValidationException(
580 "Consistent reads are not supported on global secondary indexes".to_string(),
581 ));
582 }
583
584 let (effective_pk, effective_sk) = if let Some(ref index_name) = request.index_name {
585 if let Some(keys) = lsi_keys {
586 keys
587 } else {
588 reject_vector_index(&meta, index_name)?;
594 super::gsi::parse_gsi_key_schema(&meta, index_name)?
595 }
596 } else {
597 (
598 table_key_schema.partition_key.clone(),
599 table_key_schema.sort_key.clone(),
600 )
601 };
602
603 let (key_hash, key_range): (Vec<String>, Vec<String>) = match request.index_name {
606 Some(ref index_name) if !is_lsi => super::gsi::parse_gsi_key_attrs(&meta, index_name)?,
607 _ => (
608 vec![effective_pk.clone()],
609 effective_sk.iter().cloned().collect(),
610 ),
611 };
612
613 if let Some(ref index_name) = request.index_name
617 && !is_lsi
618 && let Some(ref select) = request.select
619 && select == "ALL_ATTRIBUTES"
620 {
621 let gsi_defs = super::gsi::parse_gsi_defs(&meta)?;
622 if let Some(gsi) = gsi_defs.iter().find(|g| g.index_name == *index_name)
623 && gsi.projection_type != crate::types::ProjectionType::ALL
624 {
625 return Err(DynoxideError::ValidationException(format!(
626 "One or more parameter values were invalid: \
627 Select type ALL_ATTRIBUTES is not supported for global secondary index {} \
628 because its projection type is not ALL",
629 index_name
630 )));
631 }
632 }
633
634 if let Some(ref esk) = exclusive_start_key {
637 helpers::validate_esk_count_and_index_keys(
638 esk,
639 &meta,
640 request.index_name.as_deref(),
641 "The provided starting key is invalid",
642 true,
643 )?;
644 }
645
646 if let Some(ref esk) = exclusive_start_key {
648 helpers::validate_esk_table_keys(esk, &meta)
649 .map_err(super::query::bare_start_key_mismatch)?;
650 }
651
652 let (start_pk, start_sk) = if let Some(ref esk) = exclusive_start_key {
654 let key_string = |attrs: &[String]| {
655 let values: Option<Vec<&AttributeValue>> = attrs.iter().map(|a| esk.get(a)).collect();
656 values.and_then(|v| super::gsi::composite_key_string(&v))
657 };
658 let pk = key_string(&key_hash);
659 let sk = if key_range.is_empty() {
660 Some(String::new())
661 } else {
662 key_string(&key_range)
663 };
664 (pk, sk)
665 } else {
666 (None, None)
667 };
668
669 let (start_base_pk, start_base_sk) = if is_lsi || request.index_name.is_some() {
675 if let Some(ref esk) = exclusive_start_key {
676 let base_pk = esk
677 .get(&table_key_schema.partition_key)
678 .and_then(|v| v.to_key_string());
679 let base_sk = if let Some(sk_name) = table_key_schema.sort_key.as_ref() {
687 esk.get(sk_name).and_then(|v| v.to_key_string())
688 } else {
689 Some(String::new())
690 };
691 (base_pk, base_sk)
692 } else {
693 (None, None)
694 }
695 } else {
696 (None, None)
697 };
698
699 let scan_params = crate::storage::ScanParams {
701 limit: request.limit,
702 exclusive_start_pk: start_pk.as_deref(),
703 exclusive_start_sk: start_sk.as_deref(),
704 segment: request.segment,
705 total_segments: request.total_segments,
706 exclusive_start_base_pk: start_base_pk.as_deref(),
707 exclusive_start_base_sk: start_base_sk.as_deref(),
708 };
709 let rows = if let Some(ref index_name) = request.index_name {
710 if is_lsi {
711 storage
712 .scan_lsi_items(&request.table_name, index_name, &scan_params)
713 .await?
714 } else {
715 storage
716 .scan_gsi_items(&request.table_name, index_name, &scan_params)
717 .await?
718 }
719 } else {
720 storage
721 .scan_items(&request.table_name, &scan_params)
722 .await?
723 };
724
725 let tracker = crate::expressions::TrackedExpressionAttributes::new(
727 &request.expression_attribute_names,
728 &request.expression_attribute_values,
729 );
730
731 let filter_expr = request
733 .filter_expression
734 .as_ref()
735 .map(|expr| expressions::condition::parse(expr))
736 .transpose()
737 .map_err(DynoxideError::ValidationException)?;
738
739 if let Some(ref filter) = filter_expr {
741 let mut base_key_attrs = vec![table_key_schema.partition_key.clone()];
743 if let Some(ref sk) = table_key_schema.sort_key {
744 base_key_attrs.push(sk.clone());
745 }
746 let mut index_key_attrs = Vec::new();
747 if request.index_name.is_some() {
748 for attr in key_hash.iter().chain(&key_range) {
749 if !base_key_attrs.contains(attr) {
750 index_key_attrs.push(attr.clone());
751 }
752 }
753 }
754 if let Some((attr, is_index)) = expressions::condition::check_non_scalar_key_access(
755 filter,
756 &request.expression_attribute_names,
757 &base_key_attrs,
758 &index_key_attrs,
759 ) {
760 let prefix = if is_index { "IndexKey" } else { "Key" };
761 return Err(DynoxideError::ValidationException(format!(
762 "Key attributes must be scalars; \
763 list random access '[]' and map lookup '.' are not allowed: {prefix}: {attr}"
764 )));
765 }
766 }
767
768 let projection = if let Some(ref proj_expr) = request.projection_expression {
770 Some(
771 expressions::projection::parse(proj_expr)
772 .map_err(DynoxideError::ValidationException)?,
773 )
774 } else {
775 legacy_projection.clone()
776 };
777
778 if let Some(ref filter) = filter_expr {
780 tracker.track_condition_expr(filter);
781 }
782 if let Some(ref proj) = projection {
783 tracker.track_projection_expr(proj);
784 expressions::projection::validate(proj, &tracker)
787 .map_err(DynoxideError::ValidationException)?;
788 }
789
790 let loop_tracker = crate::expressions::TrackedExpressionAttributes::without_tracking(
792 &request.expression_attribute_names,
793 &request.expression_attribute_values,
794 );
795
796 let is_count = request
798 .select
799 .as_deref()
800 .map(|s| s.eq_ignore_ascii_case("COUNT"))
801 .unwrap_or(false);
802
803 let mut key_attrs = vec![effective_pk.clone()];
805 if let Some(ref sk) = effective_sk {
806 key_attrs.push(sk.clone());
807 }
808 if request.index_name.is_some() {
809 if !key_attrs.contains(&table_key_schema.partition_key) {
810 key_attrs.push(table_key_schema.partition_key.clone());
811 }
812 if let Some(ref sk) = table_key_schema.sort_key
813 && !key_attrs.contains(sk)
814 {
815 key_attrs.push(sk.clone());
816 }
817 }
818
819 let mut items = Vec::new();
820 let mut scanned_count = 0;
821 let mut filtered_count = 0;
822 let mut cumulative_size = 0;
823 let mut last_evaluated_item: Option<Item> = None;
824 let mut truncated_by_size = false;
825
826 let vector_defs = super::vector_index::parse_vector_defs(&meta)?;
831 let mut billed_cumulative_size = 0usize;
832
833 for (_pk, _sk, item_json) in &rows {
834 let item: Item = serde_json::from_str(item_json).map_err(|e| {
835 DynoxideError::InternalServerError(format!("Bad item JSON in storage: {e}"))
836 })?;
837
838 scanned_count += 1;
839
840 let item_size = crate::types::item_size(&item);
843 if cumulative_size + item_size > MAX_RESPONSE_SIZE && scanned_count > 1 {
844 truncated_by_size = true;
845 break;
846 }
847 cumulative_size += item_size;
848 if request.index_name.is_none() {
849 billed_cumulative_size += crate::types::billed_item_size(&item, &vector_defs);
850 }
851
852 if let Some(ref filter) = filter_expr {
854 let passes = expressions::condition::evaluate(filter, &item, &loop_tracker)
855 .map_err(DynoxideError::ValidationException)?;
856 if !passes {
857 last_evaluated_item = Some(item);
858 continue;
859 }
860 }
861
862 filtered_count += 1;
863
864 let result_item = if let Some(ref proj) = projection {
867 let no_keys: &[String] = &[];
868 expressions::projection::apply(&item, proj, &loop_tracker, no_keys)
869 .map_err(DynoxideError::ValidationException)?
870 } else {
871 item.clone()
872 };
873
874 last_evaluated_item = Some(item);
875 if !is_count {
876 items.push(result_item);
877 }
878 }
879
880 tracker.check_unused()?;
882
883 let count = if is_count {
884 filtered_count
885 } else {
886 items.len()
887 };
888
889 let has_more = truncated_by_size
891 || (request.limit.is_some() && scanned_count >= request.limit.unwrap_or(usize::MAX));
892
893 let is_gsi_scan = request.index_name.is_some() && !is_lsi;
898 let last_evaluated_key = if has_more {
899 last_evaluated_item.map(|item| {
900 let mut key: HashMap<String, AttributeValue> = key_hash
901 .iter()
902 .chain(&key_range)
903 .filter_map(|name| item.get(name).map(|v| (name.clone(), v.clone())))
904 .collect();
905 if is_lsi
907 && let Some(tsk) = table_key_schema.sort_key.as_deref()
908 && !key.contains_key(tsk)
909 && let Some(v) = item.get(tsk)
910 {
911 key.insert(tsk.to_string(), v.clone());
912 }
913 if is_gsi_scan {
915 if !key.contains_key(&table_key_schema.partition_key)
916 && let Some(v) = item.get(&table_key_schema.partition_key)
917 {
918 key.insert(table_key_schema.partition_key.clone(), v.clone());
919 }
920 if let Some(ref tsk) = table_key_schema.sort_key
921 && !key.contains_key(tsk)
922 && let Some(v) = item.get(tsk)
923 {
924 key.insert(tsk.clone(), v.clone());
925 }
926 }
927 key
928 })
929 } else {
930 None
931 };
932
933 let is_gsi = is_gsi_scan;
935 let consistent = request.consistent_read.unwrap_or(false);
936 let consumed_capacity = if is_gsi {
937 let mut gsi_units = std::collections::HashMap::new();
938 gsi_units.insert(
939 request.index_name.as_ref().unwrap().clone(),
940 crate::types::read_capacity_units_with_consistency(cumulative_size, consistent),
941 );
942 crate::types::consumed_capacity_with_indexes(
943 &request.table_name,
944 0.0,
945 &gsi_units,
946 &request.return_consumed_capacity,
947 )
948 } else {
949 crate::types::consumed_capacity(
950 &request.table_name,
951 crate::types::read_capacity_units_with_consistency(
952 if request.index_name.is_none() {
953 billed_cumulative_size
954 } else {
955 cumulative_size
956 },
957 consistent,
958 ),
959 &request.return_consumed_capacity,
960 )
961 };
962
963 Ok(ScanResponse {
964 items: if is_count { None } else { Some(items) },
965 count,
966 scanned_count,
967 last_evaluated_key,
968 consumed_capacity,
969 })
970}
971
972fn reject_vector_index(
978 meta: &crate::storage::TableMetadata,
979 index_name: &str,
980) -> crate::errors::Result<()> {
981 if super::vector_index::parse_vector_defs(meta)?
982 .iter()
983 .any(|v| v.index_name == index_name)
984 {
985 return Err(DynoxideError::ValidationException(
986 "Scan operation not supported on this index type".to_string(),
987 ));
988 }
989 Ok(())
990}