1pub mod batch_execute_statement;
2pub mod batch_get_item;
3pub mod batch_write_item;
4pub mod create_table;
5pub mod delete_item;
6pub mod delete_table;
7pub mod describe_stream;
8pub mod describe_table;
9pub mod describe_time_to_live;
10pub mod execute_statement;
11pub mod execute_transaction;
12pub mod get_item;
13pub mod get_records;
14pub mod get_shard_iterator;
15pub(crate) mod gsi;
16pub(crate) mod helpers;
17pub mod import_items;
18pub mod index_capacity;
21pub mod list_streams;
22pub mod list_tables;
23pub mod list_tags_of_resource;
24pub(crate) mod lsi;
25pub mod put_item;
26pub mod query;
27pub mod scan;
28pub(crate) mod schema_migrations;
29pub mod search_vectors;
30pub mod tag_resource;
31pub mod transact_get_items;
32pub mod transact_write_items;
33pub mod untag_resource;
34pub mod update_item;
35pub mod update_table;
36pub mod update_time_to_live;
37pub(crate) mod vector_index;
38pub mod vector_lifecycle;
42
43use crate::types::{
44 AttributeDefinition, GlobalSecondaryIndex, KeySchemaElement, LocalSecondaryIndex, Projection,
45 SearchSchemaElement, VectorAttributeDefinition, VectorIndex,
46};
47use serde::{Deserialize, Serialize};
48
49#[derive(Debug, Clone, Default, Serialize, Deserialize)]
51pub struct TableDescription {
52 #[serde(rename = "TableName")]
53 pub table_name: String,
54
55 #[serde(rename = "TableId", skip_serializing_if = "Option::is_none")]
56 pub table_id: Option<String>,
57
58 #[serde(rename = "TableArn")]
59 pub table_arn: String,
60
61 #[serde(rename = "TableStatus")]
62 pub table_status: String,
63
64 #[serde(rename = "KeySchema")]
65 pub key_schema: Vec<KeySchemaElement>,
66
67 #[serde(rename = "AttributeDefinitions")]
68 pub attribute_definitions: Vec<AttributeDefinition>,
69
70 #[serde(rename = "CreationDateTime", skip_serializing_if = "Option::is_none")]
71 pub creation_date_time: Option<f64>,
72
73 #[serde(rename = "ItemCount", skip_serializing_if = "Option::is_none")]
74 pub item_count: Option<i64>,
75
76 #[serde(rename = "TableSizeBytes", skip_serializing_if = "Option::is_none")]
77 pub table_size_bytes: Option<i64>,
78
79 #[serde(
80 rename = "ProvisionedThroughput",
81 skip_serializing_if = "Option::is_none"
82 )]
83 pub provisioned_throughput: Option<TableProvisionedThroughputDescription>,
84
85 #[serde(rename = "BillingModeSummary", skip_serializing_if = "Option::is_none")]
86 pub billing_mode_summary: Option<BillingModeSummary>,
87
88 #[serde(
89 rename = "TableThroughputModeSummary",
90 skip_serializing_if = "Option::is_none"
91 )]
92 pub table_throughput_mode_summary: Option<TableThroughputModeSummary>,
93
94 #[serde(
95 rename = "GlobalSecondaryIndexes",
96 skip_serializing_if = "Option::is_none"
97 )]
98 pub global_secondary_indexes: Option<Vec<GlobalSecondaryIndexDescription>>,
99
100 #[serde(
101 rename = "LocalSecondaryIndexes",
102 skip_serializing_if = "Option::is_none"
103 )]
104 pub local_secondary_indexes: Option<Vec<LocalSecondaryIndexDescription>>,
105
106 #[serde(rename = "VectorIndexes", skip_serializing_if = "Option::is_none")]
107 pub vector_indexes: Option<Vec<VectorIndexDescription>>,
108
109 #[serde(
110 rename = "StreamSpecification",
111 skip_serializing_if = "Option::is_none"
112 )]
113 pub stream_specification: Option<StreamSpecificationDescription>,
114
115 #[serde(rename = "LatestStreamArn", skip_serializing_if = "Option::is_none")]
116 pub latest_stream_arn: Option<String>,
117
118 #[serde(rename = "LatestStreamLabel", skip_serializing_if = "Option::is_none")]
119 pub latest_stream_label: Option<String>,
120
121 #[serde(rename = "SSEDescription", skip_serializing_if = "Option::is_none")]
122 pub sse_description: Option<SseDescription>,
123
124 #[serde(rename = "TableClassSummary", skip_serializing_if = "Option::is_none")]
125 pub table_class_summary: Option<TableClassSummary>,
126
127 #[serde(
128 rename = "DeletionProtectionEnabled",
129 skip_serializing_if = "Option::is_none"
130 )]
131 pub deletion_protection_enabled: Option<bool>,
132
133 #[serde(rename = "OnDemandThroughput", skip_serializing_if = "Option::is_none")]
134 pub on_demand_throughput: Option<crate::types::OnDemandThroughput>,
135}
136
137#[derive(Debug, Clone, Default, Serialize, Deserialize)]
139pub struct SseDescription {
140 #[serde(rename = "Status")]
141 pub status: String,
142 #[serde(rename = "SSEType", skip_serializing_if = "Option::is_none")]
143 pub sse_type: Option<String>,
144 #[serde(rename = "KMSMasterKeyArn", skip_serializing_if = "Option::is_none")]
145 pub kms_master_key_arn: Option<String>,
146}
147
148#[derive(Debug, Clone, Default, Serialize, Deserialize)]
150pub struct StreamSpecificationDescription {
151 #[serde(rename = "StreamEnabled")]
152 pub stream_enabled: bool,
153 #[serde(rename = "StreamViewType", skip_serializing_if = "Option::is_none")]
154 pub stream_view_type: Option<String>,
155}
156
157#[derive(Debug, Clone, Default, Serialize, Deserialize)]
159pub struct TableClassSummary {
160 #[serde(rename = "TableClass")]
161 pub table_class: String,
162}
163
164#[derive(Debug, Clone, Default, Serialize, Deserialize)]
166pub struct TableProvisionedThroughputDescription {
167 #[serde(rename = "ReadCapacityUnits")]
168 pub read_capacity_units: u64,
169 #[serde(rename = "WriteCapacityUnits")]
170 pub write_capacity_units: u64,
171 #[serde(rename = "NumberOfDecreasesToday")]
172 pub number_of_decreases_today: u64,
173 #[serde(
174 rename = "LastIncreaseDateTime",
175 skip_serializing_if = "Option::is_none"
176 )]
177 pub last_increase_date_time: Option<f64>,
178 #[serde(
179 rename = "LastDecreaseDateTime",
180 skip_serializing_if = "Option::is_none"
181 )]
182 pub last_decrease_date_time: Option<f64>,
183}
184
185#[derive(Debug, Clone, Default, Serialize, Deserialize)]
187pub struct BillingModeSummary {
188 #[serde(rename = "BillingMode")]
189 pub billing_mode: String,
190 #[serde(
191 rename = "LastUpdateToPayPerRequestDateTime",
192 skip_serializing_if = "Option::is_none"
193 )]
194 pub last_update_to_pay_per_request_date_time: Option<f64>,
195}
196
197#[derive(Debug, Clone, Default, Serialize, Deserialize)]
199pub struct TableThroughputModeSummary {
200 #[serde(rename = "TableThroughputMode")]
201 pub table_throughput_mode: String,
202 #[serde(
203 rename = "LastUpdateToPayPerRequestDateTime",
204 skip_serializing_if = "Option::is_none"
205 )]
206 pub last_update_to_pay_per_request_date_time: Option<f64>,
207}
208
209#[derive(Debug, Clone, Default, Serialize, Deserialize)]
211pub struct GlobalSecondaryIndexDescription {
212 #[serde(rename = "IndexName")]
213 pub index_name: String,
214 #[serde(rename = "IndexArn")]
215 pub index_arn: String,
216 #[serde(rename = "KeySchema")]
217 pub key_schema: Vec<KeySchemaElement>,
218 #[serde(rename = "Projection")]
219 pub projection: Projection,
220 #[serde(rename = "IndexStatus")]
221 pub index_status: String,
222 #[serde(
223 rename = "ProvisionedThroughput",
224 skip_serializing_if = "Option::is_none"
225 )]
226 pub provisioned_throughput: Option<TableProvisionedThroughputDescription>,
227 #[serde(rename = "ItemCount", skip_serializing_if = "Option::is_none")]
228 pub item_count: Option<i64>,
229 #[serde(rename = "IndexSizeBytes", skip_serializing_if = "Option::is_none")]
230 pub index_size_bytes: Option<i64>,
231}
232
233#[derive(Debug, Clone, Default, Serialize, Deserialize)]
235pub struct LocalSecondaryIndexDescription {
236 #[serde(rename = "IndexName")]
237 pub index_name: String,
238 #[serde(rename = "IndexArn")]
239 pub index_arn: String,
240 #[serde(rename = "KeySchema")]
241 pub key_schema: Vec<KeySchemaElement>,
242 #[serde(rename = "Projection")]
243 pub projection: Projection,
244 #[serde(rename = "ItemCount", skip_serializing_if = "Option::is_none")]
245 pub item_count: Option<i64>,
246 #[serde(rename = "IndexSizeBytes", skip_serializing_if = "Option::is_none")]
247 pub index_size_bytes: Option<i64>,
248}
249
250#[derive(Debug, Clone, Default, Serialize, Deserialize)]
258pub struct VectorIndexDescription {
259 #[serde(rename = "IndexName")]
260 pub index_name: String,
261 #[serde(rename = "SearchSchema", skip_serializing_if = "Option::is_none")]
262 pub search_schema: Option<Vec<SearchSchemaElement>>,
263 #[serde(rename = "Projection")]
264 pub projection: Projection,
265 #[serde(rename = "VectorAttribute")]
266 pub vector_attribute: VectorAttributeDefinition,
267 #[serde(rename = "Dimensions")]
268 pub dimensions: u32,
269 #[serde(rename = "DistanceFunction")]
270 pub distance_function: String,
271 #[serde(rename = "IndexStatus")]
272 pub index_status: String,
273 #[serde(rename = "Backfilling", skip_serializing_if = "Option::is_none")]
274 pub backfilling: Option<bool>,
275 #[serde(rename = "IndexSizeBytes", skip_serializing_if = "Option::is_none")]
276 pub index_size_bytes: Option<i64>,
277 #[serde(rename = "ItemCount", skip_serializing_if = "Option::is_none")]
278 pub item_count: Option<i64>,
279 #[serde(rename = "IndexArn")]
280 pub index_arn: String,
281}
282
283fn table_id_for(table_name: &str, created_at: i64) -> String {
294 uuid::Uuid::new_v5(
295 &uuid::Uuid::NAMESPACE_OID,
296 format!("dynoxide:table:{table_name}:{created_at}").as_bytes(),
297 )
298 .to_string()
299}
300
301pub(crate) fn build_table_description(
309 meta: &crate::storage::TableMetadata,
310 item_count: Option<i64>,
311 table_size_bytes: Option<i64>,
312 vector_phases: &vector_lifecycle::VectorIndexPhases,
313) -> TableDescription {
314 use crate::streams;
315
316 let key_schema: Vec<KeySchemaElement> =
317 serde_json::from_str(&meta.key_schema).unwrap_or_default();
318 let attribute_definitions: Vec<AttributeDefinition> =
319 serde_json::from_str(&meta.attribute_definitions).unwrap_or_default();
320
321 let gsi_definitions: Option<Vec<GlobalSecondaryIndex>> = meta
322 .gsi_definitions
323 .as_ref()
324 .and_then(|s| serde_json::from_str(s).ok());
325
326 let table_name = &meta.table_name;
327
328 let global_secondary_indexes = gsi_definitions.map(|gsis| {
329 gsis.into_iter()
330 .map(|gsi| {
331 let idx_arn = streams::index_arn(table_name, &gsi.index_name);
332 GlobalSecondaryIndexDescription {
333 index_name: gsi.index_name,
334 index_arn: idx_arn,
335 key_schema: gsi.key_schema,
336 projection: gsi.projection,
337 index_status: "ACTIVE".to_string(),
338 provisioned_throughput: Some(if let Some(pt) = gsi.provisioned_throughput {
339 TableProvisionedThroughputDescription {
340 read_capacity_units: pt.read_capacity_units.unwrap_or(0) as u64,
341 write_capacity_units: pt.write_capacity_units.unwrap_or(0) as u64,
342 number_of_decreases_today: 0,
343 last_increase_date_time: None,
344 last_decrease_date_time: None,
345 }
346 } else {
347 TableProvisionedThroughputDescription {
349 read_capacity_units: 0,
350 write_capacity_units: 0,
351 number_of_decreases_today: 0,
352 last_increase_date_time: None,
353 last_decrease_date_time: None,
354 }
355 }),
356 item_count: Some(0),
357 index_size_bytes: Some(0),
358 }
359 })
360 .collect()
361 });
362
363 let lsi_definitions: Option<Vec<LocalSecondaryIndex>> = meta
364 .lsi_definitions
365 .as_ref()
366 .and_then(|s| serde_json::from_str(s).ok());
367
368 let local_secondary_indexes = lsi_definitions.map(|lsis| {
369 lsis.into_iter()
370 .map(|lsi| {
371 let idx_arn = streams::index_arn(table_name, &lsi.index_name);
372 LocalSecondaryIndexDescription {
373 index_name: lsi.index_name,
374 index_arn: idx_arn,
375 key_schema: lsi.key_schema,
376 projection: lsi.projection,
377 item_count: Some(0),
378 index_size_bytes: Some(0),
379 }
380 })
381 .collect()
382 });
383
384 let vector_index_definitions: Option<Vec<VectorIndex>> = meta
385 .vector_index_definitions
386 .as_ref()
387 .and_then(|s| serde_json::from_str(s).ok());
388
389 let vector_indexes = vector_index_definitions.map(|vixs| {
394 vixs.into_iter()
395 .map(|vix| {
396 let idx_arn = streams::index_arn(table_name, &vix.index_name);
397 let phase = vector_phases.get(&vix.index_name);
398 VectorIndexDescription {
399 index_name: vix.index_name,
400 search_schema: vix.search_schema,
401 projection: vix.projection,
402 vector_attribute: vix.vector_attribute,
403 dimensions: vix.dimensions,
404 distance_function: vix.distance_function,
405 index_status: phase.index_status().to_string(),
406 backfilling: phase.backfilling(),
407 index_size_bytes: Some(0),
408 item_count: Some(0),
409 index_arn: idx_arn,
410 }
411 })
412 .collect()
413 });
414
415 let table_status = if vector_phases.any_holding_table_updating() {
420 "UPDATING".to_string()
421 } else {
422 meta.table_status.clone()
423 };
424
425 let billing_mode = meta.billing_mode.clone();
426
427 let provisioned_throughput = if let Some(pt_json) = &meta.provisioned_throughput {
428 serde_json::from_str::<serde_json::Value>(pt_json)
430 .ok()
431 .map(|v| {
432 let rcu = v
433 .get("ReadCapacityUnits")
434 .and_then(|v| v.as_i64())
435 .unwrap_or(0) as u64;
436 let wcu = v
437 .get("WriteCapacityUnits")
438 .and_then(|v| v.as_i64())
439 .unwrap_or(0) as u64;
440 let last_inc = v.get("LastIncreaseDateTime").and_then(|v| v.as_f64());
441 let last_dec = v.get("LastDecreaseDateTime").and_then(|v| v.as_f64());
442 let num_dec = v
443 .get("NumberOfDecreasesToday")
444 .and_then(|v| v.as_u64())
445 .unwrap_or(0);
446 TableProvisionedThroughputDescription {
447 read_capacity_units: rcu,
448 write_capacity_units: wcu,
449 number_of_decreases_today: num_dec,
450 last_increase_date_time: last_inc,
451 last_decrease_date_time: last_dec,
452 }
453 })
454 } else if billing_mode.as_deref() == Some("PAY_PER_REQUEST") {
455 Some(TableProvisionedThroughputDescription {
457 read_capacity_units: 0,
458 write_capacity_units: 0,
459 number_of_decreases_today: 0,
460 last_increase_date_time: None,
461 last_decrease_date_time: None,
462 })
463 } else {
464 None
465 };
466
467 let stream_specification = if meta.stream_enabled {
468 Some(StreamSpecificationDescription {
469 stream_enabled: true,
470 stream_view_type: meta.stream_view_type.clone(),
471 })
472 } else {
473 None
474 };
475
476 let latest_stream_arn = if meta.stream_enabled {
477 meta.stream_label
478 .as_ref()
479 .map(|label| streams::stream_arn(table_name, label))
480 } else {
481 None
482 };
483
484 let latest_stream_label = if meta.stream_enabled {
485 meta.stream_label.clone()
486 } else {
487 None
488 };
489
490 let sse_description = meta.sse_specification.as_ref().and_then(|json| {
494 serde_json::from_str::<crate::types::SseSpecification>(json)
495 .ok()
496 .map(|spec| {
497 let enabled = spec.enabled.unwrap_or(false);
498 SseDescription {
499 status: if enabled { "ENABLED" } else { "DISABLED" }.to_string(),
500 sse_type: if enabled {
501 spec.sse_type.or_else(|| Some("KMS".to_string()))
502 } else {
503 spec.sse_type
504 },
505 kms_master_key_arn: if enabled {
506 Some(
511 spec.kms_master_key_id
512 .map(|id| streams::kms_key_arn(&id))
513 .unwrap_or_else(|| streams::kms_key_arn(table_name)),
514 )
515 } else {
516 None
517 },
518 }
519 })
520 });
521
522 let table_class_summary = meta.table_class.as_ref().map(|tc| TableClassSummary {
523 table_class: tc.clone(),
524 });
525
526 let on_demand_throughput = meta
527 .on_demand_throughput
528 .as_ref()
529 .and_then(|json| serde_json::from_str::<crate::types::OnDemandThroughput>(json).ok());
530
531 let deletion_protection_enabled = Some(meta.deletion_protection_enabled);
532
533 TableDescription {
534 table_name: meta.table_name.clone(),
535 table_id: Some(
536 meta.table_id
537 .clone()
538 .unwrap_or_else(|| table_id_for(&meta.table_name, meta.created_at)),
539 ),
540 table_arn: streams::table_arn(table_name),
541 table_status,
542 key_schema,
543 attribute_definitions,
544 creation_date_time: Some(meta.created_at as f64),
545 item_count,
546 table_size_bytes,
547 provisioned_throughput,
548 billing_mode_summary: match billing_mode.as_deref() {
549 Some("PAY_PER_REQUEST") => Some(BillingModeSummary {
550 billing_mode: "PAY_PER_REQUEST".to_string(),
551 last_update_to_pay_per_request_date_time: None,
552 }),
553 _ => None,
554 },
555 table_throughput_mode_summary: match billing_mode.as_deref() {
556 Some("PAY_PER_REQUEST") => Some(TableThroughputModeSummary {
557 table_throughput_mode: "PAY_PER_REQUEST".to_string(),
558 last_update_to_pay_per_request_date_time: None,
559 }),
560 _ => None,
561 },
562 global_secondary_indexes,
563 local_secondary_indexes,
564 vector_indexes,
565 stream_specification,
566 latest_stream_arn,
567 latest_stream_label,
568 sse_description,
569 table_class_summary,
570 deletion_protection_enabled,
571 on_demand_throughput,
572 }
573}