Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 38 additions & 2 deletions crates/core/src/error/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,10 @@ pub enum DynamoDbError {
#[error("{0}")]
TableInUseException(String),
/// HTTP 400. `CreateBackup` on a table that does not exist or is not yet
/// `ACTIVE`. Measured on DynamoDB: `Table not found: <name>`. Distinct
/// from `ResourceNotFoundException`, which the data plane returns.
/// `ACTIVE`, and `DescribeContinuousBackups`, `UpdateContinuousBackups`
/// and `RestoreTableToPointInTime` on a table that does not exist.
/// Measured on DynamoDB: `Table not found: <name>`. Distinct from
/// `ResourceNotFoundException`, which the data plane returns.
#[error("{0}")]
TableNotFoundException(String),
/// A per-table or per-account limit was exceeded.
Expand Down Expand Up @@ -89,6 +91,21 @@ pub enum DynamoDbError {
/// Returned when a shard iterator has expired (older than 15 minutes).
#[error("{0}")]
ExpiredIteratorException(String),
/// SP-ERR-002: HTTP 400. Returned when continuous backups (point-in-time
/// recovery) cannot be enabled. The service models this exception on
/// `UpdateContinuousBackups`; ExtendDB returns it there for
/// `PointInTimeRecoveryEnabled: true`, because no storage backend
/// implements point-in-time recovery.
#[error("{0}")]
ContinuousBackupsUnavailableException(String),
/// SP-ERR-002: HTTP 400. Returned by `RestoreTableToPointInTime` when
/// point-in-time recovery is not enabled for the source table, which in
/// ExtendDB is always: no storage backend implements point-in-time
/// recovery. The service models this exception on the restore operation,
/// distinct from `ContinuousBackupsUnavailableException`, which it models
/// on `UpdateContinuousBackups`.
#[error("{0}")]
PointInTimeRecoveryUnavailableException(String),
#[error("{0}")]
ServiceUnavailable(String),
/// SP-ERR-002: Concurrent modification of the same item in a transaction.
Expand Down Expand Up @@ -146,6 +163,8 @@ impl DynamoDbError {
| Self::AccessDeniedException(_)
| Self::ExpiredTokenException(_)
| Self::ExpiredIteratorException(_)
| Self::ContinuousBackupsUnavailableException(_)
| Self::PointInTimeRecoveryUnavailableException(_)
| Self::TransactionConflictException(_)
| Self::ProvisionedThroughputExceededException(_)
| Self::ThrottlingException(_)
Expand Down Expand Up @@ -196,6 +215,12 @@ impl DynamoDbError {
Self::UnrecognizedClientException(_) => "UnrecognizedClientException",
Self::ExpiredTokenException(_) => "ExpiredTokenException",
Self::ExpiredIteratorException(_) => "ExpiredIteratorException",
Self::ContinuousBackupsUnavailableException(_) => {
"ContinuousBackupsUnavailableException"
}
Self::PointInTimeRecoveryUnavailableException(_) => {
"PointInTimeRecoveryUnavailableException"
}
Self::ServiceUnavailable(_) => "ServiceUnavailable",
Self::TransactionConflictException(_) => "TransactionConflictException",
Self::ProvisionedThroughputExceededException(_) => {
Expand Down Expand Up @@ -265,6 +290,8 @@ impl DynamoDbError {
| Self::UnrecognizedClientException(m)
| Self::ExpiredTokenException(m)
| Self::ExpiredIteratorException(m)
| Self::ContinuousBackupsUnavailableException(m)
| Self::PointInTimeRecoveryUnavailableException(m)
| Self::ServiceUnavailable(m)
| Self::TransactionConflictException(m)
| Self::ProvisionedThroughputExceededException(m)
Expand Down Expand Up @@ -379,6 +406,15 @@ mod tests {
(DynamoDbError::ValidationException(String::new()), 400),
(DynamoDbError::ExpiredTokenException(String::new()), 400),
(DynamoDbError::ExpiredIteratorException(String::new()), 400),
(
DynamoDbError::ContinuousBackupsUnavailableException(String::new()),
400,
),
(
DynamoDbError::PointInTimeRecoveryUnavailableException(String::new()),
400,
),
(DynamoDbError::TableNotFoundException(String::new()), 400),
(DynamoDbError::InvalidSignatureException(String::new()), 400),
];
for (err, expected) in cases {
Expand Down
64 changes: 6 additions & 58 deletions crates/engine/src/backup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1191,64 +1191,6 @@ fn restore_storage_err_to_dynamo(e: StorageError) -> DynamoDbError {
}
}

/// Handle `DescribeContinuousBackups`.
pub(crate) async fn handle_describe_continuous_backups(
body: Value,
ctx: &OperationContext,
) -> Result<Value, DynamoDbError> {
let table_name = required_str(&body, "TableName", "tableName")?;

let desc = ctx
.storage
.describe_continuous_backups(&ctx.account_id, table_name)
.await
.map_err(storage_err_to_dynamo)?;

serialize_output(&json!({ "ContinuousBackupsDescription": desc }))
}

/// Handle `UpdateContinuousBackups`.
pub(crate) async fn handle_update_continuous_backups(
body: Value,
ctx: &OperationContext,
) -> Result<Value, DynamoDbError> {
let table_name = required_str(&body, "TableName", "tableName")?;

let pitr_enabled = body
.get("PointInTimeRecoverySpecification")
.and_then(|v| v.get("PointInTimeRecoveryEnabled"))
.and_then(serde_json::Value::as_bool)
.unwrap_or(false);

let desc = ctx
.storage
.update_continuous_backups(&ctx.account_id, table_name, pitr_enabled)
.await
.map_err(storage_err_to_dynamo)?;

serialize_output(&json!({ "ContinuousBackupsDescription": desc }))
}

/// Handle `RestoreTableToPointInTime`.
///
/// Point-in-time recovery is not yet implemented. The previous implementation
/// faked a restore by snapshotting the current table state (ignoring
/// `RestoreDateTime`), which violates tenet 1 (fidelity over features).
/// Until real PITR is implemented, return an error. There is no storage
/// method behind this; it was removed from `BackupEngine` as unreachable.
pub(crate) async fn handle_restore_table_to_point_in_time(
_body: Value,
_ctx: &OperationContext,
) -> Result<Value, DynamoDbError> {
// TODO(fidelity): Implement real PITR using PostgreSQL temporal/history
// table approach — item_history table capturing every mutation, DISTINCT ON
// query to reconstruct state at time T, 35-day retention via background
// pruning.
Err(DynamoDbError::ValidationException(
"Point-in-time recovery restore is not yet supported".to_owned(),
))
}

/// Convert storage errors to `DynamoDB` errors.
fn storage_err_to_dynamo(e: StorageError) -> DynamoDbError {
match e {
Expand Down Expand Up @@ -1286,5 +1228,11 @@ fn storage_err_to_dynamo(e: StorageError) -> DynamoDbError {
}

mod overrides;
mod pitr;
#[cfg(test)]
mod tests;

pub(crate) use pitr::{
handle_describe_continuous_backups, handle_restore_table_to_point_in_time,
handle_update_continuous_backups,
};
Loading
Loading