// ConditionExpression support for PutItem, DeleteItem, and UpdateItem // // ConditionExpression uses the same grammar as FilterExpression but is evaluated // against the *existing* item (before the mutation). If the condition evaluates // to false, the operation is rejected with ConditionalCheckFailedException. // // When there is no existing item: // - attribute_not_exists(path) → true (attribute doesn't exist on a non-existent item) // - attribute_exists(path) → false // - All comparisons → false (no attribute to compare) // // This file provides: // 1. parse_condition_expression_string — extract ConditionExpression from JSON body // 2. evaluate_condition — evaluate parsed condition against an item // 3. Condition_Result — result enum for condition evaluation package dynamodb import "core:encoding/json" // ============================================================================ // Condition Evaluation Result // ============================================================================ Condition_Result :: enum { Passed, // Condition met (or no condition specified) Failed, // Condition not met → ConditionalCheckFailedException Parse_Error, // Malformed ConditionExpression → ValidationException } // ============================================================================ // Request Parsing // ============================================================================ // Extract the raw ConditionExpression string from the request body. parse_condition_expression_string :: proc(request_body: []byte) -> (expr: string, ok: bool) { data, parse_err := json.parse(request_body, allocator = context.temp_allocator) if parse_err != nil { return } defer json.destroy_value(data) root, root_ok := data.(json.Object) if !root_ok { return } ce_val, found := root["ConditionExpression"] if !found { return } ce_str, str_ok := ce_val.(json.String) if !str_ok { return } expr = string(ce_str) ok = true return } // ============================================================================ // Full Condition Evaluation Pipeline // // Parses ConditionExpression + ExpressionAttributeNames/Values from the // request body, then evaluates against the existing item. // // Parameters: // request_body — full JSON request body // existing_item — the item currently in the database (nil if no item exists) // attr_names — pre-parsed ExpressionAttributeNames (caller may already have these) // attr_values — pre-parsed ExpressionAttributeValues // // Returns Condition_Result: // .Passed — no ConditionExpression, or condition evaluated to true // .Failed — condition evaluated to false // .Parse_Error — ConditionExpression is malformed // ============================================================================ evaluate_condition_expression :: proc( request_body: []byte, existing_item: Maybe(Item), attr_names: Maybe(map[string]string), attr_values: map[string]Attribute_Value, ) -> Condition_Result { // Extract ConditionExpression string condition_str, has_condition := parse_condition_expression_string(request_body) if !has_condition { return .Passed // No condition → always pass } // Parse the condition into a filter tree (same grammar as FilterExpression) filter_node, parse_ok := parse_filter_expression(condition_str, attr_names, attr_values) if !parse_ok || filter_node == nil { return .Parse_Error } defer { filter_node_destroy(filter_node) } // If there is no existing item, build an empty item for evaluation. // This means attribute_not_exists → true, attribute_exists → false, // all comparisons → false (attribute not found). eval_item: Item if item, has_item := existing_item.?; has_item { eval_item = item } else { // Empty item — no attributes exist eval_item = Item{} } if evaluate_filter(eval_item, filter_node) { return .Passed } return .Failed }