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(
38 rename = "Limit",
39 default,
40 deserialize_with = "helpers::deserialize_int32"
41 )]
42 limit: Option<helpers::Int32>,
43 #[serde(rename = "ExclusiveStartKey", default)]
44 exclusive_start_key: Option<serde_json::Value>,
45 #[serde(rename = "Select", default)]
46 select: Option<String>,
47 #[serde(
48 rename = "ConsistentRead",
49 default,
50 deserialize_with = "crate::types::deserialize_lenient_flag"
51 )]
52 consistent_read: Option<bool>,
53 #[serde(rename = "IndexName", default)]
54 index_name: Option<String>,
55 #[serde(rename = "ReturnConsumedCapacity", default)]
56 return_consumed_capacity: Option<String>,
57 #[serde(rename = "KeyConditions", default)]
58 key_conditions: Option<serde_json::Value>,
59 #[serde(rename = "AttributesToGet", default)]
60 attributes_to_get: Option<Vec<String>>,
61 #[serde(rename = "QueryFilter", default)]
62 query_filter: Option<serde_json::Value>,
63 #[serde(rename = "ConditionalOperator", default)]
64 conditional_operator: Option<String>,
65}
66
67#[derive(Debug, Default)]
68pub struct QueryRequest {
69 pub table_name: String,
70 pub key_condition_expression: Option<String>,
71 pub filter_expression: Option<String>,
72 pub projection_expression: Option<String>,
73 pub expression_attribute_names: Option<HashMap<String, String>>,
74 pub expression_attribute_values: Option<HashMap<String, AttributeValue>>,
75 pub scan_index_forward: bool,
76 pub limit: Option<usize>,
77 pub exclusive_start_key: Option<HashMap<String, AttributeValue>>,
78 pub select: Option<String>,
79 pub consistent_read: Option<bool>,
80 pub index_name: Option<String>,
81 pub return_consumed_capacity: Option<String>,
82 pub key_conditions: Option<serde_json::Value>,
83 pub attributes_to_get: Option<Vec<String>>,
84 pub query_filter: Option<serde_json::Value>,
85 pub conditional_operator: Option<String>,
86 pub exclusive_start_key_raw: Option<serde_json::Value>,
91}
92
93pub(crate) fn select_rejection(select: &str) -> Option<String> {
96 if [
97 "ALL_ATTRIBUTES",
98 "ALL_PROJECTED_ATTRIBUTES",
99 "COUNT",
100 "SPECIFIC_ATTRIBUTES",
101 ]
102 .contains(&select)
103 {
104 return None;
105 }
106 Some(format!(
107 "Value '{select}' at 'select' failed to satisfy constraint: Member must satisfy \
108 enum value set: [SPECIFIC_ATTRIBUTES, COUNT, ALL_ATTRIBUTES, ALL_PROJECTED_ATTRIBUTES]"
109 ))
110}
111
112impl<'de> serde::Deserialize<'de> for QueryRequest {
113 fn deserialize<D: serde::Deserializer<'de>>(
114 deserializer: D,
115 ) -> std::result::Result<Self, D::Error> {
116 let raw = QueryRequestRaw::deserialize(deserializer)?;
117 use crate::validation::format_validation_errors;
118
119 if let Some(message) =
122 crate::types::malformed_condition_boolean(raw.key_conditions.as_ref())
123 .or_else(|| crate::types::malformed_condition_boolean(raw.query_filter.as_ref()))
124 {
125 return Err(serde::de::Error::custom(message));
126 }
127
128 let limit = raw.limit.map(|n| n.value);
146 let coerced = raw.limit.is_some_and(|n| n.coerced);
147 let limit_too_small = limit.filter(|l| *l < 1);
148 if raw.table_name.is_none() || coerced {
149 let mut errors = Vec::new();
150 errors.extend(raw.select.as_deref().and_then(select_rejection));
151 errors.extend(crate::validation::return_consumed_capacity_rejection(
152 raw.return_consumed_capacity.as_deref(),
153 ));
154 if matches!(raw.table_name.as_deref(), None | Some("")) {
155 errors.extend(crate::validation::table_name_constraint_errors(
156 raw.table_name.as_deref(),
157 crate::validation::TableNameContext::ReadWrite,
158 ));
159 }
160 errors.extend(helpers::missing_comparison_operators(
162 raw.key_conditions.as_ref(),
163 "keyConditions",
164 ));
165 errors.extend(helpers::missing_comparison_operators(
166 raw.query_filter.as_ref(),
167 "queryFilter",
168 ));
169 if let Some(limit) = limit_too_small {
170 errors.push(format!(
171 "Value '{limit}' at 'limit' failed to satisfy constraint: \
172 Member must have value greater than or equal to 1"
173 ));
174 }
175 if let Some(msg) = format_validation_errors(&errors) {
176 return Err(serde::de::Error::custom(format!("VALIDATION:{}", msg)));
177 }
178 }
179 let mut first_pass = Vec::new();
180 if limit_too_small.is_some() {
181 first_pass.push(
182 "Value at 'Limit' failed to satisfy constraint: \
183 Member must have value greater than or equal to 1"
184 .to_string(),
185 );
186 }
187 if raw.table_name.as_deref() == Some("") {
188 first_pass.push(crate::validation::EMPTY_TABLE_NAME_REJECTION.to_string());
189 }
190 if let Some(msg) = format_validation_errors(&first_pass) {
191 return Err(serde::de::Error::custom(format!("VALIDATION:{}", msg)));
192 }
193
194 let mut errors = crate::validation::item_table_name_errors(raw.table_name.as_deref());
195 let table_name = raw.table_name.unwrap_or_default();
196
197 if let Some(msg) = crate::validation::return_consumed_capacity_rejection(
199 raw.return_consumed_capacity.as_deref(),
200 ) {
201 errors.push(msg);
202 }
203
204 errors.extend(helpers::missing_comparison_operators(
207 raw.key_conditions.as_ref(),
208 "keyConditions",
209 ));
210 errors.extend(helpers::missing_comparison_operators(
211 raw.query_filter.as_ref(),
212 "queryFilter",
213 ));
214
215 if let Some(msg) = format_validation_errors(&errors) {
216 return Err(serde::de::Error::custom(format!("VALIDATION:{}", msg)));
217 }
218
219 Ok(QueryRequest {
220 table_name,
221 key_condition_expression: raw.key_condition_expression,
222 filter_expression: raw.filter_expression,
223 projection_expression: raw.projection_expression,
224 expression_attribute_names: raw.expression_attribute_names,
225 expression_attribute_values: raw.expression_attribute_values,
226 scan_index_forward: raw.scan_index_forward.unwrap_or(true),
229 limit: limit.map(|l| l as usize),
230 exclusive_start_key: None,
231 select: raw.select,
232 consistent_read: raw.consistent_read,
233 index_name: raw.index_name,
234 return_consumed_capacity: raw.return_consumed_capacity,
235 key_conditions: raw.key_conditions,
236 attributes_to_get: raw.attributes_to_get,
237 query_filter: raw.query_filter,
238 conditional_operator: raw.conditional_operator,
239 exclusive_start_key_raw: raw.exclusive_start_key,
240 })
241 }
242}
243
244#[derive(Debug, Default, Serialize)]
245pub struct QueryResponse {
246 #[serde(rename = "Items", skip_serializing_if = "Option::is_none")]
247 pub items: Option<Vec<Item>>,
250 #[serde(rename = "Count")]
251 pub count: usize,
252 #[serde(rename = "ScannedCount")]
253 pub scanned_count: usize,
254 #[serde(rename = "LastEvaluatedKey", skip_serializing_if = "Option::is_none")]
255 pub last_evaluated_key: Option<HashMap<String, AttributeValue>>,
256 #[serde(rename = "ConsumedCapacity", skip_serializing_if = "Option::is_none")]
257 pub consumed_capacity: Option<crate::types::ConsumedCapacity>,
258}
259
260pub async fn execute<S: StorageBackend>(
261 storage: &S,
262 mut request: QueryRequest,
263) -> Result<QueryResponse> {
264 let mut first_pass = Vec::new();
268 if request.limit == Some(0) {
269 first_pass.push(
270 "Value at 'Limit' failed to satisfy constraint: \
271 Member must have value greater than or equal to 1"
272 .to_string(),
273 );
274 }
275 if request.table_name.is_empty() {
276 first_pass.push(crate::validation::EMPTY_TABLE_NAME_REJECTION.to_string());
277 }
278 if let Some(msg) = crate::validation::format_validation_errors(&first_pass) {
279 return Err(DynoxideError::ValidationException(msg));
280 }
281 crate::validation::validate_item_table_name(&request.table_name)?;
282
283 request.limit = request.limit.map(|l| l.min(i32::MAX as usize));
285
286 if crate::empty_names::names_include_empty(request.attributes_to_get.as_ref())
291 || crate::empty_names::filter_values_have_empty_name(request.query_filter.as_ref())
292 || request
293 .exclusive_start_key
294 .as_ref()
295 .is_some_and(crate::empty_names::map_has_empty_name)
296 || request
297 .exclusive_start_key_raw
298 .as_ref()
299 .is_some_and(crate::empty_names::json_map_has_empty_name)
300 {
301 return Err(crate::empty_names::bare());
302 }
303 crate::empty_names::check_names(request.expression_attribute_names.as_ref())?;
304 if crate::empty_names::value_placeholder(request.expression_attribute_values.as_ref(), None)
305 .is_some()
306 {
307 return Err(crate::empty_names::bare());
308 }
309
310 {
313 let mut non_expr = Vec::new();
314 let mut expr = Vec::new();
315 if request.attributes_to_get.is_some() {
316 non_expr.push("AttributesToGet");
317 }
318 if request.query_filter.is_some()
319 && request.query_filter.as_ref().is_some_and(|v| !v.is_null())
320 {
321 non_expr.push("QueryFilter");
322 }
323 if request.conditional_operator.is_some() {
324 non_expr.push("ConditionalOperator");
325 }
326 if request.key_conditions.is_some()
327 && request
328 .key_conditions
329 .as_ref()
330 .is_some_and(|v| !v.is_null())
331 {
332 non_expr.push("KeyConditions");
333 }
334 if request.projection_expression.is_some() {
335 expr.push("ProjectionExpression");
336 }
337 if request.filter_expression.is_some() {
338 expr.push("FilterExpression");
339 }
340 if request.key_condition_expression.is_some() {
341 expr.push("KeyConditionExpression");
342 }
343 let no_raw_eav: Option<serde_json::Value> = None;
344 let ctx = helpers::ExpressionParamContext {
345 non_expression_params: non_expr,
346 expression_params: expr,
347 all_expression_param_names: vec!["FilterExpression", "KeyConditionExpression"],
348 expression_attribute_names: &request.expression_attribute_names,
349 expression_attribute_values: &request.expression_attribute_values,
350 expression_attribute_values_raw: &no_raw_eav,
351 layer: crate::validation::ValueLayer::Current,
352 };
353 helpers::validate_expression_params(&ctx)?;
354 }
355
356 if let Some(msg) = request.select.as_deref().and_then(select_rejection) {
358 return Err(DynoxideError::ValidationException(
359 crate::validation::envelope_message(&msg),
360 ));
361 }
362
363 if let Some(msg) = crate::validation::conditional_operator_misuse(
367 request.conditional_operator.as_deref(),
368 crate::validation::legacy_condition_count(request.query_filter.as_ref()),
369 ) {
370 return Err(DynoxideError::ValidationException(msg.to_string()));
371 }
372
373 crate::value_nesting::check_query_conditions(request.query_filter.as_ref())?;
376 crate::value_nesting::check_query_conditions(request.key_conditions.as_ref())?;
377 helpers::validate_filter_conditions_raw(
378 request.query_filter.as_ref(),
379 "QueryFilter",
380 crate::validation::ValueLayer::Current,
381 )?;
382 helpers::validate_filter_conditions_raw(
383 request.key_conditions.as_ref(),
384 "KeyConditions",
385 crate::validation::ValueLayer::Current,
386 )?;
387
388 helpers::validate_filter_condition_args(request.query_filter.as_ref(), "queryFilter")?;
390 helpers::validate_filter_condition_args(request.key_conditions.as_ref(), "keyConditions")?;
391
392 if let Some(ref attrs) = request.attributes_to_get {
394 helpers::validate_attributes_to_get_no_duplicates(attrs)?;
395 }
396
397 let exclusive_start_key = if let Some(ref esk_val) = request.exclusive_start_key_raw {
399 Some(
400 helpers::parse_exclusive_start_key(esk_val, crate::validation::ValueLayer::Current)
401 .map_err(start_key_value_error)?,
402 )
403 } else {
404 request.exclusive_start_key.clone()
405 };
406 crate::value_nesting::check_query_start_key(exclusive_start_key.as_ref())?;
407
408 if let Some(ref kce) = request.key_condition_expression
412 && kce.is_empty()
413 {
414 return Err(DynoxideError::ValidationException(
415 "Invalid KeyConditionExpression: The expression can not be empty;".to_string(),
416 ));
417 }
418 if let Some(ref fe) = request.filter_expression {
419 if fe.is_empty() {
420 if request.query_filter.is_none() || request.filter_expression.as_deref() == Some("") {
421 return Err(DynoxideError::ValidationException(
422 "Invalid FilterExpression: The expression can not be empty;".to_string(),
423 ));
424 }
425 } else {
426 let parsed_fe = expressions::condition::parse(fe).map_err(|e| {
427 DynoxideError::ValidationException(format!("Invalid FilterExpression: {e}"))
428 })?;
429 if let Err(e) = expressions::condition::validate_name_refs(
432 &parsed_fe,
433 &request.expression_attribute_names,
434 ) {
435 return Err(DynoxideError::ValidationException(format!(
436 "Invalid FilterExpression: {e}"
437 )));
438 }
439 if let Err(e) = expressions::condition::validate_operand_semantics(
440 &parsed_fe,
441 &request.expression_attribute_names,
442 &request.expression_attribute_values,
443 ) {
444 return Err(DynoxideError::ValidationException(format!(
445 "Invalid FilterExpression: {e}"
446 )));
447 }
448 }
449 }
450 if let Some(ref pe) = request.projection_expression
451 && pe.is_empty()
452 {
453 return Err(DynoxideError::ValidationException(
454 "Invalid ProjectionExpression: The expression can not be empty;".to_string(),
455 ));
456 }
457
458 if let Some(msg) = helpers::select_projection_rejection(
460 request.select.as_deref(),
461 request.attributes_to_get.is_some(),
462 request.projection_expression.is_some(),
463 request.index_name.is_some(),
464 ) {
465 return Err(DynoxideError::ValidationException(msg));
466 }
467
468 if let Some(ref kce) = request.key_condition_expression
470 && !kce.is_empty()
471 {
472 let temp_tracker = crate::expressions::TrackedExpressionAttributes::new(
474 &request.expression_attribute_names,
475 &request.expression_attribute_values,
476 );
477 if let Err(e) = expressions::key_condition::parse(kce, &temp_tracker) {
478 return Err(DynoxideError::ValidationException(key_condition_error(
479 kce, e,
480 )));
481 }
482 }
483
484 let meta = helpers::require_table_for_item_op(storage, &request.table_name).await?;
485 let table_key_schema = helpers::parse_key_schema(&meta)?;
486
487 let effective_pk_for_kc = if let Some(ref index_name) = request.index_name {
491 if let Some((pk, _)) = request
492 .index_name
493 .as_ref()
494 .and_then(|idx| super::lsi::parse_lsi_key_schema(&meta, idx).ok())
495 {
496 pk
497 } else if let Ok((pk, _)) = super::gsi::parse_gsi_key_schema(&meta, index_name) {
498 pk
499 } else {
500 table_key_schema.partition_key.clone()
501 }
502 } else {
503 table_key_schema.partition_key.clone()
504 };
505
506 let key_conditions: Option<HashMap<String, helpers::KeyCondition>> = match request
508 .key_conditions
509 .as_ref()
510 {
511 Some(kc_val) if request.key_condition_expression.is_none() && !kc_val.is_null() => Some(
512 helpers::read_legacy_conditions(kc_val, crate::validation::ValueLayer::Current)?,
513 ),
514 _ => None,
515 };
516 if let Some(kc) = key_conditions
517 && !kc.is_empty()
518 {
519 check_legacy_key_condition_types(&kc, &meta)?;
520 let converted = helpers::convert_key_conditions(&kc, Some(&effective_pk_for_kc))?;
521 request.key_condition_expression = Some(converted.expression);
522 let expr_values = request
523 .expression_attribute_values
524 .get_or_insert_with(HashMap::new);
525 expr_values.extend(converted.attribute_values);
526 let expr_names = request
527 .expression_attribute_names
528 .get_or_insert_with(HashMap::new);
529 expr_names.extend(converted.attribute_names);
530 }
531
532 let query_filter: Option<HashMap<String, helpers::FilterCondition>> =
534 match request.query_filter.as_ref() {
535 Some(qf_val) if request.filter_expression.is_none() && !qf_val.is_null() => Some(
536 helpers::read_legacy_conditions(qf_val, crate::validation::ValueLayer::Current)?,
537 ),
538 _ => None,
539 };
540 if let Some(qf) = query_filter
541 && !qf.is_empty()
542 {
543 let converted =
544 helpers::convert_filter_conditions(&qf, request.conditional_operator.as_deref())?;
545 if !converted.expression.is_empty() {
546 request.filter_expression = Some(converted.expression);
547 let expr_values = request
548 .expression_attribute_values
549 .get_or_insert_with(HashMap::new);
550 expr_values.extend(converted.attribute_values);
551 let expr_names = request
552 .expression_attribute_names
553 .get_or_insert_with(HashMap::new);
554 expr_names.extend(converted.attribute_names);
555 }
556 }
557
558 let legacy_projection = if request.projection_expression.is_none() {
560 request
561 .attributes_to_get
562 .as_ref()
563 .map(|attrs| helpers::attributes_to_get_to_projection(attrs))
564 } else {
565 None
566 };
567
568 let key_condition_expression = request.key_condition_expression.as_deref().ok_or_else(|| {
570 DynoxideError::ValidationException(
571 "Either the KeyConditions or KeyConditionExpression parameter must be specified in the request."
572 .to_string(),
573 )
574 })?;
575 let key_condition_expression = key_condition_expression.to_string();
576
577 let lsi_keys = request
579 .index_name
580 .as_ref()
581 .and_then(|idx| super::lsi::parse_lsi_key_schema(&meta, idx).ok());
582 let is_lsi = lsi_keys.is_some();
583
584 if request.consistent_read.unwrap_or(false) && request.index_name.is_some() && !is_lsi {
586 return Err(DynoxideError::ValidationException(
587 "Consistent reads are not supported on global secondary indexes".to_string(),
588 ));
589 }
590
591 let index_projection_type = if let Some(ref index_name) = request.index_name {
593 if is_lsi {
594 super::lsi::parse_lsi_defs(&meta)?
595 .into_iter()
596 .find(|l| l.index_name == *index_name)
597 .map(|l| l.projection_type)
598 } else {
599 super::gsi::parse_gsi_defs(&meta)?
600 .into_iter()
601 .find(|g| g.index_name == *index_name)
602 .map(|g| g.projection_type)
603 }
604 } else {
605 None
606 };
607
608 let (effective_pk, effective_sk) = if let Some(ref index_name) = request.index_name {
609 if let Some(keys) = lsi_keys.clone() {
610 keys
611 } else {
612 reject_vector_index(&meta, index_name)?;
618 super::gsi::parse_gsi_key_schema(&meta, index_name)?
619 }
620 } else {
621 (
622 table_key_schema.partition_key.clone(),
623 table_key_schema.sort_key.clone(),
624 )
625 };
626
627 let (key_hash, key_range): (Vec<String>, Vec<String>) = match request.index_name {
630 Some(ref index_name) if !is_lsi => super::gsi::parse_gsi_key_attrs(&meta, index_name)?,
631 _ => (
632 vec![effective_pk.clone()],
633 effective_sk.iter().cloned().collect(),
634 ),
635 };
636
637 if let Some(ref esk) = exclusive_start_key {
639 helpers::validate_esk_count_and_index_keys(
641 esk,
642 &meta,
643 request.index_name.as_deref(),
644 "The provided starting key is invalid",
645 false,
646 )
647 .map_err(bare_start_key_mismatch)?;
648 helpers::validate_esk_table_keys(esk, &meta).map_err(bare_start_key_mismatch)?;
650 check_start_key_not_empty(
652 esk,
653 &table_key_schema.partition_key,
654 table_key_schema.sort_key.as_deref(),
655 )?;
656 }
657
658 let tracker = crate::expressions::TrackedExpressionAttributes::new(
660 &request.expression_attribute_names,
661 &request.expression_attribute_values,
662 );
663
664 let key_cond =
666 expressions::key_condition::parse(&key_condition_expression, &tracker).map_err(|e| {
667 DynoxideError::ValidationException(key_condition_error(&key_condition_expression, e))
668 })?;
669 let bound = expressions::key_condition::bind(
670 &key_cond,
671 expressions::key_condition::KeyAttributes {
672 hash: &key_hash,
673 range: &key_range,
674 },
675 &tracker,
676 )
677 .map_err(DynoxideError::ValidationException)?;
678 check_key_condition_types(&bound, &meta)?;
679
680 let pk_str = bound.partition_key_string().ok_or_else(|| {
682 DynoxideError::ValidationException(
683 "Cannot convert partition key value to string".to_string(),
684 )
685 })?;
686
687 let (sk_condition_sql, sk_param_values) = bound.sort_key_sql(key_range.len());
689
690 if let Some(ref esk) = exclusive_start_key {
693 let table_without_sort_key = request.index_name.is_none() && effective_sk.is_none();
694 check_start_key_within_condition(esk, &bound, table_without_sort_key)?;
695 }
696
697 let effective_key_attrs: Vec<String> = key_hash.iter().chain(&key_range).cloned().collect();
700
701 if let Some(ref qf_val) = request.query_filter
703 && let Some(obj) = qf_val.as_object()
704 {
705 for attr_name in obj.keys() {
706 if effective_key_attrs.contains(attr_name) {
707 return Err(DynoxideError::ValidationException(format!(
708 "QueryFilter can only contain non-primary key attributes: \
709 Primary key attribute: {attr_name}"
710 )));
711 }
712 }
713 }
714
715 if request.query_filter.is_none()
718 && let Some(ref fe) = request.filter_expression
719 && let Ok(parsed_fe) = expressions::condition::parse(fe)
720 {
721 let top_attrs = expressions::condition::extract_top_level_attributes(
722 &parsed_fe,
723 &request.expression_attribute_names,
724 );
725 for attr in &top_attrs {
726 if effective_key_attrs.contains(attr) {
727 return Err(DynoxideError::ValidationException(format!(
728 "Filter Expression can only contain non-primary key attributes: \
729 Primary key attribute: {attr}"
730 )));
731 }
732 }
733 let mut index_key_attrs = Vec::new();
736 if request.index_name.is_some() {
737 for k in &effective_key_attrs {
739 if ![table_key_schema.partition_key.clone()]
740 .iter()
741 .chain(table_key_schema.sort_key.iter())
742 .any(|tk| tk == k)
743 {
744 index_key_attrs.push(k.clone());
745 }
746 }
747 }
748 let base_key_attrs: Vec<String> = {
749 let mut v = vec![table_key_schema.partition_key.clone()];
750 if let Some(ref sk) = table_key_schema.sort_key {
751 v.push(sk.clone());
752 }
753 v
754 };
755 if let Some((attr, is_index)) = expressions::condition::check_non_scalar_key_access(
756 &parsed_fe,
757 &request.expression_attribute_names,
758 &base_key_attrs,
759 &index_key_attrs,
760 ) {
761 let prefix = if is_index { "IndexKey" } else { "Key" };
762 return Err(DynoxideError::ValidationException(format!(
763 "Key attributes must be scalars; \
764 list random access '[]' and map lookup '.' are not allowed: {prefix}: {attr}"
765 )));
766 }
767 }
768
769 let is_index_query = request.index_name.is_some();
770
771 let start_sk = if let Some(ref esk) = exclusive_start_key {
775 if !key_range.is_empty() {
776 let values: Option<Vec<&AttributeValue>> =
777 key_range.iter().map(|a| esk.get(a)).collect();
778 values.and_then(|v| super::gsi::composite_key_string(&v))
779 } else if is_index_query {
780 Some(String::new())
782 } else {
783 None
784 }
785 } else {
786 None
787 };
788
789 let (start_base_pk, start_base_sk) = if is_index_query {
792 if let Some(ref esk) = exclusive_start_key {
793 let base_pk = esk
794 .get(&table_key_schema.partition_key)
795 .and_then(|v| v.to_key_string());
796 let base_sk = if let Some(sk_name) = table_key_schema.sort_key.as_ref() {
804 esk.get(sk_name).and_then(|v| v.to_key_string())
805 } else {
806 Some(String::new())
807 };
808 (base_pk, base_sk)
809 } else {
810 (None, None)
811 }
812 } else {
813 (None, None)
814 };
815
816 let is_select_all_attributes = request
819 .select
820 .as_deref()
821 .map(|s| s.eq_ignore_ascii_case("ALL_ATTRIBUTES"))
822 .unwrap_or(false);
823 let fetch_from_base_table = if is_select_all_attributes {
824 if let Some(ref proj_type) = index_projection_type {
825 if *proj_type != crate::types::ProjectionType::ALL {
826 if !is_lsi {
827 return Err(DynoxideError::ValidationException(format!(
828 "One or more parameter values were invalid: \
829 Select type ALL_ATTRIBUTES is not supported for global secondary index {} \
830 because its projection type is not ALL",
831 request.index_name.as_deref().unwrap_or("")
832 )));
833 }
834 true
836 } else {
837 false
838 }
839 } else {
840 false
841 }
842 } else {
843 false
844 };
845
846 let fetch_limit = request.limit;
847 let sk_params_refs: Vec<&str> = sk_param_values.iter().map(|s| s.as_str()).collect();
848
849 let query_params = crate::storage::QueryParams {
851 sk_condition: sk_condition_sql.as_deref(),
852 sk_params: &sk_params_refs,
853 forward: request.scan_index_forward,
854 limit: fetch_limit,
855 exclusive_start_sk: start_sk.as_deref(),
856 exclusive_start_base_pk: start_base_pk.as_deref(),
857 exclusive_start_base_sk: start_base_sk.as_deref(),
858 };
859 let rows = if let Some(ref index_name) = request.index_name {
860 if is_lsi {
861 storage
862 .query_lsi_items(&request.table_name, index_name, &pk_str, &query_params)
863 .await?
864 } else {
865 storage
866 .query_gsi_items(&request.table_name, index_name, &pk_str, &query_params)
867 .await?
868 }
869 } else {
870 storage
871 .query_items(&request.table_name, &pk_str, &query_params)
872 .await?
873 };
874
875 let filter_expr = request
877 .filter_expression
878 .as_ref()
879 .map(|expr| expressions::condition::parse(expr))
880 .transpose()
881 .map_err(DynoxideError::ValidationException)?;
882
883 let projection = if let Some(ref proj_expr) = request.projection_expression {
885 Some(
886 expressions::projection::parse(proj_expr)
887 .map_err(DynoxideError::ValidationException)?,
888 )
889 } else {
890 legacy_projection.clone()
891 };
892
893 if let Some(ref filter) = filter_expr {
895 tracker.track_condition_expr(filter);
896 }
897 if let Some(ref proj) = projection {
898 tracker.track_projection_expr(proj);
899 expressions::projection::validate(proj, &tracker)
902 .map_err(DynoxideError::ValidationException)?;
903 }
904
905 let loop_tracker = crate::expressions::TrackedExpressionAttributes::without_tracking(
907 &request.expression_attribute_names,
908 &request.expression_attribute_values,
909 );
910
911 let is_count = request
913 .select
914 .as_deref()
915 .map(|s| s.eq_ignore_ascii_case("COUNT"))
916 .unwrap_or(false);
917
918 let mut key_attrs = vec![effective_pk.clone()];
920 if let Some(ref sk) = effective_sk {
921 key_attrs.push(sk.clone());
922 }
923 if request.index_name.is_some() {
925 if !key_attrs.contains(&table_key_schema.partition_key) {
926 key_attrs.push(table_key_schema.partition_key.clone());
927 }
928 if let Some(ref sk) = table_key_schema.sort_key
929 && !key_attrs.contains(sk)
930 {
931 key_attrs.push(sk.clone());
932 }
933 }
934
935 let mut items = Vec::new();
936 let mut scanned_count = 0;
937 let mut filtered_count = 0;
938 let mut cumulative_size = 0;
939 let mut last_evaluated_item: Option<Item> = None;
940 let mut truncated_by_size = false;
941
942 let mut base_table_cumulative_size = 0usize;
945 let mut index_cumulative_size = 0usize;
946
947 let vector_defs = super::vector_index::parse_vector_defs(&meta)?;
952 let mut billed_cumulative_size = 0usize;
953
954 for (_pk, _sk, item_json) in &rows {
955 let index_item: Item = serde_json::from_str(item_json).map_err(|e| {
956 DynoxideError::InternalServerError(format!("Bad item JSON in storage: {e}"))
957 })?;
958
959 index_cumulative_size += crate::types::item_size(&index_item);
963 let item = if fetch_from_base_table {
964 let base_pk = index_item
965 .get(&table_key_schema.partition_key)
966 .and_then(|v| v.to_key_string())
967 .unwrap_or_default();
968 let base_sk = table_key_schema
969 .sort_key
970 .as_ref()
971 .and_then(|sk_name| index_item.get(sk_name))
972 .and_then(|v| v.to_key_string())
973 .unwrap_or_default();
974 if let Some(full_json) = storage
975 .get_item(&request.table_name, &base_pk, &base_sk)
976 .await?
977 {
978 let full_item: Item = serde_json::from_str(&full_json).map_err(|e| {
979 DynoxideError::InternalServerError(format!("Bad item JSON: {e}"))
980 })?;
981 base_table_cumulative_size +=
982 crate::types::billed_item_size(&full_item, &vector_defs);
983 full_item
984 } else {
985 index_item.clone()
986 }
987 } else {
988 index_item.clone()
989 };
990
991 scanned_count += 1;
992
993 let item_size = crate::types::item_size(&item);
996 if cumulative_size + item_size > MAX_RESPONSE_SIZE && scanned_count > 1 {
997 truncated_by_size = true;
998 break;
999 }
1000 cumulative_size += item_size;
1001 if request.index_name.is_none() {
1002 billed_cumulative_size += crate::types::billed_item_size(&item, &vector_defs);
1003 }
1004
1005 if let Some(ref filter) = filter_expr {
1007 let passes = expressions::condition::evaluate(filter, &item, &loop_tracker)
1008 .map_err(DynoxideError::ValidationException)?;
1009 if !passes {
1010 last_evaluated_item = Some(index_item);
1011 continue;
1012 }
1013 }
1014
1015 filtered_count += 1;
1016
1017 let result_item = if let Some(ref proj) = projection {
1020 let no_keys: &[String] = &[];
1021 expressions::projection::apply(&item, proj, &loop_tracker, no_keys)
1022 .map_err(DynoxideError::ValidationException)?
1023 } else {
1024 item
1025 };
1026
1027 last_evaluated_item = Some(index_item);
1028 if !is_count {
1029 items.push(result_item);
1030 }
1031 }
1032
1033 tracker.check_unused()?;
1035
1036 let count = if is_count {
1037 filtered_count
1038 } else {
1039 items.len()
1040 };
1041
1042 let has_more = truncated_by_size
1045 || (fetch_limit.is_some() && scanned_count >= fetch_limit.unwrap_or(usize::MAX));
1046
1047 let is_gsi_query = request.index_name.is_some() && !is_lsi;
1052 let last_evaluated_key = if has_more {
1053 last_evaluated_item.map(|item| {
1054 let mut key = build_last_evaluated_key(&item, &key_hash, &key_range);
1055 if is_lsi
1057 && let Some(tsk) = table_key_schema.sort_key.as_deref()
1058 && !key.contains_key(tsk)
1059 && let Some(v) = item.get(tsk)
1060 {
1061 key.insert(tsk.to_string(), v.clone());
1062 }
1063 if is_gsi_query {
1065 if !key.contains_key(&table_key_schema.partition_key)
1066 && let Some(v) = item.get(&table_key_schema.partition_key)
1067 {
1068 key.insert(table_key_schema.partition_key.clone(), v.clone());
1069 }
1070 if let Some(ref tsk) = table_key_schema.sort_key
1071 && !key.contains_key(tsk)
1072 && let Some(v) = item.get(tsk)
1073 {
1074 key.insert(tsk.clone(), v.clone());
1075 }
1076 }
1077 key
1078 })
1079 } else {
1080 None
1081 };
1082
1083 let is_gsi = is_gsi_query;
1085 let consistent = request.consistent_read.unwrap_or(false);
1086 let consumed_capacity = if is_gsi {
1087 let mut gsi_units = std::collections::HashMap::new();
1088 gsi_units.insert(
1089 request.index_name.as_ref().unwrap().clone(),
1090 crate::types::read_capacity_units_with_consistency(cumulative_size, consistent),
1091 );
1092 crate::types::consumed_capacity_with_indexes(
1093 &request.table_name,
1094 0.0,
1095 &gsi_units,
1096 &request.return_consumed_capacity,
1097 )
1098 } else if is_lsi {
1099 let (table_cap, lsi_cap) = if fetch_from_base_table {
1102 let table_rcu = crate::types::read_capacity_units_with_consistency(
1103 base_table_cumulative_size,
1104 consistent,
1105 );
1106 let lsi_rcu = crate::types::read_capacity_units_with_consistency(
1107 index_cumulative_size,
1108 consistent,
1109 );
1110 (table_rcu, lsi_rcu)
1111 } else {
1112 (
1113 0.0,
1114 crate::types::read_capacity_units_with_consistency(cumulative_size, consistent),
1115 )
1116 };
1117 let mut lsi_units = std::collections::HashMap::new();
1118 lsi_units.insert(request.index_name.as_ref().unwrap().clone(), lsi_cap);
1119 crate::types::consumed_capacity_with_secondary_indexes(
1120 &request.table_name,
1121 table_cap,
1122 &std::collections::HashMap::new(),
1123 &lsi_units,
1124 &request.return_consumed_capacity,
1125 )
1126 } else {
1127 crate::types::consumed_capacity(
1128 &request.table_name,
1129 crate::types::read_capacity_units_with_consistency(billed_cumulative_size, consistent),
1130 &request.return_consumed_capacity,
1131 )
1132 };
1133
1134 Ok(QueryResponse {
1135 items: if is_count { None } else { Some(items) },
1136 count,
1137 scanned_count,
1138 last_evaluated_key,
1139 consumed_capacity,
1140 })
1141}
1142
1143fn build_last_evaluated_key(
1146 item: &Item,
1147 hash: &[String],
1148 range: &[String],
1149) -> HashMap<String, AttributeValue> {
1150 hash.iter()
1151 .chain(range)
1152 .filter_map(|name| item.get(name).map(|v| (name.clone(), v.clone())))
1153 .collect()
1154}
1155
1156fn check_key_condition_types(
1162 bound: &expressions::key_condition::BoundKeyCondition,
1163 meta: &crate::storage::TableMetadata,
1164) -> Result<()> {
1165 let defs: Vec<crate::types::AttributeDefinition> =
1166 serde_json::from_str(&meta.attribute_definitions).map_err(|e| {
1167 DynoxideError::InternalServerError(format!("Bad attribute definitions JSON: {e}"))
1168 })?;
1169 for (attr, value) in bound.compared_values() {
1170 if let Some(def) = defs.iter().find(|d| d.attribute_name == attr)
1171 && !helpers::attr_value_matches_scalar_type(value, &def.attribute_type)
1172 {
1173 return Err(DynoxideError::ValidationException(
1174 "One or more parameter values were invalid: Condition parameter type does not \
1175 match schema type"
1176 .to_string(),
1177 ));
1178 }
1179 }
1180 Ok(())
1181}
1182
1183fn reject_vector_index(
1189 meta: &crate::storage::TableMetadata,
1190 index_name: &str,
1191) -> crate::errors::Result<()> {
1192 if super::vector_index::parse_vector_defs(meta)?
1193 .iter()
1194 .any(|v| v.index_name == index_name)
1195 {
1196 return Err(DynoxideError::ValidationException(
1197 "Query operation not supported on this index type.".to_string(),
1198 ));
1199 }
1200 Ok(())
1201}
1202
1203fn key_condition_error(expr: &str, err: String) -> String {
1210 if err.contains("Expression size has exceeded") || err.contains("redundant parentheses") {
1211 return err;
1212 }
1213 match expressions::condition::syntax_error(expr) {
1214 Some(syntax) => format!("Invalid KeyConditionExpression: {syntax}"),
1215 None => err,
1216 }
1217}
1218
1219const START_KEY_INVALID: &str = "The provided starting key is invalid";
1226
1227pub(crate) fn bare_start_key_mismatch(err: DynoxideError) -> DynoxideError {
1232 match err {
1233 DynoxideError::ValidationException(msg)
1234 if msg
1235 == "The provided starting key is invalid: \
1236 The provided key element does not match the schema"
1237 || msg == "The provided key element does not match the schema" =>
1238 {
1239 DynoxideError::ValidationException(START_KEY_INVALID.to_string())
1240 }
1241 other => other,
1242 }
1243}
1244
1245fn start_key_value_error(err: DynoxideError) -> DynoxideError {
1250 match err {
1251 DynoxideError::ValidationException(msg) => {
1252 match msg.strip_prefix("The provided starting key is invalid: ") {
1253 Some(rest) if rest.starts_with("One or more parameter values were invalid:") => {
1254 DynoxideError::ValidationException(rest.to_string())
1255 }
1256 _ => DynoxideError::ValidationException(msg),
1257 }
1258 }
1259 other => other,
1260 }
1261}
1262
1263fn check_start_key_within_condition(
1276 esk: &Item,
1277 bound: &expressions::key_condition::BoundKeyCondition,
1278 table_without_sort_key: bool,
1279) -> Result<()> {
1280 use crate::expressions::condition::{CompOp, compare_values};
1281
1282 const AT_MOST_ONE_ROW: &str = "The query can return at most one row and cannot be restarted";
1283 let refuse = |message: &str| Err(DynoxideError::ValidationException(message.to_string()));
1284 let holds = |name: &str, value: &AttributeValue| {
1285 esk.get(name)
1286 .is_some_and(|v| compare_values(v, &CompOp::Eq, value))
1287 };
1288
1289 let Some(((last_name, last_value), leading)) = bound.hash.split_last() else {
1290 return Ok(());
1291 };
1292 if leading.iter().any(|(name, value)| !holds(name, value)) {
1293 return refuse(AT_MOST_ONE_ROW);
1294 }
1295 let outside_equalities = bound
1296 .range_eq
1297 .iter()
1298 .any(|(name, value)| esk.contains_key(name) && !holds(name, value));
1299 let outside_last = bound.range_last.as_ref().is_some_and(|condition| {
1300 esk.get(condition.sk_name())
1301 .is_some_and(|value| !sort_key_satisfies(value, condition))
1302 });
1303 if outside_equalities || outside_last {
1304 return refuse("The provided starting key does not match the range key predicate");
1305 }
1306 if !holds(last_name, last_value) {
1307 if !bound.range_eq.is_empty() || bound.range_last.is_some() {
1308 return refuse(AT_MOST_ONE_ROW);
1309 }
1310 if table_without_sort_key {
1311 return refuse("The provided exclusive start key is invalid");
1312 }
1313 return refuse(
1314 "The provided starting key is outside query boundaries based on provided conditions",
1315 );
1316 }
1317 Ok(())
1318}
1319
1320fn sort_key_satisfies(
1321 value: &AttributeValue,
1322 condition: &expressions::key_condition::ResolvedSortKeyCondition,
1323) -> bool {
1324 use crate::expressions::condition::{CompOp, compare_values};
1325 use expressions::key_condition::ResolvedSortKeyCondition as Sk;
1326
1327 match condition {
1328 Sk::Eq(_, v) => compare_values(value, &CompOp::Eq, v),
1329 Sk::Lt(_, v) => compare_values(value, &CompOp::Lt, v),
1330 Sk::Le(_, v) => compare_values(value, &CompOp::Le, v),
1331 Sk::Gt(_, v) => compare_values(value, &CompOp::Gt, v),
1332 Sk::Ge(_, v) => compare_values(value, &CompOp::Ge, v),
1333 Sk::Between(_, low, high) => {
1334 compare_values(value, &CompOp::Ge, low) && compare_values(value, &CompOp::Le, high)
1335 }
1336 Sk::BeginsWith(_, prefix) => match (value, prefix) {
1337 (AttributeValue::S(s), AttributeValue::S(p)) => s.starts_with(p.as_str()),
1338 (AttributeValue::B(b), AttributeValue::B(p)) => b.starts_with(p),
1339 _ => false,
1340 },
1341 }
1342}
1343
1344fn check_legacy_key_condition_types(
1352 conditions: &HashMap<String, helpers::KeyCondition>,
1353 meta: &crate::storage::TableMetadata,
1354) -> Result<()> {
1355 use crate::types::{AttributeDefinition, ScalarAttributeType};
1356
1357 let definitions: Vec<AttributeDefinition> = serde_json::from_str(&meta.attribute_definitions)
1358 .map_err(|e| {
1359 DynoxideError::InternalServerError(format!("Bad attribute definitions JSON: {e}"))
1360 })?;
1361 let lists = || {
1362 conditions
1363 .iter()
1364 .filter_map(|(name, condition)| Some((name, condition.attribute_value_list.as_ref()?)))
1365 };
1366 if lists().any(|(_, values)| {
1367 values
1368 .windows(2)
1369 .any(|pair| std::mem::discriminant(&pair[0]) != std::mem::discriminant(&pair[1]))
1370 }) {
1371 return Err(DynoxideError::ValidationException(
1372 "One or more parameter values were invalid: \
1373 AttributeValues inside AttributeValueList must be of same type"
1374 .to_string(),
1375 ));
1376 }
1377 for (name, values) in lists() {
1378 let Some(definition) = definitions.iter().find(|d| &d.attribute_name == name) else {
1379 continue;
1380 };
1381 let matches = |value: &AttributeValue| {
1382 matches!(
1383 (value, &definition.attribute_type),
1384 (AttributeValue::S(_), ScalarAttributeType::S)
1385 | (AttributeValue::N(_), ScalarAttributeType::N)
1386 | (AttributeValue::B(_), ScalarAttributeType::B)
1387 )
1388 };
1389 if !values.iter().all(matches) {
1390 return Err(DynoxideError::ValidationException(
1391 "One or more parameter values were invalid: \
1392 Condition parameter type does not match schema type"
1393 .to_string(),
1394 ));
1395 }
1396 }
1397 Ok(())
1398}
1399
1400fn check_start_key_not_empty(
1406 esk: &Item,
1407 partition_key: &str,
1408 sort_key: Option<&str>,
1409) -> Result<()> {
1410 for name in std::iter::once(partition_key).chain(sort_key) {
1411 let kind = match esk.get(name) {
1412 Some(AttributeValue::S(s)) if s.is_empty() => "string",
1413 Some(AttributeValue::B(b)) if b.is_empty() => "binary",
1414 _ => continue,
1415 };
1416 return Err(DynoxideError::KeyEmptyValueValidation(format!(
1417 "One or more parameter values are not valid. The AttributeValue for a key \
1418 attribute cannot contain an empty {kind} value. Key: {name}"
1419 )));
1420 }
1421 Ok(())
1422}