apache/iceberg · error · IcebergAnalysisException
ALTER TABLE contains multiple distribution clauses
Error message
ALTER TABLE contains multiple distribution clauses
What it means
An ALTER TABLE write-spec statement may contain at most one distribution clause. The AST builder counts WriteDistributionSpecContext children and throws IcebergAnalysisException when more than one is present, since combining e.g. DISTRIBUTED BY HASH and DIRECTLY is ambiguous. The grammar can syntactically accept repeats, so this is a semantic check.
Solutions
- Keep only one distribution clause per WRITE spec
- Decide on a single strategy: DISTRIBUTED BY HASH(cols), DISTRIBUTED DIRECTLY, etc.
- Split into sequential ALTER statements if the change is transitional
Example fix
// before ALTER TABLE t WRITE DISTRIBUTED BY HASH(id) DISTRIBUTED DIRECTLY; // after ALTER TABLE t WRITE DISTRIBUTED BY HASH(id);
Defensive patterns
Strategy: validation
Validate before calling
def countClauses(stmt: String, kw: String): Int = stmt.toUpperCase(Locale.ROOT).split(kw).length - 1 require(countClauses(stmt, "DISTRIBUTED") <= 1, "Only one distribution clause allowed")
Try / catch
try { spark.sql(stmt) } catch { case e: IcebergAnalysisException if e.getMessage.contains("multiple distribution clauses") => log.error("Deduplicate write distribution clauses", e) } Prevention
- When composing DDL from fragments, replace prior distribution clauses rather than appending
- One distribution strategy per statement
- Add unit tests for generated ALTER statements
When it happens
Trigger: `ALTER TABLE t WRITE DISTRIBUTED BY HASH(id) DISTRIBUTED DIRECTLY` or any statement with two writeDistributionSpec clauses in one writeSpec.
Common situations: Concatenating generated SQL fragments, editing an existing ALTER statement and leaving the old distribution clause in place, or misunderstanding that the later clause overrides the earlier one (it does not — it is rejected).
Understand the failure class
Background: Conflicting config options: "cannot be used together" — configuration validation errors across open-source libraries — this error's family across 162 libraries.
Related errors
- ALTER TABLE contains multiple ordering clauses
- ALTER TABLE contains multiple distribution clauses
- ALTER TABLE contains multiple ordering clauses
- ALTER TABLE has no changes: missing both distribution and…
- ALTER TABLE has no changes: missing both distribution and…
AI-assisted analysis of apache/iceberg@86d9c8fc54 (2026-09-12).
Data as JSON: /api/errors/d73d8d0f05da2396.
Report an issue: GitHub.
Appendix: source
Thrown at spark/v4.0/spark-extensions/src/main/scala/org/apache/spark/sql/catalyst/parser/extensions/IcebergSqlExtensionsAstBuilder.scala:257
None
} else {
Some(DistributionMode.RANGE)
}
val ordering = if (orderingSpec != null && orderingSpec.order != null) {
toSeq(orderingSpec.order.fields).map(typedVisit[(Term, SortDirection, NullOrder)])
} else {
Seq.empty
}
SetWriteDistributionAndOrdering(tableName, distributionMode, ordering)
}
private def toDistributionAndOrderingSpec(
writeSpec: WriteSpecContext): (WriteDistributionSpecContext, WriteOrderingSpecContext) = {
if (writeSpec.writeDistributionSpec.size > 1) {
throw new IcebergAnalysisException("ALTER TABLE contains multiple distribution clauses")
}
if (writeSpec.writeOrderingSpec.size > 1) {
throw new IcebergAnalysisException("ALTER TABLE contains multiple ordering clauses")
}
val distributionSpec = toBuffer(writeSpec.writeDistributionSpec).headOption.orNull
val orderingSpec = toBuffer(writeSpec.writeOrderingSpec).headOption.orNull
(distributionSpec, orderingSpec)
}
/**
* Create an order field.
*/
override def visitOrderField(ctx: OrderFieldContext): (Term, SortDirection, NullOrder) = {
val term = Spark3Util.toIcebergTerm(typedVisit[Transform](ctx.transform))
val direction = Option(ctx.ASC)View on GitHub (pinned to 86d9c8fc54)