Skip to content

Commit 3a4b1ae

Browse files
yesyayenpdf-amzn
authored andcommitted
fix: validate transaction expression attributes and label parse errors per parameter
1 parent 93de8e3 commit 3a4b1ae

4 files changed

Lines changed: 286 additions & 52 deletions

File tree

crates/engine/src/transact_get_items.rs

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -161,14 +161,7 @@ pub async fn handle_transact_get_items(
161161
.filter(|i| !i.is_empty());
162162
Ok(ItemResponse { item })
163163
} else {
164-
extenddb_core::expression::validate_unused_attributes(
165-
&maps.names,
166-
&maps.values,
167-
&[],
168-
&[],
169-
&std::collections::HashSet::new(),
170-
&std::collections::HashSet::new(),
171-
)?;
164+
// No projection: transactions accept names with no expression.
172165
Ok(ItemResponse { item: opt })
173166
}
174167
})

crates/engine/src/transact_write_helpers.rs

Lines changed: 0 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -207,24 +207,6 @@ impl PreparedOp {
207207
}
208208
}
209209

210-
/// Parse an optional condition expression string.
211-
pub(crate) fn parse_optional_condition(
212-
expr: Option<&str>,
213-
limits: &extenddb_core::limits::LimitsConfig,
214-
) -> Result<Option<extenddb_core::expression::Expr>, DynamoDbError> {
215-
match expr {
216-
Some(s) if !s.is_empty() => {
217-
let tokens = crate::expression_helpers::tokenize_expression(s, limits)?;
218-
let ast = extenddb_core::expression::parse_condition_with_depth_limit(
219-
&tokens,
220-
limits.max_expression_depth,
221-
)?;
222-
Ok(Some(ast))
223-
}
224-
_ => Ok(None),
225-
}
226-
}
227-
228210
/// Validate `ClientRequestToken` format.
229211
///
230212
/// Real `DynamoDB` requires 1–36 characters, alphanumeric plus hyphens.
@@ -308,17 +290,6 @@ pub(crate) fn validate_no_key_updates(
308290
mod tests {
309291
use super::*;
310292

311-
#[test]
312-
fn transact_condition_redundant_parens_rejected_with_canonical_message() {
313-
let limits = extenddb_core::limits::LimitsConfig::default();
314-
let err = parse_optional_condition(Some("((a = :v))"), &limits).unwrap_err();
315-
assert!(
316-
matches!(&err, DynamoDbError::ValidationException(msg)
317-
if msg == "Invalid ConditionExpression: The expression has redundant parentheses;"),
318-
"got {err:?}"
319-
);
320-
}
321-
322293
#[test]
323294
fn validate_token_valid() {
324295
assert!(validate_client_request_token("abc-123").is_ok());

crates/engine/src/transact_write_items.rs

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,17 @@ use serde_json::Value;
1010
use crate::OperationContext;
1111
use crate::capacity_helpers;
1212
use crate::create_table::storage_err_to_dynamo;
13-
use crate::expression_helpers::build_expression_maps;
13+
use crate::expression_helpers::{build_expression_maps, parse_optional_condition};
1414
use crate::serialize_output;
1515
use crate::stream_capture;
1616
use crate::transact_write_helpers::{
17-
PreparedOp, compute_fingerprint, parse_optional_condition, validate_client_request_token,
18-
validate_no_key_updates,
17+
PreparedOp, compute_fingerprint, validate_client_request_token, validate_no_key_updates,
1918
};
2019
use crate::{DispatchMetrics, DispatchResult};
2120
use extenddb_core::error::DynamoDbError;
22-
use extenddb_core::expression::parse_update;
21+
use extenddb_core::expression::{
22+
ExpressionKind, parse_update_from, tokenize_for, validate_no_reserved_words,
23+
};
2324
use extenddb_core::types::{TransactWriteItem, TransactWriteItemsInput, TransactWriteItemsOutput};
2425
use extenddb_core::validation::{
2526
validate_attribute_name_sizes, validate_attribute_values_nesting_depth,
@@ -218,7 +219,9 @@ async fn prepare_write_op(
218219
put.expression_attribute_values.as_ref(),
219220
);
220221
let condition = parse_optional_condition(put.condition_expression.as_deref(), &ctx.limits)?;
221-
{
222+
// Transactions accept names/values with no expression (unlike single-item
223+
// APIs); only check for unused refs when a condition is present.
224+
if condition.is_some() {
222225
let exprs: Vec<&extenddb_core::expression::Expr> = condition.iter().collect();
223226
extenddb_core::expression::validate_unused_attributes(
224227
&maps.names,
@@ -256,7 +259,7 @@ async fn prepare_write_op(
256259
del.expression_attribute_values.as_ref(),
257260
);
258261
let condition = parse_optional_condition(del.condition_expression.as_deref(), &ctx.limits)?;
259-
{
262+
if condition.is_some() {
260263
let exprs: Vec<&extenddb_core::expression::Expr> = condition.iter().collect();
261264
extenddb_core::expression::validate_unused_attributes(
262265
&maps.names,
@@ -293,9 +296,20 @@ async fn prepare_write_op(
293296
upd.expression_attribute_names.as_ref(),
294297
upd.expression_attribute_values.as_ref(),
295298
);
296-
let update_tokens =
297-
crate::expression_helpers::tokenize_expression(&upd.update_expression, &ctx.limits)?;
298-
let actions = parse_update(&update_tokens)?;
299+
let actions = tokenize_for(
300+
&upd.update_expression,
301+
ctx.limits.max_expression_tokens,
302+
ExpressionKind::Update,
303+
)
304+
.and_then(|update_tokens| {
305+
if ctx.limits.enforce_reserved_keywords {
306+
validate_no_reserved_words(&update_tokens)?;
307+
}
308+
parse_update_from(&update_tokens, &upd.update_expression)
309+
})
310+
.map_err(|e| {
311+
crate::expression_helpers::prefix_expression_error(e, ExpressionKind::Update)
312+
})?;
299313
validate_no_key_updates(&actions, &key_info, &maps)?;
300314

301315
// Validate nesting depth of EAV values that get stored via SET actions.
@@ -351,12 +365,17 @@ async fn prepare_write_op(
351365
cc.expression_attribute_names.as_ref(),
352366
cc.expression_attribute_values.as_ref(),
353367
);
354-
let tokens =
355-
crate::expression_helpers::tokenize_expression(&cc.condition_expression, &ctx.limits)?;
356-
let condition = extenddb_core::expression::parse_condition_with_depth_limit(
357-
&tokens,
358-
ctx.limits.max_expression_depth,
359-
)?;
368+
let condition =
369+
crate::expression_helpers::tokenize_expression(&cc.condition_expression, &ctx.limits)
370+
.and_then(|tokens| {
371+
extenddb_core::expression::parse_condition_with_depth_limit(
372+
&tokens,
373+
ctx.limits.max_expression_depth,
374+
)
375+
})
376+
.map_err(|e| {
377+
crate::expression_helpers::prefix_expression_error(e, ExpressionKind::Condition)
378+
})?;
360379
{
361380
let exprs: Vec<&extenddb_core::expression::Expr> = vec![&condition];
362381
extenddb_core::expression::validate_unused_attributes(

0 commit comments

Comments
 (0)