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 for (_pk, _sk, item_json) in &rows {
948 let index_item: Item = serde_json::from_str(item_json).map_err(|e| {
949 DynoxideError::InternalServerError(format!("Bad item JSON in storage: {e}"))
950 })?;
951
952 index_cumulative_size += crate::types::item_size(&index_item);
956 let item = if fetch_from_base_table {
957 let base_pk = index_item
958 .get(&table_key_schema.partition_key)
959 .and_then(|v| v.to_key_string())
960 .unwrap_or_default();
961 let base_sk = table_key_schema
962 .sort_key
963 .as_ref()
964 .and_then(|sk_name| index_item.get(sk_name))
965 .and_then(|v| v.to_key_string())
966 .unwrap_or_default();
967 if let Some(full_json) = storage
968 .get_item(&request.table_name, &base_pk, &base_sk)
969 .await?
970 {
971 let full_item: Item = serde_json::from_str(&full_json).map_err(|e| {
972 DynoxideError::InternalServerError(format!("Bad item JSON: {e}"))
973 })?;
974 base_table_cumulative_size += crate::types::item_size(&full_item);
975 full_item
976 } else {
977 index_item.clone()
978 }
979 } else {
980 index_item.clone()
981 };
982
983 scanned_count += 1;
984
985 let item_size = crate::types::item_size(&item);
988 if cumulative_size + item_size > MAX_RESPONSE_SIZE && scanned_count > 1 {
989 truncated_by_size = true;
990 break;
991 }
992 cumulative_size += item_size;
993
994 if let Some(ref filter) = filter_expr {
996 let passes = expressions::condition::evaluate(filter, &item, &loop_tracker)
997 .map_err(DynoxideError::ValidationException)?;
998 if !passes {
999 last_evaluated_item = Some(index_item);
1000 continue;
1001 }
1002 }
1003
1004 filtered_count += 1;
1005
1006 let result_item = if let Some(ref proj) = projection {
1009 let no_keys: &[String] = &[];
1010 expressions::projection::apply(&item, proj, &loop_tracker, no_keys)
1011 .map_err(DynoxideError::ValidationException)?
1012 } else {
1013 item
1014 };
1015
1016 last_evaluated_item = Some(index_item);
1017 if !is_count {
1018 items.push(result_item);
1019 }
1020 }
1021
1022 tracker.check_unused()?;
1024
1025 let count = if is_count {
1026 filtered_count
1027 } else {
1028 items.len()
1029 };
1030
1031 let has_more = truncated_by_size
1034 || (fetch_limit.is_some() && scanned_count >= fetch_limit.unwrap_or(usize::MAX));
1035
1036 let is_gsi_query = request.index_name.is_some() && !is_lsi;
1041 let last_evaluated_key = if has_more {
1042 last_evaluated_item.map(|item| {
1043 let mut key = build_last_evaluated_key(&item, &key_hash, &key_range);
1044 if is_lsi
1046 && let Some(tsk) = table_key_schema.sort_key.as_deref()
1047 && !key.contains_key(tsk)
1048 && let Some(v) = item.get(tsk)
1049 {
1050 key.insert(tsk.to_string(), v.clone());
1051 }
1052 if is_gsi_query {
1054 if !key.contains_key(&table_key_schema.partition_key)
1055 && let Some(v) = item.get(&table_key_schema.partition_key)
1056 {
1057 key.insert(table_key_schema.partition_key.clone(), v.clone());
1058 }
1059 if let Some(ref tsk) = table_key_schema.sort_key
1060 && !key.contains_key(tsk)
1061 && let Some(v) = item.get(tsk)
1062 {
1063 key.insert(tsk.clone(), v.clone());
1064 }
1065 }
1066 key
1067 })
1068 } else {
1069 None
1070 };
1071
1072 let is_gsi = is_gsi_query;
1074 let consistent = request.consistent_read.unwrap_or(false);
1075 let consumed_capacity = if is_gsi {
1076 let mut gsi_units = std::collections::HashMap::new();
1077 gsi_units.insert(
1078 request.index_name.as_ref().unwrap().clone(),
1079 crate::types::read_capacity_units_with_consistency(cumulative_size, consistent),
1080 );
1081 crate::types::consumed_capacity_with_indexes(
1082 &request.table_name,
1083 0.0,
1084 &gsi_units,
1085 &request.return_consumed_capacity,
1086 )
1087 } else if is_lsi {
1088 let (table_cap, lsi_cap) = if fetch_from_base_table {
1091 let table_rcu = crate::types::read_capacity_units_with_consistency(
1092 base_table_cumulative_size,
1093 consistent,
1094 );
1095 let lsi_rcu = crate::types::read_capacity_units_with_consistency(
1096 index_cumulative_size,
1097 consistent,
1098 );
1099 (table_rcu, lsi_rcu)
1100 } else {
1101 (
1102 0.0,
1103 crate::types::read_capacity_units_with_consistency(cumulative_size, consistent),
1104 )
1105 };
1106 let mut lsi_units = std::collections::HashMap::new();
1107 lsi_units.insert(request.index_name.as_ref().unwrap().clone(), lsi_cap);
1108 crate::types::consumed_capacity_with_secondary_indexes(
1109 &request.table_name,
1110 table_cap,
1111 &std::collections::HashMap::new(),
1112 &lsi_units,
1113 &request.return_consumed_capacity,
1114 )
1115 } else {
1116 crate::types::consumed_capacity(
1117 &request.table_name,
1118 crate::types::read_capacity_units_with_consistency(cumulative_size, consistent),
1119 &request.return_consumed_capacity,
1120 )
1121 };
1122
1123 Ok(QueryResponse {
1124 items: if is_count { None } else { Some(items) },
1125 count,
1126 scanned_count,
1127 last_evaluated_key,
1128 consumed_capacity,
1129 })
1130}
1131
1132fn build_last_evaluated_key(
1135 item: &Item,
1136 hash: &[String],
1137 range: &[String],
1138) -> HashMap<String, AttributeValue> {
1139 hash.iter()
1140 .chain(range)
1141 .filter_map(|name| item.get(name).map(|v| (name.clone(), v.clone())))
1142 .collect()
1143}
1144
1145fn check_key_condition_types(
1151 bound: &expressions::key_condition::BoundKeyCondition,
1152 meta: &crate::storage::TableMetadata,
1153) -> Result<()> {
1154 let defs: Vec<crate::types::AttributeDefinition> =
1155 serde_json::from_str(&meta.attribute_definitions).map_err(|e| {
1156 DynoxideError::InternalServerError(format!("Bad attribute definitions JSON: {e}"))
1157 })?;
1158 for (attr, value) in bound.compared_values() {
1159 if let Some(def) = defs.iter().find(|d| d.attribute_name == attr)
1160 && !helpers::attr_value_matches_scalar_type(value, &def.attribute_type)
1161 {
1162 return Err(DynoxideError::ValidationException(
1163 "One or more parameter values were invalid: Condition parameter type does not \
1164 match schema type"
1165 .to_string(),
1166 ));
1167 }
1168 }
1169 Ok(())
1170}
1171
1172fn reject_vector_index(
1178 meta: &crate::storage::TableMetadata,
1179 index_name: &str,
1180) -> crate::errors::Result<()> {
1181 if super::vector_index::parse_vector_defs(meta)?
1182 .iter()
1183 .any(|v| v.index_name == index_name)
1184 {
1185 return Err(DynoxideError::ValidationException(
1186 "Query operation not supported on this index type.".to_string(),
1187 ));
1188 }
1189 Ok(())
1190}
1191
1192fn key_condition_error(expr: &str, err: String) -> String {
1199 if err.contains("Expression size has exceeded") || err.contains("redundant parentheses") {
1200 return err;
1201 }
1202 match expressions::condition::syntax_error(expr) {
1203 Some(syntax) => format!("Invalid KeyConditionExpression: {syntax}"),
1204 None => err,
1205 }
1206}
1207
1208const START_KEY_INVALID: &str = "The provided starting key is invalid";
1215
1216pub(crate) fn bare_start_key_mismatch(err: DynoxideError) -> DynoxideError {
1221 match err {
1222 DynoxideError::ValidationException(msg)
1223 if msg
1224 == "The provided starting key is invalid: \
1225 The provided key element does not match the schema"
1226 || msg == "The provided key element does not match the schema" =>
1227 {
1228 DynoxideError::ValidationException(START_KEY_INVALID.to_string())
1229 }
1230 other => other,
1231 }
1232}
1233
1234fn start_key_value_error(err: DynoxideError) -> DynoxideError {
1239 match err {
1240 DynoxideError::ValidationException(msg) => {
1241 match msg.strip_prefix("The provided starting key is invalid: ") {
1242 Some(rest) if rest.starts_with("One or more parameter values were invalid:") => {
1243 DynoxideError::ValidationException(rest.to_string())
1244 }
1245 _ => DynoxideError::ValidationException(msg),
1246 }
1247 }
1248 other => other,
1249 }
1250}
1251
1252fn check_start_key_within_condition(
1265 esk: &Item,
1266 bound: &expressions::key_condition::BoundKeyCondition,
1267 table_without_sort_key: bool,
1268) -> Result<()> {
1269 use crate::expressions::condition::{CompOp, compare_values};
1270
1271 const AT_MOST_ONE_ROW: &str = "The query can return at most one row and cannot be restarted";
1272 let refuse = |message: &str| Err(DynoxideError::ValidationException(message.to_string()));
1273 let holds = |name: &str, value: &AttributeValue| {
1274 esk.get(name)
1275 .is_some_and(|v| compare_values(v, &CompOp::Eq, value))
1276 };
1277
1278 let Some(((last_name, last_value), leading)) = bound.hash.split_last() else {
1279 return Ok(());
1280 };
1281 if leading.iter().any(|(name, value)| !holds(name, value)) {
1282 return refuse(AT_MOST_ONE_ROW);
1283 }
1284 let outside_equalities = bound
1285 .range_eq
1286 .iter()
1287 .any(|(name, value)| esk.contains_key(name) && !holds(name, value));
1288 let outside_last = bound.range_last.as_ref().is_some_and(|condition| {
1289 esk.get(condition.sk_name())
1290 .is_some_and(|value| !sort_key_satisfies(value, condition))
1291 });
1292 if outside_equalities || outside_last {
1293 return refuse("The provided starting key does not match the range key predicate");
1294 }
1295 if !holds(last_name, last_value) {
1296 if !bound.range_eq.is_empty() || bound.range_last.is_some() {
1297 return refuse(AT_MOST_ONE_ROW);
1298 }
1299 if table_without_sort_key {
1300 return refuse("The provided exclusive start key is invalid");
1301 }
1302 return refuse(
1303 "The provided starting key is outside query boundaries based on provided conditions",
1304 );
1305 }
1306 Ok(())
1307}
1308
1309fn sort_key_satisfies(
1310 value: &AttributeValue,
1311 condition: &expressions::key_condition::ResolvedSortKeyCondition,
1312) -> bool {
1313 use crate::expressions::condition::{CompOp, compare_values};
1314 use expressions::key_condition::ResolvedSortKeyCondition as Sk;
1315
1316 match condition {
1317 Sk::Eq(_, v) => compare_values(value, &CompOp::Eq, v),
1318 Sk::Lt(_, v) => compare_values(value, &CompOp::Lt, v),
1319 Sk::Le(_, v) => compare_values(value, &CompOp::Le, v),
1320 Sk::Gt(_, v) => compare_values(value, &CompOp::Gt, v),
1321 Sk::Ge(_, v) => compare_values(value, &CompOp::Ge, v),
1322 Sk::Between(_, low, high) => {
1323 compare_values(value, &CompOp::Ge, low) && compare_values(value, &CompOp::Le, high)
1324 }
1325 Sk::BeginsWith(_, prefix) => match (value, prefix) {
1326 (AttributeValue::S(s), AttributeValue::S(p)) => s.starts_with(p.as_str()),
1327 (AttributeValue::B(b), AttributeValue::B(p)) => b.starts_with(p),
1328 _ => false,
1329 },
1330 }
1331}
1332
1333fn check_legacy_key_condition_types(
1341 conditions: &HashMap<String, helpers::KeyCondition>,
1342 meta: &crate::storage::TableMetadata,
1343) -> Result<()> {
1344 use crate::types::{AttributeDefinition, ScalarAttributeType};
1345
1346 let definitions: Vec<AttributeDefinition> = serde_json::from_str(&meta.attribute_definitions)
1347 .map_err(|e| {
1348 DynoxideError::InternalServerError(format!("Bad attribute definitions JSON: {e}"))
1349 })?;
1350 let lists = || {
1351 conditions
1352 .iter()
1353 .filter_map(|(name, condition)| Some((name, condition.attribute_value_list.as_ref()?)))
1354 };
1355 if lists().any(|(_, values)| {
1356 values
1357 .windows(2)
1358 .any(|pair| std::mem::discriminant(&pair[0]) != std::mem::discriminant(&pair[1]))
1359 }) {
1360 return Err(DynoxideError::ValidationException(
1361 "One or more parameter values were invalid: \
1362 AttributeValues inside AttributeValueList must be of same type"
1363 .to_string(),
1364 ));
1365 }
1366 for (name, values) in lists() {
1367 let Some(definition) = definitions.iter().find(|d| &d.attribute_name == name) else {
1368 continue;
1369 };
1370 let matches = |value: &AttributeValue| {
1371 matches!(
1372 (value, &definition.attribute_type),
1373 (AttributeValue::S(_), ScalarAttributeType::S)
1374 | (AttributeValue::N(_), ScalarAttributeType::N)
1375 | (AttributeValue::B(_), ScalarAttributeType::B)
1376 )
1377 };
1378 if !values.iter().all(matches) {
1379 return Err(DynoxideError::ValidationException(
1380 "One or more parameter values were invalid: \
1381 Condition parameter type does not match schema type"
1382 .to_string(),
1383 ));
1384 }
1385 }
1386 Ok(())
1387}
1388
1389fn check_start_key_not_empty(
1395 esk: &Item,
1396 partition_key: &str,
1397 sort_key: Option<&str>,
1398) -> Result<()> {
1399 for name in std::iter::once(partition_key).chain(sort_key) {
1400 let kind = match esk.get(name) {
1401 Some(AttributeValue::S(s)) if s.is_empty() => "string",
1402 Some(AttributeValue::B(b)) if b.is_empty() => "binary",
1403 _ => continue,
1404 };
1405 return Err(DynoxideError::KeyEmptyValueValidation(format!(
1406 "One or more parameter values are not valid. The AttributeValue for a key \
1407 attribute cannot contain an empty {kind} value. Key: {name}"
1408 )));
1409 }
1410 Ok(())
1411}