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(
26 rename = "ExpressionAttributeValues",
27 default,
28 deserialize_with = "crate::types::deserialize_named_empty_and_null_refusals"
29 )]
30 expression_attribute_values: Option<HashMap<String, AttributeValue>>,
31 #[serde(
32 rename = "ScanIndexForward",
33 default,
34 deserialize_with = "crate::types::deserialize_lenient_flag"
35 )]
36 scan_index_forward: Option<bool>,
37 #[serde(rename = "Limit", default)]
38 limit: Option<usize>,
39 #[serde(rename = "ExclusiveStartKey", default)]
40 exclusive_start_key: Option<serde_json::Value>,
41 #[serde(rename = "Select", default)]
42 select: Option<String>,
43 #[serde(
44 rename = "ConsistentRead",
45 default,
46 deserialize_with = "crate::types::deserialize_lenient_flag"
47 )]
48 consistent_read: Option<bool>,
49 #[serde(rename = "IndexName", default)]
50 index_name: Option<String>,
51 #[serde(rename = "ReturnConsumedCapacity", default)]
52 return_consumed_capacity: Option<String>,
53 #[serde(rename = "KeyConditions", default)]
54 key_conditions: Option<serde_json::Value>,
55 #[serde(rename = "AttributesToGet", default)]
56 attributes_to_get: Option<Vec<String>>,
57 #[serde(rename = "QueryFilter", default)]
58 query_filter: Option<serde_json::Value>,
59 #[serde(rename = "ConditionalOperator", default)]
60 conditional_operator: Option<String>,
61}
62
63#[derive(Debug, Default)]
64pub struct QueryRequest {
65 pub table_name: String,
66 pub key_condition_expression: Option<String>,
67 pub filter_expression: Option<String>,
68 pub projection_expression: Option<String>,
69 pub expression_attribute_names: Option<HashMap<String, String>>,
70 pub expression_attribute_values: Option<HashMap<String, AttributeValue>>,
71 pub scan_index_forward: bool,
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 return_consumed_capacity: Option<String>,
78 pub key_conditions: Option<serde_json::Value>,
79 pub attributes_to_get: Option<Vec<String>>,
80 pub query_filter: Option<serde_json::Value>,
81 pub conditional_operator: Option<String>,
82 pub exclusive_start_key_raw: Option<serde_json::Value>,
87}
88
89impl<'de> serde::Deserialize<'de> for QueryRequest {
90 fn deserialize<D: serde::Deserializer<'de>>(
91 deserializer: D,
92 ) -> std::result::Result<Self, D::Error> {
93 let raw = QueryRequestRaw::deserialize(deserializer)?;
94 use crate::validation::format_validation_errors;
95
96 if let Some(message) =
99 crate::types::malformed_condition_boolean(raw.key_conditions.as_ref())
100 .or_else(|| crate::types::malformed_condition_boolean(raw.query_filter.as_ref()))
101 {
102 return Err(serde::de::Error::custom(message));
103 }
104
105 let mut first_pass = Vec::new();
116 if raw.limit == Some(0) {
117 first_pass.push(
118 "Value at 'Limit' failed to satisfy constraint: \
119 Member must have value greater than or equal to 1"
120 .to_string(),
121 );
122 }
123 if raw.table_name.as_deref() == Some("") {
124 first_pass.push(crate::validation::EMPTY_TABLE_NAME_REJECTION.to_string());
125 }
126 if let Some(msg) = format_validation_errors(&first_pass) {
127 return Err(serde::de::Error::custom(format!("VALIDATION:{}", msg)));
128 }
129
130 let mut errors = crate::validation::item_table_name_errors(raw.table_name.as_deref());
131 let table_name = raw.table_name.unwrap_or_default();
132
133 if let Some(msg) = crate::validation::return_consumed_capacity_rejection(
135 raw.return_consumed_capacity.as_deref(),
136 ) {
137 errors.push(msg);
138 }
139
140 errors.extend(helpers::missing_comparison_operators(
143 raw.key_conditions.as_ref(),
144 "keyConditions",
145 ));
146 errors.extend(helpers::missing_comparison_operators(
147 raw.query_filter.as_ref(),
148 "queryFilter",
149 ));
150
151 if let Some(msg) = format_validation_errors(&errors) {
152 return Err(serde::de::Error::custom(format!("VALIDATION:{}", msg)));
153 }
154
155 Ok(QueryRequest {
156 table_name,
157 key_condition_expression: raw.key_condition_expression,
158 filter_expression: raw.filter_expression,
159 projection_expression: raw.projection_expression,
160 expression_attribute_names: raw.expression_attribute_names,
161 expression_attribute_values: raw.expression_attribute_values,
162 scan_index_forward: raw.scan_index_forward.unwrap_or(true),
165 limit: raw.limit,
166 exclusive_start_key: None,
167 select: raw.select,
168 consistent_read: raw.consistent_read,
169 index_name: raw.index_name,
170 return_consumed_capacity: raw.return_consumed_capacity,
171 key_conditions: raw.key_conditions,
172 attributes_to_get: raw.attributes_to_get,
173 query_filter: raw.query_filter,
174 conditional_operator: raw.conditional_operator,
175 exclusive_start_key_raw: raw.exclusive_start_key,
176 })
177 }
178}
179
180#[derive(Debug, Default, Serialize)]
181pub struct QueryResponse {
182 #[serde(rename = "Items", skip_serializing_if = "Option::is_none")]
183 pub items: Option<Vec<Item>>,
186 #[serde(rename = "Count")]
187 pub count: usize,
188 #[serde(rename = "ScannedCount")]
189 pub scanned_count: usize,
190 #[serde(rename = "LastEvaluatedKey", skip_serializing_if = "Option::is_none")]
191 pub last_evaluated_key: Option<HashMap<String, AttributeValue>>,
192 #[serde(rename = "ConsumedCapacity", skip_serializing_if = "Option::is_none")]
193 pub consumed_capacity: Option<crate::types::ConsumedCapacity>,
194}
195
196pub async fn execute<S: StorageBackend>(
197 storage: &S,
198 mut request: QueryRequest,
199) -> Result<QueryResponse> {
200 crate::validation::validate_item_table_name(&request.table_name)?;
202
203 if crate::empty_names::names_include_empty(request.attributes_to_get.as_ref())
208 || crate::empty_names::filter_values_have_empty_name(request.query_filter.as_ref())
209 || request
210 .exclusive_start_key
211 .as_ref()
212 .is_some_and(crate::empty_names::map_has_empty_name)
213 || request
214 .exclusive_start_key_raw
215 .as_ref()
216 .is_some_and(crate::empty_names::json_map_has_empty_name)
217 {
218 return Err(crate::empty_names::bare());
219 }
220 crate::empty_names::check_names(request.expression_attribute_names.as_ref())?;
221 if crate::empty_names::value_placeholder(request.expression_attribute_values.as_ref(), None)
222 .is_some()
223 {
224 return Err(crate::empty_names::bare());
225 }
226
227 {
230 let mut non_expr = Vec::new();
231 let mut expr = Vec::new();
232 if request.attributes_to_get.is_some() {
233 non_expr.push("AttributesToGet");
234 }
235 if request.query_filter.is_some()
236 && request.query_filter.as_ref().is_some_and(|v| !v.is_null())
237 {
238 non_expr.push("QueryFilter");
239 }
240 if request.conditional_operator.is_some() {
241 non_expr.push("ConditionalOperator");
242 }
243 if request.key_conditions.is_some()
244 && request
245 .key_conditions
246 .as_ref()
247 .is_some_and(|v| !v.is_null())
248 {
249 non_expr.push("KeyConditions");
250 }
251 if request.projection_expression.is_some() {
252 expr.push("ProjectionExpression");
253 }
254 if request.filter_expression.is_some() {
255 expr.push("FilterExpression");
256 }
257 if request.key_condition_expression.is_some() {
258 expr.push("KeyConditionExpression");
259 }
260 let no_raw_eav: Option<serde_json::Value> = None;
261 let ctx = helpers::ExpressionParamContext {
262 non_expression_params: non_expr,
263 expression_params: expr,
264 all_expression_param_names: vec!["FilterExpression", "KeyConditionExpression"],
265 expression_attribute_names: &request.expression_attribute_names,
266 expression_attribute_values: &request.expression_attribute_values,
267 expression_attribute_values_raw: &no_raw_eav,
268 layer: crate::validation::ValueLayer::Current,
269 };
270 helpers::validate_expression_params(&ctx)?;
271 }
272
273 if let Some(ref sel) = request.select
275 && ![
276 "ALL_ATTRIBUTES",
277 "ALL_PROJECTED_ATTRIBUTES",
278 "COUNT",
279 "SPECIFIC_ATTRIBUTES",
280 ]
281 .contains(&sel.as_str())
282 {
283 return Err(DynoxideError::ValidationException(
284 crate::validation::envelope_message(&format!(
285 "Value '{sel}' at 'select' failed to satisfy constraint: Member must satisfy \
286 enum value set: [SPECIFIC_ATTRIBUTES, COUNT, ALL_ATTRIBUTES, \
287 ALL_PROJECTED_ATTRIBUTES]"
288 )),
289 ));
290 }
291
292 if let Some(msg) = crate::validation::conditional_operator_misuse(
296 request.conditional_operator.as_deref(),
297 crate::validation::legacy_condition_count(request.query_filter.as_ref()),
298 ) {
299 return Err(DynoxideError::ValidationException(msg.to_string()));
300 }
301
302 crate::value_nesting::check_query_conditions(request.query_filter.as_ref())?;
305 crate::value_nesting::check_query_conditions(request.key_conditions.as_ref())?;
306 helpers::validate_filter_conditions_raw(
307 request.query_filter.as_ref(),
308 "QueryFilter",
309 crate::validation::ValueLayer::Current,
310 )?;
311 helpers::validate_filter_conditions_raw(
312 request.key_conditions.as_ref(),
313 "KeyConditions",
314 crate::validation::ValueLayer::Current,
315 )?;
316
317 helpers::validate_filter_condition_args(request.query_filter.as_ref(), "queryFilter")?;
319 helpers::validate_filter_condition_args(request.key_conditions.as_ref(), "keyConditions")?;
320
321 if let Some(ref attrs) = request.attributes_to_get {
323 helpers::validate_attributes_to_get_no_duplicates(attrs)?;
324 }
325
326 let exclusive_start_key = if let Some(ref esk_val) = request.exclusive_start_key_raw {
328 Some(
329 helpers::parse_exclusive_start_key(esk_val, crate::validation::ValueLayer::Current)
330 .map_err(start_key_value_error)?,
331 )
332 } else {
333 request.exclusive_start_key.clone()
334 };
335 crate::value_nesting::check_query_start_key(exclusive_start_key.as_ref())?;
336
337 if let Some(ref kce) = request.key_condition_expression
341 && kce.is_empty()
342 {
343 return Err(DynoxideError::ValidationException(
344 "Invalid KeyConditionExpression: The expression can not be empty;".to_string(),
345 ));
346 }
347 if let Some(ref fe) = request.filter_expression {
348 if fe.is_empty() {
349 if request.query_filter.is_none() || request.filter_expression.as_deref() == Some("") {
350 return Err(DynoxideError::ValidationException(
351 "Invalid FilterExpression: The expression can not be empty;".to_string(),
352 ));
353 }
354 } else {
355 let parsed_fe = expressions::condition::parse(fe).map_err(|e| {
356 DynoxideError::ValidationException(format!("Invalid FilterExpression: {e}"))
357 })?;
358 if let Err(e) = expressions::condition::validate_name_refs(
361 &parsed_fe,
362 &request.expression_attribute_names,
363 ) {
364 return Err(DynoxideError::ValidationException(format!(
365 "Invalid FilterExpression: {e}"
366 )));
367 }
368 if let Err(e) = expressions::condition::validate_operand_semantics(
369 &parsed_fe,
370 &request.expression_attribute_names,
371 &request.expression_attribute_values,
372 ) {
373 return Err(DynoxideError::ValidationException(format!(
374 "Invalid FilterExpression: {e}"
375 )));
376 }
377 }
378 }
379 if let Some(ref pe) = request.projection_expression
380 && pe.is_empty()
381 {
382 return Err(DynoxideError::ValidationException(
383 "Invalid ProjectionExpression: The expression can not be empty;".to_string(),
384 ));
385 }
386
387 if request.select.as_deref() == Some("SPECIFIC_ATTRIBUTES")
391 && request.projection_expression.is_none()
392 && request.attributes_to_get.is_none()
393 {
394 return Err(DynoxideError::ValidationException(
395 "1 validation error detected: Must specify the AttributesToGet or ProjectionExpression when choosing to get SPECIFIC_ATTRIBUTES"
396 .to_string(),
397 ));
398 }
399
400 if request.projection_expression.is_some()
402 && let Some(select) = request.select.as_deref()
403 && select != "SPECIFIC_ATTRIBUTES"
404 {
405 let target = if select == "COUNT" {
407 "only the Count"
408 } else {
409 select
410 };
411 return Err(DynoxideError::ValidationException(format!(
412 "Cannot specify the ProjectionExpression when choosing to get {target}"
413 )));
414 }
415
416 if request.select.as_deref() == Some("ALL_PROJECTED_ATTRIBUTES") && request.index_name.is_none()
418 {
419 return Err(DynoxideError::ValidationException(
420 "ALL_PROJECTED_ATTRIBUTES can be used only when Querying using an IndexName"
421 .to_string(),
422 ));
423 }
424
425 if let Some(ref kce) = request.key_condition_expression
427 && !kce.is_empty()
428 {
429 let temp_tracker = crate::expressions::TrackedExpressionAttributes::new(
431 &request.expression_attribute_names,
432 &request.expression_attribute_values,
433 );
434 if let Err(e) = expressions::key_condition::parse(kce, &temp_tracker) {
435 return Err(DynoxideError::ValidationException(key_condition_error(
436 kce, e,
437 )));
438 }
439 }
440
441 let meta = helpers::require_table_for_item_op(storage, &request.table_name).await?;
442 let table_key_schema = helpers::parse_key_schema(&meta)?;
443
444 let effective_pk_for_kc = if let Some(ref index_name) = request.index_name {
448 if let Some((pk, _)) = request
449 .index_name
450 .as_ref()
451 .and_then(|idx| super::lsi::parse_lsi_key_schema(&meta, idx).ok())
452 {
453 pk
454 } else if let Ok((pk, _)) = super::gsi::parse_gsi_key_schema(&meta, index_name) {
455 pk
456 } else {
457 table_key_schema.partition_key.clone()
458 }
459 } else {
460 table_key_schema.partition_key.clone()
461 };
462
463 let key_conditions: Option<HashMap<String, helpers::KeyCondition>> = match request
465 .key_conditions
466 .as_ref()
467 {
468 Some(kc_val) if request.key_condition_expression.is_none() && !kc_val.is_null() => Some(
469 helpers::read_legacy_conditions(kc_val, crate::validation::ValueLayer::Current)?,
470 ),
471 _ => None,
472 };
473 if let Some(kc) = key_conditions
474 && !kc.is_empty()
475 {
476 check_legacy_key_condition_types(&kc, &meta)?;
477 let converted = helpers::convert_key_conditions(&kc, Some(&effective_pk_for_kc))?;
478 request.key_condition_expression = Some(converted.expression);
479 let expr_values = request
480 .expression_attribute_values
481 .get_or_insert_with(HashMap::new);
482 expr_values.extend(converted.attribute_values);
483 let expr_names = request
484 .expression_attribute_names
485 .get_or_insert_with(HashMap::new);
486 expr_names.extend(converted.attribute_names);
487 }
488
489 let query_filter: Option<HashMap<String, helpers::FilterCondition>> =
491 match request.query_filter.as_ref() {
492 Some(qf_val) if request.filter_expression.is_none() && !qf_val.is_null() => Some(
493 helpers::read_legacy_conditions(qf_val, crate::validation::ValueLayer::Current)?,
494 ),
495 _ => None,
496 };
497 if let Some(qf) = query_filter
498 && !qf.is_empty()
499 {
500 let converted =
501 helpers::convert_filter_conditions(&qf, request.conditional_operator.as_deref())?;
502 if !converted.expression.is_empty() {
503 request.filter_expression = Some(converted.expression);
504 let expr_values = request
505 .expression_attribute_values
506 .get_or_insert_with(HashMap::new);
507 expr_values.extend(converted.attribute_values);
508 let expr_names = request
509 .expression_attribute_names
510 .get_or_insert_with(HashMap::new);
511 expr_names.extend(converted.attribute_names);
512 }
513 }
514
515 let legacy_projection = if request.projection_expression.is_none() {
517 request
518 .attributes_to_get
519 .as_ref()
520 .map(|attrs| helpers::attributes_to_get_to_projection(attrs))
521 } else {
522 None
523 };
524
525 let key_condition_expression = request.key_condition_expression.as_deref().ok_or_else(|| {
527 DynoxideError::ValidationException(
528 "Either the KeyConditions or KeyConditionExpression parameter must be specified in the request."
529 .to_string(),
530 )
531 })?;
532 let key_condition_expression = key_condition_expression.to_string();
533
534 let lsi_keys = request
536 .index_name
537 .as_ref()
538 .and_then(|idx| super::lsi::parse_lsi_key_schema(&meta, idx).ok());
539 let is_lsi = lsi_keys.is_some();
540
541 if request.consistent_read.unwrap_or(false) && request.index_name.is_some() && !is_lsi {
543 return Err(DynoxideError::ValidationException(
544 "Consistent reads are not supported on global secondary indexes".to_string(),
545 ));
546 }
547
548 let index_projection_type = if let Some(ref index_name) = request.index_name {
550 if is_lsi {
551 super::lsi::parse_lsi_defs(&meta)?
552 .into_iter()
553 .find(|l| l.index_name == *index_name)
554 .map(|l| l.projection_type)
555 } else {
556 super::gsi::parse_gsi_defs(&meta)?
557 .into_iter()
558 .find(|g| g.index_name == *index_name)
559 .map(|g| g.projection_type)
560 }
561 } else {
562 None
563 };
564
565 let (effective_pk, effective_sk) = if let Some(ref index_name) = request.index_name {
566 if let Some(keys) = lsi_keys.clone() {
567 keys
568 } else {
569 reject_vector_index(&meta, index_name)?;
575 super::gsi::parse_gsi_key_schema(&meta, index_name)?
576 }
577 } else {
578 (
579 table_key_schema.partition_key.clone(),
580 table_key_schema.sort_key.clone(),
581 )
582 };
583
584 let (key_hash, key_range): (Vec<String>, Vec<String>) = match request.index_name {
587 Some(ref index_name) if !is_lsi => super::gsi::parse_gsi_key_attrs(&meta, index_name)?,
588 _ => (
589 vec![effective_pk.clone()],
590 effective_sk.iter().cloned().collect(),
591 ),
592 };
593
594 if let Some(ref esk) = exclusive_start_key {
596 helpers::validate_esk_count_and_index_keys(
598 esk,
599 &meta,
600 request.index_name.as_deref(),
601 "The provided starting key is invalid",
602 )?;
603 helpers::validate_esk_table_keys(esk, &meta).map_err(bare_start_key_mismatch)?;
605 check_start_key_not_empty(
607 esk,
608 &table_key_schema.partition_key,
609 table_key_schema.sort_key.as_deref(),
610 )?;
611 }
612
613 let tracker = crate::expressions::TrackedExpressionAttributes::new(
615 &request.expression_attribute_names,
616 &request.expression_attribute_values,
617 );
618
619 let key_cond =
621 expressions::key_condition::parse(&key_condition_expression, &tracker).map_err(|e| {
622 DynoxideError::ValidationException(key_condition_error(&key_condition_expression, e))
623 })?;
624 let bound = expressions::key_condition::bind(
625 &key_cond,
626 expressions::key_condition::KeyAttributes {
627 hash: &key_hash,
628 range: &key_range,
629 },
630 &tracker,
631 )
632 .map_err(DynoxideError::ValidationException)?;
633 check_key_condition_types(&bound, &meta)?;
634
635 let pk_str = bound.partition_key_string().ok_or_else(|| {
637 DynoxideError::ValidationException(
638 "Cannot convert partition key value to string".to_string(),
639 )
640 })?;
641
642 let (sk_condition_sql, sk_param_values) = bound.sort_key_sql(key_range.len());
644
645 if let Some(ref esk) = exclusive_start_key {
648 let table_without_sort_key = request.index_name.is_none() && effective_sk.is_none();
649 check_start_key_within_condition(esk, &bound, table_without_sort_key)?;
650 }
651
652 let effective_key_attrs: Vec<String> = key_hash.iter().chain(&key_range).cloned().collect();
655
656 if let Some(ref qf_val) = request.query_filter
658 && let Some(obj) = qf_val.as_object()
659 {
660 for attr_name in obj.keys() {
661 if effective_key_attrs.contains(attr_name) {
662 return Err(DynoxideError::ValidationException(format!(
663 "QueryFilter can only contain non-primary key attributes: \
664 Primary key attribute: {attr_name}"
665 )));
666 }
667 }
668 }
669
670 if request.query_filter.is_none()
673 && let Some(ref fe) = request.filter_expression
674 && let Ok(parsed_fe) = expressions::condition::parse(fe)
675 {
676 let top_attrs = expressions::condition::extract_top_level_attributes(
677 &parsed_fe,
678 &request.expression_attribute_names,
679 );
680 for attr in &top_attrs {
681 if effective_key_attrs.contains(attr) {
682 return Err(DynoxideError::ValidationException(format!(
683 "Filter Expression can only contain non-primary key attributes: \
684 Primary key attribute: {attr}"
685 )));
686 }
687 }
688 let mut index_key_attrs = Vec::new();
691 if request.index_name.is_some() {
692 for k in &effective_key_attrs {
694 if ![table_key_schema.partition_key.clone()]
695 .iter()
696 .chain(table_key_schema.sort_key.iter())
697 .any(|tk| tk == k)
698 {
699 index_key_attrs.push(k.clone());
700 }
701 }
702 }
703 let base_key_attrs: Vec<String> = {
704 let mut v = vec![table_key_schema.partition_key.clone()];
705 if let Some(ref sk) = table_key_schema.sort_key {
706 v.push(sk.clone());
707 }
708 v
709 };
710 if let Some((attr, is_index)) = expressions::condition::check_non_scalar_key_access(
711 &parsed_fe,
712 &request.expression_attribute_names,
713 &base_key_attrs,
714 &index_key_attrs,
715 ) {
716 let prefix = if is_index { "IndexKey" } else { "Key" };
717 return Err(DynoxideError::ValidationException(format!(
718 "Key attributes must be scalars; \
719 list random access '[]' and map lookup '.' are not allowed: {prefix}: {attr}"
720 )));
721 }
722 }
723
724 let is_index_query = request.index_name.is_some();
725
726 let start_sk = if let Some(ref esk) = exclusive_start_key {
730 if !key_range.is_empty() {
731 let values: Option<Vec<&AttributeValue>> =
732 key_range.iter().map(|a| esk.get(a)).collect();
733 values.and_then(|v| super::gsi::composite_key_string(&v))
734 } else if is_index_query {
735 Some(String::new())
737 } else {
738 None
739 }
740 } else {
741 None
742 };
743
744 let (start_base_pk, start_base_sk) = if is_index_query {
747 if let Some(ref esk) = exclusive_start_key {
748 let base_pk = esk
749 .get(&table_key_schema.partition_key)
750 .and_then(|v| v.to_key_string());
751 let base_sk = if let Some(sk_name) = table_key_schema.sort_key.as_ref() {
759 esk.get(sk_name).and_then(|v| v.to_key_string())
760 } else {
761 Some(String::new())
762 };
763 (base_pk, base_sk)
764 } else {
765 (None, None)
766 }
767 } else {
768 (None, None)
769 };
770
771 let is_select_all_attributes = request
774 .select
775 .as_deref()
776 .map(|s| s.eq_ignore_ascii_case("ALL_ATTRIBUTES"))
777 .unwrap_or(false);
778 let fetch_from_base_table = if is_select_all_attributes {
779 if let Some(ref proj_type) = index_projection_type {
780 if *proj_type != crate::types::ProjectionType::ALL {
781 if !is_lsi {
782 return Err(DynoxideError::ValidationException(format!(
783 "One or more parameter values were invalid: \
784 Select type ALL_ATTRIBUTES is not supported for global secondary index {} \
785 because its projection type is not ALL",
786 request.index_name.as_deref().unwrap_or("")
787 )));
788 }
789 true
791 } else {
792 false
793 }
794 } else {
795 false
796 }
797 } else {
798 false
799 };
800
801 let fetch_limit = request.limit;
802 let sk_params_refs: Vec<&str> = sk_param_values.iter().map(|s| s.as_str()).collect();
803
804 let query_params = crate::storage::QueryParams {
806 sk_condition: sk_condition_sql.as_deref(),
807 sk_params: &sk_params_refs,
808 forward: request.scan_index_forward,
809 limit: fetch_limit,
810 exclusive_start_sk: start_sk.as_deref(),
811 exclusive_start_base_pk: start_base_pk.as_deref(),
812 exclusive_start_base_sk: start_base_sk.as_deref(),
813 };
814 let rows = if let Some(ref index_name) = request.index_name {
815 if is_lsi {
816 storage
817 .query_lsi_items(&request.table_name, index_name, &pk_str, &query_params)
818 .await?
819 } else {
820 storage
821 .query_gsi_items(&request.table_name, index_name, &pk_str, &query_params)
822 .await?
823 }
824 } else {
825 storage
826 .query_items(&request.table_name, &pk_str, &query_params)
827 .await?
828 };
829
830 let filter_expr = request
832 .filter_expression
833 .as_ref()
834 .map(|expr| expressions::condition::parse(expr))
835 .transpose()
836 .map_err(DynoxideError::ValidationException)?;
837
838 let projection = if let Some(ref proj_expr) = request.projection_expression {
840 Some(
841 expressions::projection::parse(proj_expr)
842 .map_err(DynoxideError::ValidationException)?,
843 )
844 } else {
845 legacy_projection.clone()
846 };
847
848 if let Some(ref filter) = filter_expr {
850 tracker.track_condition_expr(filter);
851 }
852 if let Some(ref proj) = projection {
853 tracker.track_projection_expr(proj);
854 expressions::projection::validate(proj, &tracker)
857 .map_err(DynoxideError::ValidationException)?;
858 }
859
860 let loop_tracker = crate::expressions::TrackedExpressionAttributes::without_tracking(
862 &request.expression_attribute_names,
863 &request.expression_attribute_values,
864 );
865
866 let is_count = request
868 .select
869 .as_deref()
870 .map(|s| s.eq_ignore_ascii_case("COUNT"))
871 .unwrap_or(false);
872
873 let mut key_attrs = vec![effective_pk.clone()];
875 if let Some(ref sk) = effective_sk {
876 key_attrs.push(sk.clone());
877 }
878 if request.index_name.is_some() {
880 if !key_attrs.contains(&table_key_schema.partition_key) {
881 key_attrs.push(table_key_schema.partition_key.clone());
882 }
883 if let Some(ref sk) = table_key_schema.sort_key
884 && !key_attrs.contains(sk)
885 {
886 key_attrs.push(sk.clone());
887 }
888 }
889
890 let mut items = Vec::new();
891 let mut scanned_count = 0;
892 let mut filtered_count = 0;
893 let mut cumulative_size = 0;
894 let mut last_evaluated_item: Option<Item> = None;
895 let mut truncated_by_size = false;
896
897 let mut base_table_cumulative_size = 0usize;
900 let mut index_cumulative_size = 0usize;
901
902 for (_pk, _sk, item_json) in &rows {
903 let index_item: Item = serde_json::from_str(item_json).map_err(|e| {
904 DynoxideError::InternalServerError(format!("Bad item JSON in storage: {e}"))
905 })?;
906
907 index_cumulative_size += crate::types::item_size(&index_item);
911 let item = if fetch_from_base_table {
912 let base_pk = index_item
913 .get(&table_key_schema.partition_key)
914 .and_then(|v| v.to_key_string())
915 .unwrap_or_default();
916 let base_sk = table_key_schema
917 .sort_key
918 .as_ref()
919 .and_then(|sk_name| index_item.get(sk_name))
920 .and_then(|v| v.to_key_string())
921 .unwrap_or_default();
922 if let Some(full_json) = storage
923 .get_item(&request.table_name, &base_pk, &base_sk)
924 .await?
925 {
926 let full_item: Item = serde_json::from_str(&full_json).map_err(|e| {
927 DynoxideError::InternalServerError(format!("Bad item JSON: {e}"))
928 })?;
929 base_table_cumulative_size += crate::types::item_size(&full_item);
930 full_item
931 } else {
932 index_item.clone()
933 }
934 } else {
935 index_item.clone()
936 };
937
938 scanned_count += 1;
939
940 let item_size = crate::types::item_size(&item);
943 if cumulative_size + item_size > MAX_RESPONSE_SIZE && scanned_count > 1 {
944 truncated_by_size = true;
945 break;
946 }
947 cumulative_size += item_size;
948
949 if let Some(ref filter) = filter_expr {
951 let passes = expressions::condition::evaluate(filter, &item, &loop_tracker)
952 .map_err(DynoxideError::ValidationException)?;
953 if !passes {
954 last_evaluated_item = Some(index_item);
955 continue;
956 }
957 }
958
959 filtered_count += 1;
960
961 let result_item = if let Some(ref proj) = projection {
964 let no_keys: &[String] = &[];
965 expressions::projection::apply(&item, proj, &loop_tracker, no_keys)
966 .map_err(DynoxideError::ValidationException)?
967 } else {
968 item
969 };
970
971 last_evaluated_item = Some(index_item);
972 if !is_count {
973 items.push(result_item);
974 }
975 }
976
977 tracker.check_unused()?;
979
980 let count = if is_count {
981 filtered_count
982 } else {
983 items.len()
984 };
985
986 let has_more = truncated_by_size
989 || (fetch_limit.is_some() && scanned_count >= fetch_limit.unwrap_or(usize::MAX));
990
991 let is_gsi_query = request.index_name.is_some() && !is_lsi;
996 let last_evaluated_key = if has_more {
997 last_evaluated_item.map(|item| {
998 let mut key = build_last_evaluated_key(&item, &key_hash, &key_range);
999 if is_lsi
1001 && let Some(tsk) = table_key_schema.sort_key.as_deref()
1002 && !key.contains_key(tsk)
1003 && let Some(v) = item.get(tsk)
1004 {
1005 key.insert(tsk.to_string(), v.clone());
1006 }
1007 if is_gsi_query {
1009 if !key.contains_key(&table_key_schema.partition_key)
1010 && let Some(v) = item.get(&table_key_schema.partition_key)
1011 {
1012 key.insert(table_key_schema.partition_key.clone(), v.clone());
1013 }
1014 if let Some(ref tsk) = table_key_schema.sort_key
1015 && !key.contains_key(tsk)
1016 && let Some(v) = item.get(tsk)
1017 {
1018 key.insert(tsk.clone(), v.clone());
1019 }
1020 }
1021 key
1022 })
1023 } else {
1024 None
1025 };
1026
1027 let is_gsi = is_gsi_query;
1029 let consistent = request.consistent_read.unwrap_or(false);
1030 let consumed_capacity = if is_gsi {
1031 let mut gsi_units = std::collections::HashMap::new();
1032 gsi_units.insert(
1033 request.index_name.as_ref().unwrap().clone(),
1034 crate::types::read_capacity_units_with_consistency(cumulative_size, consistent),
1035 );
1036 crate::types::consumed_capacity_with_indexes(
1037 &request.table_name,
1038 0.0,
1039 &gsi_units,
1040 &request.return_consumed_capacity,
1041 )
1042 } else if is_lsi {
1043 let (table_cap, lsi_cap) = if fetch_from_base_table {
1046 let table_rcu = crate::types::read_capacity_units_with_consistency(
1047 base_table_cumulative_size,
1048 consistent,
1049 );
1050 let lsi_rcu = crate::types::read_capacity_units_with_consistency(
1051 index_cumulative_size,
1052 consistent,
1053 );
1054 (table_rcu, lsi_rcu)
1055 } else {
1056 (
1057 0.0,
1058 crate::types::read_capacity_units_with_consistency(cumulative_size, consistent),
1059 )
1060 };
1061 let mut lsi_units = std::collections::HashMap::new();
1062 lsi_units.insert(request.index_name.as_ref().unwrap().clone(), lsi_cap);
1063 crate::types::consumed_capacity_with_secondary_indexes(
1064 &request.table_name,
1065 table_cap,
1066 &std::collections::HashMap::new(),
1067 &lsi_units,
1068 &request.return_consumed_capacity,
1069 )
1070 } else {
1071 crate::types::consumed_capacity(
1072 &request.table_name,
1073 crate::types::read_capacity_units_with_consistency(cumulative_size, consistent),
1074 &request.return_consumed_capacity,
1075 )
1076 };
1077
1078 Ok(QueryResponse {
1079 items: if is_count { None } else { Some(items) },
1080 count,
1081 scanned_count,
1082 last_evaluated_key,
1083 consumed_capacity,
1084 })
1085}
1086
1087fn build_last_evaluated_key(
1090 item: &Item,
1091 hash: &[String],
1092 range: &[String],
1093) -> HashMap<String, AttributeValue> {
1094 hash.iter()
1095 .chain(range)
1096 .filter_map(|name| item.get(name).map(|v| (name.clone(), v.clone())))
1097 .collect()
1098}
1099
1100fn check_key_condition_types(
1106 bound: &expressions::key_condition::BoundKeyCondition,
1107 meta: &crate::storage::TableMetadata,
1108) -> Result<()> {
1109 let defs: Vec<crate::types::AttributeDefinition> =
1110 serde_json::from_str(&meta.attribute_definitions).map_err(|e| {
1111 DynoxideError::InternalServerError(format!("Bad attribute definitions JSON: {e}"))
1112 })?;
1113 for (attr, value) in bound.compared_values() {
1114 if let Some(def) = defs.iter().find(|d| d.attribute_name == attr)
1115 && !helpers::attr_value_matches_scalar_type(value, &def.attribute_type)
1116 {
1117 return Err(DynoxideError::ValidationException(
1118 "One or more parameter values were invalid: Condition parameter type does not \
1119 match schema type"
1120 .to_string(),
1121 ));
1122 }
1123 }
1124 Ok(())
1125}
1126
1127fn reject_vector_index(
1133 meta: &crate::storage::TableMetadata,
1134 index_name: &str,
1135) -> crate::errors::Result<()> {
1136 if super::vector_index::parse_vector_defs(meta)?
1137 .iter()
1138 .any(|v| v.index_name == index_name)
1139 {
1140 return Err(DynoxideError::ValidationException(
1141 "Query operation not supported on this index type.".to_string(),
1142 ));
1143 }
1144 Ok(())
1145}
1146
1147fn key_condition_error(expr: &str, err: String) -> String {
1154 if err.contains("Expression size has exceeded") || err.contains("redundant parentheses") {
1155 return err;
1156 }
1157 match expressions::condition::syntax_error(expr) {
1158 Some(syntax) => format!("Invalid KeyConditionExpression: {syntax}"),
1159 None => err,
1160 }
1161}
1162
1163const START_KEY_INVALID: &str = "The provided starting key is invalid";
1169
1170fn bare_start_key_mismatch(err: DynoxideError) -> DynoxideError {
1173 match err {
1174 DynoxideError::ValidationException(msg)
1175 if msg
1176 == "The provided starting key is invalid: \
1177 The provided key element does not match the schema" =>
1178 {
1179 DynoxideError::ValidationException(START_KEY_INVALID.to_string())
1180 }
1181 other => other,
1182 }
1183}
1184
1185fn start_key_value_error(err: DynoxideError) -> DynoxideError {
1190 match err {
1191 DynoxideError::ValidationException(msg) => {
1192 match msg.strip_prefix("The provided starting key is invalid: ") {
1193 Some(rest) if rest.starts_with("One or more parameter values were invalid:") => {
1194 DynoxideError::ValidationException(rest.to_string())
1195 }
1196 _ => DynoxideError::ValidationException(msg),
1197 }
1198 }
1199 other => other,
1200 }
1201}
1202
1203fn check_start_key_within_condition(
1216 esk: &Item,
1217 bound: &expressions::key_condition::BoundKeyCondition,
1218 table_without_sort_key: bool,
1219) -> Result<()> {
1220 use crate::expressions::condition::{CompOp, compare_values};
1221
1222 const AT_MOST_ONE_ROW: &str = "The query can return at most one row and cannot be restarted";
1223 let refuse = |message: &str| Err(DynoxideError::ValidationException(message.to_string()));
1224 let holds = |name: &str, value: &AttributeValue| {
1225 esk.get(name)
1226 .is_some_and(|v| compare_values(v, &CompOp::Eq, value))
1227 };
1228
1229 let Some(((last_name, last_value), leading)) = bound.hash.split_last() else {
1230 return Ok(());
1231 };
1232 if leading.iter().any(|(name, value)| !holds(name, value)) {
1233 return refuse(AT_MOST_ONE_ROW);
1234 }
1235 let outside_equalities = bound
1236 .range_eq
1237 .iter()
1238 .any(|(name, value)| esk.contains_key(name) && !holds(name, value));
1239 let outside_last = bound.range_last.as_ref().is_some_and(|condition| {
1240 esk.get(condition.sk_name())
1241 .is_some_and(|value| !sort_key_satisfies(value, condition))
1242 });
1243 if outside_equalities || outside_last {
1244 return refuse("The provided starting key does not match the range key predicate");
1245 }
1246 if !holds(last_name, last_value) {
1247 if !bound.range_eq.is_empty() || bound.range_last.is_some() {
1248 return refuse(AT_MOST_ONE_ROW);
1249 }
1250 if table_without_sort_key {
1251 return refuse("The provided exclusive start key is invalid");
1252 }
1253 return refuse(
1254 "The provided starting key is outside query boundaries based on provided conditions",
1255 );
1256 }
1257 Ok(())
1258}
1259
1260fn sort_key_satisfies(
1261 value: &AttributeValue,
1262 condition: &expressions::key_condition::ResolvedSortKeyCondition,
1263) -> bool {
1264 use crate::expressions::condition::{CompOp, compare_values};
1265 use expressions::key_condition::ResolvedSortKeyCondition as Sk;
1266
1267 match condition {
1268 Sk::Eq(_, v) => compare_values(value, &CompOp::Eq, v),
1269 Sk::Lt(_, v) => compare_values(value, &CompOp::Lt, v),
1270 Sk::Le(_, v) => compare_values(value, &CompOp::Le, v),
1271 Sk::Gt(_, v) => compare_values(value, &CompOp::Gt, v),
1272 Sk::Ge(_, v) => compare_values(value, &CompOp::Ge, v),
1273 Sk::Between(_, low, high) => {
1274 compare_values(value, &CompOp::Ge, low) && compare_values(value, &CompOp::Le, high)
1275 }
1276 Sk::BeginsWith(_, prefix) => match (value, prefix) {
1277 (AttributeValue::S(s), AttributeValue::S(p)) => s.starts_with(p.as_str()),
1278 (AttributeValue::B(b), AttributeValue::B(p)) => b.starts_with(p),
1279 _ => false,
1280 },
1281 }
1282}
1283
1284fn check_legacy_key_condition_types(
1292 conditions: &HashMap<String, helpers::KeyCondition>,
1293 meta: &crate::storage::TableMetadata,
1294) -> Result<()> {
1295 use crate::types::{AttributeDefinition, ScalarAttributeType};
1296
1297 let definitions: Vec<AttributeDefinition> = serde_json::from_str(&meta.attribute_definitions)
1298 .map_err(|e| {
1299 DynoxideError::InternalServerError(format!("Bad attribute definitions JSON: {e}"))
1300 })?;
1301 let lists = || {
1302 conditions
1303 .iter()
1304 .filter_map(|(name, condition)| Some((name, condition.attribute_value_list.as_ref()?)))
1305 };
1306 if lists().any(|(_, values)| {
1307 values
1308 .windows(2)
1309 .any(|pair| std::mem::discriminant(&pair[0]) != std::mem::discriminant(&pair[1]))
1310 }) {
1311 return Err(DynoxideError::ValidationException(
1312 "One or more parameter values were invalid: \
1313 AttributeValues inside AttributeValueList must be of same type"
1314 .to_string(),
1315 ));
1316 }
1317 for (name, values) in lists() {
1318 let Some(definition) = definitions.iter().find(|d| &d.attribute_name == name) else {
1319 continue;
1320 };
1321 let matches = |value: &AttributeValue| {
1322 matches!(
1323 (value, &definition.attribute_type),
1324 (AttributeValue::S(_), ScalarAttributeType::S)
1325 | (AttributeValue::N(_), ScalarAttributeType::N)
1326 | (AttributeValue::B(_), ScalarAttributeType::B)
1327 )
1328 };
1329 if !values.iter().all(matches) {
1330 return Err(DynoxideError::ValidationException(
1331 "One or more parameter values were invalid: \
1332 Condition parameter type does not match schema type"
1333 .to_string(),
1334 ));
1335 }
1336 }
1337 Ok(())
1338}
1339
1340fn check_start_key_not_empty(
1346 esk: &Item,
1347 partition_key: &str,
1348 sort_key: Option<&str>,
1349) -> Result<()> {
1350 for name in std::iter::once(partition_key).chain(sort_key) {
1351 let kind = match esk.get(name) {
1352 Some(AttributeValue::S(s)) if s.is_empty() => "string",
1353 Some(AttributeValue::B(b)) if b.is_empty() => "binary",
1354 _ => continue,
1355 };
1356 return Err(DynoxideError::KeyEmptyValueValidation(format!(
1357 "One or more parameter values are not valid. The AttributeValue for a key \
1358 attribute cannot contain an empty {kind} value. Key: {name}"
1359 )));
1360 }
1361 Ok(())
1362}