{"record":{"id":"e3c6d4d316c8e04e","repo":"apache/iceberg","slug":"alter-table-contains-multiple-distribution-clauses-e3c6d4","errorCode":null,"errorMessage":"ALTER TABLE contains multiple distribution clauses","messagePattern":"ALTER TABLE contains multiple distribution clauses","errorType":"exception","errorClass":"IcebergAnalysisException","httpStatus":null,"severity":"error","filePath":"spark/v4.1/spark-extensions/src/main/scala/org/apache/spark/sql/catalyst/parser/extensions/IcebergSqlExtensionsAstBuilder.scala","lineNumber":257,"sourceCode":"      None\n    } else {\n      Some(DistributionMode.RANGE)\n    }\n\n    val ordering = if (orderingSpec != null && orderingSpec.order != null) {\n      toSeq(orderingSpec.order.fields).map(typedVisit[(Term, SortDirection, NullOrder)])\n    } else {\n      Seq.empty\n    }\n\n    SetWriteDistributionAndOrdering(tableName, distributionMode, ordering)\n  }\n\n  private def toDistributionAndOrderingSpec(\n      writeSpec: WriteSpecContext): (WriteDistributionSpecContext, WriteOrderingSpecContext) = {\n\n    if (writeSpec.writeDistributionSpec.size > 1) {\n      throw new IcebergAnalysisException(\"ALTER TABLE contains multiple distribution clauses\")\n    }\n\n    if (writeSpec.writeOrderingSpec.size > 1) {\n      throw new IcebergAnalysisException(\"ALTER TABLE contains multiple ordering clauses\")\n    }\n\n    val distributionSpec = toBuffer(writeSpec.writeDistributionSpec).headOption.orNull\n    val orderingSpec = toBuffer(writeSpec.writeOrderingSpec).headOption.orNull\n\n    (distributionSpec, orderingSpec)\n  }\n\n  /**\n   * Create an order field.\n   */\n  override def visitOrderField(ctx: OrderFieldContext): (Term, SortDirection, NullOrder) = {\n    val term = Spark3Util.toIcebergTerm(typedVisit[Transform](ctx.transform))\n    val direction = Option(ctx.ASC)","sourceCodeStart":239,"sourceCodeEnd":275,"githubUrl":"https://github.com/apache/iceberg/blob/86d9c8fc543e7c56c9f624eb725f76c9baff9570/spark/v4.1/spark-extensions/src/main/scala/org/apache/spark/sql/catalyst/parser/extensions/IcebergSqlExtensionsAstBuilder.scala#L239-L275","documentation":"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.","triggerScenarios":"Running `ALTER TABLE t WRITE DISTRIBUTED BY HASH(a) DISTRIBUTED BY HASH(b)` — i.e. any statement whose writeDistributionSpec list has size > 1.","commonSituations":"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.","solutions":["Keep only one DISTRIBUTED BY (or DISTRIBUTED BY PARTITION / HASH / NONE) clause in the statement.","Merge the intent into a single clause, e.g. `DISTRIBUTED BY HASH(a, b)` instead of two HASH clauses.","If targeting writes, remember distribution and ordering are separate clauses; you can have one distribution plus one ordering."],"exampleFix":"-- before\nALTER TABLE t WRITE DISTRIBUTED BY HASH(a) DISTRIBUTED BY HASH(b)\n-- after\nALTER TABLE t WRITE DISTRIBUTED BY HASH(a, b)","handlingStrategy":"validation","validationCode":"// deduplicate distribution directives before rendering SQL\nval dist = directives.distinct\nrequire(dist.size <= 1, \"at most one DISTRIBUTED clause allowed\")","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Track distribution config as a single value, not a list.","Merge multi-column distribution into one HASH(a, b) clause.","Review generated SQL for duplicated directives after merges."],"tags":["spark-sql","alter-table","duplicate-clause"],"backgroundTag":"conflicting-config-options","analyzedSha":"86d9c8fc543e7c56c9f624eb725f76c9baff9570","analyzedAt":"2026-09-12T00:46:39.097Z","contentChangedAt":"2026-09-12T00:46:39.097Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}