Skip to content

feat(ilp): binary wire protocol#7

Open
mtopolnik wants to merge 288 commits intomainfrom
jh_experiment_new_ilp
Open

feat(ilp): binary wire protocol#7
mtopolnik wants to merge 288 commits intomainfrom
jh_experiment_new_ilp

Conversation

@mtopolnik
Copy link
Copy Markdown
Contributor

@mtopolnik mtopolnik commented Feb 25, 2026

TODO in run_tests_pipeline.yaml! Change before merging!

# TODO: remove branch once jh_experiment_new_ilp is merged
- script: git clone --depth 1 -b jh_experiment_new_ilp https://github.com/questdb/questdb.git ./questdb

Change to

- script: git clone --depth 1 https://github.com/questdb/questdb.git ./questdb

Summary

This PR adds a new WebSocket-based ingestion path to the Java client using QWP (QuestDB Wire Protocol), a binary protocol that replaces text-based ILP for higher-throughput data ingestion. The existing HTTP and TCP ILP senders remain unchanged.

Users select the new transport via Sender.builder(Transport.WEBSOCKET). The builder accepts WebSocket-specific options such as asyncMode, autoFlushBytes, autoFlushIntervalMillis, and inFlightWindowSize.

Architecture

The implementation follows a layered design:

Protocol layer (cutlass/qwp/protocol/)

  • QwpTableBuffer stores rows in columnar format using off-heap memory (zero-GC on the data path).
  • QwpSchemaHash computes XXHash64 over column names and types, enabling server-side schema caching. The client sends a full schema on the first batch and a hash reference on subsequent batches if the schema has not changed.
  • QwpGorillaEncoder applies delta-of-delta compression to timestamp columns.
  • QwpBitWriter handles bit-level packing for booleans and null bitmaps.
  • QwpConstants defines the wire format: "QWP1" magic bytes, type codes, feature flags, status codes.

Client layer (cutlass/qwp/client/)

  • QwpWebSocketSender implements the Sender interface. It uses a double-buffering scheme: the user thread writes rows into an active MicrobatchBuffer, which is sealed and handed to an I/O thread when an auto-flush trigger fires (row count, byte size, or time interval).
  • QwpWebSocketEncoder serializes QwpTableBuffer contents into binary QWP frames, including delta symbol dictionaries (only new symbols since the last acknowledged batch).
  • InFlightWindow implements a lock-free sliding window protocol that tracks batches awaiting server ACKs, providing backpressure from the server to the user thread.
  • WebSocketSendQueue runs the dedicated I/O thread, managing frame transmission and ACK/NACK response parsing.
  • GlobalSymbolDictionary assigns sequential integer IDs to symbol strings and supports delta encoding across batches.

WebSocket transport (cutlass/http/client/, cutlass/qwp/websocket/)

  • WebSocketClient is a zero-GC WebSocket implementation with platform-specific subclasses for Linux (epoll), macOS (kqueue), and Windows (select).
  • WebSocketFrameParser and WebSocketFrameWriter handle RFC 6455 frame serialization, including fragmentation, close-frame echo, and ping/pong.
  • WebSocketSendBuffer builds masked WebSocket frames directly in native memory.

Bug fixes and robustness improvements

The PR fixes a number of issues found during development and testing:

  • Fix native memory leaks in WebSocketClient constructor and on allocation failure.
  • Fix sendQueue leak on close when flush fails.
  • Fix integer overflows in buffer growth (WebSocketClient, WebSocketSendBuffer, QwpTableBuffer), array dimension products, and putBlockOfBytes().
  • Fix lone surrogate hash mismatch between schema hashing and wire encoding.
  • Fix receiveFrame() throwing instead of returning false, which masked I/O errors as timeouts.
  • Fix pong/close frames clobbering an in-progress send buffer.
  • Fix delta dictionary corruption on send failure by rolling back symbol IDs.
  • Fix stale array offsets after cancelRow() truncation.
  • Fix case-insensitive header validation in WebSocket handshake.
  • Cap receive buffer growth to prevent OOM.
  • Use SecureRnd (ChaCha20-based CSPRNG) for WebSocket masking keys instead of java.util.Random.
  • Validate table names, column names, WebSocket payload lengths, and UTF-8 low surrogates.

Code cleanup

The PR removes ~11,000 lines of dead code:

  • Delete unused utility classes: ConcurrentHashMap (3,791 lines), ConcurrentIntHashMap (3,612 lines), GenericLexer, Base64Helper, LongObjHashMap, FilesFacade, and others.
  • Remove unused methods from Numbers, Chars, Utf8s, Rnd, and ColumnType.
  • Delete obsolete classes: ParanoiaState, GeoHashes, BorrowedArray, HttpCookie.
  • Modernize code style: enhanced switch expressions, pattern variables in instanceof checks.
  • Upgrade minimum Java version from 11 to 17.

CI changes

  • Add a ClientIntegrationTests CI stage that starts a QuestDB server and runs the client's integration tests against it (both default and authenticated configurations).
  • Cache Maven dependencies in CI to speed up builds.
  • Fix sed portability for macOS CI runners.
  • Enable the HTTP server in CI test configurations (required for WebSocket).

Test plan

  • Unit tests cover all protocol building blocks: bit writer, Gorilla encoder, schema hash, column definitions, constants, table buffer, native buffer writer, off-heap memory
  • Unit tests cover WebSocket frame parsing/writing, send buffer, send queue, in-flight window, microbatch buffer, delta/global symbol dictionaries
  • QwpSenderTest (8,346 lines) exercises the full Sender API surface for all column types, null handling, cancelRow, schema changes, and error paths
  • QwpWebSocketSenderTest tests WebSocket-specific sender behavior including async mode
  • QwpWebSocketEncoderTest validates binary encoding for all column types and encoding modes
  • LineSenderBuilderWebSocketTest covers builder validation and configuration for the WebSocket transport
  • Integration tests run the client against a real QuestDB server in CI (default and authenticated)
  • assertMemoryLeak wrappers added to client tests to detect native memory leaks

mtopolnik and others added 24 commits February 24, 2026 16:37
Reorder class members (fields, methods) alphabetically by kind
and visibility across all java-questdb-client source and test
files. This follows the project convention of alphabetical
member ordering.

Remove decorative section-heading comments (// ===, // ---,
// ====================) that no longer serve a purpose after
alphabetical reordering. Incorporate the orphaned "Fast-path
API" comment block into the QwpWebSocketSender class Javadoc.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The bucket boundary constants for two's complement signed ranges
were inverted — the min and max magnitudes were swapped. For an
N-bit two's complement integer the valid range is [-2^(N-1),
2^(N-1) - 1], so:

  7-bit:  [-64, 63]    not [-63, 64]
  9-bit:  [-256, 255]  not [-255, 256]
  12-bit: [-2048, 2047] not [-2047, 2048]

With the old boundaries, a value like 64 would be placed in the
7-bit bucket, but 64 in 7-bit two's complement decodes as -64,
silently corrupting timestamp data at bucket boundaries.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The opcode parameter in beginFrame(int) was accepted but never
stored or used — the opcode only matters when writing the frame
header in endFrame(int), where all callers already pass the
correct value. Remove the misleading parameter to make the API
honest.

Also remove beginBinaryFrame() and beginTextFrame() which were
just wrappers passing an unused opcode. The single caller in
WebSocketClient is updated to call beginFrame() directly.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The single-host fallback in configuration string parsing used
DEFAULT_HTTP_PORT for all non-TCP protocols. This works by
coincidence since DEFAULT_HTTP_PORT and DEFAULT_WEBSOCKET_PORT
are both 9000, but is semantically incorrect. Use the proper
DEFAULT_WEBSOCKET_PORT constant when the protocol is WebSocket.

Also clean up Javadoc: remove a dangling isRetryable() reference
from Sender.java, fix grammar ("allows to use" -> "allows
using"), and tidy LineSenderException Javadoc.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
inc() called putAt0() directly without adding the key to the `list`
field. This caused keys() to be incomplete and valueQuick() to return
wrong results for keys inserted via inc(). Add the missing
list.add() call, consistent with putAt() and putIfAbsent().

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When a high surrogate was detected in the UTF-8 encoding paths,
the next char was consumed and used as a low surrogate without
validating it was actually in the [0xDC00, 0xDFFF] range. This
produced garbage 4-byte sequences and silently swallowed the
following character.

Add Character.isLowSurrogate(c2) checks in all 5 putUtf8/hasher
encoding sites and both utf8Length methods. Invalid surrogates
now emit '?' and re-process the consumed char on the next
iteration, consistent with Utf8s.encodeUtf16Surrogate().

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The boolean[] wrapper and anonymous WebSocketFrameHandler were
allocated on every loop iteration inside waitForAck(), generating
GC pressure on the data ingestion hot path.

Hoist both into reusable instance fields: ackResponse (WebSocket
response buffer), sawBinaryAck (plain boolean replacing the
boolean[] wrapper), and ackHandler (a static nested class
AckFrameHandler replacing the per-iteration anonymous class).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The close() method in async mode was only waiting for pending
batches to be written to the wire (via sendQueue.close()), but
did not wait for the server to acknowledge receipt. This caused
data loss when close() was called without an explicit flush(),
since the connection was torn down before the server finished
processing.

Add sendQueue.flush() and inFlightWindow.awaitEmpty() before
sendQueue.close() to match the behavior of flush().

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add TYPE_GEOHASH to elementSize(), allocateStorage(), and
addNull() so geohash values are stored as longs (8 bytes) with
-1L as the null sentinel. Also add an explicit case in
QwpConstants.getFixedTypeSize() to document that GEOHASH is
intentionally variable-width on the wire.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace the XorShift128 Rnd PRNG with a new ChaCha20-based
SecureRnd for WebSocket frame mask key generation. The previous
implementation seeded Rnd with System.nanoTime() and
System.currentTimeMillis(), which is predictable and does not
meet RFC 6455 Section 5.3's requirement for strong entropy.

SecureRnd implements ChaCha20 in counter mode (RFC 7539), seeded
once from java.security.SecureRandom at construction time. After
initialization there are zero heap allocations — all state lives
in two pre-allocated int[16] arrays. Each ChaCha20 block yields
16 mask keys, so the amortized cost is minimal.

Includes a known-answer test using the RFC 7539 Section 2.3.2
test vector to verify correctness of the ChaCha20 implementation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove the redundant bitsInBuffer % 8 outer guard. Since
ensureBits() always loads whole bytes, the invariant
(totalBitsRead + bitsInBuffer) % 8 == 0 always holds, making
bitsInBuffer % 8 and totalBitsRead % 8 equivalent checks.
The simplified version uses only totalBitsRead % 8 which more
clearly expresses the intent.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
utf8Length() correctly counts lone surrogates as 1 byte ('?'
replacement), but putUtf8() let them fall through to the BMP
3-byte encoding path. This 2-byte-per-surrogate discrepancy
corrupts varint-prefixed string lengths written by putString().

Add a Character.isSurrogate() check before the 3-byte BMP branch
in all three putUtf8() implementations: NativeBufferWriter,
WebSocketSendBuffer, and OffHeapAppendMemory. Add tests verifying
lone high/low surrogates write 1 byte and that putUtf8() and
utf8Length() agree for all surrogate edge cases.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
addSymbol() and addSymbolWithGlobalId() incremented size without
writing data or incrementing valueCount when value was null and
the column was non-nullable. This caused a permanent misalignment
between logical row count and physical data.

Delegate null values to addNull(), which already handles both
nullable (mark in bitmap) and non-nullable (write sentinel)
cases correctly. Add a test that verifies size and valueCount
stay in sync for non-nullable symbol columns with null values.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
QwpTableBuffer.addNull() had no handling for TYPE_DOUBLE_ARRAY and
TYPE_LONG_ARRAY in the non-nullable branch. This caused valueCount
and size to advance without writing array metadata (dims, shapes),
corrupting array index tracking for subsequent rows.

The fix writes an empty 1D array (dims=1, shape=0, no data elements)
as the sentinel value, keeping dims/shapes/data offsets consistent.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
sendCloseFrame() and sendPing() used the main sendBuffer to build
and send control frames. If the caller had an in-progress data
frame in sendBuffer (obtained via getSendBuffer()), these methods
would destroy it by calling sendBuffer.reset().

Switch both methods to use controlFrameBuffer, which already
exists for exactly this purpose — sendCloseFrameEcho() and
sendPongFrame() were already using it correctly.

Add unit tests that verify the sendBuffer is preserved across
sendCloseFrame() and sendPing() calls.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Numbers.ceilPow2(int) returns 2^30 for inputs between 2^30+1 and
Integer.MAX_VALUE due to internal overflow handling. This caused
grow() to allocate a buffer smaller than the required capacity.

Fix by taking the max of ceilPow2's result and the raw required
capacity before clamping to maxBufferSize.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When readFrom() parsed an error response where status != OK and the
buffer was large enough to enter the outer branch (length > offset + 2),
but msgLen was 0, the inner condition (msgLen > 0) failed without
clearing errorMessage. On reused WebSocketResponse objects this left
the error message from a previous parse visible to callers.

Add an else branch to the inner if that sets errorMessage = null when
msgLen is 0 or the message bytes are truncated. Add a regression test
that parses an error-with-message followed by an error-with-empty-message
on the same object and asserts errorMessage is null.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
QwpWebSocketSender.reset() only reset the current table buffer,
leaving other tables' data intact and pendingRowCount nonzero.
The Sender.reset() contract requires all pending state to be
discarded. Now iterate every table buffer in the map, zero
pendingRowCount and firstPendingRowTimeNanos, and clear the
current-table and cached-column references.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
ensureBits() loaded bytes into the 64-bit bitBuffer without checking
whether all 8 bits of the incoming byte would actually fit. When
bitsInBuffer exceeded 56, the left-shift lost high bits that overflowed
position 63, and bitsInBuffer could grow past 64 — silently corrupting
the buffer.

Add a bitsInBuffer <= 56 guard to the while loop so we never attempt
to load a byte when fewer than 8 free bit positions remain.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
skip() advanced the position without calling ensureCapacity(), so a
skip that exceeded the current buffer capacity would let subsequent
writes corrupt native memory past the allocation. Add the missing
ensureCapacity(bytes) call, matching the existing pattern in
WebSocketSendBuffer.skip() and OffHeapAppendMemory.skip().

Add a regression test that skips past the initial capacity and
verifies the buffer grows.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
flushPendingRows() and flushSync() reset table buffers but did not
clear cachedTimestampColumn and cachedTimestampNanosColumn. If the
table buffer's columns were ever recreated rather than just data-
reset, the stale references would become dangling. Null both fields
at the start of each flush method, consistent with what reset() and
table() already do.

Add QwpWebSocketSenderFlushCacheTest to verify the invariant for
both micros and nanos timestamp paths.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
putBlockOfBytes() accepted a long len parameter but cast it to int
before calling ensureCapacity(). When len > Integer.MAX_VALUE, the
cast wraps to a negative number, so ensureCapacity() skips the buffer
grow, but copyMemory() still uses the original long len, causing a
buffer overflow.

Validate len fits in int range before casting in both
NativeBufferWriter and WebSocketSendBuffer. Use the narrowed int
value consistently for ensureCapacity(), copyMemory(), and position
update.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Merge QwpWebSocketSenderResetTest and QwpWebSocketSenderFlushCacheTest
into a single QwpWebSocketSenderStateTest class. Both tested
QwpWebSocketSender internal state management with the same reflection
pattern and superclass. The merged class unifies the setField/
setConnected helpers into one setField method and keeps all three
test methods in alphabetical order.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@mtopolnik mtopolnik changed the title Binary wire protocol feat(ilp): binary wire protocol Feb 25, 2026
mtopolnik and others added 5 commits February 26, 2026 09:53
WebSocketSendQueue accepted a queueCapacity parameter in its
constructor, validated it, and logged it, but never used it.
The actual queue is a single volatile slot (pendingBuffer) by
design — matching the double-buffering scheme where at most one
sealed buffer is pending while the other is being filled.

Remove the parameter from the entire chain:
WebSocketSendQueue constructor, QwpWebSocketSender field and
factory methods, Sender.LineSenderBuilder API, and all tests
and benchmark clients that referenced it.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Rename elementSize() to elementSizeInBuffer() in QwpTableBuffer
to make explicit that it returns the in-memory buffer stride,
not the wire-format encoding size. Update javadoc on both
elementSizeInBuffer() and QwpConstants.getFixedTypeSize() to
document the distinction: getFixedTypeSize() returns wire sizes
(0 for bit-packed BOOLEAN, -1 for variable-width GEOHASH),
while elementSizeInBuffer() returns the off-heap buffer stride
(1 for BOOLEAN, 8 for GEOHASH).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
encodeColumn() and encodeColumnWithGlobalSymbols() had nearly
identical switch statements across 15+ type cases, differing
only in the SYMBOL case. Merge them into a single encodeColumn()
with a boolean useGlobalSymbols parameter. Similarly merge the
duplicate encodeTable() and encodeTableWithGlobalSymbols() into
one method. This removes ~100 lines of duplicated code.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove WebSocketChannel, ResponseReader, and WebSocketChannelTest.
These classes are dead code — the actual sender implementation
(QwpWebSocketSender) uses WebSocketClient and WebSocketSendQueue
instead.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
mtopolnik and others added 29 commits April 8, 2026 13:06
- Stack-allocate iovec/WSABUF in sendToScatter JNI to avoid
  per-send calloc on the UDP hot path (falls back to heap for
  >16 segments)
- Add in-progress row guard to QwpWebSocketSender.flush() so
  partial rows are rejected, matching QwpUdpSender behavior
- Merge canUseGorilla() and calculateEncodedSize() into a
  single-pass calculateEncodedSizeIfSupported() to eliminate
  a redundant scan over timestamp data
- Fix native memory leak in SegmentedNativeBufferWriter and
  WebSocketSendBuffer constructors when a later allocation fails
- Close waiter registration race in InFlightWindow by setting
  the waiting thread reference before re-checking the condition
- Remove unnecessary volatile on maxSentSymbolId (user-thread only)
- Add 49 exhaustive QwpGorillaEncoder tests covering round-trip
  encode/decode, all 5 bucket boundaries, degenerate cases, and
  the size overflow guard
- Add 57 WebSocketFrameWriter tests covering header encoding for
  all 3 payload length formats, mask key serialization, XOR
  masking with RFC 6455 vectors, and round-trip with the parser
- Remove banner comments from QwpWebSocketEncoderTest
- Fix stale ILP4/ILP v4 references in Javadoc
- Remove TODO.md, fix redundant Math.max(16,4) in NativeSegmentList

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@mtopolnik
Copy link
Copy Markdown
Contributor Author

[PR Coverage check]

😍 pass : 4224 / 5365 (78.73%)

file detail

path covered line new line coverage
🔵 io/questdb/client/cutlass/http/client/WebSocketClientWindows.java 0 22 00.00%
🔵 io/questdb/client/network/NetworkFacadeImpl.java 0 1 00.00%
🔵 io/questdb/client/std/Chars.java 0 12 00.00%
🔵 io/questdb/client/cutlass/http/client/WebSocketClientOsx.java 0 23 00.00%
🔵 io/questdb/client/cutlass/http/client/HttpClient.java 0 3 00.00%
🔵 io/questdb/client/cutlass/qwp/websocket/WebSocketCloseCode.java 0 19 00.00%
🔵 io/questdb/client/cutlass/http/client/WebSocketFrameHandler.java 1 3 33.33%
🔵 io/questdb/client/cutlass/qwp/client/QwpWebSocketSender.java 364 675 53.93%
🔵 io/questdb/client/std/LowerCaseCharSequenceIntHashMap.java 12 21 57.14%
🔵 io/questdb/client/cutlass/http/client/WebSocketSendBuffer.java 119 201 59.20%
🔵 io/questdb/client/cutlass/qwp/protocol/QwpColumnDef.java 13 22 59.09%
🔵 io/questdb/client/cutlass/qwp/protocol/QwpConstants.java 38 61 62.30%
🔵 io/questdb/client/cutlass/http/client/WebSocketClientFactory.java 5 8 62.50%
🔵 io/questdb/client/cutlass/line/udp/UdpLineChannel.java 2 3 66.67%
🔵 io/questdb/client/cutlass/http/client/WebSocketClient.java 319 469 68.02%
🔵 io/questdb/client/cutlass/http/client/WebSocketClientLinux.java 15 22 68.18%
🔵 io/questdb/client/cutlass/qwp/client/QwpUdpSender.java 661 804 82.21%
🔵 io/questdb/client/cutlass/qwp/client/SegmentedNativeBufferWriter.java 63 76 82.89%
🔵 io/questdb/client/cutlass/qwp/protocol/QwpTableBuffer.java 810 973 83.25%
🔵 io/questdb/client/cutlass/qwp/client/WebSocketResponse.java 65 77 84.42%
🔵 io/questdb/client/cutlass/qwp/client/WebSocketSendQueue.java 244 285 85.61%
🔵 io/questdb/client/Sender.java 229 266 86.09%
🔵 io/questdb/client/cutlass/qwp/client/QwpColumnWriter.java 178 197 90.36%
🔵 io/questdb/client/cutlass/qwp/client/NativeBufferWriter.java 150 160 93.75%
🔵 io/questdb/client/cutlass/qwp/client/NativeSegmentList.java 41 44 93.18%
🔵 io/questdb/client/cutlass/qwp/client/MicrobatchBuffer.java 121 128 94.53%
🔵 io/questdb/client/cutlass/qwp/client/InFlightWindow.java 148 155 95.48%
🔵 io/questdb/client/std/CharSequenceIntHashMap.java 43 45 95.56%
🔵 io/questdb/client/cutlass/qwp/protocol/QwpBitWriter.java 61 63 96.83%
🔵 io/questdb/client/cutlass/qwp/websocket/WebSocketFrameParser.java 73 76 96.05%
🔵 io/questdb/client/cutlass/qwp/protocol/OffHeapAppendMemory.java 90 92 97.83%
🔵 io/questdb/client/cutlass/qwp/client/QwpWebSocketEncoder.java 63 63 100.00%
🔵 io/questdb/client/std/CharSequenceObjHashMap.java 1 1 100.00%
🔵 io/questdb/client/cutlass/http/client/HttpClientException.java 3 3 100.00%
🔵 io/questdb/client/std/Unsafe.java 1 1 100.00%
🔵 io/questdb/client/cutlass/qwp/websocket/WebSocketOpcode.java 3 3 100.00%
🔵 io/questdb/client/std/Rnd.java 1 1 100.00%
🔵 io/questdb/client/cairo/ColumnType.java 1 1 100.00%
🔵 io/questdb/client/cutlass/qwp/client/GlobalSymbolDictionary.java 29 29 100.00%
🔵 io/questdb/client/std/str/Utf8s.java 13 13 100.00%
🔵 io/questdb/client/HttpClientConfiguration.java 1 1 100.00%
🔵 io/questdb/client/network/Net.java 5 5 100.00%
🔵 io/questdb/client/std/LowerCaseUtf8SequenceObjHashMap.java 1 1 100.00%
🔵 io/questdb/client/std/Utf8SequenceObjHashMap.java 1 1 100.00%
🔵 io/questdb/client/BuildInformationHolder.java 2 2 100.00%
🔵 io/questdb/client/cutlass/qwp/protocol/QwpGorillaEncoder.java 92 92 100.00%
🔵 io/questdb/client/cutlass/qwp/websocket/WebSocketFrameWriter.java 46 46 100.00%
🔵 io/questdb/client/std/SecureRnd.java 86 86 100.00%
🔵 io/questdb/client/std/Numbers.java 10 10 100.00%

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants