fix cloned string cleanup

This commit is contained in:
2026-02-17 02:03:40 -05:00
parent a6bf357228
commit 225a1533cc
9 changed files with 113 additions and 59 deletions

119
main.odin
View File

@@ -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)
}