2525package io .questdb .cutlass .line .array ;
2626
2727import io .questdb .cairo .ColumnType ;
28+ import io .questdb .cairo .arr .ArrayView ;
2829import io .questdb .cairo .arr .BorrowedFlatArrayView ;
2930import io .questdb .cairo .arr .DirectArray ;
3031import io .questdb .cairo .vm .api .MemoryA ;
32+ import io .questdb .client .Sender ;
3133import io .questdb .cutlass .line .LineSenderException ;
3234import io .questdb .std .QuietCloseable ;
3335
3436/**
35- * `AbstractArray` provides an interface for Java client users to create multi-dimensional arrays,
36- * supporting up to 32 dimensions.
37- * <p>It manages a contiguous block of memory to store the actual array data.
38- * To prevent memory leaks, please ensure to invoke the {@link #close()} method after usage.
39- * <p>Example of usage:
40- * <pre>{@code
41- * // Creates a 2x3x2 matrix (of rank 3)
42- * try (
43- * DoubleArray matrix3d = DoubleArray.create(2, 3, 2)) {
44- * matrix3d.set(DoubleArray.create(new double[]{1.0, 2.0}), true, 0, 0)
45- * .set(DoubleArray.create(new double[]{3.0, 4.0}), true, 0, 1)
46- * .set(DoubleArray.create(new double[]{5.0, 6.0}), true, 0, 2)
47- * .set(DoubleArray.create(new double[]{7.0, 8.0}), true, 1, 0)
48- * .set(DoubleArray.create(new double[]{9.0, 10.0}), true, 1, 1)
49- * .set(DoubleArray.create(new double[]{11.0, 12.0}), true, 1, 2);
37+ * Use this class to prepare data for an N-dimensional array column in QuestDB.
38+ * It manages a contiguous block of native memory to store the array data.
39+ * To avoid leaking this memory, make sure you use it in a try-with-resources block,
40+ * or call {@link #close()} explicitly.
41+ * <p>
42+ * Example of usage:
43+ * <pre>
44+ * // Create a 2x3 array:
45+ * try (DoubleArray matrix = new DoubleArray(2, 3)) {
5046 *
51- * // send matrix3d to line
52- * sender.table(tableName).doubleArray(columnName, matrix3d);
53- * }
47+ * // Append data in row-major order:
48+ * matrix.append(1.0).append(2.0).append(3.0) // first row
49+ * .append(4.0).append(5.0).append(6.0); // second row
5450 *
51+ * // Or, set a value at specific coordinates:
52+ * matrix.set(1.5, 0, 1); // set element at row 0, column 1
53+ *
54+ * // Send to QuestDB
55+ * sender.table("my_table").doubleArray("matrix_column", matrix).atNow();
56+ * }
5557 * }</pre>
5658 */
5759public abstract class AbstractArray implements QuietCloseable {
5860
5961 protected final DirectArray array = new DirectArray ();
60- protected final int flatLength ;
6162 protected boolean closed = false ;
63+ protected int flatLength ;
6264 protected MemoryA memA = array .startMemoryA ();
6365
6466 protected AbstractArray (int [] shape , short columnType ) {
67+ if (shape .length == 0 ) {
68+ throw new LineSenderException ("Shape must have at least one dimension" );
69+ }
70+ if (shape .length > ColumnType .ARRAY_NDIMS_LIMIT ) {
71+ throw new LineSenderException ("Maximum supported dimensionality is " +
72+ ColumnType .ARRAY_NDIMS_LIMIT + "D, but got " + shape .length + "D" );
73+ }
74+ for (int dim = 0 ; dim < shape .length ; dim ++) {
75+ if (shape [dim ] < 0 ) {
76+ throw new LineSenderException ("dimension length must not be negative [dim=" + dim +
77+ ", dimLen=" + shape [dim ] + "]" );
78+ }
79+ if (shape [dim ] > ArrayView .DIM_MAX_LEN ) {
80+ throw new LineSenderException ("dimension length out of range [dim=" + dim +
81+ ", dimLen=" + shape [dim ] + ", maxLen=" + ArrayView .DIM_MAX_LEN + "]" );
82+ }
83+ }
84+
6585 array .setType (ColumnType .encodeArrayType (columnType , shape .length ));
6686 for (int dim = 0 , size = shape .length ; dim < size ; dim ++) {
6787 array .setDimLen (dim , shape [dim ]);
@@ -86,6 +106,42 @@ public void appendToBufPtr(ArrayBufferAppender mem) {
86106 }
87107 }
88108
109+ /**
110+ * Resets the append position to the beginning of the array without modifying any data.
111+ * <p>
112+ * This method only resets the append position marker, and the array data remains unchanged.
113+ * Subsequent {@code append()} calls will start overwriting from the first element.
114+ * <p>
115+ * <strong>Use cases:</strong>
116+ * <ul>
117+ * <li>Reset the array after getting an error and/or calling {@link Sender#cancelRow()}</li>
118+ * <li>Start fresh without relying on auto-wrapping behavior</li>
119+ * </ul>
120+ * <strong>Note:</strong> to change the array dimensions, use {@code reshape()}.
121+ * This method only resets the position while maintaining the array shape.
122+ *
123+ * @see #reshape(int...)
124+ */
125+ public void clear () {
126+ assert !closed ;
127+ memA = array .startMemoryA ();
128+ }
129+
130+ /**
131+ * Closes this array and releases all associated native memory resources.
132+ * <p>
133+ * <strong>Important:</strong> after calling this method, the array becomes unusable.
134+ * Any subsequent operations (append, set, reshape, etc.) will result in undefined
135+ * behavior or exceptions.
136+ * <p>
137+ * This method is idempotent — calling it multiple times has no additional effect.
138+ * <p>
139+ * <strong>Memory Management:</strong> since the class uses native memory, failing to call
140+ * this method will result in a native memory leak. Use it inside try-with-resources, or
141+ * call explicitly in a finally block.
142+ *
143+ * @see java.lang.AutoCloseable#close()
144+ */
89145 @ Override
90146 public void close () {
91147 if (!closed ) {
@@ -94,6 +150,129 @@ public void close() {
94150 closed = true ;
95151 }
96152
153+ /**
154+ * Reshapes the array to the specified dimensions, and resets the append
155+ * position to the start of the array.
156+ *
157+ * @param shape the new dimensions for the array
158+ * @throws LineSenderException if the array is already closed or shape has invalid dimensions
159+ * @see #reshape(int)
160+ * @see #reshape(int, int)
161+ * @see #reshape(int, int, int)
162+ */
163+ public void reshape (int ... shape ) {
164+ if (closed ) {
165+ throw new LineSenderException ("Cannot reshape a closed array" );
166+ }
167+ int nDim = shape .length ;
168+ if (nDim > ColumnType .ARRAY_NDIMS_LIMIT ) {
169+ throw new LineSenderException ("Maximum supported dimensionality is " +
170+ ColumnType .ARRAY_NDIMS_LIMIT + "D, but got " + nDim + "D" );
171+ }
172+ if (nDim == 0 ) {
173+ throw new LineSenderException ("Shape must have at least one dimension" );
174+ }
175+ for (int dim = 0 ; dim < nDim ; dim ++) {
176+ if (shape [dim ] < 0 ) {
177+ throw new LineSenderException ("dimension length must not be negative [dim=" + dim +
178+ ", dimLen=" + shape [dim ] + "]" );
179+ }
180+ if (shape [dim ] > ArrayView .DIM_MAX_LEN ) {
181+ throw new LineSenderException ("dimension length out of range [dim=" + dim +
182+ ", dimLen=" + shape [dim ] + ", maxLen=" + ArrayView .DIM_MAX_LEN + "]" );
183+ }
184+ }
185+ array .setType (ColumnType .encodeArrayType (array .getElemType (), nDim ));
186+ for (int dim = 0 ; dim < nDim ; dim ++) {
187+ array .setDimLen (dim , shape [dim ]);
188+ }
189+ array .applyShape ();
190+ flatLength = array .getFlatViewLength ();
191+ memA = array .startMemoryA ();
192+ }
193+
194+ /**
195+ * Reshapes the array to a single dimension with the specified length, and resets
196+ * the append position to the start of the array.
197+ *
198+ * @param dimLen the length of the single dimension
199+ * @throws LineSenderException if the array is already closed or dimLen is negative
200+ */
201+ public void reshape (int dimLen ) {
202+ if (closed ) {
203+ throw new LineSenderException ("Cannot reshape a closed array" );
204+ }
205+ if (dimLen < 0 ) {
206+ throw new LineSenderException ("Array size must not be negative, but got " + dimLen );
207+ }
208+ if (dimLen > ArrayView .DIM_MAX_LEN ) {
209+ throw new LineSenderException ("Array size out of range [dimLen=" + dimLen +
210+ ", maxLen=" + ArrayView .DIM_MAX_LEN + "]" );
211+ }
212+ array .setType (ColumnType .encodeArrayType (array .getElemType (), 1 ));
213+ array .setDimLen (0 , dimLen );
214+ array .applyShape ();
215+ flatLength = array .getFlatViewLength ();
216+ memA = array .startMemoryA ();
217+ }
218+
219+ /**
220+ * Reshapes the array to two dimensions with the specified lengths, and resets
221+ * the append position to the start of the array.
222+ *
223+ * @param dim1 the length of the first dimension (rows)
224+ * @param dim2 the length of the second dimension (columns)
225+ * @throws LineSenderException if the array is already closed or any dimension is negative
226+ */
227+ public void reshape (int dim1 , int dim2 ) {
228+ if (closed ) {
229+ throw new LineSenderException ("Cannot reshape a closed array" );
230+ }
231+ if (dim1 < 0 || dim2 < 0 ) {
232+ throw new LineSenderException ("Array dimensions must not be negative, but got [" + dim1 + ", " + dim2 + "]" );
233+ }
234+ if (dim1 > ArrayView .DIM_MAX_LEN || dim2 > ArrayView .DIM_MAX_LEN ) {
235+ throw new LineSenderException ("Array dimensions out of range [dim1=" + dim1 +
236+ ", dim2=" + dim2 + ", maxLen=" + ArrayView .DIM_MAX_LEN + "]" );
237+ }
238+ array .setType (ColumnType .encodeArrayType (array .getElemType (), 2 ));
239+ array .setDimLen (0 , dim1 );
240+ array .setDimLen (1 , dim2 );
241+ array .applyShape ();
242+ flatLength = array .getFlatViewLength ();
243+ memA = array .startMemoryA ();
244+ }
245+
246+ /**
247+ * Reshapes the array to three dimensions with the specified lengths, and resets
248+ * the append position to the start of the array.
249+ *
250+ * @param dim1 the length of the first dimension
251+ * @param dim2 the length of the second dimension
252+ * @param dim3 the length of the third dimension
253+ * @throws LineSenderException if the array is already closed or any dimension is negative
254+ */
255+ public void reshape (int dim1 , int dim2 , int dim3 ) {
256+ if (closed ) {
257+ throw new LineSenderException ("Cannot reshape a closed array" );
258+ }
259+ if (dim1 < 0 || dim2 < 0 || dim3 < 0 ) {
260+ throw new LineSenderException ("Array dimensions must not be negative, but got [" +
261+ dim1 + ", " + dim2 + ", " + dim3 + "]" );
262+ }
263+ if (dim1 > ArrayView .DIM_MAX_LEN || dim2 > ArrayView .DIM_MAX_LEN || dim3 > ArrayView .DIM_MAX_LEN ) {
264+ throw new LineSenderException ("Array dimensions out of range [dim1=" + dim1 +
265+ ", dim2=" + dim2 + ", dim3=" + dim3 + ", maxLen=" + ArrayView .DIM_MAX_LEN + "]" );
266+ }
267+ array .setType (ColumnType .encodeArrayType (array .getElemType (), 3 ));
268+ array .setDimLen (0 , dim1 );
269+ array .setDimLen (1 , dim2 );
270+ array .setDimLen (2 , dim3 );
271+ array .applyShape ();
272+ flatLength = array .getFlatViewLength ();
273+ memA = array .startMemoryA ();
274+ }
275+
97276 protected void ensureLegalAppendPosition () {
98277 long elementSize = ColumnType .sizeOf (array .getElemType ());
99278 if (memA .getAppendOffset () == flatLength * elementSize ) {
0 commit comments