fix(schema): fall back to SHOW CREATE MATERIALIZED VIEW on Doris
This commit is contained in:
1 file changed
+123
@@ -12975,6 +12975,91 @@ mod ddl_tests {
|
||||
);
|
||||
}
|
||||
|
||||
/// Doris answers `SHOW CREATE TABLE <mv>` with a pointer to
|
||||
/// `SHOW CREATE MATERIALIZED VIEW`; the DDL request must follow that hint.
|
||||
#[tokio::test]
|
||||
async fn mysql_ddl_falls_back_to_materialized_view_after_a_doris_refusal() {
|
||||
let mut executor = FakeMysqlDdlExecutor {
|
||||
outcomes: [
|
||||
Err(mysql_server_error(
|
||||
1105,
|
||||
"errCode = 2, detailMessage = not support async materialized view, please use `show create materialized view`",
|
||||
)),
|
||||
Ok("CREATE MATERIALIZED VIEW `mv_daily` (id)".to_string()),
|
||||
]
|
||||
.into(),
|
||||
executed: Vec::new(),
|
||||
};
|
||||
|
||||
let ddl = mysql_ddl_with_executor(&mut executor, "dbx_test", "mv_daily").await.unwrap();
|
||||
|
||||
assert_eq!(ddl, "CREATE MATERIALIZED VIEW `mv_daily` (id);");
|
||||
assert_eq!(
|
||||
executor.executed,
|
||||
["SHOW CREATE TABLE `dbx_test`.`mv_daily`", "SHOW CREATE MATERIALIZED VIEW `dbx_test`.`mv_daily`"]
|
||||
);
|
||||
}
|
||||
|
||||
/// Without a database the materialized view statement stays unqualified,
|
||||
/// matching the qualifier used for the table probe.
|
||||
#[tokio::test]
|
||||
async fn mysql_ddl_falls_back_to_materialized_view_without_a_database() {
|
||||
let mut executor = FakeMysqlDdlExecutor {
|
||||
outcomes: [
|
||||
Err(mysql_server_error(
|
||||
1105,
|
||||
"not support async materialized view, please use `show create materialized view`",
|
||||
)),
|
||||
Ok("CREATE MATERIALIZED VIEW `mv_daily` (id)".to_string()),
|
||||
]
|
||||
.into(),
|
||||
executed: Vec::new(),
|
||||
};
|
||||
|
||||
let ddl = mysql_ddl_with_executor(&mut executor, "", "mv_daily").await.unwrap();
|
||||
|
||||
assert_eq!(ddl, "CREATE MATERIALIZED VIEW `mv_daily` (id);");
|
||||
assert_eq!(executor.executed, ["SHOW CREATE TABLE `mv_daily`", "SHOW CREATE MATERIALIZED VIEW `mv_daily`"]);
|
||||
}
|
||||
|
||||
/// Engines without `SHOW CREATE MATERIALIZED VIEW` answer the retry with a
|
||||
/// syntax error; the original table error is what the user should still see.
|
||||
#[tokio::test]
|
||||
async fn mysql_ddl_preserves_the_table_error_when_the_materialized_view_probe_fails() {
|
||||
let refusal = mysql_server_error(
|
||||
1105,
|
||||
"errCode = 2, detailMessage = not support async materialized view, please use `show create materialized view`",
|
||||
);
|
||||
let expected = refusal.to_string();
|
||||
let mut executor = FakeMysqlDdlExecutor {
|
||||
outcomes: [Err(refusal), Err(mysql_server_error(1064, "You have an error in your SQL syntax"))].into(),
|
||||
executed: Vec::new(),
|
||||
};
|
||||
|
||||
let error = mysql_ddl_with_executor(&mut executor, "app", "missing").await.unwrap_err();
|
||||
|
||||
assert_eq!(error, expected);
|
||||
assert_eq!(
|
||||
executor.executed,
|
||||
["SHOW CREATE TABLE `app`.`missing`", "SHOW CREATE MATERIALIZED VIEW `app`.`missing`"]
|
||||
);
|
||||
}
|
||||
|
||||
/// Unrelated failures must not trigger an extra probe, even when the server
|
||||
/// reports them with the same generic error code Doris uses.
|
||||
#[tokio::test]
|
||||
async fn mysql_ddl_does_not_probe_materialized_views_for_unrelated_failures() {
|
||||
let mut executor = FakeMysqlDdlExecutor {
|
||||
outcomes: [Err(mysql_server_error(1105, "errCode = 2, detailMessage = table is broken"))].into(),
|
||||
executed: Vec::new(),
|
||||
};
|
||||
|
||||
let error = mysql_ddl_with_executor(&mut executor, "app", "missing").await.unwrap_err();
|
||||
|
||||
assert_eq!(error, "Server error: `ERROR 1105 (HY000): errCode = 2, detailMessage = table is broken'");
|
||||
assert_eq!(executor.executed, ["SHOW CREATE TABLE `app`.`missing`"]);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn mysql_ddl_preserves_qualified_error_when_fallback_fails() {
|
||||
let first_error = mysql_server_error(1146, "qualified table doesn't exist");
|
||||
@@ -13048,6 +13133,21 @@ impl MysqlDdlQueryError {
|
||||
fn is_no_such_table(&self) -> bool {
|
||||
matches!(self, Self::Query(mysql_async::Error::Server(error)) if error.code == 1146)
|
||||
}
|
||||
|
||||
/// Doris exposes asynchronous materialized views as base tables and then
|
||||
/// refuses `SHOW CREATE TABLE` / `SHOW CREATE VIEW` on them with a pointer
|
||||
/// to another statement:
|
||||
/// `ERROR 1105 (HY000): errCode = 2, detailMessage = not support async
|
||||
/// materialized view, please use `show create materialized view``.
|
||||
/// Recognize that refusal so the DDL request can be retried with the
|
||||
/// statement the server asked for instead of failing outright.
|
||||
fn is_materialized_view_ddl_refusal(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
Self::Query(mysql_async::Error::Server(error))
|
||||
if error.message.to_ascii_lowercase().contains("materialized view")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for MysqlDdlQueryError {
|
||||
@@ -13095,6 +13195,10 @@ async fn mysql_ddl_with_executor(
|
||||
Ok(ddl) => return Ok(normalize_mysql_display_ddl(ddl)),
|
||||
Err(error) => error,
|
||||
};
|
||||
if qualified_error.is_materialized_view_ddl_refusal() {
|
||||
let name = mysql_qualified_name(database, table);
|
||||
return mysql_materialized_view_ddl(executor, &name, qualified_error).await;
|
||||
}
|
||||
if database.trim().is_empty() || !qualified_error.is_no_such_table() {
|
||||
return Err(qualified_error.to_string());
|
||||
}
|
||||
@@ -13108,6 +13212,25 @@ async fn mysql_ddl_with_executor(
|
||||
}
|
||||
}
|
||||
|
||||
/// Reads the definition of a materialized view that the server refused to
|
||||
/// describe as a table.
|
||||
///
|
||||
/// The fallback only replaces the original error when the statement actually
|
||||
/// returns a definition: engines without `SHOW CREATE MATERIALIZED VIEW`
|
||||
/// (plain MySQL, MariaDB) answer with a syntax error, so the caller still sees
|
||||
/// the error it would have seen before this fallback existed.
|
||||
async fn mysql_materialized_view_ddl(
|
||||
executor: &mut impl MysqlDdlQueryExecutor,
|
||||
qualified_name: &str,
|
||||
original_error: MysqlDdlQueryError,
|
||||
) -> Result<String, String> {
|
||||
let sql = format!("SHOW CREATE MATERIALIZED VIEW {qualified_name}");
|
||||
match executor.execute(&sql).await {
|
||||
Ok(ddl) => Ok(normalize_mysql_display_ddl(ddl)),
|
||||
Err(_) => Err(original_error.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn mysql_ddl(pool: &db::mysql::MySqlPool, database: &str, table: &str) -> Result<String, String> {
|
||||
// Use the health-checked getter so a stale pooled connection (server closed
|
||||
// it after an idle timeout, NAT/firewall dropped the TCP state, etc.) is
|
||||
|
||||
Reference in new issue
Block a user