{"record":{"id":"e46331c72e2c9217","repo":"apache/iceberg","slug":"alter-table-contains-multiple-distribution-clauses","errorCode":null,"errorMessage":"ALTER TABLE contains multiple distribution clauses","messagePattern":"ALTER TABLE contains multiple distribution clauses","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"spark/v3.5/spark-extensions/src/main/scala/org/apache/spark/sql/catalyst/parser/extensions/IcebergSqlExtensionsAstBuilder.scala","lineNumber":270,"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 AnalysisException(\"ALTER TABLE contains multiple distribution clauses\")\n    }\n\n    if (writeSpec.writeOrderingSpec.size > 1) {\n      throw new AnalysisException(\"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":252,"sourceCodeEnd":288,"githubUrl":"https://github.com/apache/iceberg/blob/86d9c8fc543e7c56c9f624eb725f76c9baff9570/spark/v3.5/spark-extensions/src/main/scala/org/apache/spark/sql/catalyst/parser/extensions/IcebergSqlExtensionsAstBuilder.scala#L252-L288","documentation":"The grammar permits a list of distribution spec tokens, but Iceberg only supports a single distribution clause per ALTER TABLE ... WRITE statement. More than one triggers an AnalysisException so the statement cannot be ambiguously interpreted.","triggerScenarios":"Writing 'ALTER TABLE t WRITE DISTRIBUTED BY PARTITION HASH(id)' or otherwise stacking two writeDistributionSpec clauses in one statement.","commonSituations":"Users combining DISTRIBUTED BY PARTITION with a second HASH(col) clause expecting both to apply; auto-generated SQL concatenating clauses.","solutions":["Keep exactly one distribution clause — combine intent into it, e.g. ALTER TABLE t WRITE DISTRIBUTED BY PARTITION;","Use HASH(col) as the single clause if keyed distribution is desired","Split into sequential ALTER TABLE statements if transitioning between modes is intended"],"exampleFix":"// before\nALTER TABLE t WRITE DISTRIBUTED BY PARTITION HASH(id);\n// after\nALTER TABLE t WRITE HASH(id);","handlingStrategy":"validation","validationCode":"val distCount = \"DISTRIBUTED\\\\s+BY|HASH\\\\(|RANGE\\\\(|\".r.findAllIn(sqlText).size\nrequire(distCount <= 1, \"Only one distribution clause allowed\")","typeGuard":null,"tryCatchPattern":"try { spark.sql(ddl) } catch { case e: AnalysisException if e.getMessage.contains(\"multiple distribution\") => log.error(\"duplicate distribution clause\"); throw e }","preventionTips":["Emit at most one distribution clause in generated DDL","Prefer a single DISTRIBUTED BY PARTITION or HASH(col) clause","Review concatenated SQL fragments for duplicated clauses"],"tags":["spark","sql","alter-table"],"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"}