Core implementation of `create_table` (Fix #4: wrapped in a transaction).
(
&self,
account_id: &str,
input: CreateTableInput,
)
| 15 | impl PostgresEngine { |
| 16 | /// Core implementation of `create_table` (Fix #4: wrapped in a transaction). |
| 17 | pub(crate) async fn create_table_impl( |
| 18 | &self, |
| 19 | account_id: &str, |
| 20 | input: CreateTableInput, |
| 21 | ) -> Result<TableDescription, StorageError> { |
| 22 | Self::validate_account_id(account_id)?; |
| 23 | let table_id = uuid::Uuid::new_v4().to_string(); |
| 24 | let table_arn = table_arn(&self.region, account_id, &input.table_name); |
| 25 | let billing_mode = input.billing_mode.unwrap_or(BillingMode::Provisioned); |
| 26 | let key_schema_json = serde_json::to_value(&input.key_schema) |
| 27 | .map_err(|e| StorageError::Internal(e.to_string()))?; |
| 28 | let attr_defs_json = serde_json::to_value(&input.attribute_definitions) |
| 29 | .map_err(|e| StorageError::Internal(e.to_string()))?; |
| 30 | let billing_str = match billing_mode { |
| 31 | BillingMode::Provisioned => "PROVISIONED", |
| 32 | BillingMode::PayPerRequest => "PAY_PER_REQUEST", |
| 33 | }; |
| 34 | // Fix #7: Use serde_json::to_value directly instead of redundant closures |
| 35 | let pt_json = input |
| 36 | .provisioned_throughput |
| 37 | .as_ref() |
| 38 | .map(serde_json::to_value) |
| 39 | .transpose() |
| 40 | .map_err(|e| StorageError::Internal(e.to_string()))?; |
| 41 | let stream_json = input |
| 42 | .stream_specification |
| 43 | .as_ref() |
| 44 | .map(serde_json::to_value) |
| 45 | .transpose() |
| 46 | .map_err(|e| StorageError::Internal(e.to_string()))?; |
| 47 | let deletion_protection = input.deletion_protection_enabled.unwrap_or(false); |
| 48 | |
| 49 | let mut tx = self |
| 50 | .pool |
| 51 | .begin() |
| 52 | .await |
| 53 | .map_err(|e| StorageError::Internal(e.to_string()))?; |
| 54 | |
| 55 | // Insert table metadata, returning creation timestamp and actual status. |
| 56 | // Use PG error code 23505 for robust duplicate detection instead of string matching. |
| 57 | // H-5: Insert as CREATING with a scheduled transition to ACTIVE, |
| 58 | // or directly as ACTIVE when control_plane_delay_seconds=0 (no async |
| 59 | // transition needed). This lets external test suites that don't call |
| 60 | // waitForActive() work correctly. |
| 61 | let (creation_epoch, actual_status): (f64, String) = sqlx::query_as( |
| 62 | r"WITH delay AS ( |
| 63 | SELECT COALESCE( |
| 64 | (SELECT value::FLOAT8 FROM settings WHERE key = 'control_plane_delay_seconds'), 0.25 |
| 65 | ) AS secs |
| 66 | ) |
| 67 | INSERT INTO tables |
| 68 | (account_id, table_name, key_schema, attribute_definitions, billing_mode, |
| 69 | provisioned_throughput, stream_specification, table_status, |
| 70 | creation_date_time, table_arn, table_id, deletion_protection_enabled, |
| 71 | status_transition_at) |
| 72 | VALUES ($1, $2, $3, $4, $5, $6, $7, |
| 73 | CASE WHEN (SELECT secs FROM delay) = 0 |
| 74 | THEN 'ACTIVE' ELSE 'CREATING' END, |
no test coverage detected