From 225a1533cceedf0fa777b79069c898dc2f57da26 Mon Sep 17 00:00:00 2001 From: biondizzle Date: Tue, 17 Feb 2026 02:03:40 -0500 Subject: [PATCH] fix cloned string cleanup --- dynamodb/condition.odin | 4 +- dynamodb/expression.odin | 3 +- dynamodb/filter.odin | 2 +- dynamodb/json.odin | 2 +- dynamodb/transact.odin | 8 +++ dynamodb/update.odin | 12 ++-- gsi_handlers.odin | 2 +- main.odin | 119 ++++++++++++++++++++++++++------------- transact_handlers.odin | 20 ++++--- 9 files changed, 113 insertions(+), 59 deletions(-) diff --git a/dynamodb/condition.odin b/dynamodb/condition.odin index 06a8b36..00871d3 100644 --- a/dynamodb/condition.odin +++ b/dynamodb/condition.odin @@ -16,6 +16,7 @@ package dynamodb import "core:encoding/json" +import "core:strings" // ============================================================================ // Condition Evaluation Result @@ -54,7 +55,7 @@ parse_condition_expression_string :: proc(request_body: []byte) -> (expr: string return } - expr = string(ce_str) + expr = strings.clone(string(ce_str)) ok = true return } @@ -88,6 +89,7 @@ evaluate_condition_expression :: proc( if !has_condition { return .Passed // No condition → always pass } + defer delete(condition_str) // Parse the condition into a filter tree (same grammar as FilterExpression) filter_node, parse_ok := parse_filter_expression(condition_str, attr_names, attr_values) diff --git a/dynamodb/expression.odin b/dynamodb/expression.odin index bf2cee7..6013e54 100644 --- a/dynamodb/expression.odin +++ b/dynamodb/expression.odin @@ -480,7 +480,7 @@ parse_key_condition_expression_string :: proc(request_body: []byte) -> (expr: st return } - expr = string(kce_str) + expr = strings.clone(string(kce_str)) ok = true return } @@ -488,6 +488,7 @@ parse_key_condition_expression_string :: proc(request_body: []byte) -> (expr: st // Convenience: parse a complete Query key condition from request body parse_query_key_condition :: proc(request_body: []byte) -> (kc: Key_Condition, ok: bool) { expression := parse_key_condition_expression_string(request_body) or_return + defer delete(expression) attr_names := parse_expression_attribute_names(request_body) defer { diff --git a/dynamodb/filter.odin b/dynamodb/filter.odin index c21444f..b92ee35 100644 --- a/dynamodb/filter.odin +++ b/dynamodb/filter.odin @@ -817,7 +817,7 @@ parse_filter_expression_string :: proc(request_body: []byte) -> (expr: string, o return } - expr = string(fe_str) + expr = strings.clone(string(fe_str)) ok = true return } diff --git a/dynamodb/json.odin b/dynamodb/json.odin index b273128..ad46044 100644 --- a/dynamodb/json.odin +++ b/dynamodb/json.odin @@ -431,7 +431,7 @@ parse_table_name :: proc(request_body: []byte) -> (string, bool) { return "", false } - return string(table_name_str), true + return strings.clone(string(table_name_str)), true } // Parse Item field from request body diff --git a/dynamodb/transact.odin b/dynamodb/transact.odin index 8ea5d02..efa0632 100644 --- a/dynamodb/transact.odin +++ b/dynamodb/transact.odin @@ -54,6 +54,10 @@ Cancellation_Reason :: struct { } transact_write_action_destroy :: proc(action: ^Transact_Write_Action) { + delete(action.table_name) + if ce, has := action.condition_expr.?; has { + delete(ce) + } if item, has := action.item.?; has { item_copy := item item_destroy(&item_copy) @@ -618,8 +622,12 @@ Transact_Get_Result :: struct { } transact_get_action_destroy :: proc(action: ^Transact_Get_Action) { + delete(action.table_name) item_destroy(&action.key) if proj, has := action.projection.?; has { + for path in proj { + delete(path) + } delete(proj) } } diff --git a/dynamodb/update.odin b/dynamodb/update.odin index c484588..6b1d761 100644 --- a/dynamodb/update.odin +++ b/dynamodb/update.odin @@ -930,7 +930,7 @@ parse_update_expression_string :: proc(request_body: []byte) -> (expr: string, o return } - expr = string(ue_str) + expr = strings.clone(string(ue_str)) ok = true return } @@ -939,24 +939,24 @@ parse_update_expression_string :: proc(request_body: []byte) -> (expr: string, o parse_return_values :: proc(request_body: []byte) -> string { data, parse_err := json.parse(request_body, allocator = context.temp_allocator) if parse_err != nil { - return "NONE" + return strings.clone("NONE") } defer json.destroy_value(data) root, root_ok := data.(json.Object) if !root_ok { - return "NONE" + return strings.clone("NONE") } rv_val, found := root["ReturnValues"] if !found { - return "NONE" + return strings.clone("NONE") } rv_str, str_ok := rv_val.(json.String) if !str_ok { - return "NONE" + return strings.clone("NONE") } - return string(rv_str) + return strings.clone(string(rv_str)) } diff --git a/gsi_handlers.odin b/gsi_handlers.odin index 8088caa..5d74ea6 100644 --- a/gsi_handlers.odin +++ b/gsi_handlers.odin @@ -259,7 +259,7 @@ parse_index_name :: proc(request_body: []byte) -> Maybe(string) { return nil } - return string(idx_str) + return strings.clone(string(idx_str)) } // ============================================================================ diff --git a/main.odin b/main.odin index 29b2f2b..7613d15 100644 --- a/main.odin +++ b/main.odin @@ -241,6 +241,7 @@ handle_describe_table :: proc(engine: ^dynamodb.Storage_Engine, request: ^HTTP_R make_error_response(response, .ValidationException, "Invalid request or missing TableName") return } + defer delete(table_name) metadata, err := dynamodb.get_table_metadata(engine, table_name) if err != .None { @@ -340,6 +341,7 @@ handle_put_item :: proc(engine: ^dynamodb.Storage_Engine, request: ^HTTP_Request make_error_response(response, .ValidationException, "Invalid request or missing TableName") return } + defer delete(table_name) item, item_ok := dynamodb.parse_item_from_request(request.body) if !item_ok { @@ -349,8 +351,9 @@ handle_put_item :: proc(engine: ^dynamodb.Storage_Engine, request: ^HTTP_Request defer dynamodb.item_destroy(&item) // ---- ConditionExpression evaluation ---- - _, has_condition := dynamodb.parse_condition_expression_string(request.body) + cond_str, has_condition := dynamodb.parse_condition_expression_string(request.body) if has_condition { + defer delete(cond_str) // Parse shared expression attributes attr_names := dynamodb.parse_expression_attribute_names(request.body) defer { @@ -468,9 +471,17 @@ handle_get_item :: proc(engine: ^dynamodb.Storage_Engine, request: ^HTTP_Request if item_val, has_item := item.?; has_item { defer dynamodb.item_destroy(&item_val) - item_json := dynamodb.serialize_item(item_val) - resp := fmt.aprintf(`{"Item":%s}`, item_json) - response_set_body(response, transmute([]byte)resp) + + // Build response directly to avoid intermediate string allocations + builder := strings.builder_make(context.allocator) + defer strings.builder_destroy(&builder) + + strings.write_string(&builder, `{"Item":`) + dynamodb.serialize_item_to_builder(&builder, item_val) + strings.write_string(&builder, `}`) + + resp_body := strings.clone(strings.to_string(builder)) + response_set_body(response, transmute([]byte)resp_body) } else { response_set_body(response, transmute([]byte)string("{}")) } @@ -571,6 +582,7 @@ handle_update_item :: proc(engine: ^dynamodb.Storage_Engine, request: ^HTTP_Requ make_error_response(response, .ValidationException, "Invalid request or missing TableName") return } + defer delete(table_name) // Parse Key key_item, key_ok := dynamodb.parse_key_from_request(request.body) @@ -586,6 +598,7 @@ handle_update_item :: proc(engine: ^dynamodb.Storage_Engine, request: ^HTTP_Requ make_error_response(response, .ValidationException, "Missing or invalid UpdateExpression") return } + defer delete(update_expr) // Parse ExpressionAttributeNames and ExpressionAttributeValues attr_names := dynamodb.parse_expression_attribute_names(request.body) @@ -661,6 +674,7 @@ handle_update_item :: proc(engine: ^dynamodb.Storage_Engine, request: ^HTTP_Requ // Parse ReturnValues return_values := dynamodb.parse_return_values(request.body) + defer delete(return_values) // Execute update old_item, new_item, err := dynamodb.update_item(engine, table_name, key_item, &plan) @@ -680,51 +694,59 @@ handle_update_item :: proc(engine: ^dynamodb.Storage_Engine, request: ^HTTP_Requ } // Build response based on ReturnValues + builder := strings.builder_make(context.allocator) + defer strings.builder_destroy(&builder) + switch return_values { case "ALL_NEW": if new_val, has := new_item.?; has { - item_json := dynamodb.serialize_item(new_val) - resp := fmt.aprintf(`{"Attributes":%s}`, item_json) - response_set_body(response, transmute([]byte)resp) + strings.write_string(&builder, `{"Attributes":`) + dynamodb.serialize_item_to_builder(&builder, new_val) + strings.write_string(&builder, `}`) } else { - response_set_body(response, transmute([]byte)string("{}")) + strings.write_string(&builder, `{}`) } case "ALL_OLD": if old, has := old_item.?; has { - item_json := dynamodb.serialize_item(old) - resp := fmt.aprintf(`{"Attributes":%s}`, item_json) - response_set_body(response, transmute([]byte)resp) + strings.write_string(&builder, `{"Attributes":`) + dynamodb.serialize_item_to_builder(&builder, old) + strings.write_string(&builder, `}`) } else { - response_set_body(response, transmute([]byte)string("{}")) + strings.write_string(&builder, `{}`) } case "UPDATED_NEW": if new_val, has := new_item.?; has { filtered := filter_updated_attributes(new_val, &plan) defer dynamodb.item_destroy(&filtered) - item_json := dynamodb.serialize_item(filtered) - resp := fmt.aprintf(`{"Attributes":%s}`, item_json) - response_set_body(response, transmute([]byte)resp) + + strings.write_string(&builder, `{"Attributes":`) + dynamodb.serialize_item_to_builder(&builder, filtered) + strings.write_string(&builder, `}`) } else { - response_set_body(response, transmute([]byte)string("{}")) + strings.write_string(&builder, `{}`) } case "UPDATED_OLD": if old, has := old_item.?; has { filtered := filter_updated_attributes(old, &plan) defer dynamodb.item_destroy(&filtered) - item_json := dynamodb.serialize_item(filtered) - resp := fmt.aprintf(`{"Attributes":%s}`, item_json) - response_set_body(response, transmute([]byte)resp) + + strings.write_string(&builder, `{"Attributes":`) + dynamodb.serialize_item_to_builder(&builder, filtered) + strings.write_string(&builder, `}`) } else { - response_set_body(response, transmute([]byte)string("{}")) + strings.write_string(&builder, `{}`) } case: // "NONE" or default - response_set_body(response, transmute([]byte)string("{}")) + strings.write_string(&builder, `{}`) } + + resp_body := strings.clone(strings.to_string(builder)) + response_set_body(response, transmute([]byte)resp_body) } handle_batch_write_item :: proc(engine: ^dynamodb.Storage_Engine, request: ^HTTP_Request, response: ^HTTP_Response) { @@ -872,7 +894,7 @@ handle_batch_write_item :: proc(engine: ^dynamodb.Storage_Engine, request: ^HTTP } append(&table_requests, dynamodb.Batch_Write_Table_Request{ - table_name = string(table_name), + table_name = strings.clone(string(table_name)), requests = requests[:], }) } @@ -1010,7 +1032,7 @@ handle_batch_get_item :: proc(engine: ^dynamodb.Storage_Engine, request: ^HTTP_R } append(&table_requests, dynamodb.Batch_Get_Table_Request{ - table_name = string(table_name), + table_name = strings.clone(string(table_name)), keys = keys[:], }) } @@ -1037,7 +1059,9 @@ handle_batch_get_item :: proc(engine: ^dynamodb.Storage_Engine, request: ^HTTP_R defer dynamodb.batch_get_result_destroy(&result) // Build response - builder := strings.builder_make() + builder := strings.builder_make(context.allocator) + defer strings.builder_destroy(&builder) + strings.write_string(&builder, `{"Responses":{`) for table_result, ti in result.responses { @@ -1050,8 +1074,7 @@ handle_batch_get_item :: proc(engine: ^dynamodb.Storage_Engine, request: ^HTTP_R if ii > 0 { strings.write_string(&builder, ",") } - item_json := dynamodb.serialize_item(item) - strings.write_string(&builder, item_json) + dynamodb.serialize_item_to_builder(&builder, item) } strings.write_string(&builder, "]") @@ -1069,8 +1092,7 @@ handle_batch_get_item :: proc(engine: ^dynamodb.Storage_Engine, request: ^HTTP_R if ki > 0 { strings.write_string(&builder, ",") } - key_json := dynamodb.serialize_item(key) - strings.write_string(&builder, key_json) + dynamodb.serialize_item_to_builder(&builder, key) } strings.write_string(&builder, "]}") @@ -1078,7 +1100,8 @@ handle_batch_get_item :: proc(engine: ^dynamodb.Storage_Engine, request: ^HTTP_R strings.write_string(&builder, "}}") - resp_body := strings.to_string(builder) + // clone the god damn string + resp_body := strings.clone(strings.to_string(builder)) response_set_body(response, transmute([]byte)resp_body) } @@ -1093,9 +1116,15 @@ handle_query :: proc(engine: ^dynamodb.Storage_Engine, request: ^HTTP_Request, r make_error_response(response, .ValidationException, "Invalid request or missing TableName") return } + defer delete(table_name) // Grab index name from request body index_name := parse_index_name(request.body) + defer { + if idx, has := index_name.?; has { + delete(idx) + } + } // Fetch table metadata early for ExclusiveStartKey parsing metadata, meta_err := dynamodb.get_table_metadata(engine, table_name) @@ -1300,9 +1329,15 @@ handle_scan :: proc(engine: ^dynamodb.Storage_Engine, request: ^HTTP_Request, re make_error_response(response, .ValidationException, "Invalid request or missing TableName") return } + defer delete(table_name) // Grab index name from request body index_name := parse_index_name(request.body) + defer { + if idx, has := index_name.?; has { + delete(idx) + } + } metadata, meta_err := dynamodb.get_table_metadata(engine, table_name) if meta_err != .None { @@ -1482,6 +1517,7 @@ apply_filter_to_items :: proc( if !has_filter { return items, true } + defer delete(filter_expr) filter_node, filter_ok := dynamodb.parse_filter_expression(filter_expr, attr_names, attr_values) if !filter_ok || filter_node == nil { @@ -1517,13 +1553,15 @@ write_items_response_with_pagination_ex :: proc( metadata: ^dynamodb.Table_Metadata, scanned_count: int, ) { - builder := strings.builder_make() + builder := strings.builder_make(context.allocator) + defer strings.builder_destroy(&builder) + strings.write_string(&builder, `{"Items":[`) + // Use serialize_item_to_builder directly an NOT serialize_item for item, i in items { if i > 0 do strings.write_string(&builder, ",") - item_json := dynamodb.serialize_item(item) - strings.write_string(&builder, item_json) + dynamodb.serialize_item_to_builder(&builder, item) } strings.write_string(&builder, `],"Count":`) @@ -1541,7 +1579,8 @@ write_items_response_with_pagination_ex :: proc( strings.write_string(&builder, "}") - resp_body := strings.to_string(builder) + // We have to Clone the string before passing to response_set_body + resp_body := strings.clone(strings.to_string(builder)) response_set_body(response, transmute([]byte)resp_body) } @@ -1561,13 +1600,15 @@ write_items_response_with_pagination :: proc( last_evaluated_key_binary: Maybe([]byte), metadata: ^dynamodb.Table_Metadata, ) { - builder := strings.builder_make() + builder := strings.builder_make(context.allocator) + defer strings.builder_destroy(&builder) + strings.write_string(&builder, `{"Items":[`) + // Use serialize_item_to_builder directly so we always get the correct response payload for item, i in items { if i > 0 do strings.write_string(&builder, ",") - item_json := dynamodb.serialize_item(item) - strings.write_string(&builder, item_json) + dynamodb.serialize_item_to_builder(&builder, item) } strings.write_string(&builder, `],"Count":`) @@ -1575,20 +1616,18 @@ write_items_response_with_pagination :: proc( strings.write_string(&builder, `,"ScannedCount":`) fmt.sbprintf(&builder, "%d", len(items)) - // Emit LastEvaluatedKey if the storage layer produced one if binary_key, has_last := last_evaluated_key_binary.?; has_last { lek_json, lek_ok := dynamodb.serialize_last_evaluated_key(binary_key, metadata) if lek_ok { strings.write_string(&builder, `,"LastEvaluatedKey":`) strings.write_string(&builder, lek_json) } - // If decoding fails we still return the items — just without a pagination token. - // The client will assume the scan/query is complete. } strings.write_string(&builder, "}") - resp_body := strings.to_string(builder) + // We have to Clone the string before passing to response_set_body + resp_body := strings.clone(strings.to_string(builder)) response_set_body(response, transmute([]byte)resp_body) } diff --git a/transact_handlers.odin b/transact_handlers.odin index b6243cc..9daabaa 100644 --- a/transact_handlers.odin +++ b/transact_handlers.odin @@ -267,7 +267,7 @@ parse_transact_put_action :: proc( if !tn_ok { return {}, false } - action.table_name = string(tn_str) + action.table_name = strings.clone(string(tn_str)) // Item item_val, item_found := obj["Item"] @@ -301,7 +301,7 @@ parse_transact_key_action :: proc( if !tn_ok { return {}, false } - action.table_name = string(tn_str) + action.table_name = strings.clone(string(tn_str)) // Key key_val, key_found := obj["Key"] @@ -335,7 +335,7 @@ parse_transact_update_action :: proc( if !tn_ok { return {}, false } - action.table_name = string(tn_str) + action.table_name = strings.clone(string(tn_str)) // Key key_val, key_found := obj["Key"] @@ -483,7 +483,9 @@ handle_transact_get_items :: proc( } // Build response - builder := strings.builder_make() + builder := strings.builder_make(context.allocator) + defer strings.builder_destroy(&builder) + strings.write_string(&builder, `{"Responses":[`) for maybe_item, i in result.items { @@ -492,8 +494,9 @@ handle_transact_get_items :: proc( } if item, has_item := maybe_item.?; has_item { - item_json := dynamodb.serialize_item(item) - fmt.sbprintf(&builder, `{{"Item":%s}}`, item_json) + strings.write_string(&builder, `{"Item":`) + dynamodb.serialize_item_to_builder(&builder, item) + strings.write_string(&builder, `}`) } else { strings.write_string(&builder, "{}") } @@ -501,7 +504,8 @@ handle_transact_get_items :: proc( strings.write_string(&builder, "]}") - resp_body := strings.to_string(builder) + // Clone the string or we gonna have issues again + resp_body := strings.clone(strings.to_string(builder)) response_set_body(response, transmute([]byte)resp_body) } @@ -519,7 +523,7 @@ parse_transact_get_action :: proc(obj: json.Object) -> (dynamodb.Transact_Get_Ac if !tn_ok { return {}, false } - action.table_name = string(tn_str) + action.table_name = strings.clone(string(tn_str)) // Key key_val, key_found := obj["Key"]