Skip to content

Commit c8224a7

Browse files
committed
client refactoring
1 parent 198cb77 commit c8224a7

14 files changed

Lines changed: 585 additions & 235 deletions

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

Lines changed: 267 additions & 166 deletions
Large diffs are not rendered by default.

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

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,21 @@
3030
* Invoked by {@link QwpQueryClient#execute(String, QwpColumnBatchHandler)}:
3131
* once per {@code RESULT_BATCH} frame via {@link #onBatch(QwpColumnBatch)},
3232
* then exactly once via either {@link #onEnd(long)} or
33-
* {@link #onError(byte, String)}.
33+
* {@link #onError(byte, String)} (or {@link #onExecDone} for non-SELECT queries).
3434
* <p>
3535
* The {@link QwpColumnBatch} passed to {@link #onBatch} is valid only for the
36-
* duration of the callback. Copy any values you need to retain.
36+
* duration of that call. Copy any values you need to retain.
37+
* <p>
38+
* <strong>Exception contract:</strong> if any callback method throws, the
39+
* exception propagates out of the {@link QwpQueryClient#execute} call on the
40+
* caller's thread and no further callbacks fire for that query. The connection
41+
* remains usable for subsequent queries.
3742
*/
3843
public interface QwpColumnBatchHandler {
3944

4045
/**
41-
* Invoked for each {@code RESULT_BATCH} received.
46+
* Invoked for each {@code RESULT_BATCH} received. Throwing from here aborts
47+
* the query (see the class-level exception contract).
4248
*
4349
* @param batch column-major view over the batch; valid until {@code onBatch} returns
4450
*/
@@ -52,7 +58,8 @@ public interface QwpColumnBatchHandler {
5258
void onEnd(long totalRows);
5359

5460
/**
55-
* Invoked exactly once if the query fails at any point.
61+
* Invoked exactly once if the query fails at any point, instead of
62+
* {@link #onEnd} / {@link #onExecDone}.
5663
*
5764
* @param status one of the QWP status codes (e.g., {@code STATUS_PARSE_ERROR})
5865
* @param message server-supplied error message (may be empty)
@@ -64,14 +71,16 @@ public interface QwpColumnBatchHandler {
6471
* a non-SELECT (DDL, INSERT, UPDATE, etc.). No batches are delivered for
6572
* such queries -- the server executes the statement and replies with a
6673
* single {@code EXEC_DONE}.
74+
* <p>
75+
* Override this method in any handler that may see non-SELECT SQL (e.g. a
76+
* generic query runner or a test harness that issues {@code CREATE TABLE}
77+
* before querying). SELECT-only handlers can rely on the default no-op and
78+
* will only ever see {@link #onBatch} + {@link #onEnd} / {@link #onError}.
6779
*
6880
* @param opType matches one of {@code CompiledQuery.SELECT} / {@code INSERT} /
6981
* {@code UPDATE} / {@code CREATE_TABLE} / etc. (server-side constants)
7082
* @param rowsAffected rows inserted / updated / deleted; 0 for pure DDL
7183
*/
7284
default void onExecDone(short opType, long rowsAffected) {
73-
// Default no-op lets existing SELECT-only implementations stay source-
74-
// compatible. A handler that ever sees a non-SELECT query should
75-
// override this method.
7685
}
7786
}

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
package io.questdb.client.cutlass.qwp.client;
2626

2727
import io.questdb.client.std.MemoryTag;
28+
import io.questdb.client.std.ObjList;
2829
import io.questdb.client.std.QuietCloseable;
2930
import io.questdb.client.std.Unsafe;
3031

@@ -92,6 +93,14 @@ public class QwpColumnLayout implements QuietCloseable {
9293
* left with stale values -- use {@link #nonNullIdx}/null-check first.
9394
*/
9495
int[] symbolRowIds;
96+
/**
97+
* SYMBOL: lazy String cache indexed by dict ID. Populated on first
98+
* {@code getSymbol}/{@code getSymbolForId}/{@code getString}(SYMBOL) call
99+
* so a query over 10M rows with N distinct symbols materialises only N
100+
* Strings per batch instead of one per row. Cleared (size reset to 0) in
101+
* {@link #clear()} because a reused layout may be rebound to a different dict.
102+
*/
103+
final ObjList<String> symbolStringCache = new ObjList<>();
95104
/**
96105
* Absolute payload address where this column's non-null values start. For
97106
* fixed-width types this is the dense values array. For strings/varchars
@@ -141,6 +150,7 @@ public void clear() {
141150
symbolDictHeapAddr = 0;
142151
symbolDictEntriesAddr = 0;
143152
symbolDictSize = 0;
153+
symbolStringCache.clear();
144154
nextAddr = 0;
145155
}
146156

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

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,17 +26,21 @@
2626

2727
/**
2828
* Per-column metadata recorded when a schema is registered on a client connection.
29+
* Internal to the QWP client; callers read column attributes through the forwarder
30+
* methods on {@link QwpColumnBatch} (for example {@link QwpColumnBatch#getColumnName}).
2931
*/
30-
public class QwpEgressColumnInfo {
31-
public String name;
32-
public int precisionBits; // valid only for GEOHASH
33-
public int scale; // valid only for DECIMAL*
34-
public byte wireType;
32+
class QwpEgressColumnInfo {
33+
String name;
34+
int precisionBits; // valid only for GEOHASH; set per-batch by QwpResultBatchDecoder.parseColumn
35+
int scale; // valid only for DECIMAL*; set per-batch by QwpResultBatchDecoder.parseColumn
36+
byte wireType;
3537

36-
public void of(String name, byte wireType, int scale, int precisionBits) {
38+
void of(String name, byte wireType) {
3739
this.name = name;
3840
this.wireType = wireType;
39-
this.scale = scale;
40-
this.precisionBits = precisionBits;
41+
// scale / precisionBits come from the per-batch column data section, not the schema.
42+
// Reset here in case a schema slot is reused for a different schema.
43+
this.scale = 0;
44+
this.precisionBits = 0;
4145
}
4246
}

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -370,7 +370,12 @@ public static QwpQueryClient fromConfig(CharSequence configurationString) {
370370
}
371371

372372
/**
373-
* Creates a plain-text (non-TLS) QWP query client.
373+
* Creates a plain-text (non-TLS) QWP query client against {@code host:port}
374+
* with all other settings at their defaults. For TLS, authentication,
375+
* custom paths, compression, or buffer pool sizing use
376+
* {@link #fromConfig(CharSequence)} with a connection string instead.
377+
*
378+
* @see #fromConfig(CharSequence)
374379
*/
375380
public static QwpQueryClient newPlainText(CharSequence host, int port) {
376381
return new QwpQueryClient(host, port);

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -405,7 +405,7 @@ private void decodePayload(QwpBatchBuffer buffer, long payload, int payloadLen)
405405
String colName = readUtf8(p, colNameLen);
406406
p += colNameLen;
407407
byte wireType = Unsafe.getUnsafe().getByte(p++);
408-
columns.getQuick(i).of(colName, wireType, 0, 0);
408+
columns.getQuick(i).of(colName, wireType);
409409
}
410410
} else if (schemaMode == QwpConstants.SCHEMA_MODE_REFERENCE) {
411411
if (schemaId >= schemaRegistry.size() || schemaRegistry.getQuick(schemaId) == null) {

core/src/main/java/io/questdb/client/std/Long256.java

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,18 @@
2525
package io.questdb.client.std;
2626

2727
/**
28-
* A 256-bit hash with string representation up to 64 hex digits following a prefix '0x'.
29-
* (e.g. 0xaba86bf575ba7fde98b6673bb7d85bf489fd71a619cddaecba5de0378e3d22ed)
28+
* A 256-bit value split into four 64-bit words, least-significant first.
29+
* Also used for QuestDB's DECIMAL256 unscaled value (same wire layout).
30+
* Pair with {@link Long256Impl} as a reusable sink.
3031
*/
3132
public interface Long256 {
3233
int BYTES = 32;
33-
}
34+
35+
long getLong0();
36+
37+
long getLong1();
38+
39+
long getLong2();
40+
41+
long getLong3();
42+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
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.std;
26+
27+
/**
28+
* Mutable concrete {@link Long256} backed by four {@code long} fields. Intended
29+
* as a reusable sink: hand the same instance to a series of producers (e.g.
30+
* QWP batch accessors) to avoid per-row allocation.
31+
*/
32+
public class Long256Impl implements Long256, Long256Sink {
33+
private long l0;
34+
private long l1;
35+
private long l2;
36+
private long l3;
37+
38+
@Override
39+
public long getLong0() {
40+
return l0;
41+
}
42+
43+
@Override
44+
public long getLong1() {
45+
return l1;
46+
}
47+
48+
@Override
49+
public long getLong2() {
50+
return l2;
51+
}
52+
53+
@Override
54+
public long getLong3() {
55+
return l3;
56+
}
57+
58+
@Override
59+
public void setAll(long l0, long l1, long l2, long l3) {
60+
this.l0 = l0;
61+
this.l1 = l1;
62+
this.l2 = l2;
63+
this.l3 = l3;
64+
}
65+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
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.std;
26+
27+
/**
28+
* Write-side of a 256-bit value split into four 64-bit words. Implementations
29+
* accept a full value in a single call, so producers can ship all four words
30+
* with one virtual dispatch instead of one per word.
31+
*/
32+
public interface Long256Sink {
33+
34+
/**
35+
* Copies four little-endian 64-bit words starting at {@code address} into
36+
* this sink. Default implementation issues four native 64-bit loads and
37+
* delegates to {@link #setAll}.
38+
*/
39+
default void fromAddress(long address) {
40+
setAll(
41+
Unsafe.getUnsafe().getLong(address),
42+
Unsafe.getUnsafe().getLong(address + 8L),
43+
Unsafe.getUnsafe().getLong(address + 16L),
44+
Unsafe.getUnsafe().getLong(address + 24L)
45+
);
46+
}
47+
48+
/**
49+
* Sets all four 64-bit words of this value. {@code l0} is the least
50+
* significant word; {@code l3} the most significant.
51+
*/
52+
void setAll(long l0, long l1, long l2, long l3);
53+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
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.std;
26+
27+
/**
28+
* Mutable 128-bit UUID value split into two 64-bit words. Intended as a
29+
* reusable sink: hand the same instance to a series of producers (e.g. QWP
30+
* batch accessors) and read {@link #getLo} / {@link #getHi} after each
31+
* {@link #setAll} call.
32+
*/
33+
public class Uuid {
34+
public static final int BYTES = 16;
35+
private long hi;
36+
private long lo;
37+
38+
/**
39+
* Loads the two 64-bit words from {@code address} (little-endian, low word first).
40+
*/
41+
public void fromAddress(long address) {
42+
setAll(
43+
Unsafe.getUnsafe().getLong(address),
44+
Unsafe.getUnsafe().getLong(address + 8L)
45+
);
46+
}
47+
48+
public long getHi() {
49+
return hi;
50+
}
51+
52+
public long getLo() {
53+
return lo;
54+
}
55+
56+
/**
57+
* Sets both 64-bit words. {@code lo} is the least significant; {@code hi}
58+
* the most significant.
59+
*/
60+
public void setAll(long lo, long hi) {
61+
this.lo = lo;
62+
this.hi = hi;
63+
}
64+
}

0 commit comments

Comments
 (0)