{"record":{"id":"d73d8d0f05da2396","repo":"apache/iceberg","slug":"alter-table-contains-multiple-distribution-clauses-d73d8d","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.0/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.0/spark-extensions/src/main/scala/org/apache/spark/sql/catalyst/parser/extensions/IcebergSqlExtensionsAstBuilder.scala#L239-L275","documentation":"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.","triggerScenarios":"`ALTER TABLE t WRITE DISTRIBUTED BY HASH(id) DISTRIBUTED DIRECTLY` or any statement with two writeDistributionSpec clauses in one writeSpec.","commonSituations":"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).","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"],"exampleFix":"// before\nALTER TABLE t WRITE DISTRIBUTED BY HASH(id) DISTRIBUTED DIRECTLY;\n// after\nALTER TABLE t WRITE DISTRIBUTED BY HASH(id);","handlingStrategy":"validation","validationCode":"def countClauses(stmt: String, kw: String): Int = stmt.toUpperCase(Locale.ROOT).split(kw).length - 1\nrequire(countClauses(stmt, \"DISTRIBUTED\") <= 1, \"Only one distribution clause allowed\")","typeGuard":null,"tryCatchPattern":"try { spark.sql(stmt) } catch { case e: IcebergAnalysisException if e.getMessage.contains(\"multiple distribution clauses\") => log.error(\"Deduplicate write distribution clauses\", e) }","preventionTips":["When composing DDL from fragments, replace prior distribution clauses rather than appending","One distribution strategy per statement","Add unit tests for generated ALTER statements"],"tags":["spark","sql","alter-table","conflict"],"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"}