{"record":{"id":"650f23d3d710546c","repo":"apache/seatunnel","slug":"schema-change-failed-650f23","errorCode":"SCHEMA_CHANGE_FAILED","errorMessage":"Failed to schemaChange","messagePattern":"Failed to schemaChange","errorType":"error_code","errorClass":"DorisSchemaChangeException","httpStatus":null,"severity":"error","filePath":"seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/sink/writer/DorisSinkWriter.java","lineNumber":232,"sourceCode":"        // In non-2PC mode each micro-batch is an independent load, so close the current load\n        // first (committing the buffered rows against the still-unaltered table) before applying\n        // the DDL, then reopen a fresh load so subsequent rows are loaded against the new schema.\n        // Mixing schemas within a single load corrupts data (e.g. column mismatch for csv/dropped\n        // columns). 2PC keeps a single transaction per checkpoint, so only name-based JSON loads\n        // are allowed to cross a schema-change boundary.\n        boolean flushBeforeSchemaChange = !dorisSinkConfig.getEnable2PC();\n        if (flushBeforeSchemaChange) {\n            flush();\n        }\n\n        this.tableSchema = tableSchemaChanger.reset(tableSchema).apply(event);\n        SeaTunnelRowType seaTunnelRowType = tableSchema.toPhysicalRowDataType();\n        this.serializer = createSerializer(this.dorisSinkConfig, seaTunnelRowType);\n\n        try {\n            schemaChangeManager.applySchemaChange(sinkTablePath, event);\n        } catch (Exception e) {\n            throw new DorisSchemaChangeException(\n                    DorisConnectorErrorCode.SCHEMA_CHANGE_FAILED, \"Failed to schemaChange\", e);\n        }\n\n        if (flushBeforeSchemaChange) {\n            startLoad(labelGenerator.generateLabel(lastCheckpointId));\n        }\n    }\n\n    private void validateSchemaChangeCompatibility() {\n        if (!dorisSinkConfig.getEnable2PC()) {\n            return;\n        }\n        String format = dorisSinkConfig.getStreamLoadProps().getProperty(LoadConstants.FORMAT_KEY);\n        if (!LoadConstants.JSON.equalsIgnoreCase(format)) {\n            throw new DorisSchemaChangeException(\n                    DorisConnectorErrorCode.SCHEMA_CHANGE_FAILED,\n                    String.format(\n                            \"Doris schema evolution with sink.enable-2pc=true only supports \"","sourceCodeStart":214,"sourceCodeEnd":250,"githubUrl":"https://github.com/apache/seatunnel/blob/cf67b549a7a6c35fa0beb12d83c62892427ea919/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/sink/writer/DorisSinkWriter.java#L214-L250","documentation":"Thrown by DorisSinkWriter.applySchemaChange when the SchemaChangeManager fails to apply a schema-change DDL event to the target Doris table. The writer wraps any exception from schemaChangeManager.applySchemaChange into a DorisSchemaChangeException with code SCHEMA_CHANGE_FAILED, keeping the original cause.","triggerScenarios":"A SchemaChangeEvent is applied via applySchemaChange (e.g. after a schema evolution event) and the Doris FE rejects the ALTER: table missing, incompatible column change, permission error, or FE/BE connectivity failure inside schemaChangeManager.applySchemaChange.","commonSituations":"Upstream schema changed (e.g. new column added in source DB) but Doris table disallows the corresponding ALTER; user lacks ALTER privilege; typo'd table path (sinkTablePath); Doris FE unreachable or returning HTTP errors; non-idempotent ADD COLUMN replayed after job restore.","solutions":["Check the wrapped cause (getCause) for the actual FE/HTTP error from Doris","Verify the table exists at sinkTablePath and the user has ALTER privilege","Reproduce the equivalent ALTER statement manually against Doris to see the rejection reason","Confirm the schema event is compatible (e.g. ADD COLUMN with supported types) and the database is not in a restoring/restoring-meta state","Ensure FE HTTP port config is correct and reachable from the job worker"],"exampleFix":"// before\nschemaChangeManager.applySchemaChange(sinkTablePath, event);\n// after\ntry {\n    if (schemaChangeManager.tableExists(sinkTablePath)) {\n        schemaChangeManager.applySchemaChange(sinkTablePath, event);\n    }\n} catch (Exception e) {\n    log.warn(\"schema change skipped for {}: {}\", sinkTablePath, e.getMessage());\n}","handlingStrategy":"try-catch","validationCode":"// before submitting job\nif (!dorisTableExists(sinkTablePath)) { throw new IllegalStateException(\"target table missing: \" + sinkTablePath); }\nif (!hasAlterPrivilege(user, sinkTablePath)) { throw new IllegalStateException(\"no ALTER privilege on \" + sinkTablePath); }","typeGuard":null,"tryCatchPattern":"try { writer.applySchemaChange(event); } catch (DorisSchemaChangeException e) { log.error(\"schema change failed, cause: {}\", e.getCause(), e); /* decide skip vs fail */ }","preventionTips":["Always log the cause chain; the Doris FE error is in getCause()","Test the equivalent ALTER statement manually before enabling schema evolution","Grant ALTER privilege to the sink user","Keep sinkTablePath (database/table) config exact"],"tags":["doris","schema-change","ddl"],"backgroundTag":"schema-validation-failed","analyzedSha":"cf67b549a7a6c35fa0beb12d83c62892427ea919","analyzedAt":"2026-09-10T21:44:55.265Z","contentChangedAt":"2026-09-10T21:44:55.265Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}