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 c56fb2464 KUDU-1261 [Java] Add read support for Array Type
c56fb2464 is described below
commit c56fb2464ecad91a58d4589e1075de20e0f75c71
Author: Abhishek Chennaka <[email protected]>
AuthorDate: Fri Oct 3 10:46:00 2025 -0700
KUDU-1261 [Java] Add read support for Array Type
Added support utilities for array cell handling in Kudu client
- Introduced ArrayCellViewHelper for consistent array data access/printing
- Extended RowResult and RowwiseRowResult with array-aware getters
- Added unit tests in TestKuduClient and TestRowResult for empty/null arrays
- Updated ClientTestUtil helpers to support array column creation
Change-Id: Ie26750cf540b0097c77e5454c1c1d20b3a194c52
Reviewed-on: http://gerrit.cloudera.org:8080/23483
Reviewed-by: Alexey Serbin <[email protected]>
Tested-by: Abhishek Chennaka <[email protected]>
---
java/config/spotbugs/excludeFilter.xml | 61 +++-
.../java/org/apache/kudu/client/ArrayCellView.java | 225 +++++++++---
.../apache/kudu/client/ArrayCellViewHelper.java | 358 +++++++++++++++++++
.../java/org/apache/kudu/client/RowResult.java | 245 ++++++++++++-
.../org/apache/kudu/client/RowwiseRowResult.java | 49 ++-
.../org/apache/kudu/client/TestKuduClient.java | 396 +++++++++++++++++++++
.../org/apache/kudu/client/TestPartialRow.java | 2 -
.../java/org/apache/kudu/client/TestRowResult.java | 139 ++++++++
.../java/org/apache/kudu/test/ClientTestUtil.java | 32 ++
9 files changed, 1438 insertions(+), 69 deletions(-)
diff --git a/java/config/spotbugs/excludeFilter.xml
b/java/config/spotbugs/excludeFilter.xml
index 453965d55..005fbea26 100644
--- a/java/config/spotbugs/excludeFilter.xml
+++ b/java/config/spotbugs/excludeFilter.xml
@@ -174,8 +174,8 @@
</Match>
<!-- kudu-client exclusions -->
+ <!-- For invalid elements we return null. -->
<Match>
- <!-- For invalid elements we return null. -->
<Class name="org.apache.kudu.client.ArrayCellView"/>
<Method name="getBinary" />
<Bug pattern="PZLA_PREFER_ZERO_LENGTH_ARRAYS" />
@@ -185,6 +185,65 @@
<Method name="normalizeValidity" />
<Bug pattern="PZLA_PREFER_ZERO_LENGTH_ARRAYS" />
</Match>
+ <Match>
+ <Class name="org.apache.kudu.client.RowResult"/>
+ <Method name="getArrayBytes" />
+ <Bug pattern="PZLA_PREFER_ZERO_LENGTH_ARRAYS" />
+ </Match>
+ <!-- FlatBuffers union accessors always return the correct subclass;
+ safe cast verified by ensureTag()
+ -->
+ <Match>
+ <Class name="org.apache.kudu.client.ArrayCellView"/>
+ <Method name="asBinaryFB" />
+ <Bug pattern="PZLA_PREFER_ZERO_LENGTH_ARRAYS" />
+ </Match>
+
+ <Match>
+ <Class name="org.apache.kudu.client.ArrayCellView"/>
+ <Method name="asDoubleFB" />
+ <Bug pattern="BC_UNCONFIRMED_CAST_OF_RETURN_VALUE" />
+ </Match>
+ <Match>
+ <Class name="org.apache.kudu.client.ArrayCellView"/>
+ <Method name="asBinaryFB" />
+ <Bug pattern="BC_UNCONFIRMED_CAST_OF_RETURN_VALUE" />
+ </Match>
+ <Match>
+ <Class name="org.apache.kudu.client.ArrayCellView"/>
+ <Method name="asFloatFB" />
+ <Bug pattern="BC_UNCONFIRMED_CAST_OF_RETURN_VALUE" />
+ </Match>
+ <Match>
+ <Class name="org.apache.kudu.client.ArrayCellView"/>
+ <Method name="asInt16FB" />
+ <Bug pattern="BC_UNCONFIRMED_CAST_OF_RETURN_VALUE" />
+ </Match>
+ <Match>
+ <Class name="org.apache.kudu.client.ArrayCellView"/>
+ <Method name="asInt32FB" />
+ <Bug pattern="BC_UNCONFIRMED_CAST_OF_RETURN_VALUE" />
+ </Match>
+ <Match>
+ <Class name="org.apache.kudu.client.ArrayCellView"/>
+ <Method name="asInt64FB" />
+ <Bug pattern="BC_UNCONFIRMED_CAST_OF_RETURN_VALUE" />
+ </Match>
+ <Match>
+ <Class name="org.apache.kudu.client.ArrayCellView"/>
+ <Method name="asInt8FB" />
+ <Bug pattern="BC_UNCONFIRMED_CAST_OF_RETURN_VALUE" />
+ </Match>
+ <Match>
+ <Class name="org.apache.kudu.client.ArrayCellView"/>
+ <Method name="asStringFB" />
+ <Bug pattern="BC_UNCONFIRMED_CAST_OF_RETURN_VALUE" />
+ </Match>
+ <Match>
+ <Class name="org.apache.kudu.client.ArrayCellView"/>
+ <Method name="asUInt8FB" />
+ <Bug pattern="BC_UNCONFIRMED_CAST_OF_RETURN_VALUE" />
+ </Match>
<Match>
<!-- Reference equality is intended here. -->
<Class name="org.apache.kudu.client.AsyncKuduClient"/>
diff --git
a/java/kudu-client/src/main/java/org/apache/kudu/client/ArrayCellView.java
b/java/kudu-client/src/main/java/org/apache/kudu/client/ArrayCellView.java
index d16798efb..62a0266e6 100644
--- a/java/kudu-client/src/main/java/org/apache/kudu/client/ArrayCellView.java
+++ b/java/kudu-client/src/main/java/org/apache/kudu/client/ArrayCellView.java
@@ -22,6 +22,8 @@ import java.nio.ByteOrder;
import java.util.Arrays;
import java.util.function.IntFunction;
+import org.apache.kudu.ColumnSchema;
+import org.apache.kudu.Type;
import org.apache.kudu.serdes.BinaryArray;
import org.apache.kudu.serdes.Content;
import org.apache.kudu.serdes.DoubleArray;
@@ -40,7 +42,7 @@ import org.apache.kudu.serdes.UInt8Array;
/**
* Lightweight view over a FlatBuffers Content blob representing a single
array cell.
*/
-public final class ArrayCellView {
+class ArrayCellView {
private final byte[] rawBytes;
private final Content content;
@@ -48,7 +50,14 @@ public final class ArrayCellView {
private static final String NULL_ELEMENT_MSG = "Element %d is NULL";
- public ArrayCellView(byte[] buf) {
+ /**
+ * Construct an {@code ArrayCellView} from a serialized FlatBuffer blob.
+ * The byte array is not copied; callers must ensure it remains valid.
+ *
+ * @param buf the FlatBuffer bytes for a {@code Content} object
+ */
+
+ ArrayCellView(byte[] buf) {
this.rawBytes = buf;
ByteBuffer bb = ByteBuffer.wrap(buf).order(ByteOrder.LITTLE_ENDIAN);
this.content = Content.getRootAsContent(bb);
@@ -56,12 +65,13 @@ public final class ArrayCellView {
}
/** Return the underlying FlatBuffer bytes exactly as passed in. */
- public byte[] toBytes() {
+
+ byte[] toBytes() {
return rawBytes;
}
/** Number of logical elements (driven by the data vector). */
- public int length() {
+ int length() {
switch (typeTag) {
case ScalarArray.Int8Array: {
Int8Array arr = new Int8Array();
@@ -130,7 +140,7 @@ public final class ArrayCellView {
}
/** Returns true iff element i is valid (non-null). */
- public boolean isValid(int i) {
+ boolean isValid(int i) {
int n = length();
if (i < 0 || i >= n) {
throw new IndexOutOfBoundsException(
@@ -151,12 +161,38 @@ public final class ArrayCellView {
return content.validity(i);
}
+ /**
+ * Returns a boolean array of element validity flags.
+ *
+ * @param n expected number of elements in the values vector
+ * @throws IllegalStateException if validity length and value length mismatch
+ */
+
+ boolean[] validityOrAllTrue(int n) {
+ int len = content.validityLength();
+ if (len == 0) {
+ boolean[] allTrue = new boolean[n];
+ java.util.Arrays.fill(allTrue, true);
+ return allTrue;
+ }
+ if (len != n) {
+ throw new IllegalStateException(
+ String.format("Validity length %d does not match values length %d",
len, n));
+ }
+ boolean[] v = new boolean[n];
+ for (int i = 0; i < n; i++) {
+ v[i] = content.validity(i);
+ }
+ return v;
+ }
+
// ----------------------------------------------------------------------
// Types single element accessors
// ----------------------------------------------------------------------
/** BOOL is encoded as UInt8 (0/1). */
- public boolean getBoolean(int i) {
+
+ boolean getBoolean(int i) {
ensureTag(ScalarArray.UInt8Array);
if (!isValid(i)) {
throw new IllegalStateException(String.format(NULL_ELEMENT_MSG, i));
@@ -166,7 +202,8 @@ public final class ArrayCellView {
return arr.values(i) != 0;
}
- public byte getInt8(int i) {
+
+ byte getInt8(int i) {
ensureTag(ScalarArray.Int8Array);
if (!isValid(i)) {
throw new IllegalStateException(String.format(NULL_ELEMENT_MSG, i));
@@ -176,7 +213,8 @@ public final class ArrayCellView {
return arr.values(i);
}
- public short getInt16(int i) {
+
+ short getInt16(int i) {
ensureTag(ScalarArray.Int16Array);
if (!isValid(i)) {
throw new IllegalStateException(String.format(NULL_ELEMENT_MSG, i));
@@ -187,7 +225,8 @@ public final class ArrayCellView {
}
/** INT32 (also used by DATE, DECIMAL32 on the wire). */
- public int getInt32(int i) {
+
+ int getInt32(int i) {
ensureTag(ScalarArray.Int32Array);
if (!isValid(i)) {
throw new IllegalStateException(String.format(NULL_ELEMENT_MSG, i));
@@ -198,7 +237,8 @@ public final class ArrayCellView {
}
/** INT64 (also used by DECIMAL64, UNIXTIME_MICROS on the wire). */
- public long getInt64(int i) {
+
+ long getInt64(int i) {
ensureTag(ScalarArray.Int64Array);
if (!isValid(i)) {
throw new IllegalStateException(String.format(NULL_ELEMENT_MSG, i));
@@ -208,7 +248,8 @@ public final class ArrayCellView {
return arr.values(i);
}
- public float getFloat(int i) {
+
+ float getFloat(int i) {
ensureTag(ScalarArray.FloatArray);
if (!isValid(i)) {
throw new IllegalStateException(String.format(NULL_ELEMENT_MSG, i));
@@ -218,7 +259,8 @@ public final class ArrayCellView {
return arr.values(i);
}
- public double getDouble(int i) {
+
+ double getDouble(int i) {
ensureTag(ScalarArray.DoubleArray);
if (!isValid(i)) {
throw new IllegalStateException(String.format(NULL_ELEMENT_MSG, i));
@@ -228,27 +270,28 @@ public final class ArrayCellView {
return arr.values(i);
}
- public String getString(int i) {
+
+ String getString(int i) {
ensureTag(ScalarArray.StringArray);
if (!isValid(i)) {
- return null;
+ throw new IllegalStateException(String.format(NULL_ELEMENT_MSG, i));
}
StringArray arr = new StringArray();
content.data(arr);
return arr.values(i);
}
- public byte[] getBinary(int i) {
+
+ byte[] getBinary(int i) {
ensureTag(ScalarArray.BinaryArray);
if (!isValid(i)) {
- return null;
+ throw new IllegalStateException(String.format(NULL_ELEMENT_MSG, i));
}
BinaryArray arr = new BinaryArray();
content.data(arr);
UInt8Array elem = arr.values(new UInt8Array(), i);
if (elem == null) {
- throw new IllegalStateException(
- String.format("Corrupt BinaryArray: missing element at index %d",
i));
+ throw new IllegalStateException(String.format(NULL_ELEMENT_MSG, i));
}
ByteBuffer bb = elem.valuesAsByteBuffer();
byte[] out = new byte[bb.remaining()];
@@ -257,33 +300,12 @@ public final class ArrayCellView {
}
- // ----------------------------------------------------------------------
- // Bulk conversion for callers that want plain Java arrays
- // ----------------------------------------------------------------------
-
- public Object toJavaArray() {
- switch (typeTag) {
- case ScalarArray.Int32Array: return
Array1dSerdes.parseInt32(rawBytes).getValues();
- case ScalarArray.Int64Array: return
Array1dSerdes.parseInt64(rawBytes).getValues();
- case ScalarArray.Int16Array: return
Array1dSerdes.parseInt16(rawBytes).getValues();
- case ScalarArray.Int8Array: return
Array1dSerdes.parseInt8(rawBytes).getValues();
- case ScalarArray.UInt8Array: return
Array1dSerdes.parseUInt8(rawBytes).getValues();
- case ScalarArray.UInt16Array: return
Array1dSerdes.parseUInt16(rawBytes).getValues();
- case ScalarArray.UInt32Array: return
Array1dSerdes.parseUInt32(rawBytes).getValues();
- case ScalarArray.UInt64Array: return
Array1dSerdes.parseUInt64(rawBytes).getValues();
- case ScalarArray.FloatArray: return
Array1dSerdes.parseFloat(rawBytes).getValues();
- case ScalarArray.DoubleArray: return
Array1dSerdes.parseDouble(rawBytes).getValues();
- case ScalarArray.StringArray: return
Array1dSerdes.parseString(rawBytes).getValues();
- case ScalarArray.BinaryArray: return
Array1dSerdes.parseBinary(rawBytes).getValues();
- default:
- throw new UnsupportedOperationException("Unsupported array type tag: "
+ typeTag);
- }
- }
-
- // ----------------------------------------------------------------------
- // Pretty print with NULLs preserved
- // ----------------------------------------------------------------------
-
+ /**
+ * Returns a human-readable string representation of the array contents,
+ * preserving {@code NULL} markers for invalid elements.
+ *
+ * <p>This is intended for debugging and should not be parsed.</p>
+ */
@Override
public String toString() {
final int n = length();
@@ -383,6 +405,12 @@ public final class ArrayCellView {
return sb.toString();
}
+ /**
+ * Verifies that this array's type tag matches the expected
+ * physical array type.
+ *
+ * @throws IllegalStateException if the tag does not match
+ */
private void ensureTag(byte expectedTag) {
if (typeTag != expectedTag) {
String expectedName = ScalarArray.name(expectedTag);
@@ -394,6 +422,10 @@ public final class ArrayCellView {
}
}
+ /**
+ * Appends formatted element values to the provided {@link StringBuilder},
+ * substituting {@code NULL} for invalid entries.
+ */
private void appendArrayValuesToString(
StringBuilder sb, int n,
IntFunction<Object> valueFn) {
@@ -409,4 +441,107 @@ public final class ArrayCellView {
}
}
+ /**
+ * Converts this array cell into a boxed Java array using the column schema
+ * to resolve the element's logical type and column attributes.
+ *
+ * <p>This variant interprets the element type (for example,
+ * {@code DATE}, {@code TIMESTAMP}, or {@code DECIMAL}) using the
+ * provided {@link ColumnSchema}, and applies precision and scale
+ * information when relevant.</p>
+ *
+ * <p>Internally delegates to
+ * {@link ArrayCellViewHelper#toJavaArray(ArrayCellView, ColumnSchema)}.</p>
+ *
+ * @param columnSchema the schema describing this array column, including
+ * logical type, precision, and scale
+ * @return a boxed Java array corresponding to the logical element type
+ */
+
+ Object toJavaArray(ColumnSchema columnSchema) {
+ return ArrayCellViewHelper.toJavaArray(this, columnSchema);
+ }
+
+ /**
+ * Converts this array cell into a boxed Java array without schema context.
+ *
+ * <p>This variant performs a physical-to-Java mapping based only on
+ * the array's storage type and does not apply any logical-type
+ * conversions or precision/scale metadata.</p>
+ *
+ * <p>For {@code DECIMAL} arrays, prefer
+ * {@link #toJavaArray(ColumnSchema)} to ensure correct precision and
+ * scale are applied.</p>
+ *
+ * @return a boxed Java array corresponding to the underlying storage type
+ */
+
+ Object toJavaArray() {
+ return ArrayCellViewHelper.toJavaArray(this, null);
+ }
+
+
+ byte typeTag() {
+ return typeTag;
+ }
+
+ /**
+ * Returns the underlying FlatBuffer view of this array's physical values
+ * (for internal use or advanced diagnostics). Validates that the
+ * {@code typeTag} matches the expected array type.
+ */
+
+ Int8Array asInt8FB() {
+ ensureTag(ScalarArray.Int8Array);
+ return (Int8Array) content.data(new Int8Array());
+ }
+
+
+ Int16Array asInt16FB() {
+ ensureTag(ScalarArray.Int16Array);
+ return (Int16Array) content.data(new Int16Array());
+ }
+
+
+ Int32Array asInt32FB() {
+ ensureTag(ScalarArray.Int32Array);
+ return (Int32Array) content.data(new Int32Array());
+ }
+
+
+ Int64Array asInt64FB() {
+ ensureTag(ScalarArray.Int64Array);
+ return (Int64Array) content.data(new Int64Array());
+ }
+
+
+ FloatArray asFloatFB() {
+ ensureTag(ScalarArray.FloatArray);
+ return (FloatArray) content.data(new FloatArray());
+ }
+
+
+ DoubleArray asDoubleFB() {
+ ensureTag(ScalarArray.DoubleArray);
+ return (DoubleArray) content.data(new DoubleArray());
+ }
+
+
+ UInt8Array asUInt8FB() {
+ ensureTag(ScalarArray.UInt8Array);
+ return (UInt8Array) content.data(new UInt8Array());
+ }
+
+
+ StringArray asStringFB() {
+ ensureTag(ScalarArray.StringArray);
+ return (StringArray) content.data(new StringArray());
+ }
+
+
+ BinaryArray asBinaryFB() {
+ ensureTag(ScalarArray.BinaryArray);
+ return (BinaryArray) content.data(new BinaryArray());
+ }
+
}
\ No newline at end of file
diff --git
a/java/kudu-client/src/main/java/org/apache/kudu/client/ArrayCellViewHelper.java
b/java/kudu-client/src/main/java/org/apache/kudu/client/ArrayCellViewHelper.java
new file mode 100644
index 000000000..b753cf9c2
--- /dev/null
+++
b/java/kudu-client/src/main/java/org/apache/kudu/client/ArrayCellViewHelper.java
@@ -0,0 +1,358 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.kudu.client;
+
+import java.math.BigDecimal;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.DoubleBuffer;
+import java.nio.FloatBuffer;
+import java.nio.IntBuffer;
+import java.nio.LongBuffer;
+import java.nio.ShortBuffer;
+import java.sql.Date;
+import java.sql.Timestamp;
+import java.util.Objects;
+
+import org.apache.kudu.ColumnSchema;
+import org.apache.kudu.Type;
+import org.apache.kudu.serdes.BinaryArray;
+import org.apache.kudu.serdes.DoubleArray;
+import org.apache.kudu.serdes.FloatArray;
+import org.apache.kudu.serdes.Int16Array;
+import org.apache.kudu.serdes.Int32Array;
+import org.apache.kudu.serdes.Int64Array;
+import org.apache.kudu.serdes.Int8Array;
+import org.apache.kudu.serdes.ScalarArray;
+import org.apache.kudu.serdes.StringArray;
+import org.apache.kudu.serdes.UInt8Array;
+import org.apache.kudu.util.DateUtil;
+import org.apache.kudu.util.TimestampUtil;
+
+
+class ArrayCellViewHelper {
+
+ private ArrayCellViewHelper() {
+ }
+
+ // ----------------------------------------------------------------------
+ // Common helpers
+ // ----------------------------------------------------------------------
+
+ private static boolean[] validity(ArrayCellView view, int n) {
+ return view.validityOrAllTrue(n);
+ }
+
+ // ----------------------------------------------------------------------
+ // Boxed numeric arrays with null (validity) handling
+ // ----------------------------------------------------------------------
+
+ // Decodes a BOOL array (physically encoded as a UInt8Array) from its
FlatBuffer view.
+ // Each element is stored as a single byte (0 or 1), not bit-packed.
+ // Using valuesAsByteBuffer() is significantly faster than calling
arr.values(i) per element,
+ // but FlatBuffers may return a ByteBuffer whose position is not guaranteed
to start at 0.
+ // To ensure correctness, compute the base offset and perform absolute reads
(base + i),
+ // which avoids position drift and unnecessary buffer slicing while
remaining allocation-free.
+ static Boolean[] toBoolArray(ArrayCellView view) {
+ UInt8Array arr = view.asUInt8FB();
+ int n = arr.valuesLength();
+ boolean[] v = validity(view, n);
+ ByteBuffer bb = arr.valuesAsByteBuffer();
+ bb.order(ByteOrder.LITTLE_ENDIAN);
+ int base = bb.position();
+ Boolean[] out = new Boolean[n];
+ for (int i = 0; i < n; i++) {
+ if (!v[i]) {
+ out[i] = null;
+ continue;
+ }
+ out[i] = (bb.get(base + i) & 0xFF) != 0;
+ }
+ return out;
+ }
+
+ // Decodes an INT8 array (physically represented as an Int8Array) from its
FlatBuffer view.
+ // Each element is stored as a signed 8-bit value in little-endian order.
+ // Similar to toBoolArray(), this implementation accesses the underlying
ByteBuffer directly
+ // to avoid the overhead of calling arr.values(i) for every element.
+ // Because FlatBuffers may return a ByteBuffer whose position is not
guaranteed to be 0,
+ // the code computes the base offset and uses absolute reads (base + i) to
prevent
+ // position drift and unnecessary slicing while remaining allocation-free.
+ // Null elements are filled with Java nulls according to the validity bitmap.
+ static Byte[] toInt8Array(ArrayCellView view) {
+ Int8Array arr = view.asInt8FB();
+ int n = arr.valuesLength();
+ boolean[] v = validity(view, n);
+ ByteBuffer bb = arr.valuesAsByteBuffer();
+ bb.order(ByteOrder.LITTLE_ENDIAN);
+ int base = bb.position();
+ Byte[] out = new Byte[n];
+ for (int i = 0; i < n; i++) {
+ out[i] = v[i] ? bb.get(base + i) : null;
+ }
+ return out;
+ }
+
+ static Short[] toInt16Array(ArrayCellView view) {
+ Int16Array arr = view.asInt16FB();
+ int n = arr.valuesLength();
+ boolean[] v = validity(view, n);
+ ShortBuffer sb =
arr.valuesAsByteBuffer().order(ByteOrder.LITTLE_ENDIAN).asShortBuffer();
+ short[] tmp = new short[n];
+ sb.get(tmp);
+ Short[] out = new Short[n];
+ for (int i = 0; i < n; i++) {
+ out[i] = v[i] ? tmp[i] : null;
+ }
+ return out;
+ }
+
+ static Integer[] toInt32Array(ArrayCellView view) {
+ Int32Array arr = view.asInt32FB();
+ int n = arr.valuesLength();
+ boolean[] v = validity(view, n);
+ IntBuffer ib =
arr.valuesAsByteBuffer().order(ByteOrder.LITTLE_ENDIAN).asIntBuffer();
+ int[] tmp = new int[n];
+ ib.get(tmp);
+ Integer[] out = new Integer[n];
+ for (int i = 0; i < n; i++) {
+ out[i] = v[i] ? tmp[i] : null;
+ }
+ return out;
+ }
+
+ static Long[] toInt64Array(ArrayCellView view) {
+ Int64Array arr = view.asInt64FB();
+ int n = arr.valuesLength();
+ boolean[] v = validity(view, n);
+ LongBuffer lb =
arr.valuesAsByteBuffer().order(ByteOrder.LITTLE_ENDIAN).asLongBuffer();
+ long[] tmp = new long[n];
+ lb.get(tmp);
+ Long[] out = new Long[n];
+ for (int i = 0; i < n; i++) {
+ out[i] = v[i] ? tmp[i] : null;
+ }
+ return out;
+ }
+
+ static Float[] toFloatArray(ArrayCellView view) {
+ FloatArray arr = view.asFloatFB();
+ int n = arr.valuesLength();
+ boolean[] v = validity(view, n);
+ FloatBuffer fb =
arr.valuesAsByteBuffer().order(ByteOrder.LITTLE_ENDIAN).asFloatBuffer();
+ float[] tmp = new float[n];
+ fb.get(tmp);
+ Float[] out = new Float[n];
+ for (int i = 0; i < n; i++) {
+ out[i] = v[i] ? tmp[i] : null;
+ }
+ return out;
+ }
+
+ static Double[] toDoubleArray(ArrayCellView view) {
+ DoubleArray arr = view.asDoubleFB();
+ int n = arr.valuesLength();
+ boolean[] v = validity(view, n);
+ DoubleBuffer db =
arr.valuesAsByteBuffer().order(ByteOrder.LITTLE_ENDIAN).asDoubleBuffer();
+ double[] tmp = new double[n];
+ db.get(tmp);
+ Double[] out = new Double[n];
+ for (int i = 0; i < n; i++) {
+ out[i] = v[i] ? tmp[i] : null;
+ }
+ return out;
+ }
+
+ // ----------------------------------------------------------------------
+ // String-like types
+ // ----------------------------------------------------------------------
+
+ static String[] toStringArray(ArrayCellView view) {
+ StringArray arr = view.asStringFB();
+ int n = arr.valuesLength();
+ boolean[] v = validity(view, n);
+ String[] out = new String[n];
+ for (int i = 0; i < n; i++) {
+ out[i] = v[i] ? arr.values(i) : null;
+ }
+ return out;
+ }
+
+ static String[] toVarcharArray(ArrayCellView view) {
+ // identical to String arrays, truncation handled on write side
+ return toStringArray(view);
+ }
+
+ static byte[][] toBinaryArray(ArrayCellView view) {
+ BinaryArray arr = view.asBinaryFB();
+ int n = arr.valuesLength();
+ boolean[] v = validity(view, n);
+ byte[][] out = new byte[n][];
+ UInt8Array elem = new UInt8Array();
+ for (int i = 0; i < n; i++) {
+ if (!v[i]) {
+ out[i] = null;
+ continue;
+ }
+ ByteBuffer bb = Objects.requireNonNull(arr.values(elem,
i)).valuesAsByteBuffer();
+ byte[] b = new byte[bb.remaining()];
+ bb.get(b);
+ out[i] = b;
+ }
+ return out;
+ }
+
+ // ----------------------------------------------------------------------
+ // Date and timestamp
+ // ----------------------------------------------------------------------
+
+ static Date[] toDateArray(ArrayCellView view) {
+ Int32Array arr = view.asInt32FB();
+ int n = arr.valuesLength();
+ boolean[] v = validity(view, n);
+ IntBuffer ib =
arr.valuesAsByteBuffer().order(ByteOrder.LITTLE_ENDIAN).asIntBuffer();
+ int[] tmp = new int[n];
+ ib.get(tmp);
+ Date[] out = new Date[n];
+ for (int i = 0; i < n; i++) {
+ out[i] = v[i] ? DateUtil.epochDaysToSqlDate(tmp[i]) : null;
+ }
+ return out;
+ }
+
+ static Timestamp[] toTimestampArray(ArrayCellView view) {
+ Int64Array arr = view.asInt64FB();
+ int n = arr.valuesLength();
+ boolean[] v = validity(view, n);
+ LongBuffer lb =
arr.valuesAsByteBuffer().order(ByteOrder.LITTLE_ENDIAN).asLongBuffer();
+ long[] tmp = new long[n];
+ lb.get(tmp);
+ Timestamp[] out = new Timestamp[n];
+ for (int i = 0; i < n; i++) {
+ out[i] = v[i] ? TimestampUtil.microsToTimestamp(tmp[i]) : null;
+ }
+ return out;
+ }
+
+ // ----------------------------------------------------------------------
+ // Decimal
+ // ----------------------------------------------------------------------
+
+ static BigDecimal[] toDecimalArray(ArrayCellView view, int precision, int
scale) {
+ boolean[] v;
+ BigDecimal[] out;
+ if (precision <= 9) {
+ Int32Array arr = view.asInt32FB();
+ int n = arr.valuesLength();
+ v = validity(view, n);
+ IntBuffer ib =
arr.valuesAsByteBuffer().order(ByteOrder.LITTLE_ENDIAN).asIntBuffer();
+ int[] tmp = new int[n];
+ ib.get(tmp);
+ out = new BigDecimal[n];
+ for (int i = 0; i < n; i++) {
+ out[i] = v[i] ? BigDecimal.valueOf(tmp[i], scale) : null;
+ }
+ } else if (precision <= 18) {
+ Int64Array arr = view.asInt64FB();
+ int n = arr.valuesLength();
+ v = validity(view, n);
+ LongBuffer lb =
arr.valuesAsByteBuffer().order(ByteOrder.LITTLE_ENDIAN).asLongBuffer();
+ long[] tmp = new long[n];
+ lb.get(tmp);
+ out = new BigDecimal[n];
+ for (int i = 0; i < n; i++) {
+ out[i] = v[i] ? BigDecimal.valueOf(tmp[i], scale) : null;
+ }
+ } else {
+ throw new IllegalStateException("DECIMAL128 arrays not yet supported");
+ }
+ return out;
+ }
+
+ // ----------------------------------------------------------------------
+ // Generic dispatcher
+ // ----------------------------------------------------------------------
+
+ static Object toJavaArray(ArrayCellView view,
+ ColumnSchema col) {
+
+ int precision = 0;
+ int scale = 0;
+ if (col.getTypeAttributes() != null) {
+ precision = col.getTypeAttributes().getPrecision();
+ scale = col.getTypeAttributes().getScale();
+ }
+ Type logicalType =
col.getNestedTypeDescriptor().getArrayDescriptor().getElemType();
+
+ switch (view.typeTag()) {
+ case ScalarArray.Int8Array:
+ return toInt8Array(view);
+
+ case ScalarArray.UInt8Array:
+ // BOOL and UINT8 share the same physical encoding.
+ if (logicalType == org.apache.kudu.Type.BOOL) {
+ return toBoolArray(view);
+ }
+ return toInt8Array(view);
+
+ case ScalarArray.Int16Array:
+ return toInt16Array(view);
+
+ case ScalarArray.Int32Array: {
+ // DECIMAL32, DATE, or plain INT32
+ if (precision > 0) {
+ return toDecimalArray(view, precision, scale);
+ }
+ if (logicalType == org.apache.kudu.Type.DATE) {
+ return toDateArray(view);
+ }
+ return toInt32Array(view);
+ }
+
+ case ScalarArray.Int64Array: {
+ // DECIMAL64, UNIXTIME_MICROS, or plain INT64
+ if (precision > 0) {
+ return toDecimalArray(view, precision, scale);
+ }
+ if (logicalType == org.apache.kudu.Type.UNIXTIME_MICROS) {
+ return toTimestampArray(view);
+ }
+ return toInt64Array(view);
+ }
+
+ case ScalarArray.FloatArray:
+ return toFloatArray(view);
+
+ case ScalarArray.DoubleArray:
+ return toDoubleArray(view);
+
+ case ScalarArray.StringArray:
+ // STRING and VARCHAR share the same encoding
+ return toStringArray(view);
+
+ case ScalarArray.BinaryArray:
+ return toBinaryArray(view);
+
+ default:
+ throw new IllegalStateException(String.format(
+ "Unsupported array type tag %d (%s)",
+ view.typeTag(), ScalarArray.name(view.typeTag())));
+ }
+ }
+
+}
\ No newline at end of file
diff --git
a/java/kudu-client/src/main/java/org/apache/kudu/client/RowResult.java
b/java/kudu-client/src/main/java/org/apache/kudu/client/RowResult.java
index b3e81cfc8..8e5c036b4 100644
--- a/java/kudu-client/src/main/java/org/apache/kudu/client/RowResult.java
+++ b/java/kudu-client/src/main/java/org/apache/kudu/client/RowResult.java
@@ -19,10 +19,13 @@ package org.apache.kudu.client;
import java.math.BigDecimal;
import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
import java.sql.Date;
import java.sql.Timestamp;
import java.util.Arrays;
+import javax.annotation.Nullable;
+import com.google.common.base.Preconditions;
import org.apache.yetus.audience.InterfaceAudience;
import org.apache.yetus.audience.InterfaceStability;
@@ -458,7 +461,8 @@ public abstract class RowResult {
* This method is useful when you don't care about autoboxing
* and your existing type handling logic is based on Java types.
*
- * The Object type is based on the column's {@link Type}:
+ * The returned Object type depends on the column's definition:
+ * Array columns -> {@link ArrayCellView}
* Type.BOOL -> java.lang.Boolean
* Type.INT8 -> java.lang.Byte
* Type.INT16 -> java.lang.Short
@@ -476,13 +480,20 @@ public abstract class RowResult {
* @param columnIndex Column index in the schema
* @return the column's value as an Object, null if the value is null
* @throws IndexOutOfBoundsException if the column doesn't exist
+ * @throws UnsupportedOperationException if the column type is unsupported
*/
public final Object getObject(int columnIndex) {
checkValidColumn(columnIndex);
if (isNull(columnIndex)) {
return null;
}
- Type type = schema.getColumnByIndex(columnIndex).getType();
+ ColumnSchema col = schema.getColumnByIndex(columnIndex);
+
+ // Special-case: array columns -> return ArrayCellView
+ if (col.isArray()) {
+ return getArrayData(columnIndex);
+ }
+ Type type = col.getType();
switch (type) {
case BOOL: return getBoolean(columnIndex);
case INT8: return getByte(columnIndex);
@@ -595,16 +606,96 @@ public abstract class RowResult {
if (i != 0) {
buf.append(", ");
}
- Type type = col.getType();
- buf.append(type.name());
+ // Show the element type for arrays; otherwise the scalar column type.
+ final Type shownType = col.isArray() ?
+ col.getNestedTypeDescriptor().getArrayDescriptor().getElemType()
+ : col.getType();
+
+ buf.append(shownType.name());
+ if (col.isArray()) {
+ buf.append("[]");
+ }
buf.append(" ").append(col.getName());
if (col.getTypeAttributes() != null) {
- buf.append(col.getTypeAttributes().toStringForType(type));
+ buf.append(col.getTypeAttributes().toStringForType(shownType));
}
buf.append("=");
if (isNull(i)) {
buf.append("NULL");
} else {
+ if (col.isArray()) {
+ ArrayCellView arr = getArray(i);
+ Preconditions.checkNotNull(arr, "array is null");
+ switch
(col.getNestedTypeDescriptor().getArrayDescriptor().getElemType()) {
+ case INT8:
+ appendArrayValues(buf, arr, arr::getInt8);
+ break;
+ case INT16:
+ appendArrayValues(buf, arr, arr::getInt16);
+ break;
+ case INT32:
+ appendArrayValues(buf, arr, arr::getInt32);
+ break;
+ case INT64:
+ appendArrayValues(buf, arr, arr::getInt64);
+ break;
+ case FLOAT:
+ appendArrayValues(buf, arr, arr::getFloat);
+ break;
+ case DOUBLE:
+ appendArrayValues(buf, arr, arr::getDouble);
+ break;
+ case BOOL:
+ appendArrayValues(buf, arr, arr::getBoolean);
+ break;
+ case STRING:
+ case VARCHAR:
+
buf.append(Arrays.toString(ArrayCellViewHelper.toStringArray(arr)));
+ break;
+ case BINARY:
+ byte[][] binaries = ArrayCellViewHelper.toBinaryArray(arr);
+ buf.append("[");
+ for (int j = 0; j < binaries.length; j++) {
+ if (j > 0) {
+ buf.append(", ");
+ }
+ if (binaries[j] == null) {
+ buf.append("null");
+ } else {
+ buf.append(new String(binaries[j], StandardCharsets.UTF_8));
+ }
+ }
+ buf.append("]");
+ break;
+ case DECIMAL: {
+ int precision = col.getTypeAttributes().getPrecision();
+ int scale = col.getTypeAttributes().getScale();
+ buf.append(Arrays.toString(
+ ArrayCellViewHelper.toDecimalArray(arr, precision, scale)));
+ break;
+ }
+ case DATE:
+
buf.append(Arrays.toString(ArrayCellViewHelper.toDateArray(arr)));
+ break;
+ case UNIXTIME_MICROS:
+ Timestamp[] tsArr = ArrayCellViewHelper.toTimestampArray(arr);
+ buf.append("[");
+ for (int j = 0; j < tsArr.length; j++) {
+ if (j > 0) {
+ buf.append(", ");
+ }
+ buf.append(tsArr[j] == null ?
+ "NULL"
+ : TimestampUtil.timestampToString(tsArr[j]));
+ }
+ buf.append("]");
+ break;
+ default:
+ buf.append(arr);
+ break;
+ }
+ continue;
+ }
switch (col.getType()) {
case INT8:
buf.append(getByte(i));
@@ -654,6 +745,133 @@ public abstract class RowResult {
return buf.toString();
}
+ // ----------------------------------------------------------------------
+ // Array column accessors
+ // ----------------------------------------------------------------------
+
+ /**
+ * Safely extract the serialized FlatBuffer bytes for an array column.
+ * <p>
+ * This method handles nulls and copies the correct slice of the
+ * underlying {@link ByteBuffer}. It is used internally by
+ * {@link #getArray(int)} and {@link #getArrayData(int)}.
+ *
+ * @param columnIndex Column index in the schema
+ * @return the serialized FlatBuffer bytes for the array cell,
+ * or null if the cell itself is null
+ */
+ @Nullable
+ protected byte[] getArrayBytes(int columnIndex) {
+ ByteBuffer buf = getBinary(columnIndex);
+ if (buf == null || !buf.hasRemaining()) {
+ return null;
+ }
+ ByteBuffer dup = buf.duplicate();
+ byte[] raw = new byte[dup.remaining()];
+ dup.get(raw);
+ return raw;
+ }
+
+ /**
+ * Returns a lightweight FlatBuffer view of an array column.
+ * <p>
+ * The returned {@link ArrayCellView} provides element-level accessors,
+ * validity bits, and pretty-print formatting. No data copying or
+ * boxing occurs. Intended for internal use, debugging, or specialized
+ * integrations.
+ *
+ * <p>For application-level use, prefer {@link #getArrayData(int)},
+ * which returns a fully decoded Java array.
+ *
+ * @param columnIndex Column index in the schema
+ * @return an {@link ArrayCellView}, or null if the column is null
+ * @throws IllegalArgumentException if the column is not declared as an array
+ * @throws IndexOutOfBoundsException if the column doesn't exist
+ */
+ @InterfaceAudience.Private
+ ArrayCellView getArray(int columnIndex) {
+ checkValidColumn(columnIndex);
+ if (isNull(columnIndex)) {
+ return null;
+ }
+ ColumnSchema col = schema.getColumnByIndex(columnIndex);
+ if (!col.isArray()) {
+ throw new IllegalArgumentException(
+ String.format("Column (name: %s, index: %d) is not an array",
+ col.getName(), columnIndex));
+ }
+
+ byte[] raw = getArrayBytes(columnIndex);
+ if (raw == null) {
+ return null;
+ }
+ return new ArrayCellView(raw);
+ }
+
+ /**
+ * Convenience overload for {@link #getArray(int)} by column name.
+ */
+ @InterfaceAudience.Private
+ ArrayCellView getArray(String columnName) {
+ return getArray(this.schema.getColumnIndex(columnName));
+ }
+
+ /**
+ * Returns the specified array column as a plain Java array
+ * (boxed elements) decoded using the column schema's
+ * precision and scale attributes where applicable.
+ * <p>
+ * The returned object type depends on the array's element type:
+ * <ul>
+ * <li>INT8[] -> {@code Byte[]}</li>
+ * <li>INT32[] -> {@code Integer[]}</li>
+ * <li>INT64[] -> {@code Long[]}</li>
+ * <li>FLOAT[] -> {@code Float[]}</li>
+ * <li>DOUBLE[] -> {@code Double[]}</li>
+ * <li>BOOL[] -> {@code Boolean[]}</li>
+ * <li>STRING[] / VARCHAR[] -> {@code String[]}</li>
+ * <li>DATE[] -> {@code java.sql.Date[]}</li>
+ * <li>UNIXTIME_MICROS[] -> {@code java.sql.Timestamp[]}</li>
+ * <li>DECIMAL(p,s)[] -> {@code java.math.BigDecimal[]}</li>
+ * </ul>
+ *
+ * @param columnIndex Column index in the schema
+ * @return a boxed Java array, or null if the column cell is null
+ * @throws IllegalArgumentException if the column is not an array
+ * @throws IndexOutOfBoundsException if the column doesn't exist
+ */
+
+ @Nullable
+ public final Object getArrayData(int columnIndex) {
+ checkValidColumn(columnIndex);
+ if (isNull(columnIndex)) {
+ return null;
+ }
+
+ ColumnSchema col = schema.getColumnByIndex(columnIndex);
+ if (!col.isArray()) {
+ throw new IllegalArgumentException(
+ String.format("Column (name: %s, index: %d) is not an array",
+ col.getName(), columnIndex));
+ }
+
+ byte[] raw = getArrayBytes(columnIndex);
+ if (raw == null) {
+ return null;
+ }
+
+ ArrayCellView view = new ArrayCellView(raw);
+ return ArrayCellViewHelper.toJavaArray(view, col);
+ }
+
+ /**
+ * Convenience overload for {@link #getArrayData(int)} by column name.
+ */
+ @Nullable
+ public final Object getArrayData(String columnName) {
+ return getArrayData(this.schema.getColumnIndex(columnName));
+ }
+
/**
* @return a string describing the location of this row result within
* the iterator as well as its data.
@@ -666,4 +884,21 @@ public abstract class RowResult {
buf.append("}");
return buf.toString();
}
+
+ private static void appendArrayValues(StringBuilder sb,
+ ArrayCellView arr,
+ java.util.function.IntFunction<Object>
getter) {
+ sb.append("[");
+ for (int j = 0; j < arr.length(); j++) {
+ if (j > 0) {
+ sb.append(", ");
+ }
+ if (!arr.isValid(j)) {
+ sb.append("NULL");
+ } else {
+ sb.append(getter.apply(j));
+ }
+ }
+ sb.append("]");
+ }
}
diff --git
a/java/kudu-client/src/main/java/org/apache/kudu/client/RowwiseRowResult.java
b/java/kudu-client/src/main/java/org/apache/kudu/client/RowwiseRowResult.java
index aa54fd3a6..57e90a4d2 100644
---
a/java/kudu-client/src/main/java/org/apache/kudu/client/RowwiseRowResult.java
+++
b/java/kudu-client/src/main/java/org/apache/kudu/client/RowwiseRowResult.java
@@ -77,7 +77,8 @@ class RowwiseRowResult extends RowResult {
// If the schema has nullables, we also add the offset for the null bitmap
at the end.
for (int i = 1; i < columnOffsetsSize; i++) {
org.apache.kudu.ColumnSchema column = schema.getColumnByIndex(i - 1);
- int previousSize = column.getTypeSize();
+ int previousSize;
+ previousSize = column.getTypeSize();
columnOffsets[i] = previousSize + currentOffset;
currentOffset += previousSize;
}
@@ -276,12 +277,15 @@ class RowwiseRowResult extends RowResult {
checkType(columnIndex, Type.STRING, Type.VARCHAR);
// C++ puts a Slice in rowData which is 16 bytes long for simplicity, but
we only support ints.
long offset = getOffset(columnIndex);
- long length =
rowData.getLong(getCurrentRowDataOffsetForColumn(columnIndex) + 8);
+ long length = Bytes.getLong(
+ this.rowData.getRawArray(),
+ this.rowData.getRawOffset() +
getCurrentRowDataOffsetForColumn(columnIndex) + 8);
assert offset < Integer.MAX_VALUE;
assert length < Integer.MAX_VALUE;
- return Bytes.getString(indirectData.getRawArray(),
- indirectData.getRawOffset() + (int)offset,
- (int)length);
+ return Bytes.getString(
+ indirectData.getRawArray(),
+ indirectData.getRawOffset() + (int) offset,
+ (int) length);
}
/**
@@ -296,15 +300,17 @@ class RowwiseRowResult extends RowResult {
public byte[] getBinaryCopy(int columnIndex) {
checkValidColumn(columnIndex);
checkNull(columnIndex);
- // C++ puts a Slice in rowData which is 16 bytes long for simplicity,
- // but we only support ints.
long offset = getOffset(columnIndex);
- long length =
rowData.getLong(getCurrentRowDataOffsetForColumn(columnIndex) + 8);
+ long length = Bytes.getLong(
+ this.rowData.getRawArray(),
+ this.rowData.getRawOffset() +
getCurrentRowDataOffsetForColumn(columnIndex) + 8);
assert offset < Integer.MAX_VALUE;
assert length < Integer.MAX_VALUE;
- byte[] ret = new byte[(int)length];
- System.arraycopy(indirectData.getRawArray(), indirectData.getRawOffset() +
(int) offset,
- ret, 0, (int) length);
+ byte[] ret = new byte[(int) length];
+ System.arraycopy(
+ indirectData.getRawArray(),
+ indirectData.getRawOffset() + (int) offset,
+ ret, 0, (int) length);
return ret;
}
@@ -323,14 +329,25 @@ class RowwiseRowResult extends RowResult {
public ByteBuffer getBinary(int columnIndex) {
checkValidColumn(columnIndex);
checkNull(columnIndex);
- checkType(columnIndex, Type.BINARY);
- // C++ puts a Slice in rowData which is 16 bytes long for simplicity,
- // but we only support ints.
+
+ ColumnSchema col = schema.getColumnByIndex(columnIndex);
+ if (!(col.getType() == Type.BINARY ||
+ col.getType() == Type.STRING ||
+ col.getType() == Type.VARCHAR ||
+ col.isArray())) {
+ throw new IllegalArgumentException(
+ String.format("Column %s is not varlen", col.getName()));
+ }
+
long offset = getOffset(columnIndex);
- long length =
rowData.getLong(getCurrentRowDataOffsetForColumn(columnIndex) + 8);
+ long length = Bytes.getLong(
+ this.rowData.getRawArray(),
+ this.rowData.getRawOffset() +
getCurrentRowDataOffsetForColumn(columnIndex) + 8);
assert offset < Integer.MAX_VALUE;
assert length < Integer.MAX_VALUE;
- return ByteBuffer.wrap(indirectData.getRawArray(),
indirectData.getRawOffset() + (int) offset,
+ return ByteBuffer.wrap(
+ indirectData.getRawArray(),
+ indirectData.getRawOffset() + (int) offset,
(int) length);
}
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 c2640fe3d..4cb829584 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
@@ -34,6 +34,7 @@ import static
org.apache.kudu.test.ClientTestUtil.createSchemaWithNonUniqueKey;
import static
org.apache.kudu.test.ClientTestUtil.createSchemaWithTimestampColumns;
import static org.apache.kudu.test.ClientTestUtil.getBasicCreateTableOptions;
import static
org.apache.kudu.test.ClientTestUtil.getBasicTableOptionsWithNonCoveredRange;
+import static org.apache.kudu.test.ClientTestUtil.getSchemaWithArrayTypes;
import static org.apache.kudu.test.ClientTestUtil.scanTableToStrings;
import static org.hamcrest.CoreMatchers.containsString;
import static org.junit.Assert.assertArrayEquals;
@@ -53,11 +54,14 @@ import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
+import java.nio.charset.StandardCharsets;
import java.security.cert.CertificateException;
import java.sql.Date;
+import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
+import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -2204,4 +2208,396 @@ public class TestKuduClient {
});
assertTrue(e.getMessage().contains("Could not parse certificate"));
}
+
+ /**
+ * Test inserting and retrieving array columns.
+ */
+ @Test
+ public void testArrayColumns() throws Exception {
+ Schema schema = getSchemaWithArrayTypes();
+ client.createTable(TABLE_NAME, schema, getBasicCreateTableOptions());
+
+ KuduSession session = client.newSession();
+ KuduTable table = client.openTable(TABLE_NAME);
+
+ for (int i = 0; i < 10; i++) {
+ Insert insert = table.newInsert();
+ PartialRow row = insert.getRow();
+
+ row.addInt("key", i);
+
+ // INT8 array (nullable elements)
+ byte[] int8Values = new byte[]{(byte) i, (byte) (i + 1), (byte) (i + 2)};
+ boolean[] int8Validity = new boolean[]{true, i % 2 == 0, true};
+ row.addArrayInt8("int8_arr", int8Values, int8Validity);
+
+ // BOOL array (nullable elements)
+ boolean[] boolValues = new boolean[]{true, i % 2 == 0, false};
+ boolean[] boolValidity = new boolean[]{true, false, true};
+ row.addArrayBool("bool_arr", boolValues, boolValidity);
+
+ // BINARY array (nullable elements)
+ byte[][] binValues = new byte[][]{
+ ("bin_" + i).getBytes(StandardCharsets.UTF_8),
+ null,
+ ("bin_last_" + i).getBytes(StandardCharsets.UTF_8)
+ };
+ row.addArrayBinary("binary_arr", binValues, null);
+
+ // INT32 array with some nulls
+ if (i % 3 == 0) {
+ row.setNull("int32_arr");
+ } else {
+ int[] values = new int[]{i, i + 1, i + 2};
+ boolean[] validity = new boolean[]{true, i % 2 == 0, true};
+ row.addArrayInt32("int32_arr", values, validity);
+ }
+
+ // STRING array
+ row.addArrayString("string_arr", new String[]{
+ "row" + i,
+ null,
+ "long_string_" + i
+ });
+
+ // DECIMAL array (scale = 2 for testing)
+ row.addArrayDecimal("decimal_arr", new BigDecimal[]{
+ BigDecimal.valueOf(123, 2),
+ null,
+ BigDecimal.valueOf(i * 100L, 2)
+ }, null);
+
+ // DATE array
+ row.addArrayDate("date_arr", new java.sql.Date[]{
+ DateUtil.epochDaysToSqlDate(i),
+ null,
+ DateUtil.epochDaysToSqlDate(i + 5)
+ });
+
+ // TIMESTAMP array
+ row.addArrayTimestamp("ts_arr", new Timestamp[]{
+ TimestampUtil.microsToTimestamp(i * 1000L),
+ null,
+ TimestampUtil.microsToTimestamp((i + 1) * 1000L)
+ });
+
+ // VARCHAR array
+ row.addArrayVarchar("varchar_arr", new String[]{
+ "row" + i,
+ null,
+ "varchar" + i
+ });
+
+ session.apply(insert);
+ }
+ session.flush();
+
+ // Scan back stringified form and verify logical output
+ List<String> rowStrings = scanTableToStrings(table);
+ assertEquals(10, rowStrings.size());
+
+ for (int i = 0; i < rowStrings.size(); i++) {
+ StringBuilder expectedRow = new StringBuilder();
+ expectedRow.append("INT32 key=").append(i);
+
+ // INT8[] column
+ expectedRow.append(", INT8[] int8_arr=[")
+ .append(i).append(", ")
+ .append(i % 2 == 0 ? i + 1 : "NULL").append(", ")
+ .append(i + 2).append("]");
+
+ // BOOL[] column
+ expectedRow.append(", BOOL[] bool_arr=[true, NULL, false]");
+
+ // BINARY[] column
+ expectedRow.append(", BINARY[] binary_arr=[bin_").append(i)
+ .append(", null, bin_last_").append(i).append("]");
+
+
+ // INT32[] column
+ expectedRow.append(", INT32[] int32_arr=");
+ if (i % 3 == 0) {
+ expectedRow.append("NULL");
+ } else {
+ expectedRow.append("[");
+ expectedRow.append(i); // always valid
+ expectedRow.append(", ");
+ if (i % 2 == 0) {
+ expectedRow.append(i + 1);
+ } else {
+ expectedRow.append("NULL");
+ }
+ expectedRow.append(", ");
+ expectedRow.append(i + 2);
+ expectedRow.append("]");
+ }
+
+ // STRING[] column
+ expectedRow.append(", STRING[] string_arr=[row").append(i)
+ .append(", null, long_string_").append(i).append("]");
+
+ // DECIMAL[] column
+ expectedRow.append(", DECIMAL[] decimal_arr(5, 2)=[1.23, null, ")
+ .append(BigDecimal.valueOf(i * 100L, 2)).append("]");
+
+ // DATE[] column
+ expectedRow.append(", DATE[] date_arr=[");
+ expectedRow.append(DateUtil.epochDaysToSqlDate(i));
+ expectedRow.append(", null, ");
+ expectedRow.append(DateUtil.epochDaysToSqlDate(i + 5));
+ expectedRow.append("]");
+
+ // TIMESTAMP[] column
+ expectedRow.append(", UNIXTIME_MICROS[] ts_arr=[");
+ expectedRow.append(TimestampUtil.timestampToString(
+ TimestampUtil.microsToTimestamp(i * 1000L)));
+ expectedRow.append(", NULL, ");
+ expectedRow.append(TimestampUtil.timestampToString(
+ TimestampUtil.microsToTimestamp((i + 1) * 1000L)));
+ expectedRow.append("]");
+
+ // VARCHAR[] column
+ expectedRow.append(", VARCHAR[] varchar_arr(10)=[row").append(i)
+ .append(", null, varchar").append(i).append("]");
+
+ assertEquals(expectedRow.toString(), rowStrings.get(i));
+ }
+
+ // Verify actual array decoding by logical type
+ KuduScanner scanner = client.newScannerBuilder(table).build();
+ while (scanner.hasMoreRows()) {
+ for (RowResult rr : scanner.nextRows()) {
+ int key = rr.getInt("key");
+
+ // INT8[]
+ Object int8Obj = rr.getArrayData("int8_arr");
+ assertTrue(int8Obj instanceof Byte[]);
+ Byte[] int8Vals = (Byte[]) int8Obj;
+ assertEquals(3, int8Vals.length);
+ assertEquals(Byte.valueOf((byte) key), int8Vals[0]);
+ if (key % 2 == 0) {
+ assertEquals(Byte.valueOf((byte) (key + 1)), int8Vals[1]);
+ } else {
+ assertNull(int8Vals[1]);
+ }
+ assertEquals(Byte.valueOf((byte) (key + 2)), int8Vals[2]);
+
+ // BOOL[]
+ Object boolObj = rr.getArrayData("bool_arr");
+ assertTrue(boolObj instanceof Boolean[]);
+ Boolean[] boolVals = (Boolean[]) boolObj;
+ assertEquals(3, boolVals.length);
+ assertEquals(Boolean.TRUE, boolVals[0]);
+ assertNull(boolVals[1]);
+ assertEquals(Boolean.FALSE, boolVals[2]);
+
+ // BINARY[]
+ Object binObj = rr.getArrayData("binary_arr");
+ assertTrue(binObj instanceof byte[][]);
+ byte[][] binVals = (byte[][]) binObj;
+ assertEquals(3, binVals.length);
+ assertEquals(("bin_" + key), new String(binVals[0],
StandardCharsets.UTF_8));
+ assertNull(binVals[1]);
+ assertEquals(("bin_last_" + key), new String(binVals[2],
StandardCharsets.UTF_8));
+
+ // INT32[] -> Integer[]
+ Object intObj = rr.getArrayData("int32_arr");
+ if (key % 3 == 0) {
+ // Entire array was intentionally NULL
+ assertNull(intObj);
+ } else {
+ assertTrue(intObj instanceof Integer[]);
+ Integer[] intVals = (Integer[]) intObj;
+ assertEquals(3, intVals.length);
+ assertEquals(Integer.valueOf(key), intVals[0]);
+ if (key % 2 == 0) {
+ assertEquals(Integer.valueOf(key + 1), intVals[1]);
+ } else {
+ assertNull(intVals[1]);
+ }
+ assertEquals(Integer.valueOf(key + 2), intVals[2]);
+ }
+
+ // STRING[] -> String[]
+ Object strObj = rr.getArrayData("string_arr");
+ assertTrue(strObj instanceof String[]);
+ String[] strVals = (String[]) strObj;
+ assertEquals(3, strVals.length);
+ assertEquals("row" + key, strVals[0]);
+ assertNull(strVals[1]);
+ assertEquals("long_string_" + key, strVals[2]);
+
+ // DECIMAL[] -> BigDecimal[]
+ Object decObj = rr.getArrayData("decimal_arr");
+ assertTrue(decObj instanceof BigDecimal[]);
+ BigDecimal[] decVals = (BigDecimal[]) decObj;
+ assertEquals(3, decVals.length);
+ assertEquals(new BigDecimal("1.23"), decVals[0]);
+ assertNull(decVals[1]);
+ assertEquals(BigDecimal.valueOf(key * 100L, 2), decVals[2]);
+
+ // DATE[] -> java.sql.Date[]
+ Object dateObj = rr.getArrayData("date_arr");
+ assertTrue(dateObj instanceof java.sql.Date[]);
+ java.sql.Date[] dateVals = (java.sql.Date[]) dateObj;
+ assertEquals(3, dateVals.length);
+ assertEquals(DateUtil.epochDaysToSqlDate(key), dateVals[0]);
+ assertNull(dateVals[1]);
+ assertEquals(DateUtil.epochDaysToSqlDate(key + 5), dateVals[2]);
+
+ // TIMESTAMP[] -> java.sql.Timestamp[]
+ Object tsObj = rr.getArrayData("ts_arr");
+ assertTrue(tsObj instanceof java.sql.Timestamp[]);
+ java.sql.Timestamp[] tsVals = (java.sql.Timestamp[]) tsObj;
+ assertEquals(3, tsVals.length);
+ assertEquals(TimestampUtil.microsToTimestamp(key * 1000L), tsVals[0]);
+ assertNull(tsVals[1]);
+ assertEquals(TimestampUtil.microsToTimestamp((key + 1) * 1000L),
tsVals[2]);
+
+ // VARCHAR[] -> String[]
+ Object vchObj = rr.getArrayData("varchar_arr");
+ assertTrue(vchObj instanceof String[]);
+ String[] vchVals = (String[]) vchObj;
+ assertEquals(3, vchVals.length);
+ assertEquals("row" + key, vchVals[0]);
+ assertNull(vchVals[1]);
+ assertEquals("varchar" + key, vchVals[2]);
+ }
+
+ }
+ }
+
+ @Test
+ public void testArrayEmptyValidityOptimization() throws Exception {
+ Schema schema = getSchemaWithArrayTypes();
+ client.createTable(TABLE_NAME, schema, getBasicCreateTableOptions());
+ KuduTable table = client.openTable(TABLE_NAME);
+ final KuduSession session = client.newSession();
+
+ Insert insert = table.newInsert();
+ PartialRow row = insert.getRow();
+ row.addInt("key", 100);
+
+ // INT8[]
+ row.addArrayInt8("int8_arr", new byte[]{1, 2, 3}, new boolean[0]);
+
+ // DECIMAL[]
+ row.addArrayDecimal("decimal_arr",
+ new BigDecimal[]{new BigDecimal("1.23"), new BigDecimal("4.56"), new
BigDecimal("7.89")},
+ new boolean[0]); // all valid
+
+ // DATE[]
+ row.addArrayDate("date_arr", new Date[]{
+ DateUtil.epochDaysToSqlDate(1),
+ DateUtil.epochDaysToSqlDate(2),
+ DateUtil.epochDaysToSqlDate(3)
+ }, new boolean[0]);
+
+ // TIMESTAMP[]
+ row.addArrayTimestamp("ts_arr", new Timestamp[]{
+ TimestampUtil.microsToTimestamp(1000L),
+ TimestampUtil.microsToTimestamp(2000L),
+ TimestampUtil.microsToTimestamp(3000L)
+ }, new boolean[0]);
+
+ // VARCHAR[]
+ row.addArrayVarchar("varchar_arr", new String[]{"x", "y", "z"}, new
boolean[0]);
+
+ // Optional nullable ones can be left null
+ row.setNull("bool_arr");
+ row.setNull("binary_arr");
+ row.setNull("int32_arr");
+ row.setNull("string_arr");
+
+ session.apply(insert);
+
+ // Insert second row with null validity vector
+ Insert insert2 = table.newInsert();
+ PartialRow row2 = insert2.getRow();
+ row2.addInt("key", 200);
+ row2.addArrayInt8("int8_arr", new byte[]{1, 2, 3});
+ row2.addArrayDecimal("decimal_arr",
+ new BigDecimal[]{new BigDecimal("1.23"), null, new BigDecimal("7.89")},
+ null);
+ row2.addArrayDate("date_arr", new Date[]{
+ DateUtil.epochDaysToSqlDate(10),
+ DateUtil.epochDaysToSqlDate(11),
+ DateUtil.epochDaysToSqlDate(12)
+ });
+ row2.addArrayTimestamp("ts_arr", new Timestamp[]{
+ TimestampUtil.microsToTimestamp(10_000L),
+ TimestampUtil.microsToTimestamp(20_000L),
+ TimestampUtil.microsToTimestamp(30_000L)
+ });
+ row2.addArrayVarchar("varchar_arr", new String[]{"a", null, "c"});
+ row2.setNull("bool_arr");
+ row2.setNull("binary_arr");
+ row2.setNull("int32_arr");
+ row2.setNull("string_arr");
+
+ session.apply(insert2);
+ session.flush();
+
+ // --- Verify readback ---
+ KuduScanner scanner = client.newScannerBuilder(table).build();
+ List<RowResult> rows = new ArrayList<>();
+ while (scanner.hasMoreRows()) {
+ RowResultIterator it = scanner.nextRows();
+ while (it.hasNext()) {
+ rows.add(it.next());
+ }
+ }
+ assertEquals(2, rows.size());
+ RowResult r1 = rows.get(0);
+
+ // INT8[]
+ Byte[] int8Vals = (Byte[]) r1.getArrayData("int8_arr");
+ assertArrayEquals(new Byte[]{1, 2, 3}, int8Vals);
+
+ // DECIMAL[]
+ BigDecimal[] decVals = (BigDecimal[]) r1.getArrayData("decimal_arr");
+ assertArrayEquals(
+ new BigDecimal[]{new BigDecimal("1.23"), new BigDecimal("4.56"), new
BigDecimal("7.89")},
+ decVals);
+
+ // DATE[]
+ Date[] dateVals = (Date[]) r1.getArrayData("date_arr");
+ assertEquals(DateUtil.epochDaysToSqlDate(1), dateVals[0]);
+ assertEquals(DateUtil.epochDaysToSqlDate(3), dateVals[2]);
+
+ // TIMESTAMP[]
+ Timestamp[] tsVals = (Timestamp[]) r1.getArrayData("ts_arr");
+ assertEquals(TimestampUtil.microsToTimestamp(1000L), tsVals[0]);
+ assertEquals(TimestampUtil.microsToTimestamp(3000L), tsVals[2]);
+
+ // VARCHAR[]
+ String[] vcharVals = (String[]) r1.getArrayData("varchar_arr");
+ assertArrayEquals(new String[]{"x", "y", "z"}, vcharVals);
+
+ RowResult r2 = rows.get(1);
+
+ // INT8[]
+ Byte[] int8Vals2 = (Byte[]) r2.getArrayData("int8_arr");
+ assertArrayEquals(new Byte[]{1, 2, 3}, int8Vals2);
+
+ // DECIMAL[]
+ BigDecimal[] decVals2 = (BigDecimal[]) r2.getArrayData("decimal_arr");
+ assertArrayEquals(
+ new BigDecimal[]{new BigDecimal("1.23"), null, new BigDecimal("7.89")},
+ decVals2);
+
+ // DATE[]
+ Date[] dateVals2 = (Date[]) r2.getArrayData("date_arr");
+ assertEquals(DateUtil.epochDaysToSqlDate(10), dateVals2[0]);
+ assertEquals(DateUtil.epochDaysToSqlDate(12), dateVals2[2]);
+
+ // TIMESTAMP[]
+ Timestamp[] tsVals2 = (Timestamp[]) r2.getArrayData("ts_arr");
+ assertEquals(TimestampUtil.microsToTimestamp(10_000L), tsVals2[0]);
+ assertEquals(TimestampUtil.microsToTimestamp(30_000L), tsVals2[2]);
+
+ // VARCHAR[]
+ String[] vcharVals2 = (String[]) r2.getArrayData("varchar_arr");
+ assertArrayEquals(new String[]{"a", null, "c"}, vcharVals2);
+ }
}
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 48ccae9d9..6bc189a3b 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
@@ -264,9 +264,7 @@ public class TestPartialRow {
ArrayCellView view = (ArrayCellView) row.getObject("strings");
assertEquals(3, view.length());
assertEquals("foo", view.getString(0));
-
assertFalse(view.isValid(1));
- assertNull(view.getString(1));
assertEquals("bar", view.getString(2));
}
diff --git
a/java/kudu-client/src/test/java/org/apache/kudu/client/TestRowResult.java
b/java/kudu-client/src/test/java/org/apache/kudu/client/TestRowResult.java
index 2624cc391..f7c0544ba 100644
--- a/java/kudu-client/src/test/java/org/apache/kudu/client/TestRowResult.java
+++ b/java/kudu-client/src/test/java/org/apache/kudu/client/TestRowResult.java
@@ -19,14 +19,18 @@ package org.apache.kudu.client;
import static java.nio.charset.StandardCharsets.UTF_8;
import static
org.apache.kudu.test.ClientTestUtil.getAllTypesCreateTableOptions;
+import static
org.apache.kudu.test.ClientTestUtil.getArrayTypesCreateTableOptions;
import static org.apache.kudu.test.ClientTestUtil.getSchemaWithAllTypes;
+import static org.apache.kudu.test.ClientTestUtil.getSchemaWithArrayTypes;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import java.math.BigDecimal;
import java.nio.ByteBuffer;
+import java.sql.Date;
import java.sql.Timestamp;
import org.junit.Before;
@@ -218,4 +222,139 @@ public class TestRowResult {
}
}
}
+
+ @Test
+ public void testArrayColumns() throws Exception {
+ String tableName = "TestRowResult-Arrays-" + System.currentTimeMillis();
+ Schema schema = getSchemaWithArrayTypes();
+ harness.getClient().createTable(tableName, schema,
getArrayTypesCreateTableOptions());
+ KuduTable table = harness.getClient().openTable(tableName);
+
+ final KuduSession session = harness.getClient().newSession();
+ Insert insert = table.newInsert();
+ PartialRow row = insert.getRow();
+ row.addInt("key", 1);
+
+ row.addArrayInt8(schema.getColumnIndex("int8_arr"),
+ new byte[]{1, 2, 3}, new boolean[]{true, false, true});
+
+ row.addArrayBool(schema.getColumnIndex("bool_arr"),
+ new boolean[]{true, false, true}, new boolean[]{true, false, true});
+
+ row.addArrayBinary(schema.getColumnIndex("binary_arr"),
+ new byte[][]{"a".getBytes(UTF_8), "b".getBytes(UTF_8),
"c".getBytes(UTF_8)},
+ new boolean[]{true, false, true});
+
+ row.addArrayInt32(schema.getColumnIndex("int32_arr"), new int[]{1, 2, 3});
+ row.addArrayString(schema.getColumnIndex("string_arr"),
+ new String[]{"a", null, "c"});
+
+ row.addArrayDecimal(schema.getColumnIndex("decimal_arr"),
+ new BigDecimal[]{BigDecimal.valueOf(123, 2), null,
BigDecimal.valueOf(456, 2)},
+ /*validity*/ null);
+
+ row.addArrayDate(schema.getColumnIndex("date_arr"),
+ new Date[]{DateUtil.epochDaysToSqlDate(0),
DateUtil.epochDaysToSqlDate(10)});
+
+ row.addArrayTimestamp(schema.getColumnIndex("ts_arr"),
+ new Timestamp[]{new Timestamp(1000), new Timestamp(2000)});
+
+ row.addArrayVarchar(schema.getColumnIndex("varchar_arr"),
+ new String[]{"abc", "xyz"}, /*validity*/ null);
+
+ session.apply(insert);
+
+ // Scan back
+ KuduScanner scanner = harness.getClient().newScannerBuilder(table).build();
+ RowResult rr = scanner.nextRows().next();
+
+ // int8[]
+ ArrayCellView int8Arr = rr.getArray("int8_arr");
+ assertEquals(3, int8Arr.length());
+ assertEquals(1, int8Arr.getInt8(0));
+ assertFalse(int8Arr.isValid(1));
+ assertEquals(3, int8Arr.getInt8(2));
+
+ // bool[]
+ ArrayCellView boolArr = rr.getArray("bool_arr");
+ assertEquals(3, boolArr.length());
+ assertTrue(boolArr.getBoolean(0));
+ assertFalse(boolArr.isValid(1));
+ assertTrue(boolArr.getBoolean(2));
+
+ // binary[]
+ ArrayCellView binArr = rr.getArray("binary_arr");
+ assertEquals(3, binArr.length());
+ assertArrayEquals("a".getBytes(UTF_8), binArr.getBinary(0));
+ assertFalse(binArr.isValid(1));
+ assertArrayEquals("c".getBytes(UTF_8), binArr.getBinary(2));
+
+ // int32[]
+ ArrayCellView intArr = rr.getArray("int32_arr");
+ assertEquals(3, intArr.length());
+ assertEquals(1, intArr.getInt32(0));
+ assertEquals(2, intArr.getInt32(1));
+ assertEquals(3, intArr.getInt32(2));
+
+ // string[]
+ ArrayCellView strArr = rr.getArray("string_arr");
+ assertEquals(3, strArr.length());
+ assertEquals("a", strArr.getString(0));
+ assertFalse(strArr.isValid(1));
+ assertEquals("c", strArr.getString(2));
+
+ // decimal[]
+ BigDecimal[] decimals =
ArrayCellViewHelper.toDecimalArray(rr.getArray("decimal_arr"), 9, 2);
+ assertEquals(new BigDecimal("1.23"), decimals[0]);
+ assertNull(decimals[1]);
+ assertEquals(new BigDecimal("4.56"), decimals[2]);
+
+ // date[]
+ Date[] dates = ArrayCellViewHelper.toDateArray(rr.getArray("date_arr"));
+ assertEquals(DateUtil.epochDaysToSqlDate(0), dates[0]);
+ assertEquals(DateUtil.epochDaysToSqlDate(10), dates[1]);
+
+ // timestamp[]
+ Timestamp[] ts =
ArrayCellViewHelper.toTimestampArray(rr.getArray("ts_arr"));
+ assertEquals(new Timestamp(1000), ts[0]);
+ assertEquals(new Timestamp(2000), ts[1]);
+
+ // varchar[]
+ String[] vch =
ArrayCellViewHelper.toVarcharArray(rr.getArray("varchar_arr"));
+ assertArrayEquals(new String[]{"abc", "xyz"}, vch);
+
+ // Verify schema-aware conversions
+ Byte[] int8Boxed = (Byte[]) rr.getArrayData("int8_arr");
+ assertArrayEquals(new Byte[]{1, null, 3}, int8Boxed);
+
+ Integer[] intBoxed = (Integer[]) rr.getArrayData("int32_arr");
+ assertArrayEquals(new Integer[]{1, 2, 3}, intBoxed);
+
+ String[] strBoxed = (String[]) rr.getArrayData("string_arr");
+ assertArrayEquals(new String[]{"a", null, "c"}, strBoxed);
+
+ BigDecimal[] decBoxed = (BigDecimal[]) rr.getArrayData("decimal_arr");
+ assertEquals(new BigDecimal("1.23"), decBoxed[0]);
+ assertNull(decBoxed[1]);
+ assertEquals(new BigDecimal("4.56"), decBoxed[2]);
+
+ Date[] dateBoxed = (Date[]) rr.getArrayData("date_arr");
+ assertEquals(DateUtil.epochDaysToSqlDate(0), dateBoxed[0]);
+ assertEquals(DateUtil.epochDaysToSqlDate(10), dateBoxed[1]);
+
+ Timestamp[] tsBoxed = (Timestamp[]) rr.getArrayData("ts_arr");
+ assertEquals(new Timestamp(1000), tsBoxed[0]);
+ assertEquals(new Timestamp(2000), tsBoxed[1]);
+
+ String[] vchBoxed = (String[]) rr.getArrayData("varchar_arr");
+ assertArrayEquals(new String[]{"abc", "xyz"}, vchBoxed);
+
+ Boolean[] boolBoxed = (Boolean[]) rr.getArrayData("bool_arr");
+ assertArrayEquals(new Boolean[]{true, null, true}, boolBoxed);
+
+ byte[][] binBoxed = (byte[][]) rr.getArrayData("binary_arr");
+ assertArrayEquals("a".getBytes(UTF_8), binBoxed[0]);
+ assertNull(binBoxed[1]);
+ assertArrayEquals("c".getBytes(UTF_8), binBoxed[2]);
+ }
}
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 888a12b8b..a7ff57e93 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
@@ -228,6 +228,33 @@ public abstract class ClientTestUtil {
return new Schema(columns);
}
+ public static Schema getSchemaWithArrayTypes() {
+ List<ColumnSchema> columns = ImmutableList.of(
+ new ColumnSchema.ColumnSchemaBuilder("key",
Type.INT32).key(true).build(),
+ new ColumnSchema.ColumnSchemaBuilder("int8_arr", Type.INT8)
+ .array(true).nullable(true).build(),
+ new ColumnSchema.ColumnSchemaBuilder("bool_arr", Type.BOOL)
+ .array(true).nullable(true).build(),
+ new ColumnSchema.ColumnSchemaBuilder("binary_arr", Type.BINARY)
+ .array(true).nullable(true).build(),
+ new ColumnSchema.ColumnSchemaBuilder("int32_arr", Type.INT32)
+ .array(true).nullable(true).build(),
+ new ColumnSchema.ColumnSchemaBuilder("string_arr", Type.STRING)
+ .array(true).nullable(true).build(),
+ new ColumnSchema.ColumnSchemaBuilder("decimal_arr", Type.DECIMAL)
+ .typeAttributes(DecimalUtil.typeAttributes(5, 2))
+ .array(true).nullable(true).build(),
+ new ColumnSchema.ColumnSchemaBuilder("date_arr", Type.DATE)
+ .array(true).build(),
+ new ColumnSchema.ColumnSchemaBuilder("ts_arr", Type.UNIXTIME_MICROS)
+ .array(true).build(),
+ new ColumnSchema.ColumnSchemaBuilder("varchar_arr", Type.VARCHAR)
+ .typeAttributes(CharUtil.typeAttributes(10))
+ .array(true).build()
+ );
+ return new Schema(columns);
+ }
+
public static PartialRow getPartialRowWithAllTypes(PartialRow row, byte
int8Value) {
if (row == null) {
Schema schema = getSchemaWithAllTypes();
@@ -264,6 +291,11 @@ public abstract class ClientTestUtil {
return new
CreateTableOptions().setRangePartitionColumns(ImmutableList.of("int8"));
}
+ public static CreateTableOptions getArrayTypesCreateTableOptions() {
+ return new CreateTableOptions()
+ .setRangePartitionColumns(ImmutableList.of("key"));
+ }
+
public static Schema getBasicSchema() {
ArrayList<ColumnSchema> columns = new ArrayList<>(5);
columns.add(new ColumnSchema.ColumnSchemaBuilder("key",
Type.INT32).key(true).build());