consolidate
This commit is contained in:
522
dynamodb/item_codec.odin
Normal file
522
dynamodb/item_codec.odin
Normal file
@@ -0,0 +1,522 @@
|
||||
// Binary TLV (Type-Length-Value) encoding for DynamoDB items
|
||||
// Replaces JSON storage with efficient binary format
|
||||
// Format: [attribute_count][name_len][name][type_tag][value_len][value]...
|
||||
package dynamodb
|
||||
|
||||
import "core:bytes"
|
||||
import "core:slice"
|
||||
|
||||
// Type tags for binary encoding (1 byte each)
|
||||
Type_Tag :: enum u8 {
|
||||
// Scalar types
|
||||
String = 0x01, // S
|
||||
Number = 0x02, // N (stored as string)
|
||||
Binary = 0x03, // B (base64 string)
|
||||
Boolean = 0x04, // BOOL
|
||||
Null = 0x05, // NULL
|
||||
|
||||
// Set types
|
||||
String_Set = 0x10, // SS
|
||||
Number_Set = 0x11, // NS
|
||||
Binary_Set = 0x12, // BS
|
||||
|
||||
// Complex types
|
||||
List = 0x20, // L
|
||||
Map = 0x21, // M
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Encoding (Item → Binary)
|
||||
// ============================================================================
|
||||
|
||||
// Encode an Item to binary TLV format
|
||||
// Format: [attribute_count:varint][attributes...]
|
||||
// Each attribute: [name_len:varint][name:bytes][type_tag:u8][value_encoded:bytes]
|
||||
encode :: proc(item: Item) -> ([]byte, bool) {
|
||||
buf: bytes.Buffer
|
||||
bytes.buffer_init_allocator(&buf, 0, 1024, context.allocator)
|
||||
defer bytes.buffer_destroy(&buf)
|
||||
|
||||
// Write attribute count
|
||||
encode_varint(&buf, len(item))
|
||||
|
||||
// Collect and sort keys for deterministic encoding
|
||||
keys := make([dynamic]string, context.temp_allocator)
|
||||
for key in item {
|
||||
append(&keys, key)
|
||||
}
|
||||
|
||||
slice.sort_by(keys[:], proc(a, b: string) -> bool {
|
||||
return a < b
|
||||
})
|
||||
|
||||
// Encode each attribute
|
||||
for key in keys {
|
||||
value := item[key]
|
||||
|
||||
// Write attribute name
|
||||
encode_varint(&buf, len(key))
|
||||
bytes.buffer_write_string(&buf, key)
|
||||
|
||||
// Encode attribute value
|
||||
ok := encode_attribute_value(&buf, value)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
|
||||
return bytes.buffer_to_bytes(&buf), true
|
||||
}
|
||||
|
||||
// Encode an AttributeValue to binary format
|
||||
encode_attribute_value :: proc(buf: ^bytes.Buffer, attr: Attribute_Value) -> bool {
|
||||
switch v in attr {
|
||||
case String:
|
||||
bytes.buffer_write_byte(buf, u8(Type_Tag.String))
|
||||
encode_varint(buf, len(v))
|
||||
bytes.buffer_write_string(buf, string(v))
|
||||
|
||||
case Number:
|
||||
bytes.buffer_write_byte(buf, u8(Type_Tag.Number))
|
||||
encode_varint(buf, len(v))
|
||||
bytes.buffer_write_string(buf, string(v))
|
||||
|
||||
case Binary:
|
||||
bytes.buffer_write_byte(buf, u8(Type_Tag.Binary))
|
||||
encode_varint(buf, len(v))
|
||||
bytes.buffer_write_string(buf, string(v))
|
||||
|
||||
case Bool:
|
||||
bytes.buffer_write_byte(buf, u8(Type_Tag.Boolean))
|
||||
bytes.buffer_write_byte(buf, 1 if bool(v) else 0)
|
||||
|
||||
case Null:
|
||||
bytes.buffer_write_byte(buf, u8(Type_Tag.Null))
|
||||
// NULL has no value bytes
|
||||
|
||||
case String_Set:
|
||||
bytes.buffer_write_byte(buf, u8(Type_Tag.String_Set))
|
||||
encode_varint(buf, len(v))
|
||||
for s in v {
|
||||
encode_varint(buf, len(s))
|
||||
bytes.buffer_write_string(buf, s)
|
||||
}
|
||||
|
||||
case Number_Set:
|
||||
bytes.buffer_write_byte(buf, u8(Type_Tag.Number_Set))
|
||||
encode_varint(buf, len(v))
|
||||
for n in v {
|
||||
encode_varint(buf, len(n))
|
||||
bytes.buffer_write_string(buf, n)
|
||||
}
|
||||
|
||||
case Binary_Set:
|
||||
bytes.buffer_write_byte(buf, u8(Type_Tag.Binary_Set))
|
||||
encode_varint(buf, len(v))
|
||||
for b in v {
|
||||
encode_varint(buf, len(b))
|
||||
bytes.buffer_write_string(buf, b)
|
||||
}
|
||||
|
||||
case List:
|
||||
bytes.buffer_write_byte(buf, u8(Type_Tag.List))
|
||||
encode_varint(buf, len(v))
|
||||
for item in v {
|
||||
ok := encode_attribute_value(buf, item)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
case Map:
|
||||
bytes.buffer_write_byte(buf, u8(Type_Tag.Map))
|
||||
encode_varint(buf, len(v))
|
||||
|
||||
// Collect and sort keys for deterministic encoding
|
||||
keys := make([dynamic]string, context.temp_allocator)
|
||||
for key in v {
|
||||
append(&keys, key)
|
||||
}
|
||||
|
||||
slice.sort_by(keys[:], proc(a, b: string) -> bool {
|
||||
return a < b
|
||||
})
|
||||
|
||||
// Encode each map entry
|
||||
for key in keys {
|
||||
value := v[key]
|
||||
encode_varint(buf, len(key))
|
||||
bytes.buffer_write_string(buf, key)
|
||||
ok := encode_attribute_value(buf, value)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Decoding (Binary → Item)
|
||||
// ============================================================================
|
||||
|
||||
// Binary decoder helper
|
||||
Binary_Decoder :: struct {
|
||||
data: []byte,
|
||||
pos: int,
|
||||
}
|
||||
|
||||
decoder_read_byte :: proc(decoder: ^Binary_Decoder) -> (u8, bool) {
|
||||
if decoder.pos >= len(decoder.data) {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
byte := decoder.data[decoder.pos]
|
||||
decoder.pos += 1
|
||||
return byte, true
|
||||
}
|
||||
|
||||
decoder_read_bytes :: proc(decoder: ^Binary_Decoder, length: int) -> ([]byte, bool) {
|
||||
if decoder.pos + length > len(decoder.data) {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
bytes := decoder.data[decoder.pos:decoder.pos + length]
|
||||
decoder.pos += length
|
||||
return bytes, true
|
||||
}
|
||||
|
||||
decoder_read_varint :: proc(decoder: ^Binary_Decoder) -> (int, bool) {
|
||||
result: int = 0
|
||||
shift: uint = 0
|
||||
|
||||
for decoder.pos < len(decoder.data) {
|
||||
byte := decoder.data[decoder.pos]
|
||||
decoder.pos += 1
|
||||
|
||||
result |= int(byte & 0x7F) << shift
|
||||
|
||||
if (byte & 0x80) == 0 {
|
||||
return result, true
|
||||
}
|
||||
|
||||
shift += 7
|
||||
if shift >= 64 {
|
||||
return 0, false // Varint overflow
|
||||
}
|
||||
}
|
||||
|
||||
return 0, false // Unexpected end of data
|
||||
}
|
||||
|
||||
// Decode binary TLV format back into an Item
|
||||
decode :: proc(data: []byte) -> (Item, bool) {
|
||||
decoder := Binary_Decoder{data = data, pos = 0}
|
||||
|
||||
attr_count, count_ok := decoder_read_varint(&decoder)
|
||||
if !count_ok {
|
||||
return {}, false
|
||||
}
|
||||
|
||||
item := make(Item)
|
||||
|
||||
for _ in 0..<attr_count {
|
||||
// Read attribute name
|
||||
name_len, name_len_ok := decoder_read_varint(&decoder)
|
||||
if !name_len_ok {
|
||||
// Cleanup on error
|
||||
item_destroy(&item)
|
||||
return {}, false
|
||||
}
|
||||
|
||||
name_bytes, name_ok := decoder_read_bytes(&decoder, name_len)
|
||||
if !name_ok {
|
||||
item_destroy(&item)
|
||||
return {}, false
|
||||
}
|
||||
|
||||
owned_name := string(name_bytes)
|
||||
owned_name = transmute(string)slice.clone(transmute([]byte)owned_name)
|
||||
|
||||
// Read attribute value
|
||||
value, value_ok := decode_attribute_value(&decoder)
|
||||
if !value_ok {
|
||||
delete(owned_name)
|
||||
item_destroy(&item)
|
||||
return {}, false
|
||||
}
|
||||
|
||||
item[owned_name] = value
|
||||
}
|
||||
|
||||
return item, true
|
||||
}
|
||||
|
||||
// Decode an AttributeValue from binary format
|
||||
decode_attribute_value :: proc(decoder: ^Binary_Decoder) -> (Attribute_Value, bool) {
|
||||
type_byte, type_ok := decoder_read_byte(decoder)
|
||||
if !type_ok {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
type_tag := Type_Tag(type_byte)
|
||||
|
||||
switch type_tag {
|
||||
case .String:
|
||||
length, len_ok := decoder_read_varint(decoder)
|
||||
if !len_ok {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
data, data_ok := decoder_read_bytes(decoder, length)
|
||||
if !data_ok {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
str := string(data)
|
||||
owned := transmute(string)slice.clone(transmute([]byte)str)
|
||||
return String(owned), true
|
||||
|
||||
case .Number:
|
||||
length, len_ok := decoder_read_varint(decoder)
|
||||
if !len_ok {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
data, data_ok := decoder_read_bytes(decoder, length)
|
||||
if !data_ok {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
str := string(data)
|
||||
owned := transmute(string)slice.clone(transmute([]byte)str)
|
||||
return Number(owned), true
|
||||
|
||||
case .Binary:
|
||||
length, len_ok := decoder_read_varint(decoder)
|
||||
if !len_ok {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
data, data_ok := decoder_read_bytes(decoder, length)
|
||||
if !data_ok {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
str := string(data)
|
||||
owned := transmute(string)slice.clone(transmute([]byte)str)
|
||||
return Binary(owned), true
|
||||
|
||||
case .Boolean:
|
||||
byte, byte_ok := decoder_read_byte(decoder)
|
||||
if !byte_ok {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return Bool(byte != 0), true
|
||||
|
||||
case .Null:
|
||||
return Null(true), true
|
||||
|
||||
case .String_Set:
|
||||
count, count_ok := decoder_read_varint(decoder)
|
||||
if !count_ok {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
strings := make([]string, count)
|
||||
|
||||
for i in 0..<count {
|
||||
length, len_ok := decoder_read_varint(decoder)
|
||||
if !len_ok {
|
||||
// Cleanup on error
|
||||
for j in 0..<i {
|
||||
delete(strings[j])
|
||||
}
|
||||
delete(strings)
|
||||
return nil, false
|
||||
}
|
||||
|
||||
data, data_ok := decoder_read_bytes(decoder, length)
|
||||
if !data_ok {
|
||||
for j in 0..<i {
|
||||
delete(strings[j])
|
||||
}
|
||||
delete(strings)
|
||||
return nil, false
|
||||
}
|
||||
|
||||
str := string(data)
|
||||
strings[i] = transmute(string)slice.clone(transmute([]byte)str)
|
||||
}
|
||||
|
||||
return String_Set(strings), true
|
||||
|
||||
case .Number_Set:
|
||||
count, count_ok := decoder_read_varint(decoder)
|
||||
if !count_ok {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
numbers := make([]string, count)
|
||||
|
||||
for i in 0..<count {
|
||||
length, len_ok := decoder_read_varint(decoder)
|
||||
if !len_ok {
|
||||
for j in 0..<i {
|
||||
delete(numbers[j])
|
||||
}
|
||||
delete(numbers)
|
||||
return nil, false
|
||||
}
|
||||
|
||||
data, data_ok := decoder_read_bytes(decoder, length)
|
||||
if !data_ok {
|
||||
for j in 0..<i {
|
||||
delete(numbers[j])
|
||||
}
|
||||
delete(numbers)
|
||||
return nil, false
|
||||
}
|
||||
|
||||
str := string(data)
|
||||
numbers[i] = transmute(string)slice.clone(transmute([]byte)str)
|
||||
}
|
||||
|
||||
return Number_Set(numbers), true
|
||||
|
||||
case .Binary_Set:
|
||||
count, count_ok := decoder_read_varint(decoder)
|
||||
if !count_ok {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
binaries := make([]string, count)
|
||||
|
||||
for i in 0..<count {
|
||||
length, len_ok := decoder_read_varint(decoder)
|
||||
if !len_ok {
|
||||
for j in 0..<i {
|
||||
delete(binaries[j])
|
||||
}
|
||||
delete(binaries)
|
||||
return nil, false
|
||||
}
|
||||
|
||||
data, data_ok := decoder_read_bytes(decoder, length)
|
||||
if !data_ok {
|
||||
for j in 0..<i {
|
||||
delete(binaries[j])
|
||||
}
|
||||
delete(binaries)
|
||||
return nil, false
|
||||
}
|
||||
|
||||
str := string(data)
|
||||
binaries[i] = transmute(string)slice.clone(transmute([]byte)str)
|
||||
}
|
||||
|
||||
return Binary_Set(binaries), true
|
||||
|
||||
case .List:
|
||||
count, count_ok := decoder_read_varint(decoder)
|
||||
if !count_ok {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
list := make([]Attribute_Value, count)
|
||||
|
||||
for i in 0..<count {
|
||||
value, value_ok := decode_attribute_value(decoder)
|
||||
if !value_ok {
|
||||
// Cleanup on error
|
||||
for j in 0..<i {
|
||||
item := list[j]
|
||||
attr_value_destroy(&item)
|
||||
}
|
||||
delete(list)
|
||||
return nil, false
|
||||
}
|
||||
|
||||
list[i] = value
|
||||
}
|
||||
|
||||
return List(list), true
|
||||
|
||||
case .Map:
|
||||
count, count_ok := decoder_read_varint(decoder)
|
||||
if !count_ok {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
attr_map := make(map[string]Attribute_Value)
|
||||
|
||||
for _ in 0..<count {
|
||||
// Read key
|
||||
key_len, key_len_ok := decoder_read_varint(decoder)
|
||||
if !key_len_ok {
|
||||
// Cleanup on error
|
||||
for k, v in attr_map {
|
||||
delete(k)
|
||||
v_copy := v
|
||||
attr_value_destroy(&v_copy)
|
||||
}
|
||||
delete(attr_map)
|
||||
return nil, false
|
||||
}
|
||||
|
||||
key_bytes, key_ok := decoder_read_bytes(decoder, key_len)
|
||||
if !key_ok {
|
||||
for k, v in attr_map {
|
||||
delete(k)
|
||||
v_copy := v
|
||||
attr_value_destroy(&v_copy)
|
||||
}
|
||||
delete(attr_map)
|
||||
return nil, false
|
||||
}
|
||||
|
||||
key := string(key_bytes)
|
||||
owned_key := transmute(string)slice.clone(transmute([]byte)key)
|
||||
|
||||
// Read value
|
||||
value, value_ok := decode_attribute_value(decoder)
|
||||
if !value_ok {
|
||||
delete(owned_key)
|
||||
for k, v in attr_map {
|
||||
delete(k)
|
||||
v_copy := v
|
||||
attr_value_destroy(&v_copy)
|
||||
}
|
||||
delete(attr_map)
|
||||
return nil, false
|
||||
}
|
||||
|
||||
attr_map[owned_key] = value
|
||||
}
|
||||
|
||||
return Map(attr_map), true
|
||||
}
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Varint Encoding (Encodes a varint length prefix)
|
||||
// ============================================================================
|
||||
|
||||
encode_varint :: proc(buf: ^bytes.Buffer, value: int) {
|
||||
v := value
|
||||
for {
|
||||
byte := u8(v & 0x7F)
|
||||
v >>= 7
|
||||
|
||||
if v == 0 {
|
||||
bytes.buffer_write_byte(buf, byte)
|
||||
return
|
||||
} else {
|
||||
bytes.buffer_write_byte(buf, byte | 0x80)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@ package dynamodb
|
||||
|
||||
import "core:encoding/json"
|
||||
import "core:fmt"
|
||||
import "core:mem"
|
||||
import "core:slice"
|
||||
import "core:strings"
|
||||
|
||||
@@ -20,7 +19,7 @@ parse_item :: proc(json_bytes: []byte) -> (Item, bool) {
|
||||
return {}, false
|
||||
}
|
||||
defer json.destroy_value(data)
|
||||
|
||||
|
||||
return parse_item_from_value(data)
|
||||
}
|
||||
|
||||
@@ -31,12 +30,12 @@ parse_item_from_value :: proc(value: json.Value) -> (Item, bool) {
|
||||
if !ok {
|
||||
return {}, false
|
||||
}
|
||||
|
||||
|
||||
item := make(Item)
|
||||
|
||||
|
||||
for key, val in obj {
|
||||
attr_name := strings.clone(key)
|
||||
|
||||
|
||||
attr_value, attr_ok := parse_attribute_value(val)
|
||||
if !attr_ok {
|
||||
// Cleanup on error
|
||||
@@ -49,10 +48,10 @@ parse_item_from_value :: proc(value: json.Value) -> (Item, bool) {
|
||||
delete(attr_name)
|
||||
return {}, false
|
||||
}
|
||||
|
||||
|
||||
item[attr_name] = attr_value
|
||||
}
|
||||
|
||||
|
||||
return item, true
|
||||
}
|
||||
|
||||
@@ -63,12 +62,12 @@ parse_attribute_value :: proc(value: json.Value) -> (Attribute_Value, bool) {
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
|
||||
// DynamoDB attribute must have exactly one key (the type indicator)
|
||||
if len(obj) != 1 {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
|
||||
// Get the single key-value pair
|
||||
for type_name, type_value in obj {
|
||||
// String
|
||||
@@ -79,7 +78,7 @@ parse_attribute_value :: proc(value: json.Value) -> (Attribute_Value, bool) {
|
||||
}
|
||||
return String(strings.clone(string(str))), true
|
||||
}
|
||||
|
||||
|
||||
// Number (stored as string)
|
||||
if type_name == "N" {
|
||||
str, str_ok := type_value.(json.String)
|
||||
@@ -88,7 +87,7 @@ parse_attribute_value :: proc(value: json.Value) -> (Attribute_Value, bool) {
|
||||
}
|
||||
return Number(strings.clone(string(str))), true
|
||||
}
|
||||
|
||||
|
||||
// Binary (base64 string)
|
||||
if type_name == "B" {
|
||||
str, str_ok := type_value.(json.String)
|
||||
@@ -97,7 +96,7 @@ parse_attribute_value :: proc(value: json.Value) -> (Attribute_Value, bool) {
|
||||
}
|
||||
return Binary(strings.clone(string(str))), true
|
||||
}
|
||||
|
||||
|
||||
// Boolean
|
||||
if type_name == "BOOL" {
|
||||
b, b_ok := type_value.(json.Boolean)
|
||||
@@ -106,7 +105,7 @@ parse_attribute_value :: proc(value: json.Value) -> (Attribute_Value, bool) {
|
||||
}
|
||||
return Bool(b), true
|
||||
}
|
||||
|
||||
|
||||
// Null
|
||||
if type_name == "NULL" {
|
||||
b, b_ok := type_value.(json.Boolean)
|
||||
@@ -115,16 +114,16 @@ parse_attribute_value :: proc(value: json.Value) -> (Attribute_Value, bool) {
|
||||
}
|
||||
return Null(b), true
|
||||
}
|
||||
|
||||
|
||||
// String Set
|
||||
if type_name == "SS" {
|
||||
arr, arr_ok := type_value.(json.Array)
|
||||
if !arr_ok {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
|
||||
strings_arr := make([]string, len(arr))
|
||||
|
||||
|
||||
for item, i in arr {
|
||||
str, str_ok := item.(json.String)
|
||||
if !str_ok {
|
||||
@@ -137,19 +136,19 @@ parse_attribute_value :: proc(value: json.Value) -> (Attribute_Value, bool) {
|
||||
}
|
||||
strings_arr[i] = strings.clone(string(str))
|
||||
}
|
||||
|
||||
|
||||
return String_Set(strings_arr), true
|
||||
}
|
||||
|
||||
|
||||
// Number Set
|
||||
if type_name == "NS" {
|
||||
arr, arr_ok := type_value.(json.Array)
|
||||
if !arr_ok {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
|
||||
numbers_arr := make([]string, len(arr))
|
||||
|
||||
|
||||
for item, i in arr {
|
||||
str, str_ok := item.(json.String)
|
||||
if !str_ok {
|
||||
@@ -162,19 +161,19 @@ parse_attribute_value :: proc(value: json.Value) -> (Attribute_Value, bool) {
|
||||
}
|
||||
numbers_arr[i] = strings.clone(string(str))
|
||||
}
|
||||
|
||||
|
||||
return Number_Set(numbers_arr), true
|
||||
}
|
||||
|
||||
|
||||
// Binary Set
|
||||
if type_name == "BS" {
|
||||
arr, arr_ok := type_value.(json.Array)
|
||||
if !arr_ok {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
|
||||
binaries_arr := make([]string, len(arr))
|
||||
|
||||
|
||||
for item, i in arr {
|
||||
str, str_ok := item.(json.String)
|
||||
if !str_ok {
|
||||
@@ -187,19 +186,19 @@ parse_attribute_value :: proc(value: json.Value) -> (Attribute_Value, bool) {
|
||||
}
|
||||
binaries_arr[i] = strings.clone(string(str))
|
||||
}
|
||||
|
||||
|
||||
return Binary_Set(binaries_arr), true
|
||||
}
|
||||
|
||||
|
||||
// List
|
||||
if type_name == "L" {
|
||||
arr, arr_ok := type_value.(json.Array)
|
||||
if !arr_ok {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
|
||||
list := make([]Attribute_Value, len(arr))
|
||||
|
||||
|
||||
for item, i in arr {
|
||||
val, val_ok := parse_attribute_value(item)
|
||||
if !val_ok {
|
||||
@@ -213,22 +212,22 @@ parse_attribute_value :: proc(value: json.Value) -> (Attribute_Value, bool) {
|
||||
}
|
||||
list[i] = val
|
||||
}
|
||||
|
||||
|
||||
return List(list), true
|
||||
}
|
||||
|
||||
|
||||
// Map
|
||||
if type_name == "M" {
|
||||
map_obj, map_ok := type_value.(json.Object)
|
||||
if !map_ok {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
|
||||
attr_map := make(map[string]Attribute_Value)
|
||||
|
||||
|
||||
for map_key, map_val in map_obj {
|
||||
key := strings.clone(map_key)
|
||||
|
||||
|
||||
val, val_ok := parse_attribute_value(map_val)
|
||||
if !val_ok {
|
||||
// Cleanup on error
|
||||
@@ -241,14 +240,14 @@ parse_attribute_value :: proc(value: json.Value) -> (Attribute_Value, bool) {
|
||||
delete(attr_map)
|
||||
return nil, false
|
||||
}
|
||||
|
||||
|
||||
attr_map[key] = val
|
||||
}
|
||||
|
||||
|
||||
return Map(attr_map), true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
@@ -261,9 +260,9 @@ parse_attribute_value :: proc(value: json.Value) -> (Attribute_Value, bool) {
|
||||
serialize_item :: proc(item: Item) -> string {
|
||||
builder := strings.builder_make()
|
||||
defer strings.builder_destroy(&builder)
|
||||
|
||||
|
||||
serialize_item_to_builder(&builder, item)
|
||||
|
||||
|
||||
return strings.clone(strings.to_string(builder))
|
||||
}
|
||||
|
||||
@@ -272,16 +271,16 @@ serialize_item_to_builder :: proc(b: ^strings.Builder, item: Item) {
|
||||
// Collect and sort keys for deterministic output
|
||||
keys := make([dynamic]string, context.temp_allocator)
|
||||
defer delete(keys)
|
||||
|
||||
|
||||
for key in item {
|
||||
append(&keys, key)
|
||||
}
|
||||
|
||||
|
||||
// Sort keys alphabetically
|
||||
slice.sort_by(keys[:], proc(a, b: string) -> bool {
|
||||
return a < b
|
||||
})
|
||||
|
||||
|
||||
strings.write_string(b, "{")
|
||||
for key, i in keys {
|
||||
if i > 0 {
|
||||
@@ -299,19 +298,19 @@ serialize_attribute_value :: proc(b: ^strings.Builder, attr: Attribute_Value) {
|
||||
switch v in attr {
|
||||
case String:
|
||||
fmt.sbprintf(b, `{"S":"%s"}`, string(v))
|
||||
|
||||
|
||||
case Number:
|
||||
fmt.sbprintf(b, `{"N":"%s"}`, string(v))
|
||||
|
||||
|
||||
case Binary:
|
||||
fmt.sbprintf(b, `{"B":"%s"}`, string(v))
|
||||
|
||||
|
||||
case Bool:
|
||||
fmt.sbprintf(b, `{"BOOL":%v}`, bool(v))
|
||||
|
||||
|
||||
case Null:
|
||||
strings.write_string(b, `{"NULL":true}`)
|
||||
|
||||
|
||||
case String_Set:
|
||||
strings.write_string(b, `{"SS":[`)
|
||||
for s, i in v {
|
||||
@@ -321,7 +320,7 @@ serialize_attribute_value :: proc(b: ^strings.Builder, attr: Attribute_Value) {
|
||||
fmt.sbprintf(b, `"%s"`, s)
|
||||
}
|
||||
strings.write_string(b, "]}")
|
||||
|
||||
|
||||
case Number_Set:
|
||||
strings.write_string(b, `{"NS":[`)
|
||||
for n, i in v {
|
||||
@@ -331,7 +330,7 @@ serialize_attribute_value :: proc(b: ^strings.Builder, attr: Attribute_Value) {
|
||||
fmt.sbprintf(b, `"%s"`, n)
|
||||
}
|
||||
strings.write_string(b, "]}")
|
||||
|
||||
|
||||
case Binary_Set:
|
||||
strings.write_string(b, `{"BS":[`)
|
||||
for bin, i in v {
|
||||
@@ -341,7 +340,7 @@ serialize_attribute_value :: proc(b: ^strings.Builder, attr: Attribute_Value) {
|
||||
fmt.sbprintf(b, `"%s"`, bin)
|
||||
}
|
||||
strings.write_string(b, "]}")
|
||||
|
||||
|
||||
case List:
|
||||
strings.write_string(b, `{"L":[`)
|
||||
for item, i in v {
|
||||
@@ -351,20 +350,20 @@ serialize_attribute_value :: proc(b: ^strings.Builder, attr: Attribute_Value) {
|
||||
serialize_attribute_value(b, item)
|
||||
}
|
||||
strings.write_string(b, "]}")
|
||||
|
||||
|
||||
case Map:
|
||||
strings.write_string(b, `{"M":{`)
|
||||
|
||||
|
||||
// Collect and sort keys for deterministic output
|
||||
keys := make([dynamic]string, context.temp_allocator)
|
||||
for key in v {
|
||||
append(&keys, key)
|
||||
}
|
||||
|
||||
|
||||
slice.sort_by(keys[:], proc(a, b: string) -> bool {
|
||||
return a < b
|
||||
})
|
||||
|
||||
|
||||
for key, i in keys {
|
||||
if i > 0 {
|
||||
strings.write_string(b, ",")
|
||||
@@ -373,7 +372,7 @@ serialize_attribute_value :: proc(b: ^strings.Builder, attr: Attribute_Value) {
|
||||
value := v[key]
|
||||
serialize_attribute_value(b, value)
|
||||
}
|
||||
|
||||
|
||||
strings.write_string(b, "}}")
|
||||
}
|
||||
}
|
||||
@@ -389,22 +388,22 @@ parse_table_name :: proc(request_body: []byte) -> (string, bool) {
|
||||
return "", false
|
||||
}
|
||||
defer json.destroy_value(data)
|
||||
|
||||
|
||||
root, ok := data.(json.Object)
|
||||
if !ok {
|
||||
return "", false
|
||||
}
|
||||
|
||||
|
||||
table_name_val, found := root["TableName"]
|
||||
if !found {
|
||||
return "", false
|
||||
}
|
||||
|
||||
|
||||
table_name_str, str_ok := table_name_val.(json.String)
|
||||
if !str_ok {
|
||||
return "", false
|
||||
}
|
||||
|
||||
|
||||
return string(table_name_str), true
|
||||
}
|
||||
|
||||
@@ -416,17 +415,17 @@ parse_item_from_request :: proc(request_body: []byte) -> (Item, bool) {
|
||||
return {}, false
|
||||
}
|
||||
defer json.destroy_value(data)
|
||||
|
||||
|
||||
root, ok := data.(json.Object)
|
||||
if !ok {
|
||||
return {}, false
|
||||
}
|
||||
|
||||
|
||||
item_val, found := root["Item"]
|
||||
if !found {
|
||||
return {}, false
|
||||
}
|
||||
|
||||
|
||||
return parse_item_from_value(item_val)
|
||||
}
|
||||
|
||||
@@ -438,17 +437,17 @@ parse_key_from_request :: proc(request_body: []byte) -> (Item, bool) {
|
||||
return {}, false
|
||||
}
|
||||
defer json.destroy_value(data)
|
||||
|
||||
|
||||
root, ok := data.(json.Object)
|
||||
if !ok {
|
||||
return {}, false
|
||||
}
|
||||
|
||||
|
||||
key_val, found := root["Key"]
|
||||
if !found {
|
||||
return {}, false
|
||||
}
|
||||
|
||||
|
||||
return parse_item_from_value(key_val)
|
||||
}
|
||||
|
||||
@@ -464,17 +463,17 @@ parse_limit :: proc(request_body: []byte) -> int {
|
||||
return 0
|
||||
}
|
||||
defer json.destroy_value(data)
|
||||
|
||||
|
||||
root, ok := data.(json.Object)
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
|
||||
|
||||
limit_val, found := root["Limit"]
|
||||
if !found {
|
||||
return 0
|
||||
}
|
||||
|
||||
|
||||
// JSON numbers can be either Integer or Float
|
||||
#partial switch v in limit_val {
|
||||
case json.Integer:
|
||||
@@ -482,7 +481,7 @@ parse_limit :: proc(request_body: []byte) -> int {
|
||||
case json.Float:
|
||||
return int(v)
|
||||
}
|
||||
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -494,24 +493,24 @@ parse_exclusive_start_key :: proc(request_body: []byte) -> Maybe([]byte) {
|
||||
return nil
|
||||
}
|
||||
defer json.destroy_value(data)
|
||||
|
||||
|
||||
root, ok := data.(json.Object)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
key_val, found := root["ExclusiveStartKey"]
|
||||
if !found {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
// Parse as Item first
|
||||
key_item, item_ok := parse_item_from_value(key_val)
|
||||
if !item_ok {
|
||||
return nil
|
||||
}
|
||||
defer item_destroy(&key_item)
|
||||
|
||||
|
||||
// Convert to binary key bytes (this will be done by the storage layer)
|
||||
// For now, just return nil - the storage layer will handle the conversion
|
||||
return nil
|
||||
@@ -521,6 +520,6 @@ parse_exclusive_start_key :: proc(request_body: []byte) -> Maybe([]byte) {
|
||||
serialize_last_evaluated_key :: proc(key: Key) -> string {
|
||||
item := key_to_item(key, {}) // Empty key_schema since we don't need validation here
|
||||
defer item_destroy(&item)
|
||||
|
||||
|
||||
return serialize_item(item)
|
||||
}
|
||||
|
||||
271
dynamodb/key_codec.odin
Normal file
271
dynamodb/key_codec.odin
Normal file
@@ -0,0 +1,271 @@
|
||||
package dynamodb
|
||||
|
||||
import "core:bytes"
|
||||
|
||||
// Entity type prefix bytes for namespacing
|
||||
Entity_Type :: enum u8 {
|
||||
Meta = 0x01, // Table metadata
|
||||
Data = 0x02, // Item data
|
||||
GSI = 0x03, // Global secondary index
|
||||
LSI = 0x04, // Local secondary index
|
||||
}
|
||||
|
||||
|
||||
// Decode a varint length prefix from a byte slice.
|
||||
// Reads starting at data[offset^] and advances offset^ past the varint on success.
|
||||
decode_varint :: proc(data: []byte, offset: ^int) -> (value: int, ok: bool) {
|
||||
i := offset^
|
||||
if i < 0 || i >= len(data) {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
u: u64 = 0
|
||||
shift: u32 = 0
|
||||
|
||||
for {
|
||||
if i >= len(data) {
|
||||
return 0, false // truncated
|
||||
}
|
||||
|
||||
b := data[i]
|
||||
i += 1
|
||||
|
||||
u |= u64(b & 0x7F) << shift
|
||||
|
||||
if (b & 0x80) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
shift += 7
|
||||
if shift >= 64 {
|
||||
return 0, false // malformed / overflow
|
||||
}
|
||||
}
|
||||
|
||||
// ensure it fits in int on this platform
|
||||
max_int := int((~uint(0)) >> 1)
|
||||
if u > u64(max_int) {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
offset^ = i
|
||||
return int(u), true
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// Build metadata key: [meta][table_name]
|
||||
build_meta_key :: proc(table_name: string) -> []byte {
|
||||
buf: bytes.Buffer
|
||||
bytes.buffer_init_allocator(&buf, 0, 256, context.allocator)
|
||||
|
||||
// Write entity type
|
||||
bytes.buffer_write_byte(&buf, u8(Entity_Type.Meta))
|
||||
|
||||
// Write table name with length prefix
|
||||
encode_varint(&buf, len(table_name))
|
||||
bytes.buffer_write_string(&buf, table_name)
|
||||
|
||||
return bytes.buffer_to_bytes(&buf)
|
||||
}
|
||||
|
||||
// Build data key: [data][table_name][pk_value][sk_value?]
|
||||
build_data_key :: proc(table_name: string, pk_value: []byte, sk_value: Maybe([]byte)) -> []byte {
|
||||
buf: bytes.Buffer
|
||||
bytes.buffer_init_allocator(&buf, 0, 512, context.allocator)
|
||||
|
||||
// Write entity type
|
||||
bytes.buffer_write_byte(&buf, u8(Entity_Type.Data))
|
||||
|
||||
// Write table name
|
||||
encode_varint(&buf, len(table_name))
|
||||
bytes.buffer_write_string(&buf, table_name)
|
||||
|
||||
// Write partition key
|
||||
encode_varint(&buf, len(pk_value))
|
||||
bytes.buffer_write(&buf, pk_value)
|
||||
|
||||
// Write sort key if present
|
||||
if sk, ok := sk_value.?; ok {
|
||||
encode_varint(&buf, len(sk))
|
||||
bytes.buffer_write(&buf, sk)
|
||||
}
|
||||
|
||||
return bytes.buffer_to_bytes(&buf)
|
||||
}
|
||||
|
||||
// Build table prefix for scanning: [data][table_name]
|
||||
build_table_prefix :: proc(table_name: string) -> []byte {
|
||||
buf: bytes.Buffer
|
||||
bytes.buffer_init_allocator(&buf, 0, 256, context.allocator)
|
||||
|
||||
// Write entity type
|
||||
bytes.buffer_write_byte(&buf, u8(Entity_Type.Data))
|
||||
|
||||
// Write table name
|
||||
encode_varint(&buf, len(table_name))
|
||||
bytes.buffer_write_string(&buf, table_name)
|
||||
|
||||
return bytes.buffer_to_bytes(&buf)
|
||||
}
|
||||
|
||||
// Build partition prefix for querying: [data][table_name][pk_value]
|
||||
build_partition_prefix :: proc(table_name: string, pk_value: []byte) -> []byte {
|
||||
buf: bytes.Buffer
|
||||
bytes.buffer_init_allocator(&buf, 0, 512, context.allocator)
|
||||
|
||||
// Write entity type
|
||||
bytes.buffer_write_byte(&buf, u8(Entity_Type.Data))
|
||||
|
||||
// Write table name
|
||||
encode_varint(&buf, len(table_name))
|
||||
bytes.buffer_write_string(&buf, table_name)
|
||||
|
||||
// Write partition key
|
||||
encode_varint(&buf, len(pk_value))
|
||||
bytes.buffer_write(&buf, pk_value)
|
||||
|
||||
return bytes.buffer_to_bytes(&buf)
|
||||
}
|
||||
|
||||
// Build GSI key: [gsi][table_name][index_name][gsi_pk][gsi_sk?]
|
||||
build_gsi_key :: proc(table_name: string, index_name: string, gsi_pk: []byte, gsi_sk: Maybe([]byte)) -> []byte {
|
||||
buf: bytes.Buffer
|
||||
bytes.buffer_init_allocator(&buf, 0, 512, context.allocator)
|
||||
|
||||
// Write entity type
|
||||
bytes.buffer_write_byte(&buf, u8(Entity_Type.GSI))
|
||||
|
||||
// Write table name
|
||||
encode_varint(&buf, len(table_name))
|
||||
bytes.buffer_write_string(&buf, table_name)
|
||||
|
||||
// Write index name
|
||||
encode_varint(&buf, len(index_name))
|
||||
bytes.buffer_write_string(&buf, index_name)
|
||||
|
||||
// Write GSI partition key
|
||||
encode_varint(&buf, len(gsi_pk))
|
||||
bytes.buffer_write(&buf, gsi_pk)
|
||||
|
||||
// Write GSI sort key if present
|
||||
if sk, ok := gsi_sk.?; ok {
|
||||
encode_varint(&buf, len(sk))
|
||||
bytes.buffer_write(&buf, sk)
|
||||
}
|
||||
|
||||
return bytes.buffer_to_bytes(&buf)
|
||||
}
|
||||
|
||||
// Build LSI key: [lsi][table_name][index_name][pk][lsi_sk]
|
||||
build_lsi_key :: proc(table_name: string, index_name: string, pk: []byte, lsi_sk: []byte) -> []byte {
|
||||
buf: bytes.Buffer
|
||||
bytes.buffer_init_allocator(&buf, 0, 512, context.allocator)
|
||||
|
||||
// Write entity type
|
||||
bytes.buffer_write_byte(&buf, u8(Entity_Type.LSI))
|
||||
|
||||
// Write table name
|
||||
encode_varint(&buf, len(table_name))
|
||||
bytes.buffer_write_string(&buf, table_name)
|
||||
|
||||
// Write index name
|
||||
encode_varint(&buf, len(index_name))
|
||||
bytes.buffer_write_string(&buf, index_name)
|
||||
|
||||
// Write partition key
|
||||
encode_varint(&buf, len(pk))
|
||||
bytes.buffer_write(&buf, pk)
|
||||
|
||||
// Write LSI sort key
|
||||
encode_varint(&buf, len(lsi_sk))
|
||||
bytes.buffer_write(&buf, lsi_sk)
|
||||
|
||||
return bytes.buffer_to_bytes(&buf)
|
||||
}
|
||||
|
||||
// Key decoder for reading binary keys
|
||||
Key_Decoder :: struct {
|
||||
data: []byte,
|
||||
pos: int,
|
||||
}
|
||||
|
||||
decoder_read_entity_type :: proc(decoder: ^Key_Decoder) -> (Entity_Type, bool) {
|
||||
if decoder.pos >= len(decoder.data) {
|
||||
return .Meta, false
|
||||
}
|
||||
|
||||
entity_type := Entity_Type(decoder.data[decoder.pos])
|
||||
decoder.pos += 1
|
||||
return entity_type, true
|
||||
}
|
||||
|
||||
decoder_read_segment :: proc(decoder: ^Key_Decoder) -> (segment: []byte, ok: bool) {
|
||||
// Read length
|
||||
length := decode_varint(decoder.data, &decoder.pos) or_return
|
||||
|
||||
// Read data
|
||||
if decoder.pos + length > len(decoder.data) {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// Return slice (owned by caller via context.allocator)
|
||||
segment = make([]byte, length, context.allocator)
|
||||
copy(segment, decoder.data[decoder.pos:decoder.pos + length])
|
||||
decoder.pos += length
|
||||
|
||||
return segment, true
|
||||
}
|
||||
|
||||
decoder_read_segment_borrowed :: proc(decoder: ^Key_Decoder) -> (segment: []byte, ok: bool) {
|
||||
// Read length
|
||||
length := decode_varint(decoder.data, &decoder.pos) or_return
|
||||
|
||||
// Return borrowed slice
|
||||
if decoder.pos + length > len(decoder.data) {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
segment = decoder.data[decoder.pos:decoder.pos + length]
|
||||
decoder.pos += length
|
||||
|
||||
return segment, true
|
||||
}
|
||||
|
||||
decoder_has_more :: proc(decoder: ^Key_Decoder) -> bool {
|
||||
return decoder.pos < len(decoder.data)
|
||||
}
|
||||
|
||||
// Decode a data key back into components
|
||||
Decoded_Data_Key :: struct {
|
||||
table_name: string,
|
||||
pk_value: []byte,
|
||||
sk_value: Maybe([]byte),
|
||||
}
|
||||
|
||||
decode_data_key :: proc(key: []byte) -> (result: Decoded_Data_Key, ok: bool) {
|
||||
decoder := Key_Decoder{data = key, pos = 0}
|
||||
|
||||
// Read and verify entity type
|
||||
entity_type := decoder_read_entity_type(&decoder) or_return
|
||||
if entity_type != .Data {
|
||||
return {}, false
|
||||
}
|
||||
|
||||
// Read table name
|
||||
table_name_bytes := decoder_read_segment(&decoder) or_return
|
||||
result.table_name = string(table_name_bytes)
|
||||
|
||||
// Read partition key
|
||||
result.pk_value = decoder_read_segment(&decoder) or_return
|
||||
|
||||
// Read sort key if present
|
||||
if decoder_has_more(&decoder) {
|
||||
sk := decoder_read_segment(&decoder) or_return
|
||||
result.sk_value = sk
|
||||
}
|
||||
|
||||
return result, true
|
||||
}
|
||||
@@ -1,15 +1,12 @@
|
||||
// Storage engine mapping DynamoDB operations to RocksDB
|
||||
package dynamodb
|
||||
|
||||
import "core:encoding/json"
|
||||
import "core:fmt"
|
||||
import "core:mem"
|
||||
import "core:slice"
|
||||
import "core:strings"
|
||||
import "core:sync"
|
||||
import "core:time"
|
||||
import "../key_codec"
|
||||
import "../item_codec"
|
||||
import "../rocksdb"
|
||||
|
||||
Storage_Error :: enum {
|
||||
@@ -218,12 +215,12 @@ serialize_table_metadata :: proc(metadata: ^Table_Metadata) -> ([]byte, bool) {
|
||||
meta_item["CreationDateTime"] = Number(fmt.aprint(metadata.creation_date_time))
|
||||
|
||||
// Encode to binary
|
||||
return item_codec.encode(meta_item)
|
||||
return encode(meta_item)
|
||||
}
|
||||
|
||||
// Deserialize table metadata from binary format
|
||||
deserialize_table_metadata :: proc(data: []byte, allocator: mem.Allocator) -> (Table_Metadata, bool) {
|
||||
meta_item, ok := item_codec.decode(data)
|
||||
meta_item, ok := decode(data)
|
||||
if !ok {
|
||||
return {}, false
|
||||
}
|
||||
@@ -239,7 +236,7 @@ deserialize_table_metadata :: proc(data: []byte, allocator: mem.Allocator) -> (T
|
||||
|
||||
// Get table metadata
|
||||
get_table_metadata :: proc(engine: ^Storage_Engine, table_name: string) -> (Table_Metadata, Storage_Error) {
|
||||
meta_key := key_codec.build_meta_key(table_name)
|
||||
meta_key := build_meta_key(table_name)
|
||||
defer delete(meta_key)
|
||||
|
||||
value, get_err := rocksdb.db_get(&engine.db, meta_key)
|
||||
@@ -275,7 +272,7 @@ create_table :: proc(
|
||||
defer sync.rw_mutex_unlock(table_lock)
|
||||
|
||||
// Check if table already exists
|
||||
meta_key := key_codec.build_meta_key(table_name)
|
||||
meta_key := build_meta_key(table_name)
|
||||
defer delete(meta_key)
|
||||
|
||||
existing, get_err := rocksdb.db_get(&engine.db, meta_key)
|
||||
@@ -340,7 +337,7 @@ delete_table :: proc(engine: ^Storage_Engine, table_name: string) -> Storage_Err
|
||||
defer sync.rw_mutex_unlock(table_lock)
|
||||
|
||||
// Check table exists
|
||||
meta_key := key_codec.build_meta_key(table_name)
|
||||
meta_key := build_meta_key(table_name)
|
||||
defer delete(meta_key)
|
||||
|
||||
existing, get_err := rocksdb.db_get(&engine.db, meta_key)
|
||||
@@ -403,11 +400,11 @@ put_item :: proc(engine: ^Storage_Engine, table_name: string, item: Item) -> Sto
|
||||
}
|
||||
|
||||
// Build storage key
|
||||
storage_key := key_codec.build_data_key(table_name, key_values.pk, key_values.sk)
|
||||
storage_key := build_data_key(table_name, key_values.pk, key_values.sk)
|
||||
defer delete(storage_key)
|
||||
|
||||
// Encode item
|
||||
encoded_item, encode_ok := item_codec.encode(item)
|
||||
encoded_item, encode_ok := encode(item)
|
||||
if !encode_ok {
|
||||
return .Serialization_Error
|
||||
}
|
||||
@@ -456,7 +453,7 @@ get_item :: proc(engine: ^Storage_Engine, table_name: string, key: Item) -> (May
|
||||
}
|
||||
|
||||
// Build storage key
|
||||
storage_key := key_codec.build_data_key(table_name, key_values.pk, key_values.sk)
|
||||
storage_key := build_data_key(table_name, key_values.pk, key_values.sk)
|
||||
defer delete(storage_key)
|
||||
|
||||
// Get from RocksDB
|
||||
@@ -470,7 +467,7 @@ get_item :: proc(engine: ^Storage_Engine, table_name: string, key: Item) -> (May
|
||||
defer delete(value)
|
||||
|
||||
// Decode item
|
||||
item, decode_ok := item_codec.decode(value)
|
||||
item, decode_ok := decode(value)
|
||||
if !decode_ok {
|
||||
return nil, .Serialization_Error
|
||||
}
|
||||
@@ -512,7 +509,7 @@ delete_item :: proc(engine: ^Storage_Engine, table_name: string, key: Item) -> S
|
||||
}
|
||||
|
||||
// Build storage key
|
||||
storage_key := key_codec.build_data_key(table_name, key_values.pk, key_values.sk)
|
||||
storage_key := build_data_key(table_name, key_values.pk, key_values.sk)
|
||||
defer delete(storage_key)
|
||||
|
||||
// Delete from RocksDB
|
||||
|
||||
@@ -51,13 +51,13 @@ key_destroy :: proc(key: ^Key) {
|
||||
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:
|
||||
@@ -65,10 +65,10 @@ key_from_item :: proc(item: Item, key_schema: []Key_Schema_Element) -> (Key, boo
|
||||
case:
|
||||
return {}, false
|
||||
}
|
||||
|
||||
|
||||
// Deep copy the attribute value
|
||||
copied := attr_value_deep_copy(attr)
|
||||
|
||||
|
||||
switch schema_elem.key_type {
|
||||
case .HASH:
|
||||
pk_value = copied
|
||||
@@ -76,17 +76,17 @@ key_from_item :: proc(item: Item, key_schema: []Key_Schema_Element) -> (Key, boo
|
||||
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
|
||||
@@ -97,10 +97,10 @@ key_to_item :: proc(key: Key, key_schema: []Key_Schema_Element) -> Item {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
item[schema_elem.attribute_name] = attr_value_deep_copy(attr_value)
|
||||
}
|
||||
|
||||
|
||||
return item
|
||||
}
|
||||
|
||||
@@ -112,8 +112,8 @@ Key_Values :: struct {
|
||||
|
||||
key_get_values :: proc(key: ^Key) -> (Key_Values, bool) {
|
||||
pk_bytes: []byte
|
||||
|
||||
switch v in key.pk {
|
||||
|
||||
#partial switch v in key.pk {
|
||||
case String:
|
||||
pk_bytes = transmute([]byte)string(v)
|
||||
case Number:
|
||||
@@ -121,12 +121,13 @@ key_get_values :: proc(key: ^Key) -> (Key_Values, bool) {
|
||||
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 {
|
||||
switch v in sk {
|
||||
#partial switch v in sk {
|
||||
case String:
|
||||
sk_bytes = transmute([]byte)string(v)
|
||||
case Number:
|
||||
@@ -134,10 +135,11 @@ key_get_values :: proc(key: ^Key) -> (Key_Values, bool) {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -289,9 +291,9 @@ operation_from_target :: proc(target: string) -> Operation {
|
||||
if !strings.has_prefix(target, prefix) {
|
||||
return .Unknown
|
||||
}
|
||||
|
||||
|
||||
op_name := target[len(prefix):]
|
||||
|
||||
|
||||
switch op_name {
|
||||
case "CreateTable": return .CreateTable
|
||||
case "DeleteTable": return .DeleteTable
|
||||
@@ -309,7 +311,7 @@ operation_from_target :: proc(target: string) -> Operation {
|
||||
case "TransactGetItems": return .TransactGetItems
|
||||
case "TransactWriteItems": return .TransactWriteItems
|
||||
}
|
||||
|
||||
|
||||
return .Unknown
|
||||
}
|
||||
|
||||
@@ -327,7 +329,7 @@ DynamoDB_Error_Type :: enum {
|
||||
|
||||
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"
|
||||
@@ -346,7 +348,7 @@ error_to_response :: proc(err_type: DynamoDB_Error_Type, message: string) -> str
|
||||
case .SerializationException:
|
||||
type_str = "com.amazonaws.dynamodb.v20120810#SerializationException"
|
||||
}
|
||||
|
||||
|
||||
return fmt.aprintf(`{{"__type":"%s","message":"%s"}}`, type_str, message)
|
||||
}
|
||||
|
||||
@@ -410,30 +412,35 @@ attr_value_destroy :: proc(attr: ^Attribute_Value) {
|
||||
for s in v {
|
||||
delete(s)
|
||||
}
|
||||
delete([]string(v))
|
||||
slice := v
|
||||
delete(slice)
|
||||
case Number_Set:
|
||||
for n in v {
|
||||
delete(n)
|
||||
}
|
||||
delete([]string(v))
|
||||
slice := v
|
||||
delete(slice)
|
||||
case Binary_Set:
|
||||
for b in v {
|
||||
delete(b)
|
||||
}
|
||||
delete([]string(v))
|
||||
slice := v
|
||||
delete(slice)
|
||||
case List:
|
||||
for item in v {
|
||||
item_copy := item
|
||||
attr_value_destroy(&item_copy)
|
||||
}
|
||||
delete([]Attribute_Value(v))
|
||||
list := v
|
||||
delete(list)
|
||||
case Map:
|
||||
for key, val in v {
|
||||
delete(key)
|
||||
val_copy := val
|
||||
attr_value_destroy(&val_copy)
|
||||
}
|
||||
delete(map[string]Attribute_Value(v))
|
||||
m := v
|
||||
delete(m)
|
||||
case Bool, Null:
|
||||
// Nothing to free
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user