Files
jormun-db/dynamodb/types.odin

492 lines
11 KiB
Odin
Raw Normal View History

2026-02-15 08:55:22 -05:00
package dynamodb
import "core:fmt"
import "core:strings"
// DynamoDB AttributeValue - the core data type
Attribute_Value :: union {
2026-02-16 10:52:35 -05:00
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
2026-02-15 08:55:22 -05:00
}
String :: distinct string
Binary :: distinct string
Bool :: distinct bool
Null :: distinct bool
String_Set :: distinct []string
DDB_Number_Set :: distinct []DDB_Number
2026-02-15 08:55:22 -05:00
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)
2026-02-15 13:56:08 -05:00
2026-02-15 08:55:22 -05:00
for schema_elem in key_schema {
attr, ok := item[schema_elem.attribute_name]
if !ok {
return {}, false
}
2026-02-15 13:56:08 -05:00
2026-02-15 08:55:22 -05:00
// Validate that key is a scalar type (S, N, or B)
#partial switch _ in attr {
2026-02-16 10:52:35 -05:00
case String, DDB_Number, Binary:
2026-02-15 08:55:22 -05:00
// Valid key type
case:
return {}, false
}
2026-02-15 13:56:08 -05:00
2026-02-15 08:55:22 -05:00
// Deep copy the attribute value
copied := attr_value_deep_copy(attr)
2026-02-15 13:56:08 -05:00
2026-02-15 08:55:22 -05:00
switch schema_elem.key_type {
case .HASH:
pk_value = copied
case .RANGE:
sk_value = copied
}
}
2026-02-15 13:56:08 -05:00
2026-02-15 08:55:22 -05:00
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)
2026-02-15 13:56:08 -05:00
2026-02-15 08:55:22 -05:00
for schema_elem in key_schema {
attr_value: Attribute_Value
2026-02-15 13:56:08 -05:00
2026-02-15 08:55:22 -05:00
switch schema_elem.key_type {
case .HASH:
attr_value = key.pk
case .RANGE:
if sk, ok := key.sk.?; ok {
attr_value = sk
} else {
continue
}
}
2026-02-15 13:56:08 -05:00
2026-02-15 08:55:22 -05:00
item[schema_elem.attribute_name] = attr_value_deep_copy(attr_value)
}
2026-02-15 13:56:08 -05:00
2026-02-15 08:55:22 -05:00
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
2026-02-15 13:56:08 -05:00
#partial switch v in key.pk {
2026-02-15 08:55:22 -05:00
case String:
pk_bytes = transmute([]byte)string(v)
2026-02-16 10:52:35 -05:00
case DDB_Number:
pk_bytes = encode_ddb_number_for_sort(v)
2026-02-15 08:55:22 -05:00
case Binary:
pk_bytes = transmute([]byte)string(v)
case:
return {}, false
}
2026-02-15 13:56:08 -05:00
2026-02-15 08:55:22 -05:00
sk_bytes: Maybe([]byte)
if sk, ok := key.sk.?; ok {
2026-02-15 13:56:08 -05:00
#partial switch v in sk {
2026-02-15 08:55:22 -05:00
case String:
sk_bytes = transmute([]byte)string(v)
2026-02-16 10:52:35 -05:00
case DDB_Number:
sk_bytes = encode_ddb_number_for_sort(v)
2026-02-15 08:55:22 -05:00
case Binary:
sk_bytes = transmute([]byte)string(v)
case:
return {}, false
}
}
2026-02-15 13:56:08 -05:00
2026-02-15 08:55:22 -05:00
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"
}
2026-02-15 15:04:43 -05:00
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
}
2026-02-15 08:55:22 -05:00
// 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
}
2026-02-15 13:56:08 -05:00
2026-02-15 08:55:22 -05:00
op_name := target[len(prefix):]
2026-02-15 13:56:08 -05:00
2026-02-15 08:55:22 -05:00
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
}
2026-02-15 13:56:08 -05:00
2026-02-15 08:55:22 -05:00
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
2026-02-15 13:56:08 -05:00
2026-02-15 08:55:22 -05:00
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"
}
2026-02-15 13:56:08 -05:00
2026-02-15 08:55:22 -05:00
return fmt.aprintf(`{{"__type":"%s","message":"%s"}}`, type_str, message)
}
2026-02-15 15:04:43 -05:00
// 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 {
2026-02-16 10:52:35 -05:00
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)))
2026-02-15 15:04:43 -05:00
}
2026-02-16 10:52:35 -05:00
return String(strings.clone(string(raw_bytes)))
2026-02-15 15:04:43 -05:00
}
2026-02-15 08:55:22 -05:00
// 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)
2026-02-15 08:55:22 -05:00
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)
2026-02-15 08:55:22 -05:00
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)
2026-02-15 08:55:22 -05:00
case Binary:
delete(string(v))
case String_Set:
for s in v {
delete(s)
}
2026-02-15 13:56:08 -05:00
slice := v
delete(slice)
case DDB_Number_Set:
for num in v {
delete(num.integer_part)
delete(num.fractional_part)
}
delete(v)
2026-02-15 08:55:22 -05:00
case Binary_Set:
for b in v {
delete(b)
}
2026-02-15 13:56:08 -05:00
slice := v
delete(slice)
2026-02-15 08:55:22 -05:00
case List:
for item in v {
item_copy := item
attr_value_destroy(&item_copy)
}
2026-02-15 13:56:08 -05:00
list := v
delete(list)
2026-02-15 08:55:22 -05:00
case Map:
for key, val in v {
delete(key)
val_copy := val
attr_value_destroy(&val_copy)
}
2026-02-15 13:56:08 -05:00
m := v
delete(m)
2026-02-15 08:55:22 -05:00
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^)
2026-02-16 10:52:35 -05:00
}