apache/iceberg · error · IcebergAnalysisException

ALTER TABLE contains multiple distribution clauses

Error message

ALTER TABLE contains multiple distribution clauses

What it means

The grammar technically allows repeated distribution spec tokens in a WRITE clause, but semantic validation rejects a statement containing more than one distribution clause, throwing this IcebergAnalysisException because at most one distribution spec is meaningful.

Solutions

  1. Keep only one DISTRIBUTED BY (or DISTRIBUTED BY PARTITION / HASH / NONE) clause in the statement.
  2. Merge the intent into a single clause, e.g. `DISTRIBUTED BY HASH(a, b)` instead of two HASH clauses.
  3. If targeting writes, remember distribution and ordering are separate clauses; you can have one distribution plus one ordering.

Example fix

-- before
ALTER TABLE t WRITE DISTRIBUTED BY HASH(a) DISTRIBUTED BY HASH(b)
-- after
ALTER TABLE t WRITE DISTRIBUTED BY HASH(a, b)
Defensive patterns

Strategy: validation

Validate before calling

// deduplicate distribution directives before rendering SQL
val dist = directives.distinct
require(dist.size <= 1, "at most one DISTRIBUTED clause allowed")

Prevention

When it happens

Trigger: Running `ALTER TABLE t WRITE DISTRIBUTED BY HASH(a) DISTRIBUTED BY HASH(b)` — i.e. any statement whose writeDistributionSpec list has size > 1.

Common situations: SQL generated by concatenating multiple distribution directives, merge conflicts in generated SQL scripts, or editing an existing ALTER TABLE line and adding a second DISTRIBUTED BY instead of editing the first.

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


AI-assisted analysis of apache/iceberg@86d9c8fc54 (2026-09-12). Data as JSON: /api/errors/e3c6d4d316c8e04e. Report an issue: GitHub.

Appendix: source

Thrown at spark/v4.1/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)