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 QueryRequestRaw {
15 #[serde(rename = "TableName", default)]
16 table_name: Option<String>,
17 #[serde(rename = "KeyConditionExpression", default)]
18 key_condition_expression: Option<String>,
19 #[serde(rename = "FilterExpression", default)]
20 filter_expression: Option<String>,
21 #[serde(rename = "ProjectionExpression", default)]
22 projection_expression: Option<String>,
23 #[serde(rename = "ExpressionAttributeNames", default)]
24 expression_attribute_names: Option<HashMap<String, String>>,
25 #[serde(rename = "ExpressionAttributeValues", default)]
26 expression_attribute_values: Option<HashMap<String, AttributeValue>>,
27 #[serde(rename = "ScanIndexForward", default = "default_true")]
28 scan_index_forward: bool,
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(rename = "ConsistentRead", default)]
36 consistent_read: Option<bool>,
37 #[serde(rename = "IndexName", default)]
38 index_name: Option<String>,
39 #[serde(rename = "ReturnConsumedCapacity", default)]
40 return_consumed_capacity: Option<String>,
41 #[serde(rename = "KeyConditions", default)]
42 key_conditions: Option<serde_json::Value>,
43 #[serde(rename = "AttributesToGet", default)]
44 attributes_to_get: Option<Vec<String>>,
45 #[serde(rename = "QueryFilter", default)]
46 query_filter: Option<serde_json::Value>,
47 #[serde(rename = "ConditionalOperator", default)]
48 conditional_operator: Option<String>,
49}
50
51fn default_true() -> bool {
52 true
53}
54
55#[derive(Debug, Default)]
56pub struct QueryRequest {
57 pub table_name: String,
58 pub key_condition_expression: Option<String>,
59 pub filter_expression: Option<String>,
60 pub projection_expression: Option<String>,
61 pub expression_attribute_names: Option<HashMap<String, String>>,
62 pub expression_attribute_values: Option<HashMap<String, AttributeValue>>,
63 pub scan_index_forward: bool,
64 pub limit: Option<usize>,
65 pub exclusive_start_key: Option<HashMap<String, AttributeValue>>,
66 pub select: Option<String>,
67 pub consistent_read: Option<bool>,
68 pub index_name: Option<String>,
69 pub return_consumed_capacity: Option<String>,
70 pub key_conditions: Option<serde_json::Value>,
71 pub attributes_to_get: Option<Vec<String>>,
72 pub query_filter: Option<serde_json::Value>,
73 pub conditional_operator: Option<String>,
74 pub exclusive_start_key_raw: Option<serde_json::Value>,
79}
80
81impl<'de> serde::Deserialize<'de> for QueryRequest {
82 fn deserialize<D: serde::Deserializer<'de>>(
83 deserializer: D,
84 ) -> std::result::Result<Self, D::Error> {
85 let raw = QueryRequestRaw::deserialize(deserializer)?;
86 use crate::validation::{
87 TableNameContext, format_validation_errors, table_name_constraint_errors,
88 };
89
90 let mut errors = Vec::new();
91 errors.extend(table_name_constraint_errors(
92 raw.table_name.as_deref(),
93 TableNameContext::ReadWrite,
94 ));
95 let table_name = raw.table_name.unwrap_or_default();
96
97 if let Some(msg) = crate::validation::return_consumed_capacity_rejection(
99 raw.return_consumed_capacity.as_deref(),
100 ) {
101 errors.push(msg);
102 }
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(limit) = raw.limit
126 && limit == 0
127 {
128 errors.push(
129 "Value at 'Limit' failed to satisfy constraint: \
130 Member must have value greater than or equal to 1"
131 .to_string(),
132 );
133 }
134
135 if let Some(msg) = format_validation_errors(&errors) {
136 return Err(serde::de::Error::custom(format!("VALIDATION:{}", msg)));
137 }
138
139 Ok(QueryRequest {
140 table_name,
141 key_condition_expression: raw.key_condition_expression,
142 filter_expression: raw.filter_expression,
143 projection_expression: raw.projection_expression,
144 expression_attribute_names: raw.expression_attribute_names,
145 expression_attribute_values: raw.expression_attribute_values,
146 scan_index_forward: raw.scan_index_forward,
147 limit: raw.limit,
148 exclusive_start_key: None,
149 select: raw.select,
150 consistent_read: raw.consistent_read,
151 index_name: raw.index_name,
152 return_consumed_capacity: raw.return_consumed_capacity,
153 key_conditions: raw.key_conditions,
154 attributes_to_get: raw.attributes_to_get,
155 query_filter: raw.query_filter,
156 conditional_operator: raw.conditional_operator,
157 exclusive_start_key_raw: raw.exclusive_start_key,
158 })
159 }
160}
161
162#[derive(Debug, Default, Serialize)]
163pub struct QueryResponse {
164 #[serde(rename = "Items", skip_serializing_if = "Option::is_none")]
165 pub items: Option<Vec<Item>>,
166 #[serde(rename = "Count")]
167 pub count: usize,
168 #[serde(rename = "ScannedCount")]
169 pub scanned_count: usize,
170 #[serde(rename = "LastEvaluatedKey", skip_serializing_if = "Option::is_none")]
171 pub last_evaluated_key: Option<HashMap<String, AttributeValue>>,
172 #[serde(rename = "ConsumedCapacity", skip_serializing_if = "Option::is_none")]
173 pub consumed_capacity: Option<crate::types::ConsumedCapacity>,
174}
175
176pub async fn execute<S: StorageBackend>(
177 storage: &S,
178 mut request: QueryRequest,
179) -> Result<QueryResponse> {
180 crate::validation::validate_table_name(&request.table_name)?;
182
183 {
186 let mut non_expr = Vec::new();
187 let mut expr = Vec::new();
188 if request.attributes_to_get.is_some() {
189 non_expr.push("AttributesToGet");
190 }
191 if request.query_filter.is_some()
192 && request.query_filter.as_ref().is_some_and(|v| !v.is_null())
193 {
194 non_expr.push("QueryFilter");
195 }
196 if request.conditional_operator.is_some() {
197 non_expr.push("ConditionalOperator");
198 }
199 if request.key_conditions.is_some()
200 && request
201 .key_conditions
202 .as_ref()
203 .is_some_and(|v| !v.is_null())
204 {
205 non_expr.push("KeyConditions");
206 }
207 if request.projection_expression.is_some() {
208 expr.push("ProjectionExpression");
209 }
210 if request.filter_expression.is_some() {
211 expr.push("FilterExpression");
212 }
213 if request.key_condition_expression.is_some() {
214 expr.push("KeyConditionExpression");
215 }
216 let no_raw_eav: Option<serde_json::Value> = None;
217 let ctx = helpers::ExpressionParamContext {
218 non_expression_params: non_expr,
219 expression_params: expr,
220 all_expression_param_names: vec!["FilterExpression", "KeyConditionExpression"],
221 expression_attribute_names: &request.expression_attribute_names,
222 expression_attribute_values: &request.expression_attribute_values,
223 expression_attribute_values_raw: &no_raw_eav,
224 };
225 helpers::validate_expression_params(&ctx)?;
226 }
227
228 helpers::validate_filter_conditions_raw(request.query_filter.as_ref(), "QueryFilter")?;
230 helpers::validate_filter_conditions_raw(request.key_conditions.as_ref(), "KeyConditions")?;
231
232 helpers::validate_filter_condition_args(request.query_filter.as_ref())?;
234 helpers::validate_filter_condition_args(request.key_conditions.as_ref())?;
235
236 if let Some(ref attrs) = request.attributes_to_get {
238 helpers::validate_attributes_to_get_no_duplicates(attrs)?;
239 }
240
241 let exclusive_start_key = if let Some(ref esk_val) = request.exclusive_start_key_raw {
243 Some(helpers::parse_exclusive_start_key(esk_val)?)
244 } else {
245 request.exclusive_start_key.clone()
246 };
247
248 if let Some(ref kce) = request.key_condition_expression
252 && kce.is_empty()
253 {
254 return Err(DynoxideError::ValidationException(
255 "Invalid KeyConditionExpression: The expression can not be empty;".to_string(),
256 ));
257 }
258 if let Some(ref fe) = request.filter_expression {
259 if fe.is_empty() {
260 if request.query_filter.is_none() || request.filter_expression.as_deref() == Some("") {
261 return Err(DynoxideError::ValidationException(
262 "Invalid FilterExpression: The expression can not be empty;".to_string(),
263 ));
264 }
265 } else {
266 let parsed_fe = expressions::condition::parse(fe).map_err(|e| {
267 DynoxideError::ValidationException(format!("Invalid FilterExpression: {e}"))
268 })?;
269 if let Err(e) = expressions::condition::validate_name_refs(
272 &parsed_fe,
273 &request.expression_attribute_names,
274 ) {
275 return Err(DynoxideError::ValidationException(format!(
276 "Invalid FilterExpression: {e}"
277 )));
278 }
279 if let Err(e) = expressions::condition::validate_operand_semantics(
280 &parsed_fe,
281 &request.expression_attribute_names,
282 &request.expression_attribute_values,
283 ) {
284 return Err(DynoxideError::ValidationException(format!(
285 "Invalid FilterExpression: {e}"
286 )));
287 }
288 }
289 }
290 if let Some(ref pe) = request.projection_expression
291 && pe.is_empty()
292 {
293 return Err(DynoxideError::ValidationException(
294 "Invalid ProjectionExpression: The expression can not be empty;".to_string(),
295 ));
296 }
297
298 if request.select.as_deref() == Some("SPECIFIC_ATTRIBUTES")
302 && request.projection_expression.is_none()
303 && request.attributes_to_get.is_none()
304 {
305 return Err(DynoxideError::ValidationException(
306 "1 validation error detected: Must specify the AttributesToGet or ProjectionExpression when choosing to get SPECIFIC_ATTRIBUTES"
307 .to_string(),
308 ));
309 }
310
311 if request.projection_expression.is_some()
313 && let Some(select) = request.select.as_deref()
314 && select != "SPECIFIC_ATTRIBUTES"
315 {
316 let target = if select == "COUNT" {
318 "only the Count"
319 } else {
320 select
321 };
322 return Err(DynoxideError::ValidationException(format!(
323 "Cannot specify the ProjectionExpression when choosing to get {target}"
324 )));
325 }
326
327 if request.select.as_deref() == Some("ALL_PROJECTED_ATTRIBUTES") && request.index_name.is_none()
329 {
330 return Err(DynoxideError::ValidationException(
331 "ALL_PROJECTED_ATTRIBUTES can be used only when Querying using an IndexName"
332 .to_string(),
333 ));
334 }
335
336 if let Some(ref kce) = request.key_condition_expression
338 && !kce.is_empty()
339 {
340 let temp_tracker = crate::expressions::TrackedExpressionAttributes::new(
342 &request.expression_attribute_names,
343 &request.expression_attribute_values,
344 );
345 if let Err(e) = expressions::key_condition::parse(kce, &temp_tracker) {
346 return Err(DynoxideError::ValidationException(e));
347 }
348 }
349
350 let meta = helpers::require_table_for_item_op(storage, &request.table_name).await?;
351 let table_key_schema = helpers::parse_key_schema(&meta)?;
352
353 let effective_pk_for_kc = if let Some(ref index_name) = request.index_name {
357 if let Some((pk, _)) = request
358 .index_name
359 .as_ref()
360 .and_then(|idx| super::lsi::parse_lsi_key_schema(&meta, idx).ok())
361 {
362 pk
363 } else if let Ok((pk, _)) = super::gsi::parse_gsi_key_schema(&meta, index_name) {
364 pk
365 } else {
366 table_key_schema.partition_key.clone()
367 }
368 } else {
369 table_key_schema.partition_key.clone()
370 };
371
372 if request.key_condition_expression.is_none()
374 && let Some(ref kc_val) = request.key_conditions
375 && let Ok(kc) =
376 serde_json::from_value::<HashMap<String, helpers::KeyCondition>>(kc_val.clone())
377 && !kc.is_empty()
378 {
379 let converted = helpers::convert_key_conditions(&kc, Some(&effective_pk_for_kc))?;
380 request.key_condition_expression = Some(converted.expression);
381 let expr_values = request
382 .expression_attribute_values
383 .get_or_insert_with(HashMap::new);
384 expr_values.extend(converted.attribute_values);
385 let expr_names = request
386 .expression_attribute_names
387 .get_or_insert_with(HashMap::new);
388 expr_names.extend(converted.attribute_names);
389 }
390
391 if request.filter_expression.is_none()
393 && let Some(ref qf_val) = request.query_filter
394 && let Ok(qf) =
395 serde_json::from_value::<HashMap<String, helpers::FilterCondition>>(qf_val.clone())
396 && !qf.is_empty()
397 {
398 let converted =
399 helpers::convert_filter_conditions(&qf, request.conditional_operator.as_deref())?;
400 if !converted.expression.is_empty() {
401 request.filter_expression = Some(converted.expression);
402 let expr_values = request
403 .expression_attribute_values
404 .get_or_insert_with(HashMap::new);
405 expr_values.extend(converted.attribute_values);
406 let expr_names = request
407 .expression_attribute_names
408 .get_or_insert_with(HashMap::new);
409 expr_names.extend(converted.attribute_names);
410 }
411 }
412
413 let legacy_projection = if request.projection_expression.is_none() {
415 request
416 .attributes_to_get
417 .as_ref()
418 .map(|attrs| helpers::attributes_to_get_to_projection(attrs))
419 } else {
420 None
421 };
422
423 let key_condition_expression = request.key_condition_expression.as_deref().ok_or_else(|| {
425 DynoxideError::ValidationException(
426 "Either the KeyConditions or KeyConditionExpression parameter must be specified in the request."
427 .to_string(),
428 )
429 })?;
430 let key_condition_expression = key_condition_expression.to_string();
431
432 let lsi_keys = request
434 .index_name
435 .as_ref()
436 .and_then(|idx| super::lsi::parse_lsi_key_schema(&meta, idx).ok());
437 let is_lsi = lsi_keys.is_some();
438
439 if request.consistent_read.unwrap_or(false) && request.index_name.is_some() && !is_lsi {
441 return Err(DynoxideError::ValidationException(
442 "Consistent reads are not supported on global secondary indexes".to_string(),
443 ));
444 }
445
446 let index_projection_type = if let Some(ref index_name) = request.index_name {
448 if is_lsi {
449 super::lsi::parse_lsi_defs(&meta)?
450 .into_iter()
451 .find(|l| l.index_name == *index_name)
452 .map(|l| l.projection_type)
453 } else {
454 super::gsi::parse_gsi_defs(&meta)?
455 .into_iter()
456 .find(|g| g.index_name == *index_name)
457 .map(|g| g.projection_type)
458 }
459 } else {
460 None
461 };
462
463 let (effective_pk, effective_sk) = if let Some(ref index_name) = request.index_name {
464 if let Some(keys) = lsi_keys {
465 keys
466 } else {
467 reject_vector_index(&meta, index_name)?;
473 super::gsi::parse_gsi_key_schema(&meta, index_name)?
474 }
475 } else {
476 (
477 table_key_schema.partition_key.clone(),
478 table_key_schema.sort_key.clone(),
479 )
480 };
481
482 if let Some(ref esk) = exclusive_start_key {
484 helpers::validate_esk_count_and_index_keys(
486 esk,
487 &meta,
488 request.index_name.as_deref(),
489 "The provided starting key is invalid",
490 )?;
491 helpers::validate_esk_table_keys(esk, &meta)?;
493 }
494
495 let tracker = crate::expressions::TrackedExpressionAttributes::new(
497 &request.expression_attribute_names,
498 &request.expression_attribute_values,
499 );
500
501 let key_cond = expressions::key_condition::parse(&key_condition_expression, &tracker)
503 .map_err(DynoxideError::ValidationException)?;
504
505 if key_cond.pk_name != effective_pk {
507 return Err(DynoxideError::ValidationException(format!(
508 "Query condition missed key schema element: {}",
509 effective_pk
510 )));
511 }
512
513 let resolved = expressions::key_condition::resolve_values(&key_cond, &tracker)
515 .map_err(DynoxideError::ValidationException)?;
516
517 let pk_str = resolved.pk_value.to_key_string().ok_or_else(|| {
519 DynoxideError::ValidationException(
520 "Cannot convert partition key value to string".to_string(),
521 )
522 })?;
523
524 let (sk_condition_sql, sk_param_values) = match resolved.sk_condition {
526 Some(ref sk_cond) => {
527 if let Some(ref eff_sk) = effective_sk {
529 if sk_cond.sk_name() != eff_sk {
530 return Err(DynoxideError::ValidationException(format!(
531 "Query condition missed key schema element: {eff_sk}"
532 )));
533 }
534 } else {
535 return Err(DynoxideError::ValidationException(
536 "Query filter contains a sort key condition but the table has no sort key"
537 .to_string(),
538 ));
539 }
540
541 expressions::key_condition::sk_conditions_to_sql(std::slice::from_ref(sk_cond))
542 }
543 None => (None, Vec::new()),
544 };
545
546 let mut effective_key_attrs = vec![effective_pk.clone()];
549 if let Some(ref sk) = effective_sk {
550 effective_key_attrs.push(sk.clone());
551 }
552
553 if let Some(ref qf_val) = request.query_filter
555 && let Some(obj) = qf_val.as_object()
556 {
557 for attr_name in obj.keys() {
558 if effective_key_attrs.contains(attr_name) {
559 return Err(DynoxideError::ValidationException(format!(
560 "QueryFilter can only contain non-primary key attributes: \
561 Primary key attribute: {attr_name}"
562 )));
563 }
564 }
565 }
566
567 if request.query_filter.is_none()
570 && let Some(ref fe) = request.filter_expression
571 && let Ok(parsed_fe) = expressions::condition::parse(fe)
572 {
573 let top_attrs = expressions::condition::extract_top_level_attributes(
574 &parsed_fe,
575 &request.expression_attribute_names,
576 );
577 for attr in &top_attrs {
578 if effective_key_attrs.contains(attr) {
579 return Err(DynoxideError::ValidationException(format!(
580 "Filter Expression can only contain non-primary key attributes: \
581 Primary key attribute: {attr}"
582 )));
583 }
584 }
585 let mut index_key_attrs = Vec::new();
588 if request.index_name.is_some() {
589 for k in &effective_key_attrs {
591 if ![table_key_schema.partition_key.clone()]
592 .iter()
593 .chain(table_key_schema.sort_key.iter())
594 .any(|tk| tk == k)
595 {
596 index_key_attrs.push(k.clone());
597 }
598 }
599 }
600 let base_key_attrs: Vec<String> = {
601 let mut v = vec![table_key_schema.partition_key.clone()];
602 if let Some(ref sk) = table_key_schema.sort_key {
603 v.push(sk.clone());
604 }
605 v
606 };
607 if let Some((attr, is_index)) = expressions::condition::check_non_scalar_key_access(
608 &parsed_fe,
609 &request.expression_attribute_names,
610 &base_key_attrs,
611 &index_key_attrs,
612 ) {
613 let prefix = if is_index { "IndexKey" } else { "Key" };
614 return Err(DynoxideError::ValidationException(format!(
615 "Key attributes must be scalars; \
616 list random access '[]' and map lookup '.' are not allowed: {prefix}: {attr}"
617 )));
618 }
619 }
620
621 let is_index_query = request.index_name.is_some();
622
623 let start_sk = if let Some(ref esk) = exclusive_start_key {
627 if let Some(ref sk_name) = effective_sk {
628 esk.get(sk_name).and_then(|v| v.to_key_string())
629 } else if is_index_query {
630 Some(String::new())
632 } else {
633 None
634 }
635 } else {
636 None
637 };
638
639 let (start_base_pk, start_base_sk) = if is_index_query {
642 if let Some(ref esk) = exclusive_start_key {
643 let base_pk = esk
644 .get(&table_key_schema.partition_key)
645 .and_then(|v| v.to_key_string());
646 let base_sk = if let Some(sk_name) = table_key_schema.sort_key.as_ref() {
654 esk.get(sk_name).and_then(|v| v.to_key_string())
655 } else {
656 Some(String::new())
657 };
658 (base_pk, base_sk)
659 } else {
660 (None, None)
661 }
662 } else {
663 (None, None)
664 };
665
666 let is_select_all_attributes = request
669 .select
670 .as_deref()
671 .map(|s| s.eq_ignore_ascii_case("ALL_ATTRIBUTES"))
672 .unwrap_or(false);
673 let fetch_from_base_table = if is_select_all_attributes {
674 if let Some(ref proj_type) = index_projection_type {
675 if *proj_type != crate::types::ProjectionType::ALL {
676 if !is_lsi {
677 return Err(DynoxideError::ValidationException(format!(
678 "One or more parameter values were invalid: \
679 Select type ALL_ATTRIBUTES is not supported for global secondary index {} \
680 because its projection type is not ALL",
681 request.index_name.as_deref().unwrap_or("")
682 )));
683 }
684 true
686 } else {
687 false
688 }
689 } else {
690 false
691 }
692 } else {
693 false
694 };
695
696 let fetch_limit = request.limit;
697 let sk_params_refs: Vec<&str> = sk_param_values.iter().map(|s| s.as_str()).collect();
698
699 let query_params = crate::storage::QueryParams {
701 sk_condition: sk_condition_sql.as_deref(),
702 sk_params: &sk_params_refs,
703 forward: request.scan_index_forward,
704 limit: fetch_limit,
705 exclusive_start_sk: start_sk.as_deref(),
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 .query_lsi_items(&request.table_name, index_name, &pk_str, &query_params)
713 .await?
714 } else {
715 storage
716 .query_gsi_items(&request.table_name, index_name, &pk_str, &query_params)
717 .await?
718 }
719 } else {
720 storage
721 .query_items(&request.table_name, &pk_str, &query_params)
722 .await?
723 };
724
725 let filter_expr = request
727 .filter_expression
728 .as_ref()
729 .map(|expr| expressions::condition::parse(expr))
730 .transpose()
731 .map_err(DynoxideError::ValidationException)?;
732
733 let projection = if let Some(ref proj_expr) = request.projection_expression {
735 Some(
736 expressions::projection::parse(proj_expr)
737 .map_err(DynoxideError::ValidationException)?,
738 )
739 } else {
740 legacy_projection.clone()
741 };
742
743 if let Some(ref filter) = filter_expr {
745 tracker.track_condition_expr(filter);
746 }
747 if let Some(ref proj) = projection {
748 tracker.track_projection_expr(proj);
749 expressions::projection::validate(proj, &tracker)
752 .map_err(DynoxideError::ValidationException)?;
753 }
754
755 let loop_tracker = crate::expressions::TrackedExpressionAttributes::without_tracking(
757 &request.expression_attribute_names,
758 &request.expression_attribute_values,
759 );
760
761 let is_count = request
763 .select
764 .as_deref()
765 .map(|s| s.eq_ignore_ascii_case("COUNT"))
766 .unwrap_or(false);
767
768 let mut key_attrs = vec![effective_pk.clone()];
770 if let Some(ref sk) = effective_sk {
771 key_attrs.push(sk.clone());
772 }
773 if request.index_name.is_some() {
775 if !key_attrs.contains(&table_key_schema.partition_key) {
776 key_attrs.push(table_key_schema.partition_key.clone());
777 }
778 if let Some(ref sk) = table_key_schema.sort_key
779 && !key_attrs.contains(sk)
780 {
781 key_attrs.push(sk.clone());
782 }
783 }
784
785 let mut items = Vec::new();
786 let mut scanned_count = 0;
787 let mut filtered_count = 0;
788 let mut cumulative_size = 0;
789 let mut last_evaluated_item: Option<Item> = None;
790 let mut truncated_by_size = false;
791
792 let mut base_table_cumulative_size = 0usize;
795 let mut index_cumulative_size = 0usize;
796
797 for (_pk, _sk, item_json) in &rows {
798 let index_item: Item = serde_json::from_str(item_json).map_err(|e| {
799 DynoxideError::InternalServerError(format!("Bad item JSON in storage: {e}"))
800 })?;
801
802 index_cumulative_size += crate::types::item_size(&index_item);
806 let item = if fetch_from_base_table {
807 let base_pk = index_item
808 .get(&table_key_schema.partition_key)
809 .and_then(|v| v.to_key_string())
810 .unwrap_or_default();
811 let base_sk = table_key_schema
812 .sort_key
813 .as_ref()
814 .and_then(|sk_name| index_item.get(sk_name))
815 .and_then(|v| v.to_key_string())
816 .unwrap_or_default();
817 if let Some(full_json) = storage
818 .get_item(&request.table_name, &base_pk, &base_sk)
819 .await?
820 {
821 let full_item: Item = serde_json::from_str(&full_json).map_err(|e| {
822 DynoxideError::InternalServerError(format!("Bad item JSON: {e}"))
823 })?;
824 base_table_cumulative_size += crate::types::item_size(&full_item);
825 full_item
826 } else {
827 index_item.clone()
828 }
829 } else {
830 index_item.clone()
831 };
832
833 scanned_count += 1;
834
835 let item_size = crate::types::item_size(&item);
838 if cumulative_size + item_size > MAX_RESPONSE_SIZE && scanned_count > 1 {
839 truncated_by_size = true;
840 break;
841 }
842 cumulative_size += item_size;
843
844 if let Some(ref filter) = filter_expr {
846 let passes = expressions::condition::evaluate(filter, &item, &loop_tracker)
847 .map_err(DynoxideError::ValidationException)?;
848 if !passes {
849 last_evaluated_item = Some(index_item);
850 continue;
851 }
852 }
853
854 filtered_count += 1;
855
856 let result_item = if let Some(ref proj) = projection {
859 let no_keys: &[String] = &[];
860 expressions::projection::apply(&item, proj, &loop_tracker, no_keys)
861 .map_err(DynoxideError::ValidationException)?
862 } else {
863 item
864 };
865
866 last_evaluated_item = Some(index_item);
867 if !is_count {
868 items.push(result_item);
869 }
870 }
871
872 tracker.check_unused()?;
874
875 let count = if is_count {
876 filtered_count
877 } else {
878 items.len()
879 };
880
881 let has_more = truncated_by_size
884 || (fetch_limit.is_some() && scanned_count >= fetch_limit.unwrap_or(usize::MAX));
885
886 let is_gsi_query = request.index_name.is_some() && !is_lsi;
891 let last_evaluated_key = if has_more {
892 last_evaluated_item.map(|item| {
893 let mut key = build_last_evaluated_key(&item, &effective_pk, effective_sk.as_deref());
894 if is_lsi
896 && let Some(tsk) = table_key_schema.sort_key.as_deref()
897 && !key.contains_key(tsk)
898 && let Some(v) = item.get(tsk)
899 {
900 key.insert(tsk.to_string(), v.clone());
901 }
902 if is_gsi_query {
904 if !key.contains_key(&table_key_schema.partition_key)
905 && let Some(v) = item.get(&table_key_schema.partition_key)
906 {
907 key.insert(table_key_schema.partition_key.clone(), v.clone());
908 }
909 if let Some(ref tsk) = table_key_schema.sort_key
910 && !key.contains_key(tsk)
911 && let Some(v) = item.get(tsk)
912 {
913 key.insert(tsk.clone(), v.clone());
914 }
915 }
916 key
917 })
918 } else {
919 None
920 };
921
922 let is_gsi = is_gsi_query;
924 let consistent = request.consistent_read.unwrap_or(false);
925 let consumed_capacity = if is_gsi {
926 let mut gsi_units = std::collections::HashMap::new();
927 gsi_units.insert(
928 request.index_name.as_ref().unwrap().clone(),
929 crate::types::read_capacity_units_with_consistency(cumulative_size, consistent),
930 );
931 crate::types::consumed_capacity_with_indexes(
932 &request.table_name,
933 0.0,
934 &gsi_units,
935 &request.return_consumed_capacity,
936 )
937 } else if is_lsi {
938 let (table_cap, lsi_cap) = if fetch_from_base_table {
941 let table_rcu = crate::types::read_capacity_units_with_consistency(
942 base_table_cumulative_size,
943 consistent,
944 );
945 let lsi_rcu = crate::types::read_capacity_units_with_consistency(
946 index_cumulative_size,
947 consistent,
948 );
949 (table_rcu, lsi_rcu)
950 } else {
951 (
952 0.0,
953 crate::types::read_capacity_units_with_consistency(cumulative_size, consistent),
954 )
955 };
956 let mut lsi_units = std::collections::HashMap::new();
957 lsi_units.insert(request.index_name.as_ref().unwrap().clone(), lsi_cap);
958 crate::types::consumed_capacity_with_secondary_indexes(
959 &request.table_name,
960 table_cap,
961 &std::collections::HashMap::new(),
962 &lsi_units,
963 &request.return_consumed_capacity,
964 )
965 } else {
966 crate::types::consumed_capacity(
967 &request.table_name,
968 crate::types::read_capacity_units_with_consistency(cumulative_size, consistent),
969 &request.return_consumed_capacity,
970 )
971 };
972
973 Ok(QueryResponse {
974 items: if is_count { None } else { Some(items) },
975 count,
976 scanned_count,
977 last_evaluated_key,
978 consumed_capacity,
979 })
980}
981
982fn build_last_evaluated_key(
983 item: &Item,
984 pk_name: &str,
985 sk_name: Option<&str>,
986) -> HashMap<String, AttributeValue> {
987 let mut key = HashMap::new();
988 if let Some(pk_val) = item.get(pk_name) {
989 key.insert(pk_name.to_string(), pk_val.clone());
990 }
991 if let Some(sk) = sk_name
992 && let Some(sk_val) = item.get(sk)
993 {
994 key.insert(sk.to_string(), sk_val.clone());
995 }
996 key
997}
998
999fn reject_vector_index(
1005 meta: &crate::storage::TableMetadata,
1006 index_name: &str,
1007) -> crate::errors::Result<()> {
1008 if super::vector_index::parse_vector_defs(meta)?
1009 .iter()
1010 .any(|v| v.index_name == index_name)
1011 {
1012 return Err(DynoxideError::ValidationException(
1013 "Query operation not supported on this index type.".to_string(),
1014 ));
1015 }
1016 Ok(())
1017}