Files
jormun-db/dynamodb/types.odin
2026-02-15 13:56:08 -05:00

458 lines
9.9 KiB
Odin

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