{"record":{"id":"fe7893222aa05ef5","repo":"apache/iceberg","slug":"alter-table-contains-multiple-ordering-clauses-fe7893","errorCode":null,"errorMessage":"ALTER TABLE contains multiple ordering clauses","messagePattern":"ALTER TABLE contains multiple ordering clauses","errorType":"exception","errorClass":"IcebergAnalysisException","httpStatus":null,"severity":"error","filePath":"spark/v4.2/spark-extensions/src/main/scala/org/apache/spark/sql/catalyst/parser/extensions/IcebergSqlExtensionsAstBuilder.scala","lineNumber":261,"sourceCode":"\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)\n      .map(_ => SortDirection.ASC)\n      .orElse(Option(ctx.DESC).map(_ => SortDirection.DESC))\n      .getOrElse(SortDirection.ASC)\n    val nullOrder = Option(ctx.FIRST)","sourceCodeStart":243,"sourceCodeEnd":279,"githubUrl":"https://github.com/apache/iceberg/blob/86d9c8fc543e7c56c9f624eb725f76c9baff9570/spark/v4.2/spark-extensions/src/main/scala/org/apache/spark/sql/catalyst/parser/extensions/IcebergSqlExtensionsAstBuilder.scala#L243-L279","documentation":"Analogous to the distribution check: a WRITE spec may carry at most one ordering clause, and the AST builder throws this IcebergAnalysisException when writeSpec.writeOrderingSpec contains more than one. Two ordering clauses would leave the resulting SortOrder ambiguous, so the statement is rejected at parse/AST-build time.","triggerScenarios":"Executing `ALTER TABLE t WRITE ORDERED BY a ORDERED BY b`, or combining `ORDERED BY` with `LOCALLY ORDERED BY` in the same WRITE spec.","commonSituations":"Generated DDL appending default and user ordering; hand-editing an ALTER statement and leaving both the old and new ORDERED BY clauses in place.","solutions":["Keep a single ordering clause listing all sort keys: `ORDERED BY a, b` instead of two clauses.","Choose either global `ORDERED BY` or `LOCALLY ORDERED BY` per statement, not both.","Regenerate the DDL from one source of truth if tooling emitted duplicate clauses."],"exampleFix":"-- before\nALTER TABLE t WRITE ORDERED BY a LOCALLY ORDERED BY b\n-- after\nALTER TABLE t WRITE ORDERED BY a, b","handlingStrategy":"validation","validationCode":"val orderCount = \"(LOCALLY\\\\s+)?ORDERED\\\\s+BY\".r.findAllMatchIn(writeSpec).size\nrequire(orderCount <= 1, \"Only one WRITE ordering clause is allowed\")","typeGuard":null,"tryCatchPattern":null,"preventionTips":["List all sort keys in a single ORDERED BY clause","Never combine ORDERED BY with LOCALLY ORDERED BY in one statement","Edit-in-place DDL: remove the old ordering clause when adding a new one"],"tags":["spark","sql-ddl","write-spec","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"}