package dynamodb import "core:fmt" import "core:strings" // DynamoDB AttributeValue - the core data type Attribute_Value :: union { String, // S Number, // N (stored as string) Binary, // B (base64) Bool, // BOOL Null, // NULL String_Set, // SS Number_Set, // NS Binary_Set, // BS List, // L Map, // M } String :: distinct string Number :: distinct string Binary :: distinct string Bool :: distinct bool Null :: distinct bool String_Set :: distinct []string Number_Set :: distinct []string 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, 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 Number: pk_bytes = transmute([]byte)string(v) case Binary: pk_bytes = transmute([]byte)string(v) case: // Keys should only be scalar types (S, N, or B) 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 Number: sk_bytes = transmute([]byte)string(v) case Binary: sk_bytes = transmute([]byte)string(v) case: // Keys should only be scalar types 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 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) } // 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 Number: return Number(strings.clone(string(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 Number_Set: ns := make([]string, len(v)) for n, i in v { ns[i] = strings.clone(n) } return Number_Set(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 Number: delete(string(v)) case Binary: delete(string(v)) case String_Set: for s in v { delete(s) } slice := v delete(slice) case Number_Set: for n in v { delete(n) } slice := v delete(slice) 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^) }