This is an automated email from the ASF dual-hosted git repository.

achennaka pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/kudu.git


The following commit(s) were added to refs/heads/master by this push:
     new 625ade7ae KUDU-1261 [Java] Array Datatype client support
625ade7ae is described below

commit 625ade7aea2adfaa7c8966b9846f960d2b61f3bf
Author: Abhishek Chennaka <[email protected]>
AuthorDate: Wed Sep 10 20:24:50 2025 -0700

    KUDU-1261 [Java] Array Datatype client support
    
    This patch adds DDL support to the Kudu Java client, including:
    - Creating ColumnSchemas with Nested DataTypes
    - Creating and altering tables with Nested DataType columns
    
    Currently, only 1D array types are supported.
    
    Corresponding tests have been added to:
    - Validate the SchemaBuilder behavior
    - Verify table schemas after creation and alteration
    
    Excluded creating NESTED Type columns directly in tests that
    create randomized Schema.
    
    Change-Id: I02b5fc52d984d424b7be53e3cc4e3236a8d33aa9
    Reviewed-on: http://gerrit.cloudera.org:8080/23419
    Tested-by: Abhishek Chennaka <[email protected]>
    Reviewed-by: Alexey Serbin <[email protected]>
---
 .../org/apache/kudu/backup/TestKuduBackup.scala    |   1 +
 .../main/java/org/apache/kudu/ColumnSchema.java    | 241 +++++++++++++++++++--
 .../src/main/java/org/apache/kudu/Type.java        |   6 +-
 .../org/apache/kudu/client/ProtobufHelper.java     |  45 +++-
 .../java/org/apache/kudu/TestColumnSchema.java     |  71 ++++++
 .../org/apache/kudu/client/TestKuduClient.java     |  69 ++++++
 .../org/apache/kudu/client/TestPartialRow.java     |   3 +-
 .../spark/tools/DistributedDataGeneratorTest.scala |   5 +-
 .../java/org/apache/kudu/test/ClientTestUtil.java  |  37 ++++
 9 files changed, 451 insertions(+), 27 deletions(-)

diff --git 
a/java/kudu-backup/src/test/scala/org/apache/kudu/backup/TestKuduBackup.scala 
b/java/kudu-backup/src/test/scala/org/apache/kudu/backup/TestKuduBackup.scala
index cdf69811e..e2f2c481a 100644
--- 
a/java/kudu-backup/src/test/scala/org/apache/kudu/backup/TestKuduBackup.scala
+++ 
b/java/kudu-backup/src/test/scala/org/apache/kudu/backup/TestKuduBackup.scala
@@ -1031,6 +1031,7 @@ class TestKuduBackup extends KuduTestSuite {
     val keyColumnCount = random.nextInt(columnCount) + 1 // At least one key.
     val schemaGenerator = new SchemaGeneratorBuilder()
       .random(random)
+      .excludeTypes(Type.NESTED)
       .columnCount(columnCount)
       .keyColumnCount(keyColumnCount)
       .build()
diff --git a/java/kudu-client/src/main/java/org/apache/kudu/ColumnSchema.java 
b/java/kudu-client/src/main/java/org/apache/kudu/ColumnSchema.java
index 26b679deb..3be19a028 100644
--- a/java/kudu-client/src/main/java/org/apache/kudu/ColumnSchema.java
+++ b/java/kudu-client/src/main/java/org/apache/kudu/ColumnSchema.java
@@ -52,6 +52,8 @@ public class ColumnSchema {
   private final Common.DataType wireType;
   private final String comment;
 
+  private final NestedTypeDescriptor nestedTypeDescriptor;
+
   /**
    * Specifies the encoding of data for a column on disk.
    * Not all encodings are available for all data types.
@@ -111,7 +113,7 @@ public class ColumnSchema {
                        Object defaultValue, int desiredBlockSize, Encoding 
encoding,
                        CompressionAlgorithm compressionAlgorithm,
                        ColumnTypeAttributes typeAttributes, Common.DataType 
wireType,
-                       String comment) {
+                       String comment, NestedTypeDescriptor 
nestedTypeDescriptor) {
     this.name = name;
     this.type = type;
     this.key = key;
@@ -127,6 +129,7 @@ public class ColumnSchema {
     this.typeSize = type.getSize(typeAttributes);
     this.wireType = wireType;
     this.comment = comment;
+    this.nestedTypeDescriptor = nestedTypeDescriptor;
   }
 
   /**
@@ -248,6 +251,145 @@ public class ColumnSchema {
     return comment;
   }
 
+  /** Returns true if this column represents an array (1D). */
+  public boolean isArray() {
+    return nestedTypeDescriptor != null && nestedTypeDescriptor.isArray();
+  }
+
+  /** Return nested descriptor or null if none. */
+  public NestedTypeDescriptor getNestedTypeDescriptor() {
+    return nestedTypeDescriptor;
+  }
+
+
+  /**
+   * Top-level container for nested type descriptors (ARRAY, MAP, STRUCT, ...).
+   * Placed here as a static inner class to keep schema-related types together.
+   */
+  public static final class NestedTypeDescriptor {
+    private final Descriptor descriptor;
+
+    private NestedTypeDescriptor(Descriptor descriptor) {
+      this.descriptor = Objects.requireNonNull(descriptor, "descriptor");
+    }
+
+    public boolean isArray() {
+      return descriptor.array != null;
+    }
+
+    public ArrayTypeDescriptor getArrayDescriptor() {
+      if (!isArray()) {
+        throw new IllegalStateException("Not an array descriptor");
+      }
+      return descriptor.array;
+    }
+
+    private static final class Descriptor {
+      final ArrayTypeDescriptor array;
+
+      private Descriptor(ArrayTypeDescriptor array) {
+        if (array == null) {
+          throw new IllegalArgumentException("ArrayTypeDescriptor must not be 
null");
+        }
+        this.array = array;
+      }
+
+      static Descriptor forArray(ArrayTypeDescriptor arr) {
+        return new Descriptor(arr);
+      }
+
+      @Override
+      public boolean equals(Object o) {
+        if (this == o) {
+          return true;
+        }
+        if (!(o instanceof Descriptor)) {
+          return false;
+        }
+        Descriptor that = (Descriptor) o;
+        return Objects.equals(array, that.array);
+      }
+
+      @Override
+      public int hashCode() {
+        return Objects.hash(array);
+      }
+    }
+
+    public static NestedTypeDescriptor forArray(ArrayTypeDescriptor arr) {
+      return new NestedTypeDescriptor(Descriptor.forArray(arr));
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) {
+        return true;
+      }
+      if (!(o instanceof NestedTypeDescriptor)) {
+        return false;
+      }
+      NestedTypeDescriptor that = (NestedTypeDescriptor) o;
+      return Objects.equals(descriptor, that.descriptor);
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(descriptor);
+    }
+
+    @Override
+    public String toString() {
+      if (isArray()) {
+        return descriptor.array.toString();
+      }
+      return "unknown-nested-type";
+    }
+  }
+
+  /**
+   * Descriptor for array-specific data.
+   */
+  public static final class ArrayTypeDescriptor {
+    private final Type elemType;
+
+    public ArrayTypeDescriptor(Type elemType) {
+      Objects.requireNonNull(elemType, "elemType");
+      if (elemType == Type.NESTED) {
+        // if element is nested, we would need nested descriptor in future
+        // for now disallow without explicit nested descriptor support
+        throw new IllegalArgumentException("Nested element without descriptor 
not supported");
+      }
+      this.elemType = elemType;
+    }
+
+    public Type getElemType() {
+      return elemType;
+    }
+
+    @Override
+    public String toString() {
+      String base = elemType.getName();
+      return base + " 1D-ARRAY";
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) {
+        return true;
+      }
+      if (!(o instanceof ArrayTypeDescriptor)) {
+        return false;
+      }
+      ArrayTypeDescriptor that = (ArrayTypeDescriptor) o;
+      return  elemType == that.elemType;
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(elemType);
+    }
+  }
+
   @Override
   public boolean equals(Object o) {
     if (this == o) {
@@ -297,7 +439,7 @@ public class ColumnSchema {
     private static final List<Type> TYPES_WITH_ATTRIBUTES = 
Arrays.asList(Type.DECIMAL,
                                                                          
Type.VARCHAR);
     private final String name;
-    private final Type type;
+    private Type type;
     private boolean key = false;
     private boolean keyUnique = false;
     private boolean nullable = false;
@@ -309,6 +451,11 @@ public class ColumnSchema {
     private ColumnTypeAttributes typeAttributes = null;
     private Common.DataType wireType = null;
     private String comment = "";
+    // Nested descriptor captured when array(true) called
+    private NestedTypeDescriptor nestedTypeDescriptor = null;
+
+    private boolean isArray = false;
+
 
     /**
      * Constructor for the required parameters.
@@ -323,6 +470,10 @@ public class ColumnSchema {
             Schema.getAutoIncrementingColumnName() + " is reserved by Kudu 
engine");
       }
       this.name = name;
+      if (type == Type.NESTED) {
+        throw new IllegalArgumentException("Column " +
+            name + " cannot be set to NESTED type. Use 
ColumnSchemaBuilder.array(true) instead");
+      }
       this.type = type;
     }
 
@@ -344,6 +495,7 @@ public class ColumnSchema {
       this.typeAttributes = that.typeAttributes;
       this.wireType = that.wireType;
       this.comment = that.comment;
+      this.nestedTypeDescriptor = that.nestedTypeDescriptor;
     }
 
     /**
@@ -482,29 +634,82 @@ public class ColumnSchema {
       return this;
     }
 
+    /**
+     * Marks this builder as creating a 1D array column. This records the 
intent,
+     * but does NOT immediately mutate the builder's declared 'type' into 
NESTED.
+     * Final promotion/validation happens inside build().
+     */
+    public ColumnSchemaBuilder array(boolean isArray) {
+      if (isArray) {
+        if (this.type == Type.NESTED) {
+          throw new IllegalArgumentException("Builder.type must be scalar when 
calling " +
+              "array(true)");
+        }
+        this.isArray = true;
+      } else {
+        this.isArray = false;
+      }
+      return this;
+    }
+
     /**
      * Builds a {@link ColumnSchema} using the passed parameters.
-     * @return a new {@link ColumnSchema}
+     * If array(true) was called, this method will create ArrayTypeDescriptor 
and
+     * NestedTypeDescriptor, promote the column to Type.NESTED and perform all
+     * necessary validation.
      */
     public ColumnSchema build() {
-      // Set the wire type if it wasn't explicitly set.
-      if (wireType == null) {
-        this.wireType = type.getDataType(typeAttributes);
+      final Type finalType;
+
+      if (this.isArray) {
+        // The builder's `type` currently holds the element (scalar) type.
+        if (this.type == Type.NESTED) {
+          throw new IllegalArgumentException("Builder.type must be scalar when 
creating " +
+              "an array column");
+        }
+        ColumnSchema.ArrayTypeDescriptor arrDesc = new 
ColumnSchema.ArrayTypeDescriptor(this.type);
+        nestedTypeDescriptor = 
ColumnSchema.NestedTypeDescriptor.forArray(arrDesc);
+        finalType = Type.NESTED;
+      } else {
+        nestedTypeDescriptor = null;
+        finalType = this.type;
       }
-      if (type == Type.VARCHAR) {
-        if (typeAttributes == null || !typeAttributes.hasLength() ||
-            typeAttributes.getLength() < CharUtil.MIN_VARCHAR_LENGTH ||
-            typeAttributes.getLength() > CharUtil.MAX_VARCHAR_LENGTH) {
-          throw new IllegalArgumentException(
-            String.format("VARCHAR's length must be set and between %d and %d",
-                          CharUtil.MIN_VARCHAR_LENGTH, 
CharUtil.MAX_VARCHAR_LENGTH));
+
+      // Validate type attributes:
+      // - For scalar VARCHAR: typeAttributes.length must be set and in bounds.
+      // - For array element VARCHAR: same requirements apply to column-level 
typeAttributes
+      if (finalType != Type.NESTED) {
+        if (finalType == Type.VARCHAR) {
+          if (typeAttributes == null || !typeAttributes.hasLength() ||
+              typeAttributes.getLength() < CharUtil.MIN_VARCHAR_LENGTH ||
+              typeAttributes.getLength() > CharUtil.MAX_VARCHAR_LENGTH) {
+            throw new IllegalArgumentException(
+                String.format("VARCHAR's length must be set and between %d and 
%d",
+                    CharUtil.MIN_VARCHAR_LENGTH, CharUtil.MAX_VARCHAR_LENGTH));
+          }
+        }
+      } else {
+        // For arrays, element type rules apply.
+        Type elemType = 
nestedTypeDescriptor.getArrayDescriptor().getElemType();
+        if (elemType == Type.VARCHAR) {
+          if (typeAttributes == null || !typeAttributes.hasLength() ||
+              typeAttributes.getLength() < CharUtil.MIN_VARCHAR_LENGTH ||
+              typeAttributes.getLength() > CharUtil.MAX_VARCHAR_LENGTH) {
+            throw new IllegalArgumentException(
+                String.format("Array element VARCHAR's length must be set and 
between %d and %d",
+                    CharUtil.MIN_VARCHAR_LENGTH, CharUtil.MAX_VARCHAR_LENGTH));
+          }
         }
       }
 
-      return new ColumnSchema(name, type, key, keyUnique, nullable, immutable,
-                              /* autoIncrementing */false, defaultValue,
-                              desiredBlockSize, encoding, compressionAlgorithm,
-                              typeAttributes, wireType, comment);
+      if (wireType == null) {
+        this.wireType = finalType.getDataType(typeAttributes);
+      }
+
+      // Finally build immutable ColumnSchema with nested descriptor if any.
+      return new ColumnSchema(name, finalType, key, keyUnique, nullable, 
immutable,
+              /* autoIncrementing */ false, defaultValue, desiredBlockSize, 
encoding,
+              compressionAlgorithm, typeAttributes, wireType, comment, 
nestedTypeDescriptor);
     }
   }
 
@@ -591,7 +796,7 @@ public class ColumnSchema {
                               /* nullable */false, /* immutable */false,
                               /* autoIncrementing */true, /* defaultValue 
*/null,
                               desiredBlockSize, encoding, compressionAlgorithm,
-                              /* typeAttributes */null, wireType, comment);
+                              /* typeAttributes */null, wireType, comment, 
null);
     }
   }
 }
diff --git a/java/kudu-client/src/main/java/org/apache/kudu/Type.java 
b/java/kudu-client/src/main/java/org/apache/kudu/Type.java
index 9d550b66c..8c91685d0 100644
--- a/java/kudu-client/src/main/java/org/apache/kudu/Type.java
+++ b/java/kudu-client/src/main/java/org/apache/kudu/Type.java
@@ -50,7 +50,8 @@ public enum Type {
   UNIXTIME_MICROS(DataType.UNIXTIME_MICROS, "unixtime_micros"),
   DECIMAL(Arrays.asList(DataType.DECIMAL32, DataType.DECIMAL64, 
DataType.DECIMAL128), "decimal"),
   VARCHAR(DataType.VARCHAR, "varchar"),
-  DATE(DataType.DATE, "date");
+  DATE(DataType.DATE, "date"),
+  NESTED(DataType.NESTED, "nested");
 
   private final ImmutableList<DataType> dataTypes;
   private final String name;
@@ -148,6 +149,7 @@ public enum Type {
       case STRING:
       case BINARY:
       case VARCHAR:
+      case NESTED:
         return 8 + 8; // offset then string length
       case BOOL:
       case INT8:
@@ -204,6 +206,8 @@ public enum Type {
       case DECIMAL64:
       case DECIMAL128:
         return DECIMAL;
+      case NESTED:
+        return NESTED;
       default:
         throw new IllegalArgumentException("the provided data type doesn't map 
" +
             "to any known one: " + type.getDescriptorForType().getFullName());
diff --git 
a/java/kudu-client/src/main/java/org/apache/kudu/client/ProtobufHelper.java 
b/java/kudu-client/src/main/java/org/apache/kudu/client/ProtobufHelper.java
index a9c015bb0..7e605adec 100644
--- a/java/kudu-client/src/main/java/org/apache/kudu/client/ProtobufHelper.java
+++ b/java/kudu-client/src/main/java/org/apache/kudu/client/ProtobufHelper.java
@@ -17,6 +17,7 @@
 
 package org.apache.kudu.client;
 
+import static com.google.common.base.Preconditions.checkState;
 import static java.nio.charset.StandardCharsets.UTF_8;
 
 import java.math.BigDecimal;
@@ -132,6 +133,28 @@ public class ProtobufHelper {
         !column.getComment().isEmpty()) {
       schemaBuilder.setComment(column.getComment());
     }
+
+    // Handle nested/array column: populate NestedDataTypePB.array.type with 
the type of
+    // array elements
+    if (column.isArray()) {
+      ColumnSchema.NestedTypeDescriptor nested = 
column.getNestedTypeDescriptor();
+      checkState(nested == null || nested.isArray(),
+              "Column reports isArray() but nested descriptor is not ARRAY");
+
+      ColumnSchema.ArrayTypeDescriptor arr = nested.getArrayDescriptor();
+      Type elemType = arr.getElemType();
+
+      Common.NestedDataTypePB.ArrayTypeDescriptor.Builder arrPb =
+              Common.NestedDataTypePB.ArrayTypeDescriptor.newBuilder();
+
+      Common.DataType elemPbType = 
elemType.getDataType(column.getTypeAttributes());
+      arrPb.setType(elemPbType);
+
+      Common.NestedDataTypePB.Builder nestedPb = 
Common.NestedDataTypePB.newBuilder();
+      nestedPb.setArray(arrPb);
+
+      schemaBuilder.setNestedType(nestedPb);
+    }
     return schemaBuilder.build();
   }
 
@@ -171,13 +194,25 @@ public class ProtobufHelper {
                              .build();
     }
 
-    Type type = Type.getTypeForDataType(pb.getType());
     ColumnTypeAttributes typeAttributes = pb.hasTypeAttributes() ?
         pbToColumnTypeAttributes(pb.getTypeAttributes()) : null;
-    Object defaultValue = pb.hasWriteDefaultValue() ?
-        byteStringToObject(type, typeAttributes, pb.getWriteDefaultValue()) : 
null;
-    ColumnSchema.ColumnSchemaBuilder csb =
-        new ColumnSchema.ColumnSchemaBuilder(pb.getName(), type);
+    Object defaultValue;
+    ColumnSchema.ColumnSchemaBuilder csb;
+    // Set the name, type and populate default value for nested column.
+    if (pb.hasNestedType() && pb.getNestedType().hasArray()) {
+      Common.NestedDataTypePB.ArrayTypeDescriptor arrPb = 
pb.getNestedType().getArray();
+      Type elemType = Type.getTypeForDataType(arrPb.getType());
+      csb = new ColumnSchema.ColumnSchemaBuilder(pb.getName(), elemType);
+      csb.array(true);
+      // TODO(achennaka): Re-visit the read and write default implementation 
for nested types.
+      defaultValue = pb.hasWriteDefaultValue() ?
+              byteStringToObject(elemType, typeAttributes, 
pb.getWriteDefaultValue()) : null;
+    } else {
+      Type type = Type.getTypeForDataType(pb.getType());
+      csb = new ColumnSchema.ColumnSchemaBuilder(pb.getName(), type);
+      defaultValue = pb.hasWriteDefaultValue() ?
+              byteStringToObject(type, typeAttributes, 
pb.getWriteDefaultValue()) : null;
+    }
     if (pb.getIsKey() && isKeyUnique) {
       csb.key(true);
     } else {
diff --git 
a/java/kudu-client/src/test/java/org/apache/kudu/TestColumnSchema.java 
b/java/kudu-client/src/test/java/org/apache/kudu/TestColumnSchema.java
index c1fd3c9e5..c11300ecf 100644
--- a/java/kudu-client/src/test/java/org/apache/kudu/TestColumnSchema.java
+++ b/java/kudu-client/src/test/java/org/apache/kudu/TestColumnSchema.java
@@ -159,4 +159,75 @@ public class TestColumnSchema {
     Assert.assertTrue(thrown.getMessage().contains("Column name " +
         Schema.getAutoIncrementingColumnName() + " is reserved by Kudu 
engine"));
   }
+
+  @Test
+  public void testArrayTypeColumn() {
+    // STRING[]
+    ColumnSchema strArrayCol = new ColumnSchema.ColumnSchemaBuilder("str_arr", 
Type.STRING)
+        .array(true)      // promotes to NESTED with element type STRING
+        .nullable(true)   // the array itself can be null
+        .build();
+
+    assertEquals("str_arr", strArrayCol.getName());
+    assertEquals(Type.NESTED, strArrayCol.getType());
+    Assert.assertTrue(strArrayCol.isNullable());
+    assertEquals(Type.STRING,
+        
strArrayCol.getNestedTypeDescriptor().getArrayDescriptor().getElemType());
+
+    // DECIMAL(10,4)[]
+    ColumnTypeAttributes decimalAttrs = new 
ColumnTypeAttributes.ColumnTypeAttributesBuilder()
+         .precision(10)
+         .scale(4)
+         .build();
+
+    ColumnSchema decimalArrayCol = new 
ColumnSchema.ColumnSchemaBuilder("dec_arr", Type.DECIMAL)
+        .typeAttributes(decimalAttrs)
+        .array(true)
+        .nullable(true)
+        .build();
+
+    assertEquals("dec_arr", decimalArrayCol.getName());
+    assertEquals(Type.NESTED, decimalArrayCol.getType());
+    Assert.assertTrue(decimalArrayCol.isNullable());
+    assertEquals(Type.DECIMAL,
+        
decimalArrayCol.getNestedTypeDescriptor().getArrayDescriptor().getElemType());
+
+    // INT32[] (non-nullable)
+    ColumnSchema intArrayCol = new ColumnSchema.ColumnSchemaBuilder("int_arr", 
Type.INT32)
+        .array(true)
+        .nullable(false)
+        .build();
+
+    assertEquals("int_arr", intArrayCol.getName());
+    assertEquals(Type.NESTED, intArrayCol.getType());
+    Assert.assertFalse(intArrayCol.isNullable());
+    assertEquals(Type.INT32,
+        
intArrayCol.getNestedTypeDescriptor().getArrayDescriptor().getElemType());
+
+    // VARCHAR(50)[]
+    ColumnTypeAttributes varcharAttrs = new 
ColumnTypeAttributes.ColumnTypeAttributesBuilder()
+        .length(50)
+        .build();
+
+    ColumnSchema varcharArrayCol = new 
ColumnSchema.ColumnSchemaBuilder("varchar_arr", Type.VARCHAR)
+        .typeAttributes(varcharAttrs)
+        .array(true)
+        .nullable(true)
+        .build();
+
+    assertEquals("varchar_arr", varcharArrayCol.getName());
+    assertEquals(Type.NESTED, varcharArrayCol.getType());
+    Assert.assertTrue(varcharArrayCol.isNullable());
+    assertEquals(Type.VARCHAR,
+        
varcharArrayCol.getNestedTypeDescriptor().getArrayDescriptor().getElemType());
+
+    // Test constructor restriction: cannot pass NESTED directly
+    IllegalArgumentException thrown = 
Assert.assertThrows(IllegalArgumentException.class, () ->
+            new ColumnSchema.ColumnSchemaBuilder("nested", Type.NESTED)
+    );
+    Assert.assertTrue(thrown.getMessage().contains(
+        "Column nested cannot be set to NESTED type. Use 
ColumnSchemaBuilder.array(true) instead"
+    ));
+
+  }
 }
diff --git 
a/java/kudu-client/src/test/java/org/apache/kudu/client/TestKuduClient.java 
b/java/kudu-client/src/test/java/org/apache/kudu/client/TestKuduClient.java
index 8f7073b13..be42de4ec 100644
--- a/java/kudu-client/src/test/java/org/apache/kudu/client/TestKuduClient.java
+++ b/java/kudu-client/src/test/java/org/apache/kudu/client/TestKuduClient.java
@@ -26,6 +26,7 @@ import static 
org.apache.kudu.test.ClientTestUtil.countRowsInScan;
 import static org.apache.kudu.test.ClientTestUtil.createBasicSchemaInsert;
 import static org.apache.kudu.test.ClientTestUtil.createManyStringsSchema;
 import static org.apache.kudu.test.ClientTestUtil.createManyVarcharsSchema;
+import static org.apache.kudu.test.ClientTestUtil.createSchemaWithArrayColumns;
 import static 
org.apache.kudu.test.ClientTestUtil.createSchemaWithBinaryColumns;
 import static org.apache.kudu.test.ClientTestUtil.createSchemaWithDateColumns;
 import static 
org.apache.kudu.test.ClientTestUtil.createSchemaWithDecimalColumns;
@@ -1173,6 +1174,74 @@ public class TestKuduClient {
     client.deleteTable(TABLE_NAME);
   }
 
+  /**
+   * Test creating a table schema with an array type column.
+   */
+  @Test(timeout = 100000)
+  public void testArrayTypeColumnCreationAndAlter() throws Exception {
+    Schema schema = createSchemaWithArrayColumns();
+    client.createTable(TABLE_NAME, schema, getBasicCreateTableOptions());
+
+    KuduTable table = client.openTable(TABLE_NAME);
+    Schema returnedSchema = table.getSchema();
+
+    // Verify initial columns (as in your original test)
+    assertEquals(5, returnedSchema.getColumnCount());
+    assertTrue(returnedSchema.getColumn("int_arr").isArray());
+    assertFalse(returnedSchema.getColumn("int_arr").isKey());
+    assertTrue(returnedSchema.getColumn("nullable_int_arr").isArray());
+    assertTrue(returnedSchema.getColumn("dec_arr").isArray());
+    assertTrue(returnedSchema.getColumn("str_arr").isArray());
+
+    // ---- Sub-scenario: Alter table ----
+    AlterTableOptions alter = new AlterTableOptions();
+
+    // Add new array columns
+    ColumnSchema newFloatArray =
+            new ColumnSchema.ColumnSchemaBuilder("string_arr", Type.STRING)
+                    .array(true)
+                    .nullable(true)
+                    .build();
+    alter.addColumn(newFloatArray);
+
+    ColumnSchema newIntCol =
+            new ColumnSchema.ColumnSchemaBuilder("int_col", Type.INT32)
+                    .array(false)
+                    .nullable(true)
+                    .build();
+    alter.addColumn(newIntCol);
+
+    // Drop existing array column
+    alter.dropColumn("nullable_int_arr");
+
+    client.alterTable(TABLE_NAME, alter);
+
+    // Validate altered schema
+    table = client.openTable(TABLE_NAME);
+    returnedSchema = table.getSchema();
+
+    // Column count should stay 6 (added two, dropped one)
+    assertEquals(6, returnedSchema.getColumnCount());
+
+    // Check the new columns
+    ColumnSchema floatArr = returnedSchema.getColumn("string_arr");
+    assertTrue(floatArr.isArray());
+    assertTrue(floatArr.isNullable());
+    assertEquals(Type.NESTED, floatArr.getType());
+    assertEquals(Type.STRING,
+            
floatArr.getNestedTypeDescriptor().getArrayDescriptor().getElemType());
+    ColumnSchema intCol = returnedSchema.getColumn("int_col");
+    assertFalse(intCol.isArray());
+    assertTrue(intCol.isNullable());
+    assertNotEquals(Type.NESTED, intCol.getType());
+    assertEquals(Type.INT32, intCol.getType());
+
+    // Check dropped column is gone
+    assertFalse(returnedSchema.hasColumn("nullable_int_arr"));
+
+    client.deleteTable(TABLE_NAME);
+  }
+
   /**
    * Test inserting and retrieving rows from a table that has a range partition
    * with custom hash schema.
diff --git 
a/java/kudu-client/src/test/java/org/apache/kudu/client/TestPartialRow.java 
b/java/kudu-client/src/test/java/org/apache/kudu/client/TestPartialRow.java
index e2e01978e..df8dd4e70 100644
--- a/java/kudu-client/src/test/java/org/apache/kudu/client/TestPartialRow.java
+++ b/java/kudu-client/src/test/java/org/apache/kudu/client/TestPartialRow.java
@@ -489,7 +489,8 @@ public class TestPartialRow {
 
   // Shift the type one position to force the wrong type for all types.
   private Type getShiftedType(Type type) {
-    int shiftedPosition = (type.ordinal() + 1) % Type.values().length;
+    // TODO(achennaka) : remove the stopgap once serdes of array datatype is 
implemented.
+    int shiftedPosition = (type.ordinal() + 1) % (Type.values().length - 1);
     return Type.values()[shiftedPosition];
   }
 
diff --git 
a/java/kudu-spark-tools/src/test/scala/org/apache/kudu/spark/tools/DistributedDataGeneratorTest.scala
 
b/java/kudu-spark-tools/src/test/scala/org/apache/kudu/spark/tools/DistributedDataGeneratorTest.scala
index 4c7bd4681..e7486a39e 100644
--- 
a/java/kudu-spark-tools/src/test/scala/org/apache/kudu/spark/tools/DistributedDataGeneratorTest.scala
+++ 
b/java/kudu-spark-tools/src/test/scala/org/apache/kudu/spark/tools/DistributedDataGeneratorTest.scala
@@ -36,8 +36,9 @@ class DistributedDataGeneratorTest extends KuduTestSuite {
 
   private val generator = new SchemaGenerator.SchemaGeneratorBuilder()
     .random(RandomUtils.getRandom)
-    // These types don't have enough values to prevent collisions.
-    .excludeTypes(Type.BOOL, Type.INT8)
+    // BOOL and INT8 types don't have enough values to prevent collisions.
+    // NESTED cannot be set directly as a Type.
+    .excludeTypes(Type.BOOL, Type.INT8, Type.NESTED)
     // Ensure decimals have enough values to prevent collisions.
     .precisionRange(DecimalUtil.MAX_DECIMAL32_PRECISION, 
DecimalUtil.MAX_DECIMAL_PRECISION)
     .build()
diff --git 
a/java/kudu-test-utils/src/main/java/org/apache/kudu/test/ClientTestUtil.java 
b/java/kudu-test-utils/src/main/java/org/apache/kudu/test/ClientTestUtil.java
index 3cd85d015..888a12b8b 100644
--- 
a/java/kudu-test-utils/src/main/java/org/apache/kudu/test/ClientTestUtil.java
+++ 
b/java/kudu-test-utils/src/main/java/org/apache/kudu/test/ClientTestUtil.java
@@ -513,4 +513,41 @@ public abstract class ClientTestUtil {
         .build());
     return new Schema(columns);
   }
+
+  public static Schema createSchemaWithArrayColumns() {
+    ArrayList<ColumnSchema> columns = new ArrayList<>();
+    // Primary key column
+    columns.add(new ColumnSchema.ColumnSchemaBuilder("key", Type.INT32)
+            .key(true)
+            .build());
+
+    // Non-nullable 1D array of INT32
+    columns.add(new ColumnSchema.ColumnSchemaBuilder("int_arr", Type.INT32)
+            .array(true)
+            .nullable(false)
+            .build());
+
+    // Nullable 1D array of INT32
+    columns.add(new ColumnSchema.ColumnSchemaBuilder("nullable_int_arr", 
Type.INT32)
+            .array(true)
+            .nullable(true)
+            .build());
+
+    // Nullable 1D array of DECIMAL with precision/scale
+    columns.add(new ColumnSchema.ColumnSchemaBuilder("dec_arr", Type.DECIMAL)
+            .array(true)
+            .nullable(true)
+            .typeAttributes(new 
ColumnTypeAttributes.ColumnTypeAttributesBuilder()
+                    .precision(10)
+                    .scale(2)
+                    .build())
+            .build());
+
+    // Nullable 1D array of STRING
+    columns.add(new ColumnSchema.ColumnSchemaBuilder("str_arr", Type.STRING)
+            .array(true)
+            .nullable(true)
+            .build());
+    return new Schema(columns);
+  }
 }

Reply via email to