(
cmd: FailSqlCommand,
state: &mut State,
)
| 402 | } |
| 403 | |
| 404 | pub async fn run_fail_sql( |
| 405 | cmd: FailSqlCommand, |
| 406 | state: &mut State, |
| 407 | ) -> Result<ControlFlow, anyhow::Error> { |
| 408 | use Statement::{AlterSink, Commit, CreateConnection, Fetch, Rollback}; |
| 409 | |
| 410 | let stmts = mz_sql_parser::parser::parse_statements(&cmd.query) |
| 411 | .map_err(|e| format!("unable to parse SQL: {}: {}", cmd.query, e)); |
| 412 | |
| 413 | // Allow for statements that could not be parsed. |
| 414 | // This way such statements can be used for negative testing in .td files |
| 415 | let stmt = match stmts { |
| 416 | Ok(s) => { |
| 417 | if s.len() != 1 { |
| 418 | bail!("expected one statement, but got {}", s.len()); |
| 419 | } |
| 420 | Some(s.into_element().ast) |
| 421 | } |
| 422 | Err(_) => None, |
| 423 | }; |
| 424 | |
| 425 | let expected_error = match cmd.expected_error { |
| 426 | SqlExpectedError::Contains(s) => ErrorMatcher::Contains(s), |
| 427 | SqlExpectedError::Exact(s) => ErrorMatcher::Exact(s), |
| 428 | SqlExpectedError::Regex(s) => ErrorMatcher::Regex(s.parse()?), |
| 429 | SqlExpectedError::Timeout => ErrorMatcher::Timeout, |
| 430 | }; |
| 431 | let expected_detail = cmd.expected_detail.map(ErrorMatcher::Contains); |
| 432 | let expected_hint = cmd.expected_hint.map(ErrorMatcher::Contains); |
| 433 | |
| 434 | let query = &cmd.query; |
| 435 | print_query(query, stmt.as_ref()); |
| 436 | |
| 437 | let should_retry = match &stmt { |
| 438 | // Do not retry statements that could not be parsed |
| 439 | None => false, |
| 440 | // Do not retry COMMIT and ROLLBACK. Once the transaction has errored out and has |
| 441 | // been aborted, retrying COMMIT or ROLLBACK will actually start succeeding, which |
| 442 | // causes testdrive to emit a confusing "query succeded but expected error" message. |
| 443 | Some(Commit(_)) | Some(Rollback(_)) => false, |
| 444 | // FETCH should not be retried because it consumes data on each response. |
| 445 | Some(Fetch(_)) => false, |
| 446 | Some(AlterSink(_)) => false, |
| 447 | Some(CreateConnection(_)) => false, |
| 448 | Some(_) => true, |
| 449 | }; |
| 450 | |
| 451 | state.error_line_count = 0; |
| 452 | state.error_string = "".to_string(); |
| 453 | let res = match should_retry { |
| 454 | true => Retry::default() |
| 455 | .initial_backoff(state.initial_backoff) |
| 456 | .factor(state.backoff_factor) |
| 457 | .max_duration(state.timeout) |
| 458 | .max_tries(state.max_tries), |
| 459 | false => Retry::default().max_duration(state.timeout).max_tries(1), |
| 460 | } |
| 461 | .retry_async_with_state_canceling(state, |retry_state, state| { |
no test coverage detected