package dynamodb import "core:fmt" import "core:strings" // DynamoDB AttributeValue - the core data type Attribute_Value :: union { String, // S DDB_Number, // N — decimal-preserving numeric type Binary, // B (base64) Bool, // BOOL Null, // NULL String_Set, // SS DDB_Number_Set, // NS Binary_Set, // BS List, // L Map, // M } String :: distinct string Binary :: distinct string Bool :: distinct bool Null :: distinct bool String_Set :: distinct []string DDB_Number_Set :: distinct []DDB_Number Binary_Set :: distinct []string List :: distinct []Attribute_Value Map :: distinct map[string]Attribute_Value // Item is a map of attribute names to values Item :: map[string]Attribute_Value // Key represents a DynamoDB key (partition key + optional sort key) Key :: struct { pk: Attribute_Value, sk: Maybe(Attribute_Value), } // Free a key key_destroy :: proc(key: ^Key) { attr_value_destroy(&key.pk) if sk, ok := key.sk.?; ok { sk_copy := sk attr_value_destroy(&sk_copy) } } // Extract key from item based on key schema key_from_item :: proc(item: Item, key_schema: []Key_Schema_Element) -> (Key, bool) { pk_value: Attribute_Value sk_value: Maybe(Attribute_Value) for schema_elem in key_schema { attr, ok := item[schema_elem.attribute_name] if !ok { return {}, false } // Validate that key is a scalar type (S, N, or B) #partial switch _ in attr { case String, DDB_Number, Binary: // Valid key type case: return {}, false } // Deep copy the attribute value copied := attr_value_deep_copy(attr) switch schema_elem.key_type { case .HASH: pk_value = copied case .RANGE: sk_value = copied } } return Key{pk = pk_value, sk = sk_value}, true } // Convert key to item key_to_item :: proc(key: Key, key_schema: []Key_Schema_Element) -> Item { item := make(Item) for schema_elem in key_schema { attr_value: Attribute_Value switch schema_elem.key_type { case .HASH: attr_value = key.pk case .RANGE: if sk, ok := key.sk.?; ok { attr_value = sk } else { continue } } item[schema_elem.attribute_name] = attr_value_deep_copy(attr_value) } return item } // Extract raw byte values from key Key_Values :: struct { pk: []byte, sk: Maybe([]byte), } key_get_values :: proc(key: ^Key) -> (Key_Values, bool) { pk_bytes: []byte #partial switch v in key.pk { case String: pk_bytes = transmute([]byte)string(v) case DDB_Number: pk_bytes = encode_ddb_number_for_sort(v) case Binary: pk_bytes = transmute([]byte)string(v) case: return {}, false } sk_bytes: Maybe([]byte) if sk, ok := key.sk.?; ok { #partial switch v in sk { case String: sk_bytes = transmute([]byte)string(v) case DDB_Number: sk_bytes = encode_ddb_number_for_sort(v) case Binary: sk_bytes = transmute([]byte)string(v) case: return {}, false } } return Key_Values{pk = pk_bytes, sk = sk_bytes}, true } // Key type Key_Type :: enum { HASH, RANGE, } key_type_to_string :: proc(kt: Key_Type) -> string { switch kt { case .HASH: return "HASH" case .RANGE: return "RANGE" } return "HASH" } key_type_from_string :: proc(s: string) -> (Key_Type, bool) { switch s { case "HASH": return .HASH, true case "RANGE": return .RANGE, true } return .HASH, false } // Scalar attribute type Scalar_Attribute_Type :: enum { S, // String N, // Number B, // Binary } scalar_type_to_string :: proc(t: Scalar_Attribute_Type) -> string { switch t { case .S: return "S" case .N: return "N" case .B: return "B" } return "S" } scalar_type_from_string :: proc(s: string) -> (Scalar_Attribute_Type, bool) { switch s { case "S": return .S, true case "N": return .N, true case "B": return .B, true } return .S, false } // Key schema element Key_Schema_Element :: struct { attribute_name: string, key_type: Key_Type, } // Attribute definition Attribute_Definition :: struct { attribute_name: string, attribute_type: Scalar_Attribute_Type, } // Projection type for indexes Projection_Type :: enum { ALL, KEYS_ONLY, INCLUDE, } // Projection Projection :: struct { projection_type: Projection_Type, non_key_attributes: Maybe([]string), } // Global secondary index Global_Secondary_Index :: struct { index_name: string, key_schema: []Key_Schema_Element, projection: Projection, } // Local secondary index Local_Secondary_Index :: struct { index_name: string, key_schema: []Key_Schema_Element, projection: Projection, } // Table status Table_Status :: enum { CREATING, UPDATING, DELETING, ACTIVE, INACCESSIBLE_ENCRYPTION_CREDENTIALS, ARCHIVING, ARCHIVED, } table_status_to_string :: proc(status: Table_Status) -> string { switch status { case .CREATING: return "CREATING" case .UPDATING: return "UPDATING" case .DELETING: return "DELETING" case .ACTIVE: return "ACTIVE" case .INACCESSIBLE_ENCRYPTION_CREDENTIALS: return "INACCESSIBLE_ENCRYPTION_CREDENTIALS" case .ARCHIVING: return "ARCHIVING" case .ARCHIVED: return "ARCHIVED" } return "ACTIVE" } table_status_from_string :: proc(s: string) -> Table_Status { switch s { case "CREATING": return .CREATING case "UPDATING": return .UPDATING case "DELETING": return .DELETING case "ACTIVE": return .ACTIVE case "ARCHIVING": return .ARCHIVING case "ARCHIVED": return .ARCHIVED } return .ACTIVE } // Table description Table_Description :: struct { table_name: string, key_schema: []Key_Schema_Element, attribute_definitions: []Attribute_Definition, table_status: Table_Status, creation_date_time: i64, item_count: u64, table_size_bytes: u64, global_secondary_indexes: Maybe([]Global_Secondary_Index), local_secondary_indexes: Maybe([]Local_Secondary_Index), } // DynamoDB operation types Operation :: enum { CreateTable, DeleteTable, DescribeTable, ListTables, UpdateTable, PutItem, GetItem, DeleteItem, UpdateItem, Query, Scan, BatchGetItem, BatchWriteItem, TransactGetItems, TransactWriteItems, Unknown, } operation_from_target :: proc(target: string) -> Operation { prefix :: "DynamoDB_20120810." if !strings.has_prefix(target, prefix) { return .Unknown } op_name := target[len(prefix):] switch op_name { case "CreateTable": return .CreateTable case "DeleteTable": return .DeleteTable case "DescribeTable": return .DescribeTable case "ListTables": return .ListTables case "UpdateTable": return .UpdateTable case "PutItem": return .PutItem case "GetItem": return .GetItem case "DeleteItem": return .DeleteItem case "UpdateItem": return .UpdateItem case "Query": return .Query case "Scan": return .Scan case "BatchGetItem": return .BatchGetItem case "BatchWriteItem": return .BatchWriteItem case "TransactGetItems": return .TransactGetItems case "TransactWriteItems": return .TransactWriteItems } return .Unknown } // DynamoDB error types DynamoDB_Error_Type :: enum { ValidationException, ResourceNotFoundException, ResourceInUseException, ConditionalCheckFailedException, ProvisionedThroughputExceededException, ItemCollectionSizeLimitExceededException, InternalServerError, SerializationException, } error_to_response :: proc(err_type: DynamoDB_Error_Type, message: string) -> string { type_str: string switch err_type { case .ValidationException: type_str = "com.amazonaws.dynamodb.v20120810#ValidationException" case .ResourceNotFoundException: type_str = "com.amazonaws.dynamodb.v20120810#ResourceNotFoundException" case .ResourceInUseException: type_str = "com.amazonaws.dynamodb.v20120810#ResourceInUseException" case .ConditionalCheckFailedException: type_str = "com.amazonaws.dynamodb.v20120810#ConditionalCheckFailedException" case .ProvisionedThroughputExceededException: type_str = "com.amazonaws.dynamodb.v20120810#ProvisionedThroughputExceededException" case .ItemCollectionSizeLimitExceededException: type_str = "com.amazonaws.dynamodb.v20120810#ItemCollectionSizeLimitExceededException" case .InternalServerError: type_str = "com.amazonaws.dynamodb.v20120810#InternalServerError" case .SerializationException: type_str = "com.amazonaws.dynamodb.v20120810#SerializationException" } return fmt.aprintf(`{{"__type":"%s","message":"%s"}}`, type_str, message) } // Build an Attribute_Value with the correct scalar type from raw bytes build_attribute_value_with_type :: proc(raw_bytes: []byte, attr_type: Scalar_Attribute_Type) -> Attribute_Value { switch attr_type { case .S: return String(strings.clone(string(raw_bytes))) case .N: // Key bytes are canonical-encoded via encode_ddb_number_for_sort. // Decode them back to a DDB_Number. ddb_num, ok := decode_ddb_number_from_sort(raw_bytes) if ok { return clone_ddb_number(ddb_num) } // Fallback: try interpreting as a plain numeric string fallback_num, fb_ok := parse_ddb_number(string(raw_bytes)) if fb_ok { return fallback_num } // Last resort — return as string (shouldn't happen) return String(strings.clone(string(raw_bytes))) case .B: return Binary(strings.clone(string(raw_bytes))) } return String(strings.clone(string(raw_bytes))) } // Deep copy an attribute value attr_value_deep_copy :: proc(attr: Attribute_Value) -> Attribute_Value { switch v in attr { case String: return String(strings.clone(string(v))) case DDB_Number: return clone_ddb_number(v) case Binary: return Binary(strings.clone(string(v))) case Bool: return v case Null: return v case String_Set: ss := make([]string, len(v)) for s, i in v { ss[i] = strings.clone(s) } return String_Set(ss) case DDB_Number_Set: ddb_ns := make([]DDB_Number, len(v)) for num, i in v { ddb_ns[i] = clone_ddb_number(num) } return DDB_Number_Set(ddb_ns) case Binary_Set: bs := make([]string, len(v)) for b, i in v { bs[i] = strings.clone(b) } return Binary_Set(bs) case List: list := make([]Attribute_Value, len(v)) for item, i in v { list[i] = attr_value_deep_copy(item) } return List(list) case Map: m := make(map[string]Attribute_Value) for key, val in v { m[strings.clone(key)] = attr_value_deep_copy(val) } return Map(m) } return nil } // Free an attribute value attr_value_destroy :: proc(attr: ^Attribute_Value) { switch v in attr { case String: delete(string(v)) case DDB_Number: delete(v.integer_part) delete(v.fractional_part) case Binary: delete(string(v)) case String_Set: for s in v { delete(s) } slice := v delete(slice) case DDB_Number_Set: for num in v { delete(num.integer_part) delete(num.fractional_part) } delete(v) case Binary_Set: for b in v { delete(b) } slice := v delete(slice) case List: for item in v { item_copy := item attr_value_destroy(&item_copy) } list := v delete(list) case Map: for key, val in v { delete(key) val_copy := val attr_value_destroy(&val_copy) } m := v delete(m) case Bool, Null: // Nothing to free } } // Free an item item_destroy :: proc(item: ^Item) { for key, val in item { delete(key) val_copy := val attr_value_destroy(&val_copy) } delete(item^) }