global secondary indexes

This commit is contained in:
2026-02-16 02:15:15 -05:00
parent 31e80ac572
commit 972e6ece5e
8 changed files with 1369 additions and 22 deletions

175
main.odin
View File

@@ -173,8 +173,25 @@ handle_create_table :: proc(engine: ^dynamodb.Storage_Engine, request: ^HTTP_Req
return
}
// Parse GlobalSecondaryIndexes (optional)
gsis := parse_global_secondary_indexes(root, attr_defs)
defer {
if gsi_list, has := gsis.?; has {
for &g in gsi_list {
delete(g.index_name)
for &ks in g.key_schema { delete(ks.attribute_name) }
delete(g.key_schema)
if nka, has_nka := g.projection.non_key_attributes.?; has_nka {
for a in nka { delete(a) }
delete(nka)
}
}
delete(gsi_list)
}
}
// Create the table
desc, create_err := dynamodb.create_table(engine, string(table_name), key_schema, attr_defs)
desc, create_err := dynamodb.create_table(engine, string(table_name), key_schema, attr_defs, gsis)
if create_err != .None {
#partial switch create_err {
case .Table_Already_Exists:
@@ -261,7 +278,30 @@ handle_describe_table :: proc(engine: ^dynamodb.Storage_Engine, request: ^HTTP_R
ad.attribute_name, dynamodb.scalar_type_to_string(ad.attribute_type))
}
strings.write_string(&builder, `]}}`)
strings.write_string(&builder, `]`)
// Include GSI Info — INSIDE the Table object, before the closing braces
if gsis, has_gsis := metadata.global_secondary_indexes.?; has_gsis && len(gsis) > 0 {
strings.write_string(&builder, `,"GlobalSecondaryIndexes":[`)
for gsi, gi in gsis {
if gi > 0 do strings.write_string(&builder, ",")
strings.write_string(&builder, `{"IndexName":"`)
strings.write_string(&builder, gsi.index_name)
strings.write_string(&builder, `","KeySchema":[`)
for ks, ki in gsi.key_schema {
if ki > 0 do strings.write_string(&builder, ",")
fmt.sbprintf(&builder, `{"AttributeName":"%s","KeyType":"%s"}`,
ks.attribute_name, dynamodb.key_type_to_string(ks.key_type))
}
strings.write_string(&builder, `],"Projection":{"ProjectionType":"`)
strings.write_string(&builder, projection_type_to_string(gsi.projection.projection_type))
strings.write_string(&builder, `"},"IndexStatus":"ACTIVE"}`)
}
strings.write_string(&builder, "]")
}
// Close Table object and root object
strings.write_string(&builder, `}}`)
resp_body := strings.to_string(builder)
response_set_body(response, transmute([]byte)resp_body)
@@ -1054,6 +1094,9 @@ handle_query :: proc(engine: ^dynamodb.Storage_Engine, request: ^HTTP_Request, r
return
}
// Grab index name from request body
index_name := parse_index_name(request.body)
// Fetch table metadata early for ExclusiveStartKey parsing
metadata, meta_err := dynamodb.get_table_metadata(engine, table_name)
if meta_err != .None {
@@ -1081,6 +1124,8 @@ handle_query :: proc(engine: ^dynamodb.Storage_Engine, request: ^HTTP_Request, r
copy(pk_owned, pk_bytes)
defer delete(pk_owned)
// ---- Parse shared parameters BEFORE the GSI/table branch ----
// Parse Limit
limit := dynamodb.parse_limit(request.body)
if limit == 0 {
@@ -1107,13 +1152,6 @@ handle_query :: proc(engine: ^dynamodb.Storage_Engine, request: ^HTTP_Request, r
sk_condition = skc
}
result, err := dynamodb.query(engine, table_name, pk_owned, exclusive_start_key, limit, sk_condition)
if err != .None {
handle_storage_error(response, err)
return
}
defer dynamodb.query_result_destroy(&result)
// ---- Parse ExpressionAttributeNames/Values for filter/projection ----
attr_names := dynamodb.parse_expression_attribute_names(request.body)
defer {
@@ -1137,6 +1175,62 @@ handle_query :: proc(engine: ^dynamodb.Storage_Engine, request: ^HTTP_Request, r
delete(attr_values)
}
// ---- GSI query path ----
if idx_name, has_idx := index_name.?; has_idx {
_, gsi_found := dynamodb.find_gsi(&metadata, idx_name)
if !gsi_found {
make_error_response(response, .ValidationException,
fmt.tprintf("The table does not have the specified index: %s", idx_name))
return
}
result, err := dynamodb.gsi_query(engine, table_name, idx_name,
pk_owned, exclusive_start_key, limit, sk_condition)
if err != .None {
handle_storage_error(response, err)
return
}
defer dynamodb.query_result_destroy(&result)
// Apply FilterExpression
filtered_items := apply_filter_to_items(request.body, result.items, attr_names, attr_values)
scanned_count := len(result.items)
// Apply ProjectionExpression
projection, has_proj := dynamodb.parse_projection_expression(request.body, attr_names)
final_items: []dynamodb.Item
if has_proj && len(projection) > 0 {
projected := make([]dynamodb.Item, len(filtered_items))
for item, i in filtered_items {
projected[i] = dynamodb.apply_projection(item, projection)
}
final_items = projected
} else {
final_items = filtered_items
}
write_items_response_with_pagination_ex(
response, final_items, result.last_evaluated_key, &metadata, scanned_count,
)
if has_proj && len(projection) > 0 {
for &item in final_items {
dynamodb.item_destroy(&item)
}
delete(final_items)
}
return
}
// ---- Main table query path ----
result, err := dynamodb.query(engine, table_name, pk_owned, exclusive_start_key, limit, sk_condition)
if err != .None {
handle_storage_error(response, err)
return
}
defer dynamodb.query_result_destroy(&result)
// ---- Apply FilterExpression (post-query filter) ----
filtered_items := apply_filter_to_items(request.body, result.items, attr_names, attr_values)
scanned_count := len(result.items)
@@ -1177,6 +1271,9 @@ handle_scan :: proc(engine: ^dynamodb.Storage_Engine, request: ^HTTP_Request, re
return
}
// Grab index name from request body
index_name := parse_index_name(request.body)
metadata, meta_err := dynamodb.get_table_metadata(engine, table_name)
if meta_err != .None {
handle_storage_error(response, meta_err)
@@ -1202,13 +1299,6 @@ handle_scan :: proc(engine: ^dynamodb.Storage_Engine, request: ^HTTP_Request, re
}
}
result, err := dynamodb.scan(engine, table_name, exclusive_start_key, limit)
if err != .None {
handle_storage_error(response, err)
return
}
defer dynamodb.scan_result_destroy(&result)
// ---- Parse ExpressionAttributeNames/Values for filter/projection ----
attr_names := dynamodb.parse_expression_attribute_names(request.body)
defer {
@@ -1232,6 +1322,59 @@ handle_scan :: proc(engine: ^dynamodb.Storage_Engine, request: ^HTTP_Request, re
delete(attr_values)
}
// ---- GSI scan path ----
if idx_name, has_idx := index_name.?; has_idx {
_, gsi_found := dynamodb.find_gsi(&metadata, idx_name)
if !gsi_found {
make_error_response(response, .ValidationException,
fmt.tprintf("The table does not have the specified index: %s", idx_name))
return
}
result, err := dynamodb.gsi_scan(engine, table_name, idx_name, exclusive_start_key, limit)
if err != .None {
handle_storage_error(response, err)
return
}
defer dynamodb.scan_result_destroy(&result)
filtered_items := apply_filter_to_items(request.body, result.items, attr_names, attr_values)
scanned_count := len(result.items)
projection, has_proj := dynamodb.parse_projection_expression(request.body, attr_names)
final_items: []dynamodb.Item
if has_proj && len(projection) > 0 {
projected := make([]dynamodb.Item, len(filtered_items))
for item, i in filtered_items {
projected[i] = dynamodb.apply_projection(item, projection)
}
final_items = projected
} else {
final_items = filtered_items
}
write_items_response_with_pagination_ex(
response, final_items, result.last_evaluated_key, &metadata, scanned_count,
)
if has_proj && len(projection) > 0 {
for &item in final_items {
dynamodb.item_destroy(&item)
}
delete(final_items)
}
return
}
// ---- Main table scan path ----
result, err := dynamodb.scan(engine, table_name, exclusive_start_key, limit)
if err != .None {
handle_storage_error(response, err)
return
}
defer dynamodb.scan_result_destroy(&result)
// ---- Apply FilterExpression ----
filtered_items := apply_filter_to_items(request.body, result.items, attr_names, attr_values)
scanned_count := len(result.items)