118 lines
3.9 KiB
Odin
118 lines
3.9 KiB
Odin
|
|
// 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
|
||
|
|
}
|