Skip to content

Commit 28ef787

Browse files
committed
client API refactor
1 parent c8224a7 commit 28ef787

13 files changed

Lines changed: 3066 additions & 11 deletions

File tree

core/src/main/java/io/questdb/client/cutlass/qwp/client/ColumnView.java

Lines changed: 455 additions & 0 deletions
Large diffs are not rendered by default.

core/src/main/java/io/questdb/client/cutlass/qwp/client/QwpColumnBatch.java

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@
6767
* NULL. Per-accessor NULL behaviour is not repeated in method docs; this
6868
* paragraph is the contract.
6969
* <p>
70-
* <strong>Four zero-allocation idioms</strong> -- pick whichever matches the
70+
* <strong>Five zero-allocation idioms</strong> -- pick whichever matches the
7171
* wire type of the column:
7272
* <ul>
7373
* <li><b>Primitive accessors</b> ({@link #getLongValue}, {@link #getIntValue},
@@ -86,6 +86,13 @@
8686
* {@link #getSymbolId}) materialises each distinct SYMBOL dict entry into
8787
* a {@link String} at most once per batch, regardless of how many rows
8888
* reference it.</li>
89+
* <li><b>Row / column views</b> ({@link #row(int)}, {@link #column(int)},
90+
* {@link #forEachRow(RowCallback)}) wrap the {@code (col, row)} primitives
91+
* with single-arg accessors. {@link RowView} pins the current row;
92+
* {@link ColumnView} pins the column once and exposes a direct-address
93+
* surface ({@link ColumnView#valuesAddr()}, {@link ColumnView#nullBitmapAddr()},
94+
* etc.) for SIMD or JNI consumers. Both flyweights are batch-owned and
95+
* reused.</li>
8996
* </ul>
9097
* The heap-allocating convenience accessors {@link #getString(int, int)},
9198
* {@link #getBinary(int, int)}, and {@link #getDoubleArrayElements(int, int)}
@@ -97,6 +104,10 @@ public class QwpColumnBatch {
97104
// BINARY views -- re-pointed per call, never re-allocated.
98105
private final DirectByteSlice binaryA = new DirectByteSlice();
99106
private final DirectByteSlice binaryB = new DirectByteSlice();
107+
// One ColumnView per column index, lazily created by column(int). Slots are
108+
// re-pointed in place across batches; the list grows but never shrinks so a
109+
// wide-then-narrow query sequence keeps reusing the same instances.
110+
private final ObjList<ColumnView> columnViews = new ObjList<>();
100111
// Reusable views for zero-alloc UTF-8 access. strA and strB are dual views
101112
// (same pattern as QuestDB Record.getStrA/getStrB) so callers can compare
102113
// two cells without one overwriting the other.
@@ -109,6 +120,8 @@ public class QwpColumnBatch {
109120
long payloadLimit;
110121
long requestId;
111122
int rowCount;
123+
// Lazily created on first row(int) / forEachRow call; reused for the batch lifetime.
124+
private RowView rowView;
112125

113126
/**
114127
* Server-assigned monotonic sequence number for this batch within the query.
@@ -119,6 +132,43 @@ public long batchSeq() {
119132
return batchSeq;
120133
}
121134

135+
/**
136+
* Returns the {@link ColumnView} for {@code col}, pinned and ready to read.
137+
* The view is owned by the batch and cached per column index, so calling
138+
* {@code column(0)} and {@code column(1)} returns two distinct instances
139+
* that can be held side-by-side. Calling {@code column(c)} a second time
140+
* with the same {@code c} returns the same instance with its layout
141+
* pointer refreshed against the current batch state.
142+
*/
143+
public ColumnView column(int col) {
144+
if (columnViews.size() <= col) {
145+
columnViews.setPos(col + 1);
146+
}
147+
ColumnView view = columnViews.getQuick(col);
148+
if (view == null) {
149+
view = new ColumnView(this);
150+
columnViews.setQuick(col, view);
151+
}
152+
return view.of(col);
153+
}
154+
155+
/**
156+
* Iterates rows in this batch and invokes {@code callback} for each one.
157+
* The {@link RowView} handed to the callback is re-pointed in place across
158+
* iterations -- do not retain it past {@link RowCallback#onRow(RowView)}.
159+
* Throwing from the callback aborts iteration and propagates the exception.
160+
*/
161+
public void forEachRow(RowCallback callback) {
162+
if (rowView == null) {
163+
rowView = new RowView(this);
164+
}
165+
int n = rowCount;
166+
for (int r = 0; r < n; r++) {
167+
rowView.of(r);
168+
callback.onRow(rowView);
169+
}
170+
}
171+
122172
/**
123173
* Returns the dimensionality of the ARRAY value at {@code (col, row)}, or 0 if
124174
* the row is null. Caller must know the column is an ARRAY type.
@@ -584,6 +634,19 @@ public long requestId() {
584634
return requestId;
585635
}
586636

637+
/**
638+
* Returns the cached {@link RowView} pointed at {@code row}. Use as an
639+
* escape hatch from {@link #forEachRow(RowCallback)} when you need to
640+
* drive iteration manually (e.g., interleaved with another data source).
641+
* Single shared instance per batch; two calls overwrite each other.
642+
*/
643+
public RowView row(int row) {
644+
if (rowView == null) {
645+
rowView = new RowView(this);
646+
}
647+
return rowView.of(row);
648+
}
649+
587650
/**
588651
* Address of the column's packed non-null values in the payload buffer.
589652
* Layout depends on the wire type:
@@ -605,8 +668,9 @@ public long valuesAddr(int col) {
605668
* Fast null check once the layout is in hand. Inlining pattern used by all the
606669
* typed accessors: load layout once, check bitmap, read value. Eliminates the
607670
* second {@code ObjList.getQuick(col)} that separate {@code isNull(col,row)} would cost.
671+
* Also reused by {@link ColumnView} so it can keep the layout pointer cached.
608672
*/
609-
private static boolean isLayoutNull(QwpColumnLayout l, int row) {
673+
static boolean isLayoutNull(QwpColumnLayout l, int row) {
610674
if (l.nullBitmapAddr == 0) return false;
611675
byte bm = Unsafe.getUnsafe().getByte(l.nullBitmapAddr + (row >>> 3));
612676
return (bm & (1 << (row & 7))) != 0;
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*+*****************************************************************************
2+
* ___ _ ____ ____
3+
* / _ \ _ _ ___ ___| |_| _ \| __ )
4+
* | | | | | | |/ _ \/ __| __| | | | _ \
5+
* | |_| | |_| | __/\__ \ |_| |_| | |_) |
6+
* \__\_\\__,_|\___||___/\__|____/|____/
7+
*
8+
* Copyright (c) 2014-2019 Appsicle
9+
* Copyright (c) 2019-2026 QuestDB
10+
*
11+
* Licensed under the Apache License, Version 2.0 (the "License");
12+
* you may not use this file except in compliance with the License.
13+
* You may obtain a copy of the License at
14+
*
15+
* http://www.apache.org/licenses/LICENSE-2.0
16+
*
17+
* Unless required by applicable law or agreed to in writing, software
18+
* distributed under the License is distributed on an "AS IS" BASIS,
19+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20+
* See the License for the specific language governing permissions and
21+
* limitations under the License.
22+
*
23+
******************************************************************************/
24+
25+
package io.questdb.client.cutlass.qwp.client;
26+
27+
/**
28+
* Per-row callback consumed by {@link QwpColumnBatch#forEachRow(RowCallback)}.
29+
* The {@link RowView} handed to {@link #onRow(RowView)} is a reusable flyweight
30+
* pointing at the current row; do not retain it past the call. To capture a
31+
* row's values, copy them out of the view.
32+
* <p>
33+
* Throwing from {@link #onRow} aborts iteration and propagates the exception
34+
* out of {@code forEachRow} on the caller's thread.
35+
*/
36+
@FunctionalInterface
37+
public interface RowCallback {
38+
39+
/**
40+
* Invoked once for each row in the batch, in row-index order starting at 0.
41+
*
42+
* @param row reusable view bound to the current row; valid only for the
43+
* duration of this call
44+
*/
45+
void onRow(RowView row);
46+
}

0 commit comments

Comments
 (0)