{"record":{"id":"ffcf4f180a14437a","repo":"databendlabs/databend","slug":"logic-error-expected-createtable-plan","errorCode":null,"errorMessage":"logic error: expected CreateTable plan","messagePattern":"logic error: expected CreateTable plan","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/query/service/src/history_tables/alter_table.rs","lineNumber":53,"sourceCode":"\npub async fn get_schemas(\n    ctx: Arc<QueryContext>,\n    new_create_sql: &str,\n    table_name: &str,\n) -> Result<(TableSchemaRef, TableSchemaRef)> {\n    let old_table_schema = ThreadTracker::tracking_future(ctx.get_table(\n        CATALOG_DEFAULT,\n        \"system_history\",\n        table_name,\n    ))\n    .await?\n    .schema();\n    let mut planner = Planner::new(ctx.clone());\n    let (create_plan, _) = ThreadTracker::tracking_future(planner.plan_sql(new_create_sql)).await?;\n    let new_table_schema = match create_plan {\n        Plan::CreateTable(plan) => plan.schema,\n        _ => {\n            unreachable!(\"logic error: expected CreateTable plan\")\n        }\n    };\n    Ok((old_table_schema, new_table_schema))\n}\n\npub async fn get_alter_table_sql(\n    ctx: Arc<QueryContext>,\n    new_create_sql: &str,\n    table_name: &str,\n) -> Result<Vec<String>> {\n    let mut tracking_payload = ThreadTracker::new_tracking_payload();\n    tracking_payload.capture_log_settings = Some(CaptureLogSettings::capture_off());\n\n    let (old_table_schema, new_table_schema) = tracking_payload\n        .tracking(get_schemas(ctx, new_create_sql, table_name))\n        .await?;\n    // The table schema change follow \"open-closed principle\", only accept adding new fields.\n    // If the new table schema has less or equal fields than the old one, means older version","sourceCodeStart":35,"sourceCodeEnd":71,"githubUrl":"https://github.com/databendlabs/databend/blob/288d84d76e20a2f8f7173bda9691eb6ece301aa9/src/query/service/src/history_tables/alter_table.rs#L35-L71","documentation":"This panic fires in get_schemas (src/query/service/src/history_tables/alter_table.rs:53) after planning a rewritten CREATE TABLE statement. The code assumes plan_sql on a CREATE TABLE statement always yields Plan::CreateTable; any other Plan variant hits the unreachable!(\"logic error: expected CreateTable plan\") branch. It is an internal invariant violation, meaning the SQL string passed to the planner was not a CREATE TABLE statement as expected.","triggerScenarios":"get_alter_table_sql reconstructs a `CREATE TABLE` statement from the table's create option and plans it via Planner::plan_sql; the panic occurs if the planned result is not Plan::CreateTable — e.g. the generated SQL was mutated into another statement kind, or the planner binding changed and returned a different plan variant.","commonSituations":"Running ALTER TABLE on history/system tables after an internal refactor of plan_sql output; users hit it as an abrupt query-node panic ('logic error: expected CreateTable plan') when altering a table whose generated create SQL no longer plans as CreateTable, typically after a version upgrade or with unusual table options.","solutions":["Upgrade to a release where the ALTER TABLE schema-diff code plans a verified CREATE TABLE statement (or where the plan match handles more variants)","Inspect the generated new_create_sql (enable query logging) and confirm the table's CREATE statement is well-formed; recreate the table if its metadata is corrupted","Avoid ALTER TABLE on system/history tables; file a bug with the exact ALTER TABLE statement and Databend version","As a code fix, replace unreachable! with a returned ErrorCode::Internal error describing the unexpected plan variant"],"exampleFix":"// before\nlet new_table_schema = match create_plan {\n    Plan::CreateTable(plan) => plan.schema,\n    _ => unreachable!(\"logic error: expected CreateTable plan\"),\n};\n// after\nlet new_table_schema = match create_plan {\n    Plan::CreateTable(plan) => plan.schema,\n    other => {\n        return Err(ErrorCode::Internal(format!(\n            \"expected CreateTable plan, got {:?}\", other\n        )));\n    }\n};","handlingStrategy":"validation","validationCode":"// ensure the target is a user table before ALTER TABLE\nlet table = ctx.get_table(catalog, database, table_name).await?;\nif table.engine().is_empty() {\n    return Err(\"cannot determine table engine; ALTER may be unsupported\".into());\n}","typeGuard":"if let Plan::CreateTable(plan) = &create_plan { /* use plan.schema */ } else { /* handle error */ }","tryCatchPattern":"match planner.plan_sql(sql).await {\n    Ok((Plan::CreateTable(p), _)) => p.schema,\n    Ok((other, _)) => return Err(ErrorCode::Internal(format!(\"unexpected plan {other:?}\"))),\n    Err(e) => return Err(e),\n}","preventionTips":["Do not ALTER TABLE system/history tables; use supported management commands","Pin Databend versions and check ALTER TABLE release notes before upgrading","Report panics with the exact statement so maintainers can convert unreachable! into proper errors"],"tags":["rust","unreachable-panic","planner","alter-table"],"backgroundTag":"internal-invariant-violation","analyzedSha":"288d84d76e20a2f8f7173bda9691eb6ece301aa9","analyzedAt":"2026-09-11T11:29:36.208Z","contentChangedAt":"2026-09-11T11:29:36.208Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}