2020import org .apache .spark .annotation .Experimental ;
2121import org .apache .spark .sql .types .DataType ;
2222
23+ import javax .annotation .Nullable ;
2324import java .util .Arrays ;
2425import java .util .Objects ;
2526
@@ -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,44 @@ public int hashCode() {
259298 }
260299 }
261300
301+ interface ColumnPosition {
302+ final class First implements ColumnPosition {
303+ @ Override
304+ public String toString () {
305+ return "FIRST" ;
306+ }
307+ }
308+
309+ final class After implements ColumnPosition {
310+ private final String [] column ;
311+
312+ public After (String [] column ) {
313+ assert column != null ;
314+ this .column = column ;
315+ }
316+
317+ @ Override
318+ public String toString () {
319+ return "AFTER " + CatalogV2Implicits .quoteNameParts (column );
320+ }
321+
322+ @ Override
323+ public boolean equals (Object o ) {
324+ if (this == o ) return true ;
325+ if (o == null || getClass () != o .getClass ()) return false ;
326+ After after = (After ) o ;
327+ return Arrays .equals (column , after .column );
328+ }
329+
330+ @ Override
331+ public int hashCode () {
332+ return Arrays .hashCode (column );
333+ }
334+ }
335+
336+ First FIRST = new First ();
337+ }
338+
262339 interface ColumnChange extends TableChange {
263340 String [] fieldNames ();
264341 }
@@ -275,12 +352,19 @@ final class AddColumn implements ColumnChange {
275352 private final DataType dataType ;
276353 private final boolean isNullable ;
277354 private final String comment ;
278-
279- private AddColumn (String [] fieldNames , DataType dataType , boolean isNullable , String comment ) {
355+ private final ColumnPosition position ;
356+
357+ private AddColumn (
358+ String [] fieldNames ,
359+ DataType dataType ,
360+ boolean isNullable ,
361+ String comment ,
362+ ColumnPosition position ) {
280363 this .fieldNames = fieldNames ;
281364 this .dataType = dataType ;
282365 this .isNullable = isNullable ;
283366 this .comment = comment ;
367+ this .position = position ;
284368 }
285369
286370 @ Override
@@ -296,10 +380,16 @@ public boolean isNullable() {
296380 return isNullable ;
297381 }
298382
383+ @ Nullable
299384 public String comment () {
300385 return comment ;
301386 }
302387
388+ @ Nullable
389+ public ColumnPosition position () {
390+ return position ;
391+ }
392+
303393 @ Override
304394 public boolean equals (Object o ) {
305395 if (this == o ) return true ;
@@ -308,12 +398,13 @@ public boolean equals(Object o) {
308398 return isNullable == addColumn .isNullable &&
309399 Arrays .equals (fieldNames , addColumn .fieldNames ) &&
310400 dataType .equals (addColumn .dataType ) &&
311- comment .equals (addColumn .comment );
401+ Objects .equals (comment , addColumn .comment ) &&
402+ Objects .equals (position , addColumn .position );
312403 }
313404
314405 @ Override
315406 public int hashCode () {
316- int result = Objects .hash (dataType , isNullable , comment );
407+ int result = Objects .hash (dataType , isNullable , comment , position );
317408 result = 31 * result + Arrays .hashCode (fieldNames );
318409 return result ;
319410 }
@@ -453,6 +544,48 @@ public int hashCode() {
453544 }
454545 }
455546
547+ /**
548+ * A TableChange to update the position of a field.
549+ * <p>
550+ * The field names are used to find the field to update.
551+ * <p>
552+ * If the field does not exist, the change must result in an {@link IllegalArgumentException}.
553+ */
554+ final class UpdateColumnPosition implements ColumnChange {
555+ private final String [] fieldNames ;
556+ private final ColumnPosition position ;
557+
558+ private UpdateColumnPosition (String [] fieldNames , ColumnPosition position ) {
559+ this .fieldNames = fieldNames ;
560+ this .position = position ;
561+ }
562+
563+ @ Override
564+ public String [] fieldNames () {
565+ return fieldNames ;
566+ }
567+
568+ public ColumnPosition position () {
569+ return position ;
570+ }
571+
572+ @ Override
573+ public boolean equals (Object o ) {
574+ if (this == o ) return true ;
575+ if (o == null || getClass () != o .getClass ()) return false ;
576+ UpdateColumnPosition that = (UpdateColumnPosition ) o ;
577+ return Arrays .equals (fieldNames , that .fieldNames ) &&
578+ position .equals (that .position );
579+ }
580+
581+ @ Override
582+ public int hashCode () {
583+ int result = Objects .hash (position );
584+ result = 31 * result + Arrays .hashCode (fieldNames );
585+ return result ;
586+ }
587+ }
588+
456589 /**
457590 * A TableChange to delete a field.
458591 * <p>
0 commit comments