1717
1818package org .apache .spark .sql .connector .catalog ;
1919
20- import org .apache .spark .annotation .Experimental ;
21- import org .apache .spark .sql .types .DataType ;
22-
2320import java .util .Arrays ;
2421import java .util .Objects ;
22+ import javax .annotation .Nullable ;
23+
24+ import org .apache .spark .annotation .Experimental ;
25+ import org .apache .spark .sql .types .DataType ;
2526
2627/**
2728 * TableChange subclasses represent requested changes to a table. These are passed to
@@ -76,7 +77,7 @@ static TableChange removeProperty(String property) {
7677 * @return a TableChange for the addition
7778 */
7879 static TableChange addColumn (String [] fieldNames , DataType dataType ) {
79- return new AddColumn (fieldNames , dataType , true , null );
80+ return new AddColumn (fieldNames , dataType , true , null , null );
8081 }
8182
8283 /**
@@ -92,7 +93,7 @@ static TableChange addColumn(String[] fieldNames, DataType dataType) {
9293 * @return a TableChange for the addition
9394 */
9495 static TableChange addColumn (String [] fieldNames , DataType dataType , boolean isNullable ) {
95- return new AddColumn (fieldNames , dataType , isNullable , null );
96+ return new AddColumn (fieldNames , dataType , isNullable , null , null );
9697 }
9798
9899 /**
@@ -113,7 +114,30 @@ static TableChange addColumn(
113114 DataType dataType ,
114115 boolean isNullable ,
115116 String comment ) {
116- return new AddColumn (fieldNames , dataType , isNullable , comment );
117+ return new AddColumn (fieldNames , dataType , isNullable , comment , null );
118+ }
119+
120+ /**
121+ * Create a TableChange for adding a column.
122+ * <p>
123+ * If the field already exists, the change will result in an {@link IllegalArgumentException}.
124+ * If the new field is nested and its parent does not exist or is not a struct, the change will
125+ * result in an {@link IllegalArgumentException}.
126+ *
127+ * @param fieldNames field names of the new column
128+ * @param dataType the new column's data type
129+ * @param isNullable whether the new column can contain null
130+ * @param comment the new field's comment string
131+ * @param position the new columns's position
132+ * @return a TableChange for the addition
133+ */
134+ static TableChange addColumn (
135+ String [] fieldNames ,
136+ DataType dataType ,
137+ boolean isNullable ,
138+ String comment ,
139+ ColumnPosition position ) {
140+ return new AddColumn (fieldNames , dataType , isNullable , comment , position );
117141 }
118142
119143 /**
@@ -180,6 +204,21 @@ static TableChange updateColumnComment(String[] fieldNames, String newComment) {
180204 return new UpdateColumnComment (fieldNames , newComment );
181205 }
182206
207+ /**
208+ * Create a TableChange for updating the position of a field.
209+ * <p>
210+ * The name is used to find the field to update.
211+ * <p>
212+ * If the field does not exist, the change will result in an {@link IllegalArgumentException}.
213+ *
214+ * @param fieldNames field names of the column to update
215+ * @param newPosition the new position
216+ * @return a TableChange for the update
217+ */
218+ static TableChange updateColumnPosition (String [] fieldNames , ColumnPosition newPosition ) {
219+ return new UpdateColumnPosition (fieldNames , newPosition );
220+ }
221+
183222 /**
184223 * Create a TableChange for deleting a field.
185224 * <p>
@@ -259,6 +298,60 @@ public int hashCode() {
259298 }
260299 }
261300
301+ interface ColumnPosition {
302+ First FIRST = new First ();
303+
304+ static ColumnPosition After (String [] column ) {
305+ return new After (column );
306+ }
307+ }
308+
309+ /**
310+ * Column position FIRST means the specified column should be the first column.
311+ */
312+ final class First implements ColumnPosition {
313+ private First () {}
314+
315+ @ Override
316+ public String toString () {
317+ return "FIRST" ;
318+ }
319+ }
320+
321+ /**
322+ * Column position AFTER means the specified column should be put after the given `column`.
323+ */
324+ final class After implements ColumnPosition {
325+ private final String [] column ;
326+
327+ private After (String [] column ) {
328+ assert column != null ;
329+ this .column = column ;
330+ }
331+
332+ public String [] getColumn () {
333+ return column ;
334+ }
335+
336+ @ Override
337+ public String toString () {
338+ return "AFTER " + CatalogV2Implicits .quoteNameParts (column );
339+ }
340+
341+ @ Override
342+ public boolean equals (Object o ) {
343+ if (this == o ) return true ;
344+ if (o == null || getClass () != o .getClass ()) return false ;
345+ After after = (After ) o ;
346+ return Arrays .equals (column , after .column );
347+ }
348+
349+ @ Override
350+ public int hashCode () {
351+ return Arrays .hashCode (column );
352+ }
353+ }
354+
262355 interface ColumnChange extends TableChange {
263356 String [] fieldNames ();
264357 }
@@ -275,12 +368,19 @@ final class AddColumn implements ColumnChange {
275368 private final DataType dataType ;
276369 private final boolean isNullable ;
277370 private final String comment ;
278-
279- private AddColumn (String [] fieldNames , DataType dataType , boolean isNullable , String comment ) {
371+ private final ColumnPosition position ;
372+
373+ private AddColumn (
374+ String [] fieldNames ,
375+ DataType dataType ,
376+ boolean isNullable ,
377+ String comment ,
378+ ColumnPosition position ) {
280379 this .fieldNames = fieldNames ;
281380 this .dataType = dataType ;
282381 this .isNullable = isNullable ;
283382 this .comment = comment ;
383+ this .position = position ;
284384 }
285385
286386 @ Override
@@ -296,10 +396,16 @@ public boolean isNullable() {
296396 return isNullable ;
297397 }
298398
399+ @ Nullable
299400 public String comment () {
300401 return comment ;
301402 }
302403
404+ @ Nullable
405+ public ColumnPosition position () {
406+ return position ;
407+ }
408+
303409 @ Override
304410 public boolean equals (Object o ) {
305411 if (this == o ) return true ;
@@ -308,12 +414,13 @@ public boolean equals(Object o) {
308414 return isNullable == addColumn .isNullable &&
309415 Arrays .equals (fieldNames , addColumn .fieldNames ) &&
310416 dataType .equals (addColumn .dataType ) &&
311- comment .equals (addColumn .comment );
417+ Objects .equals (comment , addColumn .comment ) &&
418+ Objects .equals (position , addColumn .position );
312419 }
313420
314421 @ Override
315422 public int hashCode () {
316- int result = Objects .hash (dataType , isNullable , comment );
423+ int result = Objects .hash (dataType , isNullable , comment , position );
317424 result = 31 * result + Arrays .hashCode (fieldNames );
318425 return result ;
319426 }
@@ -453,6 +560,48 @@ public int hashCode() {
453560 }
454561 }
455562
563+ /**
564+ * A TableChange to update the position of a field.
565+ * <p>
566+ * The field names are used to find the field to update.
567+ * <p>
568+ * If the field does not exist, the change must result in an {@link IllegalArgumentException}.
569+ */
570+ final class UpdateColumnPosition implements ColumnChange {
571+ private final String [] fieldNames ;
572+ private final ColumnPosition position ;
573+
574+ private UpdateColumnPosition (String [] fieldNames , ColumnPosition position ) {
575+ this .fieldNames = fieldNames ;
576+ this .position = position ;
577+ }
578+
579+ @ Override
580+ public String [] fieldNames () {
581+ return fieldNames ;
582+ }
583+
584+ public ColumnPosition position () {
585+ return position ;
586+ }
587+
588+ @ Override
589+ public boolean equals (Object o ) {
590+ if (this == o ) return true ;
591+ if (o == null || getClass () != o .getClass ()) return false ;
592+ UpdateColumnPosition that = (UpdateColumnPosition ) o ;
593+ return Arrays .equals (fieldNames , that .fieldNames ) &&
594+ position .equals (that .position );
595+ }
596+
597+ @ Override
598+ public int hashCode () {
599+ int result = Objects .hash (position );
600+ result = 31 * result + Arrays .hashCode (fieldNames );
601+ return result ;
602+ }
603+ }
604+
456605 /**
457606 * A TableChange to delete a field.
458607 * <p>
0 commit comments