shes working
This commit is contained in:
@@ -322,17 +322,25 @@ serialize_item_to_builder :: proc(b: ^strings.Builder, item: Item) {
|
|||||||
serialize_attribute_value :: proc(b: ^strings.Builder, attr: Attribute_Value) {
|
serialize_attribute_value :: proc(b: ^strings.Builder, attr: Attribute_Value) {
|
||||||
switch v in attr {
|
switch v in attr {
|
||||||
case String:
|
case String:
|
||||||
fmt.sbprintf(b, `{"S":"%s"}`, string(v))
|
strings.write_string(b, `{"S":"`)
|
||||||
|
strings.write_string(b, string(v))
|
||||||
|
strings.write_string(b, `"}`)
|
||||||
|
|
||||||
case DDB_Number:
|
case DDB_Number:
|
||||||
num_str := format_ddb_number(v)
|
num_str := format_ddb_number(v)
|
||||||
fmt.sbprintf(b, `{"N":"%s"}`, num_str)
|
strings.write_string(b, `{"N":"`)
|
||||||
|
strings.write_string(b, num_str)
|
||||||
|
strings.write_string(b, `"}`)
|
||||||
|
|
||||||
case Binary:
|
case Binary:
|
||||||
fmt.sbprintf(b, `{"B":"%s"}`, string(v))
|
strings.write_string(b, `{"B":"`)
|
||||||
|
strings.write_string(b, string(v))
|
||||||
|
strings.write_string(b, `"}`)
|
||||||
|
|
||||||
case Bool:
|
case Bool:
|
||||||
fmt.sbprintf(b, `{"BOOL":%v}`, bool(v))
|
strings.write_string(b, `{"BOOL":`)
|
||||||
|
if bool(v) { strings.write_string(b, "true") } else { strings.write_string(b, "false") }
|
||||||
|
strings.write_string(b, "}")
|
||||||
|
|
||||||
case Null:
|
case Null:
|
||||||
strings.write_string(b, `{"NULL":true}`)
|
strings.write_string(b, `{"NULL":true}`)
|
||||||
|
|||||||
@@ -532,6 +532,10 @@ get_table_metadata :: proc(engine: ^Storage_Engine, table_name: string) -> (Tabl
|
|||||||
return {}, .Serialization_Error
|
return {}, .Serialization_Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// table_name is not stored in the serialized blob (it IS the RocksDB key),
|
||||||
|
// so we populate it here from the argument we already have.
|
||||||
|
metadata.table_name = strings.clone(table_name, engine.allocator)
|
||||||
|
|
||||||
return metadata, .None
|
return metadata, .None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -532,9 +532,9 @@ update_item_batch :: proc(
|
|||||||
}
|
}
|
||||||
defer item_destroy(&existing_item)
|
defer item_destroy(&existing_item)
|
||||||
|
|
||||||
// Apply update plan
|
// Apply update plan.
|
||||||
if !execute_update_plan(&existing_item, plan) {
|
if exec_err := execute_update_plan(&existing_item, plan); exec_err != .None {
|
||||||
return .Invalid_Key
|
return .Validation_Error
|
||||||
}
|
}
|
||||||
|
|
||||||
// Encode updated item
|
// Encode updated item
|
||||||
|
|||||||
@@ -594,7 +594,23 @@ is_clause_keyword :: proc(tok: string) -> bool {
|
|||||||
// Execute Update Plan — apply mutations to an Item (in-place)
|
// Execute Update Plan — apply mutations to an Item (in-place)
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
execute_update_plan :: proc(item: ^Item, plan: ^Update_Plan) -> bool {
|
// Reasons an update plan can fail at execution time.
|
||||||
|
// All of these map to ValidationException at the HTTP layer.
|
||||||
|
Update_Exec_Error :: enum {
|
||||||
|
None,
|
||||||
|
// SET x = source +/- val: source attribute does not exist in the item
|
||||||
|
Operand_Not_Found,
|
||||||
|
// SET x = source +/- val: source or value attribute is not a Number
|
||||||
|
Operand_Not_Number,
|
||||||
|
// SET x = list_append(source, val): source attribute is not a List
|
||||||
|
Operand_Not_List,
|
||||||
|
// ADD path val: existing attribute is not a Number, String_Set, or Number_Set
|
||||||
|
Add_Type_Mismatch,
|
||||||
|
// ADD path val: value type does not match the existing set type
|
||||||
|
Add_Value_Type_Mismatch,
|
||||||
|
}
|
||||||
|
|
||||||
|
execute_update_plan :: proc(item: ^Item, plan: ^Update_Plan) -> Update_Exec_Error {
|
||||||
// Execute SET actions
|
// Execute SET actions
|
||||||
for &action in plan.sets {
|
for &action in plan.sets {
|
||||||
switch action.value_kind {
|
switch action.value_kind {
|
||||||
@@ -613,11 +629,11 @@ execute_update_plan :: proc(item: ^Item, plan: ^Update_Plan) -> bool {
|
|||||||
if src, found := item[action.source]; found {
|
if src, found := item[action.source]; found {
|
||||||
existing = src
|
existing = src
|
||||||
} else {
|
} else {
|
||||||
return false // source attribute not found
|
return .Operand_Not_Found
|
||||||
}
|
}
|
||||||
result, add_ok := numeric_add(existing, action.value)
|
result, add_ok := numeric_add(existing, action.value)
|
||||||
if !add_ok {
|
if !add_ok {
|
||||||
return false
|
return .Operand_Not_Number
|
||||||
}
|
}
|
||||||
if old, found := item[action.path]; found {
|
if old, found := item[action.path]; found {
|
||||||
old_copy := old
|
old_copy := old
|
||||||
@@ -632,11 +648,11 @@ execute_update_plan :: proc(item: ^Item, plan: ^Update_Plan) -> bool {
|
|||||||
if src, found := item[action.source]; found {
|
if src, found := item[action.source]; found {
|
||||||
existing = src
|
existing = src
|
||||||
} else {
|
} else {
|
||||||
return false
|
return .Operand_Not_Found
|
||||||
}
|
}
|
||||||
result, sub_ok := numeric_subtract(existing, action.value)
|
result, sub_ok := numeric_subtract(existing, action.value)
|
||||||
if !sub_ok {
|
if !sub_ok {
|
||||||
return false
|
return .Operand_Not_Number
|
||||||
}
|
}
|
||||||
if old, found := item[action.path]; found {
|
if old, found := item[action.path]; found {
|
||||||
old_copy := old
|
old_copy := old
|
||||||
@@ -664,7 +680,7 @@ execute_update_plan :: proc(item: ^Item, plan: ^Update_Plan) -> bool {
|
|||||||
if l, is_list := src.(List); is_list {
|
if l, is_list := src.(List); is_list {
|
||||||
existing_list = ([]Attribute_Value)(l)
|
existing_list = ([]Attribute_Value)(l)
|
||||||
} else {
|
} else {
|
||||||
return false
|
return .Operand_Not_List
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
existing_list = {}
|
existing_list = {}
|
||||||
@@ -674,7 +690,7 @@ execute_update_plan :: proc(item: ^Item, plan: ^Update_Plan) -> bool {
|
|||||||
if l, is_list := action.value.(List); is_list {
|
if l, is_list := action.value.(List); is_list {
|
||||||
append_list = ([]Attribute_Value)(l)
|
append_list = ([]Attribute_Value)(l)
|
||||||
} else {
|
} else {
|
||||||
return false
|
return .Operand_Not_List
|
||||||
}
|
}
|
||||||
|
|
||||||
new_list := make([]Attribute_Value, len(existing_list) + len(append_list))
|
new_list := make([]Attribute_Value, len(existing_list) + len(append_list))
|
||||||
@@ -711,7 +727,7 @@ execute_update_plan :: proc(item: ^Item, plan: ^Update_Plan) -> bool {
|
|||||||
case DDB_Number:
|
case DDB_Number:
|
||||||
result, add_ok := numeric_add(existing, action.value)
|
result, add_ok := numeric_add(existing, action.value)
|
||||||
if !add_ok {
|
if !add_ok {
|
||||||
return false
|
return .Operand_Not_Number
|
||||||
}
|
}
|
||||||
old_copy := existing
|
old_copy := existing
|
||||||
attr_value_destroy(&old_copy)
|
attr_value_destroy(&old_copy)
|
||||||
@@ -727,7 +743,7 @@ execute_update_plan :: proc(item: ^Item, plan: ^Update_Plan) -> bool {
|
|||||||
delete_key(item, action.path)
|
delete_key(item, action.path)
|
||||||
item[strings.clone(action.path)] = String_Set(merged)
|
item[strings.clone(action.path)] = String_Set(merged)
|
||||||
} else {
|
} else {
|
||||||
return false
|
return .Add_Value_Type_Mismatch
|
||||||
}
|
}
|
||||||
|
|
||||||
case DDB_Number_Set:
|
case DDB_Number_Set:
|
||||||
@@ -738,11 +754,11 @@ execute_update_plan :: proc(item: ^Item, plan: ^Update_Plan) -> bool {
|
|||||||
delete_key(item, action.path)
|
delete_key(item, action.path)
|
||||||
item[strings.clone(action.path)] = DDB_Number_Set(merged)
|
item[strings.clone(action.path)] = DDB_Number_Set(merged)
|
||||||
} else {
|
} else {
|
||||||
return false
|
return .Add_Value_Type_Mismatch
|
||||||
}
|
}
|
||||||
|
|
||||||
case:
|
case:
|
||||||
return false
|
return .Add_Type_Mismatch
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Attribute doesn't exist — create it
|
// Attribute doesn't exist — create it
|
||||||
@@ -786,7 +802,7 @@ execute_update_plan :: proc(item: ^Item, plan: ^Update_Plan) -> bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return .None
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|||||||
@@ -73,14 +73,14 @@ update_item :: proc(
|
|||||||
return nil, nil, .RocksDB_Error
|
return nil, nil, .RocksDB_Error
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply update plan
|
// Apply update plan.
|
||||||
if !execute_update_plan(&existing_item, plan) {
|
if exec_err := execute_update_plan(&existing_item, plan); exec_err != .None {
|
||||||
item_destroy(&existing_item)
|
item_destroy(&existing_item)
|
||||||
if old, has := old_item.?; has {
|
if old, has := old_item.?; has {
|
||||||
old_copy := old
|
old_copy := old
|
||||||
item_destroy(&old_copy)
|
item_destroy(&old_copy)
|
||||||
}
|
}
|
||||||
return nil, nil, .Invalid_Key
|
return nil, nil, .Validation_Error
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate key attributes are still present and correct type
|
// Validate key attributes are still present and correct type
|
||||||
|
|||||||
38
main.odin
38
main.odin
@@ -219,6 +219,7 @@ handle_delete_table :: proc(engine: ^dynamodb.Storage_Engine, request: ^HTTP_Req
|
|||||||
make_error_response(response, .ValidationException, "Invalid request or missing TableName")
|
make_error_response(response, .ValidationException, "Invalid request or missing TableName")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
defer delete(table_name)
|
||||||
|
|
||||||
err := dynamodb.delete_table(engine, table_name)
|
err := dynamodb.delete_table(engine, table_name)
|
||||||
if err != .None {
|
if err != .None {
|
||||||
@@ -267,16 +268,22 @@ handle_describe_table :: proc(engine: ^dynamodb.Storage_Engine, request: ^HTTP_R
|
|||||||
|
|
||||||
for ks, i in metadata.key_schema {
|
for ks, i in metadata.key_schema {
|
||||||
if i > 0 do strings.write_string(&builder, ",")
|
if i > 0 do strings.write_string(&builder, ",")
|
||||||
fmt.sbprintf(&builder, `{"AttributeName":"%s","KeyType":"%s"}`,
|
strings.write_string(&builder, `{"AttributeName":"`)
|
||||||
ks.attribute_name, dynamodb.key_type_to_string(ks.key_type))
|
strings.write_string(&builder, ks.attribute_name)
|
||||||
|
strings.write_string(&builder, `","KeyType":"`)
|
||||||
|
strings.write_string(&builder, dynamodb.key_type_to_string(ks.key_type))
|
||||||
|
strings.write_string(&builder, `"}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
strings.write_string(&builder, `],"AttributeDefinitions":[`)
|
strings.write_string(&builder, `],"AttributeDefinitions":[`)
|
||||||
|
|
||||||
for ad, i in metadata.attribute_definitions {
|
for ad, i in metadata.attribute_definitions {
|
||||||
if i > 0 do strings.write_string(&builder, ",")
|
if i > 0 do strings.write_string(&builder, ",")
|
||||||
fmt.sbprintf(&builder, `{"AttributeName":"%s","AttributeType":"%s"}`,
|
strings.write_string(&builder, `{"AttributeName":"`)
|
||||||
ad.attribute_name, dynamodb.scalar_type_to_string(ad.attribute_type))
|
strings.write_string(&builder, ad.attribute_name)
|
||||||
|
strings.write_string(&builder, `","AttributeType":"`)
|
||||||
|
strings.write_string(&builder, dynamodb.scalar_type_to_string(ad.attribute_type))
|
||||||
|
strings.write_string(&builder, `"}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
strings.write_string(&builder, `]`)
|
strings.write_string(&builder, `]`)
|
||||||
@@ -291,8 +298,11 @@ handle_describe_table :: proc(engine: ^dynamodb.Storage_Engine, request: ^HTTP_R
|
|||||||
strings.write_string(&builder, `","KeySchema":[`)
|
strings.write_string(&builder, `","KeySchema":[`)
|
||||||
for ks, ki in gsi.key_schema {
|
for ks, ki in gsi.key_schema {
|
||||||
if ki > 0 do strings.write_string(&builder, ",")
|
if ki > 0 do strings.write_string(&builder, ",")
|
||||||
fmt.sbprintf(&builder, `{"AttributeName":"%s","KeyType":"%s"}`,
|
strings.write_string(&builder, `{"AttributeName":"`)
|
||||||
ks.attribute_name, dynamodb.key_type_to_string(ks.key_type))
|
strings.write_string(&builder, ks.attribute_name)
|
||||||
|
strings.write_string(&builder, `","KeyType":"`)
|
||||||
|
strings.write_string(&builder, dynamodb.key_type_to_string(ks.key_type))
|
||||||
|
strings.write_string(&builder, `"}`)
|
||||||
}
|
}
|
||||||
strings.write_string(&builder, `],"Projection":{"ProjectionType":"`)
|
strings.write_string(&builder, `],"Projection":{"ProjectionType":"`)
|
||||||
strings.write_string(&builder, projection_type_to_string(gsi.projection.projection_type))
|
strings.write_string(&builder, projection_type_to_string(gsi.projection.projection_type))
|
||||||
@@ -455,6 +465,7 @@ handle_get_item :: proc(engine: ^dynamodb.Storage_Engine, request: ^HTTP_Request
|
|||||||
make_error_response(response, .ValidationException, "Invalid request or missing TableName")
|
make_error_response(response, .ValidationException, "Invalid request or missing TableName")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
defer delete(table_name)
|
||||||
|
|
||||||
key, key_ok := dynamodb.parse_key_from_request(request.body)
|
key, key_ok := dynamodb.parse_key_from_request(request.body)
|
||||||
if !key_ok {
|
if !key_ok {
|
||||||
@@ -493,6 +504,7 @@ handle_delete_item :: proc(engine: ^dynamodb.Storage_Engine, request: ^HTTP_Requ
|
|||||||
make_error_response(response, .ValidationException, "Invalid request or missing TableName")
|
make_error_response(response, .ValidationException, "Invalid request or missing TableName")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
defer delete(table_name)
|
||||||
|
|
||||||
key, key_ok := dynamodb.parse_key_from_request(request.body)
|
key, key_ok := dynamodb.parse_key_from_request(request.body)
|
||||||
if !key_ok {
|
if !key_ok {
|
||||||
@@ -939,9 +951,13 @@ handle_batch_write_item :: proc(engine: ^dynamodb.Storage_Engine, request: ^HTTP
|
|||||||
item_json := dynamodb.serialize_item(req.item)
|
item_json := dynamodb.serialize_item(req.item)
|
||||||
switch req.type {
|
switch req.type {
|
||||||
case .Put:
|
case .Put:
|
||||||
fmt.sbprintf(&builder, `{"PutRequest":{"Item":%s}}`, item_json)
|
strings.write_string(&builder, `{"PutRequest":{"Item":`)
|
||||||
|
strings.write_string(&builder, item_json)
|
||||||
|
strings.write_string(&builder, "}}")
|
||||||
case .Delete:
|
case .Delete:
|
||||||
fmt.sbprintf(&builder, `{"DeleteRequest":{"Key":%s}}`, item_json)
|
strings.write_string(&builder, `{"DeleteRequest":{"Key":`)
|
||||||
|
strings.write_string(&builder, item_json)
|
||||||
|
strings.write_string(&builder, "}}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1086,7 +1102,9 @@ handle_batch_get_item :: proc(engine: ^dynamodb.Storage_Engine, request: ^HTTP_R
|
|||||||
if ti > 0 {
|
if ti > 0 {
|
||||||
strings.write_string(&builder, ",")
|
strings.write_string(&builder, ",")
|
||||||
}
|
}
|
||||||
fmt.sbprintf(&builder, `"%s":{"Keys":[`, table_req.table_name)
|
strings.write_string(&builder, `"`)
|
||||||
|
strings.write_string(&builder, table_req.table_name)
|
||||||
|
strings.write_string(&builder, `":{"Keys":["`)
|
||||||
|
|
||||||
for key, ki in table_req.keys {
|
for key, ki in table_req.keys {
|
||||||
if ki > 0 {
|
if ki > 0 {
|
||||||
@@ -1650,6 +1668,8 @@ handle_storage_error :: proc(response: ^HTTP_Response, err: dynamodb.Storage_Err
|
|||||||
make_error_response(response, .ValidationException, "One or more required key attributes are missing")
|
make_error_response(response, .ValidationException, "One or more required key attributes are missing")
|
||||||
case .Invalid_Key:
|
case .Invalid_Key:
|
||||||
make_error_response(response, .ValidationException, "Invalid key: type mismatch or malformed key value")
|
make_error_response(response, .ValidationException, "Invalid key: type mismatch or malformed key value")
|
||||||
|
case .Validation_Error:
|
||||||
|
make_error_response(response, .ValidationException, "Invalid request: type mismatch or incompatible operand")
|
||||||
case .Serialization_Error:
|
case .Serialization_Error:
|
||||||
make_error_response(response, .InternalServerError, "Internal serialization error")
|
make_error_response(response, .InternalServerError, "Internal serialization error")
|
||||||
case .RocksDB_Error:
|
case .RocksDB_Error:
|
||||||
|
|||||||
Reference in New Issue
Block a user