dont ever use cstring again
This commit is contained in:
9
TODO.md
9
TODO.md
@@ -14,22 +14,25 @@ This tracks the rewrite from Zig to Odin and remaining features.
|
|||||||
- [x] Main entry point with arena pattern demo
|
- [x] Main entry point with arena pattern demo
|
||||||
- [x] .gitignore
|
- [x] .gitignore
|
||||||
- [x] HTTP Server Scaffolding
|
- [x] HTTP Server Scaffolding
|
||||||
|
- [x] JSON Parser
|
||||||
|
- [x] Item_codec
|
||||||
|
- [x] Storage
|
||||||
|
|
||||||
## 🚧 In Progress (Need to Complete)
|
## 🚧 In Progress (Need to Complete)
|
||||||
|
|
||||||
### Core Modules
|
### Core Modules
|
||||||
|
|
||||||
- [ ] **dynamodb/json.odin** - DynamoDB JSON parsing and serialization
|
- [x] **dynamodb/json.odin** - DynamoDB JSON parsing and serialization
|
||||||
- Parse `{"S": "value"}` format
|
- Parse `{"S": "value"}` format
|
||||||
- Serialize AttributeValue to DynamoDB JSON
|
- Serialize AttributeValue to DynamoDB JSON
|
||||||
- Parse request bodies (PutItem, GetItem, etc.)
|
- Parse request bodies (PutItem, GetItem, etc.)
|
||||||
|
|
||||||
- [ ] **item_codec/item_codec.odin** - Binary TLV encoding for items
|
- [x] **item_codec/item_codec.odin** - Binary TLV encoding for items
|
||||||
- Encode Item to binary TLV format
|
- Encode Item to binary TLV format
|
||||||
- Decode binary TLV back to Item
|
- Decode binary TLV back to Item
|
||||||
- Type tag handling for all DynamoDB types
|
- Type tag handling for all DynamoDB types
|
||||||
|
|
||||||
- [ ] **dynamodb/storage.odin** - Storage engine with RocksDB
|
- [x] **dynamodb/storage.odin** - Storage engine with RocksDB
|
||||||
- Table metadata management
|
- Table metadata management
|
||||||
- create_table, delete_table, describe_table, list_tables
|
- create_table, delete_table, describe_table, list_tables
|
||||||
- put_item, get_item, delete_item
|
- put_item, get_item, delete_item
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# Output file
|
# Output file
|
||||||
OUTPUT_FILE="project_context.txt"
|
OUTPUT_FILE="jormundb-odin-project_context.txt"
|
||||||
|
|
||||||
# Directories to exclude
|
# Directories to exclude
|
||||||
EXCLUDE_DIRS=("build" "data" ".git")
|
EXCLUDE_DIRS=("build" "data" ".git")
|
||||||
@@ -10,7 +10,7 @@ EXCLUDE_DIRS=("build" "data" ".git")
|
|||||||
INCLUDE_EXTENSIONS=("odin" "Makefile" "md" "json" "h" "cc")
|
INCLUDE_EXTENSIONS=("odin" "Makefile" "md" "json" "h" "cc")
|
||||||
|
|
||||||
# Special files to include (without extension)
|
# Special files to include (without extension)
|
||||||
INCLUDE_FILES=()
|
INCLUDE_FILES=("Makefile")
|
||||||
|
|
||||||
# Clear the output file
|
# Clear the output file
|
||||||
> "$OUTPUT_FILE"
|
> "$OUTPUT_FILE"
|
||||||
|
|||||||
531
dynamodb/storage.odin
Normal file
531
dynamodb/storage.odin
Normal file
@@ -0,0 +1,531 @@
|
|||||||
|
// 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 {
|
||||||
|
None,
|
||||||
|
Table_Not_Found,
|
||||||
|
Table_Already_Exists,
|
||||||
|
Item_Not_Found,
|
||||||
|
Invalid_Key,
|
||||||
|
Missing_Key_Attribute,
|
||||||
|
Serialization_Error,
|
||||||
|
RocksDB_Error,
|
||||||
|
Out_Of_Memory,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Result type for Scan operations with pagination
|
||||||
|
Scan_Result :: struct {
|
||||||
|
items: []Item,
|
||||||
|
last_evaluated_key: Maybe([]byte),
|
||||||
|
}
|
||||||
|
|
||||||
|
scan_result_destroy :: proc(result: ^Scan_Result) {
|
||||||
|
for item in result.items {
|
||||||
|
item_copy := item
|
||||||
|
item_destroy(&item_copy)
|
||||||
|
}
|
||||||
|
delete(result.items)
|
||||||
|
|
||||||
|
if last_key, ok := result.last_evaluated_key.?; ok {
|
||||||
|
delete(last_key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Result type for Query operations with pagination
|
||||||
|
Query_Result :: struct {
|
||||||
|
items: []Item,
|
||||||
|
last_evaluated_key: Maybe([]byte),
|
||||||
|
}
|
||||||
|
|
||||||
|
query_result_destroy :: proc(result: ^Query_Result) {
|
||||||
|
for item in result.items {
|
||||||
|
item_copy := item
|
||||||
|
item_destroy(&item_copy)
|
||||||
|
}
|
||||||
|
delete(result.items)
|
||||||
|
|
||||||
|
if last_key, ok := result.last_evaluated_key.?; ok {
|
||||||
|
delete(last_key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// In-memory representation of table metadata
|
||||||
|
Table_Metadata :: struct {
|
||||||
|
table_name: string,
|
||||||
|
key_schema: []Key_Schema_Element,
|
||||||
|
attribute_definitions: []Attribute_Definition,
|
||||||
|
table_status: Table_Status,
|
||||||
|
creation_date_time: i64,
|
||||||
|
global_secondary_indexes: Maybe([]Global_Secondary_Index),
|
||||||
|
local_secondary_indexes: Maybe([]Local_Secondary_Index),
|
||||||
|
}
|
||||||
|
|
||||||
|
table_metadata_destroy :: proc(metadata: ^Table_Metadata, allocator: mem.Allocator) {
|
||||||
|
delete(metadata.table_name, allocator)
|
||||||
|
|
||||||
|
for ks in metadata.key_schema {
|
||||||
|
delete(ks.attribute_name, allocator)
|
||||||
|
}
|
||||||
|
delete(metadata.key_schema, allocator)
|
||||||
|
|
||||||
|
for ad in metadata.attribute_definitions {
|
||||||
|
delete(ad.attribute_name, allocator)
|
||||||
|
}
|
||||||
|
delete(metadata.attribute_definitions, allocator)
|
||||||
|
|
||||||
|
// TODO: Free GSI/LSI if we implement them
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the partition key attribute name
|
||||||
|
table_metadata_get_partition_key_name :: proc(metadata: ^Table_Metadata) -> Maybe(string) {
|
||||||
|
for ks in metadata.key_schema {
|
||||||
|
if ks.key_type == .HASH {
|
||||||
|
return ks.attribute_name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the sort key attribute name (if any)
|
||||||
|
table_metadata_get_sort_key_name :: proc(metadata: ^Table_Metadata) -> Maybe(string) {
|
||||||
|
for ks in metadata.key_schema {
|
||||||
|
if ks.key_type == .RANGE {
|
||||||
|
return ks.attribute_name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Storage engine
|
||||||
|
Storage_Engine :: struct {
|
||||||
|
db: rocksdb.DB,
|
||||||
|
allocator: mem.Allocator,
|
||||||
|
table_locks: map[string]^sync.RW_Mutex,
|
||||||
|
table_locks_mutex: sync.Mutex,
|
||||||
|
}
|
||||||
|
|
||||||
|
storage_engine_init :: proc(allocator: mem.Allocator, data_dir: string) -> (^Storage_Engine, Storage_Error) {
|
||||||
|
db, db_err := rocksdb.db_open(data_dir, true)
|
||||||
|
if db_err != .None {
|
||||||
|
return nil, .RocksDB_Error
|
||||||
|
}
|
||||||
|
|
||||||
|
engine := new(Storage_Engine, allocator)
|
||||||
|
engine.db = db
|
||||||
|
engine.allocator = allocator
|
||||||
|
engine.table_locks = make(map[string]^sync.RW_Mutex, allocator = allocator)
|
||||||
|
|
||||||
|
return engine, .None
|
||||||
|
}
|
||||||
|
|
||||||
|
storage_engine_destroy :: proc(engine: ^Storage_Engine) {
|
||||||
|
// Free all table locks
|
||||||
|
for key, lock in engine.table_locks {
|
||||||
|
delete(key, engine.allocator)
|
||||||
|
free(lock, engine.allocator)
|
||||||
|
}
|
||||||
|
delete(engine.table_locks)
|
||||||
|
|
||||||
|
// Close database
|
||||||
|
rocksdb.db_close(&engine.db)
|
||||||
|
|
||||||
|
// Free engine
|
||||||
|
free(engine, engine.allocator)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get or create a table lock
|
||||||
|
get_or_create_table_lock :: proc(engine: ^Storage_Engine, table_name: string) -> ^sync.RW_Mutex {
|
||||||
|
sync.mutex_lock(&engine.table_locks_mutex)
|
||||||
|
defer sync.mutex_unlock(&engine.table_locks_mutex)
|
||||||
|
|
||||||
|
if lock, found := engine.table_locks[table_name]; found {
|
||||||
|
return lock
|
||||||
|
}
|
||||||
|
|
||||||
|
lock := new(sync.RW_Mutex, engine.allocator)
|
||||||
|
owned_name := strings.clone(table_name, engine.allocator)
|
||||||
|
engine.table_locks[owned_name] = lock
|
||||||
|
return lock
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove a table lock
|
||||||
|
remove_table_lock :: proc(engine: ^Storage_Engine, table_name: string) {
|
||||||
|
sync.mutex_lock(&engine.table_locks_mutex)
|
||||||
|
defer sync.mutex_unlock(&engine.table_locks_mutex)
|
||||||
|
|
||||||
|
if lock, found := engine.table_locks[table_name]; found {
|
||||||
|
delete(table_name, engine.allocator)
|
||||||
|
free(lock, engine.allocator)
|
||||||
|
delete_key(&engine.table_locks, table_name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Table Metadata Operations
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// Serialize table metadata to binary format
|
||||||
|
serialize_table_metadata :: proc(metadata: ^Table_Metadata) -> ([]byte, bool) {
|
||||||
|
// Create a temporary item to hold metadata
|
||||||
|
meta_item := make(Item, context.temp_allocator)
|
||||||
|
defer delete(meta_item)
|
||||||
|
|
||||||
|
// Encode key schema as JSON string
|
||||||
|
ks_builder := strings.builder_make(context.temp_allocator)
|
||||||
|
defer strings.builder_destroy(&ks_builder)
|
||||||
|
|
||||||
|
strings.write_string(&ks_builder, "[")
|
||||||
|
for ks, i in metadata.key_schema {
|
||||||
|
if i > 0 {
|
||||||
|
strings.write_string(&ks_builder, ",")
|
||||||
|
}
|
||||||
|
fmt.sbprintf(&ks_builder, `{"AttributeName":"%s","KeyType":"%s"}`,
|
||||||
|
ks.attribute_name, key_type_to_string(ks.key_type))
|
||||||
|
}
|
||||||
|
strings.write_string(&ks_builder, "]")
|
||||||
|
|
||||||
|
meta_item["KeySchema"] = String(strings.clone(strings.to_string(ks_builder)))
|
||||||
|
|
||||||
|
// Encode attribute definitions as JSON string
|
||||||
|
ad_builder := strings.builder_make(context.temp_allocator)
|
||||||
|
defer strings.builder_destroy(&ad_builder)
|
||||||
|
|
||||||
|
strings.write_string(&ad_builder, "[")
|
||||||
|
for ad, i in metadata.attribute_definitions {
|
||||||
|
if i > 0 {
|
||||||
|
strings.write_string(&ad_builder, ",")
|
||||||
|
}
|
||||||
|
fmt.sbprintf(&ad_builder, `{"AttributeName":"%s","AttributeType":"%s"}`,
|
||||||
|
ad.attribute_name, scalar_type_to_string(ad.attribute_type))
|
||||||
|
}
|
||||||
|
strings.write_string(&ad_builder, "]")
|
||||||
|
|
||||||
|
meta_item["AttributeDefinitions"] = String(strings.clone(strings.to_string(ad_builder)))
|
||||||
|
|
||||||
|
// Add other metadata
|
||||||
|
meta_item["TableStatus"] = String(strings.clone(table_status_to_string(metadata.table_status)))
|
||||||
|
meta_item["CreationDateTime"] = Number(fmt.aprint(metadata.creation_date_time))
|
||||||
|
|
||||||
|
// Encode to binary
|
||||||
|
return item_codec.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)
|
||||||
|
if !ok {
|
||||||
|
return {}, false
|
||||||
|
}
|
||||||
|
defer item_destroy(&meta_item)
|
||||||
|
|
||||||
|
metadata: Table_Metadata
|
||||||
|
|
||||||
|
// TODO: Parse KeySchema and AttributeDefinitions from JSON strings
|
||||||
|
// For now, return empty - this will be implemented when needed
|
||||||
|
|
||||||
|
return metadata, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
defer delete(meta_key)
|
||||||
|
|
||||||
|
value, get_err := rocksdb.db_get(&engine.db, meta_key)
|
||||||
|
if get_err != .None {
|
||||||
|
if get_err == .NotFound {
|
||||||
|
return {}, .Table_Not_Found
|
||||||
|
}
|
||||||
|
return {}, .RocksDB_Error
|
||||||
|
}
|
||||||
|
defer delete(value)
|
||||||
|
|
||||||
|
metadata, ok := deserialize_table_metadata(value, engine.allocator)
|
||||||
|
if !ok {
|
||||||
|
return {}, .Serialization_Error
|
||||||
|
}
|
||||||
|
|
||||||
|
return metadata, .None
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Table Operations
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// Create table
|
||||||
|
create_table :: proc(
|
||||||
|
engine: ^Storage_Engine,
|
||||||
|
table_name: string,
|
||||||
|
key_schema: []Key_Schema_Element,
|
||||||
|
attribute_definitions: []Attribute_Definition,
|
||||||
|
) -> (Table_Description, Storage_Error) {
|
||||||
|
table_lock := get_or_create_table_lock(engine, table_name)
|
||||||
|
sync.rw_mutex_lock(table_lock)
|
||||||
|
defer sync.rw_mutex_unlock(table_lock)
|
||||||
|
|
||||||
|
// Check if table already exists
|
||||||
|
meta_key := key_codec.build_meta_key(table_name)
|
||||||
|
defer delete(meta_key)
|
||||||
|
|
||||||
|
existing, get_err := rocksdb.db_get(&engine.db, meta_key)
|
||||||
|
if get_err == .None && len(existing) > 0 {
|
||||||
|
delete(existing)
|
||||||
|
return {}, .Table_Already_Exists
|
||||||
|
}
|
||||||
|
if get_err == .None {
|
||||||
|
delete(existing)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create metadata
|
||||||
|
now := time.now()._nsec / 1_000_000_000
|
||||||
|
|
||||||
|
metadata := Table_Metadata{
|
||||||
|
table_name = strings.clone(table_name, engine.allocator),
|
||||||
|
key_schema = slice.clone(key_schema, engine.allocator),
|
||||||
|
attribute_definitions = slice.clone(attribute_definitions, engine.allocator),
|
||||||
|
table_status = .ACTIVE,
|
||||||
|
creation_date_time = now,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deep copy key schema and attr defs
|
||||||
|
for &ks in metadata.key_schema {
|
||||||
|
ks.attribute_name = strings.clone(ks.attribute_name, engine.allocator)
|
||||||
|
}
|
||||||
|
for &ad in metadata.attribute_definitions {
|
||||||
|
ad.attribute_name = strings.clone(ad.attribute_name, engine.allocator)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serialize and store
|
||||||
|
meta_value, serialize_ok := serialize_table_metadata(&metadata)
|
||||||
|
if !serialize_ok {
|
||||||
|
table_metadata_destroy(&metadata, engine.allocator)
|
||||||
|
return {}, .Serialization_Error
|
||||||
|
}
|
||||||
|
defer delete(meta_value)
|
||||||
|
|
||||||
|
put_err := rocksdb.db_put(&engine.db, meta_key, meta_value)
|
||||||
|
if put_err != .None {
|
||||||
|
return {}, .RocksDB_Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return description
|
||||||
|
desc := Table_Description{
|
||||||
|
table_name = table_name,
|
||||||
|
key_schema = key_schema,
|
||||||
|
attribute_definitions = attribute_definitions,
|
||||||
|
table_status = .ACTIVE,
|
||||||
|
creation_date_time = now,
|
||||||
|
item_count = 0,
|
||||||
|
table_size_bytes = 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
return desc, .None
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete table
|
||||||
|
delete_table :: proc(engine: ^Storage_Engine, table_name: string) -> Storage_Error {
|
||||||
|
table_lock := get_or_create_table_lock(engine, table_name)
|
||||||
|
sync.rw_mutex_lock(table_lock)
|
||||||
|
defer sync.rw_mutex_unlock(table_lock)
|
||||||
|
|
||||||
|
// Check table exists
|
||||||
|
meta_key := key_codec.build_meta_key(table_name)
|
||||||
|
defer delete(meta_key)
|
||||||
|
|
||||||
|
existing, get_err := rocksdb.db_get(&engine.db, meta_key)
|
||||||
|
if get_err == .NotFound {
|
||||||
|
return .Table_Not_Found
|
||||||
|
}
|
||||||
|
if get_err != .None {
|
||||||
|
return .RocksDB_Error
|
||||||
|
}
|
||||||
|
delete(existing)
|
||||||
|
|
||||||
|
// Delete metadata
|
||||||
|
del_err := rocksdb.db_delete(&engine.db, meta_key)
|
||||||
|
if del_err != .None {
|
||||||
|
return .RocksDB_Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Delete all items in table using iterator
|
||||||
|
// For now, just delete metadata
|
||||||
|
|
||||||
|
remove_table_lock(engine, table_name)
|
||||||
|
return .None
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Item Operations
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// Put item
|
||||||
|
put_item :: proc(engine: ^Storage_Engine, table_name: string, item: Item) -> Storage_Error {
|
||||||
|
table_lock := get_or_create_table_lock(engine, table_name)
|
||||||
|
sync.rw_mutex_shared_lock(table_lock)
|
||||||
|
defer sync.rw_mutex_shared_unlock(table_lock)
|
||||||
|
|
||||||
|
// Get table metadata
|
||||||
|
metadata, meta_err := get_table_metadata(engine, table_name)
|
||||||
|
if meta_err != .None {
|
||||||
|
return meta_err
|
||||||
|
}
|
||||||
|
defer table_metadata_destroy(&metadata, engine.allocator)
|
||||||
|
|
||||||
|
// Extract key from item
|
||||||
|
key, key_ok := key_from_item(item, metadata.key_schema)
|
||||||
|
if !key_ok {
|
||||||
|
return .Missing_Key_Attribute
|
||||||
|
}
|
||||||
|
defer {
|
||||||
|
pk := key.pk
|
||||||
|
attr_value_destroy(&pk)
|
||||||
|
if sk, ok := key.sk.?; ok {
|
||||||
|
sk_copy := sk
|
||||||
|
attr_value_destroy(&sk_copy)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get key values
|
||||||
|
key_values, kv_ok := key_get_values(&key)
|
||||||
|
if !kv_ok {
|
||||||
|
return .Invalid_Key
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build storage key
|
||||||
|
storage_key := key_codec.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)
|
||||||
|
if !encode_ok {
|
||||||
|
return .Serialization_Error
|
||||||
|
}
|
||||||
|
defer delete(encoded_item)
|
||||||
|
|
||||||
|
// Store in RocksDB
|
||||||
|
put_err := rocksdb.db_put(&engine.db, storage_key, encoded_item)
|
||||||
|
if put_err != .None {
|
||||||
|
return .RocksDB_Error
|
||||||
|
}
|
||||||
|
|
||||||
|
return .None
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get item
|
||||||
|
get_item :: proc(engine: ^Storage_Engine, table_name: string, key: Item) -> (Maybe(Item), Storage_Error) {
|
||||||
|
table_lock := get_or_create_table_lock(engine, table_name)
|
||||||
|
sync.rw_mutex_shared_lock(table_lock)
|
||||||
|
defer sync.rw_mutex_shared_unlock(table_lock)
|
||||||
|
|
||||||
|
// Get table metadata
|
||||||
|
metadata, meta_err := get_table_metadata(engine, table_name)
|
||||||
|
if meta_err != .None {
|
||||||
|
return nil, meta_err
|
||||||
|
}
|
||||||
|
defer table_metadata_destroy(&metadata, engine.allocator)
|
||||||
|
|
||||||
|
// Extract key
|
||||||
|
key_struct, key_ok := key_from_item(key, metadata.key_schema)
|
||||||
|
if !key_ok {
|
||||||
|
return nil, .Missing_Key_Attribute
|
||||||
|
}
|
||||||
|
defer {
|
||||||
|
pk := key_struct.pk
|
||||||
|
attr_value_destroy(&pk)
|
||||||
|
if sk, ok := key_struct.sk.?; ok {
|
||||||
|
sk_copy := sk
|
||||||
|
attr_value_destroy(&sk_copy)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get key values
|
||||||
|
key_values, kv_ok := key_get_values(&key_struct)
|
||||||
|
if !kv_ok {
|
||||||
|
return nil, .Invalid_Key
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build storage key
|
||||||
|
storage_key := key_codec.build_data_key(table_name, key_values.pk, key_values.sk)
|
||||||
|
defer delete(storage_key)
|
||||||
|
|
||||||
|
// Get from RocksDB
|
||||||
|
value, get_err := rocksdb.db_get(&engine.db, storage_key)
|
||||||
|
if get_err == .NotFound {
|
||||||
|
return nil, .None
|
||||||
|
}
|
||||||
|
if get_err != .None {
|
||||||
|
return nil, .RocksDB_Error
|
||||||
|
}
|
||||||
|
defer delete(value)
|
||||||
|
|
||||||
|
// Decode item
|
||||||
|
item, decode_ok := item_codec.decode(value)
|
||||||
|
if !decode_ok {
|
||||||
|
return nil, .Serialization_Error
|
||||||
|
}
|
||||||
|
|
||||||
|
return item, .None
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete item
|
||||||
|
delete_item :: proc(engine: ^Storage_Engine, table_name: string, key: Item) -> Storage_Error {
|
||||||
|
table_lock := get_or_create_table_lock(engine, table_name)
|
||||||
|
sync.rw_mutex_shared_lock(table_lock)
|
||||||
|
defer sync.rw_mutex_shared_unlock(table_lock)
|
||||||
|
|
||||||
|
// Get table metadata
|
||||||
|
metadata, meta_err := get_table_metadata(engine, table_name)
|
||||||
|
if meta_err != .None {
|
||||||
|
return meta_err
|
||||||
|
}
|
||||||
|
defer table_metadata_destroy(&metadata, engine.allocator)
|
||||||
|
|
||||||
|
// Extract key
|
||||||
|
key_struct, key_ok := key_from_item(key, metadata.key_schema)
|
||||||
|
if !key_ok {
|
||||||
|
return .Missing_Key_Attribute
|
||||||
|
}
|
||||||
|
defer {
|
||||||
|
pk := key_struct.pk
|
||||||
|
attr_value_destroy(&pk)
|
||||||
|
if sk, ok := key_struct.sk.?; ok {
|
||||||
|
sk_copy := sk
|
||||||
|
attr_value_destroy(&sk_copy)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get key values
|
||||||
|
key_values, kv_ok := key_get_values(&key_struct)
|
||||||
|
if !kv_ok {
|
||||||
|
return .Invalid_Key
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build storage key
|
||||||
|
storage_key := key_codec.build_data_key(table_name, key_values.pk, key_values.sk)
|
||||||
|
defer delete(storage_key)
|
||||||
|
|
||||||
|
// Delete from RocksDB
|
||||||
|
del_err := rocksdb.db_delete(&engine.db, storage_key)
|
||||||
|
if del_err != .None {
|
||||||
|
return .RocksDB_Error
|
||||||
|
}
|
||||||
|
|
||||||
|
return .None
|
||||||
|
}
|
||||||
|
|
||||||
|
// List tables (simplified - returns empty list for now)
|
||||||
|
list_tables :: proc(engine: ^Storage_Engine) -> []string {
|
||||||
|
// TODO: Implement by iterating over meta keys
|
||||||
|
return {}
|
||||||
|
}
|
||||||
528
item_codec/item_codec.odin
Normal file
528
item_codec/item_codec.odin
Normal file
@@ -0,0 +1,528 @@
|
|||||||
|
// 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 item_codec
|
||||||
|
|
||||||
|
import "core:bytes"
|
||||||
|
import "core:encoding/varint"
|
||||||
|
import "core:slice"
|
||||||
|
import "../dynamodb"
|
||||||
|
|
||||||
|
// 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: dynamodb.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: dynamodb.Attribute_Value) -> bool {
|
||||||
|
switch v in attr {
|
||||||
|
case dynamodb.String:
|
||||||
|
bytes.buffer_write_byte(buf, u8(Type_Tag.String))
|
||||||
|
encode_varint(buf, len(v))
|
||||||
|
bytes.buffer_write_string(buf, string(v))
|
||||||
|
|
||||||
|
case dynamodb.Number:
|
||||||
|
bytes.buffer_write_byte(buf, u8(Type_Tag.Number))
|
||||||
|
encode_varint(buf, len(v))
|
||||||
|
bytes.buffer_write_string(buf, string(v))
|
||||||
|
|
||||||
|
case dynamodb.Binary:
|
||||||
|
bytes.buffer_write_byte(buf, u8(Type_Tag.Binary))
|
||||||
|
encode_varint(buf, len(v))
|
||||||
|
bytes.buffer_write_string(buf, string(v))
|
||||||
|
|
||||||
|
case dynamodb.Bool:
|
||||||
|
bytes.buffer_write_byte(buf, u8(Type_Tag.Boolean))
|
||||||
|
bytes.buffer_write_byte(buf, 1 if bool(v) else 0)
|
||||||
|
|
||||||
|
case dynamodb.Null:
|
||||||
|
bytes.buffer_write_byte(buf, u8(Type_Tag.Null))
|
||||||
|
// NULL has no value bytes
|
||||||
|
|
||||||
|
case dynamodb.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 dynamodb.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 dynamodb.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 dynamodb.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 dynamodb.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_init :: proc(data: []byte) -> Binary_Decoder {
|
||||||
|
return Binary_Decoder{data = data, pos = 0}
|
||||||
|
}
|
||||||
|
|
||||||
|
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) -> (dynamodb.Item, bool) {
|
||||||
|
decoder := decoder_init(data)
|
||||||
|
|
||||||
|
attr_count, count_ok := decoder_read_varint(&decoder)
|
||||||
|
if !count_ok {
|
||||||
|
return {}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
item := make(dynamodb.Item)
|
||||||
|
|
||||||
|
for i in 0..<attr_count {
|
||||||
|
// Read attribute name
|
||||||
|
name_len, name_len_ok := decoder_read_varint(&decoder)
|
||||||
|
if !name_len_ok {
|
||||||
|
// Cleanup on error
|
||||||
|
dynamodb.item_destroy(&item)
|
||||||
|
return {}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
name_bytes, name_ok := decoder_read_bytes(&decoder, name_len)
|
||||||
|
if !name_ok {
|
||||||
|
dynamodb.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)
|
||||||
|
dynamodb.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) -> (dynamodb.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 dynamodb.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 dynamodb.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 dynamodb.Binary(owned), true
|
||||||
|
|
||||||
|
case .Boolean:
|
||||||
|
byte, byte_ok := decoder_read_byte(decoder)
|
||||||
|
if !byte_ok {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
return dynamodb.Bool(byte != 0), true
|
||||||
|
|
||||||
|
case .Null:
|
||||||
|
return dynamodb.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 dynamodb.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 dynamodb.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 dynamodb.Binary_Set(binaries), true
|
||||||
|
|
||||||
|
case .List:
|
||||||
|
count, count_ok := decoder_read_varint(decoder)
|
||||||
|
if !count_ok {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
list := make([]dynamodb.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]
|
||||||
|
dynamodb.attr_value_destroy(&item)
|
||||||
|
}
|
||||||
|
delete(list)
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
list[i] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
return dynamodb.List(list), true
|
||||||
|
|
||||||
|
case .Map:
|
||||||
|
count, count_ok := decoder_read_varint(decoder)
|
||||||
|
if !count_ok {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
attr_map := make(map[string]dynamodb.Attribute_Value)
|
||||||
|
|
||||||
|
for i 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
|
||||||
|
dynamodb.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
|
||||||
|
dynamodb.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
|
||||||
|
dynamodb.attr_value_destroy(&v_copy)
|
||||||
|
}
|
||||||
|
delete(attr_map)
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
attr_map[owned_key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
return dynamodb.Map(attr_map), true
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Varint Encoding
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user