Skip to content

Commit de85c22

Browse files
committed
zstd
1 parent c756825 commit de85c22

10 files changed

Lines changed: 492 additions & 12 deletions

File tree

.gitmodules

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[submodule "core/src/main/c/share/zstd"]
2+
path = core/src/main/c/share/zstd
3+
url = https://github.com/facebook/zstd.git

core/CMakeLists.txt

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,42 @@ set(
4949
src/main/c/share/byte_sink.h
5050
)
5151

52+
# libzstd is included via a git submodule at src/main/c/share/zstd (pinned to
53+
# upstream tag v1.5.7). Covers the client side of the QWP egress compression
54+
# feature; the server-side compressor lives in the Rust qdbr crate and isn't
55+
# linked into this library. Only the decompress-only subset of upstream is
56+
# compiled -- the compress/ directory is left out entirely. zstd_jni.c is our
57+
# JNI glue and lives alongside the submodule (not inside) so upstream resets
58+
# don't disturb it.
59+
if (NOT EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/src/main/c/share/zstd/lib/zstd.h)
60+
message(FATAL_ERROR
61+
"libzstd submodule not initialised. Run:\n"
62+
" git submodule update --init --recursive\n"
63+
"from java-questdb-client/.")
64+
endif ()
65+
set(
66+
ZSTD_FILES
67+
src/main/c/share/zstd_jni.c
68+
src/main/c/share/zstd/lib/common/debug.c
69+
src/main/c/share/zstd/lib/common/entropy_common.c
70+
src/main/c/share/zstd/lib/common/error_private.c
71+
src/main/c/share/zstd/lib/common/fse_decompress.c
72+
src/main/c/share/zstd/lib/common/pool.c
73+
src/main/c/share/zstd/lib/common/threading.c
74+
src/main/c/share/zstd/lib/common/xxhash.c
75+
src/main/c/share/zstd/lib/common/zstd_common.c
76+
src/main/c/share/zstd/lib/decompress/huf_decompress.c
77+
src/main/c/share/zstd/lib/decompress/zstd_ddict.c
78+
src/main/c/share/zstd/lib/decompress/zstd_decompress_block.c
79+
src/main/c/share/zstd/lib/decompress/zstd_decompress.c
80+
)
81+
# x86_64-only hand-tuned Huffman decoder; C fallback kicks in when
82+
# ZSTD_DISABLE_ASM is set.
83+
if (ARCH_AMD64 AND NOT WIN32)
84+
list(APPEND ZSTD_FILES src/main/c/share/zstd/lib/decompress/huf_decompress_amd64.S)
85+
endif ()
86+
list(APPEND SOURCE_FILES ${ZSTD_FILES})
87+
5288
# JNI includes
5389
include_directories($ENV{JAVA_HOME}/include/)
5490

@@ -111,6 +147,20 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${OUTPUT})
111147

112148
add_library(questdb SHARED ${SOURCE_FILES})
113149

150+
# libzstd public header is at zstd/lib/zstd.h; internal headers live under
151+
# zstd/lib/common/. Both directories go on the include path so zstd_jni.c can
152+
# use the short include "zstd.h" and the upstream .c files can find their own
153+
# siblings without patching.
154+
target_include_directories(questdb PRIVATE
155+
${CMAKE_CURRENT_SOURCE_DIR}/src/main/c/share/zstd/lib
156+
${CMAKE_CURRENT_SOURCE_DIR}/src/main/c/share/zstd/lib/common)
157+
158+
# Drop the zstd-internal hand-written amd64 assembly on platforms that can't
159+
# assemble it; libzstd falls back to a C implementation when this is set.
160+
if (NOT ARCH_AMD64 OR WIN32)
161+
target_compile_definitions(questdb PRIVATE ZSTD_DISABLE_ASM=1)
162+
endif ()
163+
114164
set(COMMON_OPTIONS "-Wno-gnu-anonymous-struct;-Wno-nested-anon-types;-Wno-unused-parameter;-fPIC;-fno-rtti;-fno-exceptions")
115165

116166
set(DEBUG_OPTIONS "-Wall;-pedantic;-Wextra;-g;-O0")

core/src/main/c/share/zstd

Submodule zstd added at f8745da

core/src/main/c/share/zstd_jni.c

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
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+
/*
26+
* JNI wrapper over the bundled libzstd (decompression only). The server ships
27+
* compression support in the Rust qdbr crate; this file covers the client
28+
* decompression path so RESULT_BATCH frames with FLAG_ZSTD can be decoded
29+
* without any external native dependency.
30+
*
31+
* libzstd is vendored as a git submodule at share/zstd/ pinned to v1.5.7;
32+
* this file lives alongside (not inside) the submodule so upstream resets
33+
* don't nuke our JNI glue.
34+
*/
35+
36+
#include <jni.h>
37+
#include <stdlib.h>
38+
#include "zstd.h"
39+
40+
JNIEXPORT jlong JNICALL Java_io_questdb_client_std_Zstd_createDCtx(
41+
JNIEnv *env, jclass cls) {
42+
return (jlong) (uintptr_t) ZSTD_createDCtx();
43+
}
44+
45+
JNIEXPORT void JNICALL Java_io_questdb_client_std_Zstd_freeDCtx(
46+
JNIEnv *env, jclass cls, jlong ptr) {
47+
if (ptr != 0) {
48+
ZSTD_freeDCtx((ZSTD_DCtx *) (uintptr_t) ptr);
49+
}
50+
}
51+
52+
JNIEXPORT jlong JNICALL Java_io_questdb_client_std_Zstd_decompress(
53+
JNIEnv *env, jclass cls,
54+
jlong ctx,
55+
jlong src_addr, jlong src_len,
56+
jlong dst_addr, jlong dst_cap) {
57+
if (ctx == 0) {
58+
return -1;
59+
}
60+
ZSTD_DCtx *dctx = (ZSTD_DCtx *) (uintptr_t) ctx;
61+
size_t n = ZSTD_decompressDCtx(
62+
dctx,
63+
(void *) (uintptr_t) dst_addr, (size_t) dst_cap,
64+
(const void *) (uintptr_t) src_addr, (size_t) src_len);
65+
if (ZSTD_isError(n)) {
66+
unsigned code = ZSTD_getErrorCode(n);
67+
return -(jlong) code;
68+
}
69+
return (jlong) n;
70+
}

core/src/main/java/io/questdb/client/cutlass/http/client/WebSocketClient.java

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,12 @@ public abstract class WebSocketClient implements QuietCloseable {
105105
private CharSequence host;
106106
private int port;
107107
// QWP version negotiation
108+
// Verbatim header value sent as X-QWP-Accept-Encoding during upgrade, e.g.
109+
// "zstd;level=3,raw". When null, the header is omitted and the server ships
110+
// batches uncompressed. The echoed X-QWP-Content-Encoding response header
111+
// is intentionally not parsed: the RESULT_BATCH decoder branches on
112+
// FLAG_ZSTD in every frame, which is the authoritative signal.
113+
private String qwpAcceptEncoding;
108114
private String qwpClientId;
109115
private int qwpMaxVersion = 1;
110116
// Receive buffer (native memory)
@@ -376,6 +382,16 @@ public void sendPing(int timeout) {
376382
}
377383
}
378384

385+
/**
386+
* Sets the value sent as the {@code X-QWP-Accept-Encoding} upgrade header,
387+
* e.g. {@code "zstd;level=3,raw"}. Pass {@code null} to omit the header
388+
* entirely (server ships uncompressed batches). Must be called before
389+
* {@link #upgrade}.
390+
*/
391+
public void setQwpAcceptEncoding(String acceptEncoding) {
392+
this.qwpAcceptEncoding = acceptEncoding;
393+
}
394+
379395
/**
380396
* Sets the QWP client identifier sent in the X-QWP-Client-Id upgrade header.
381397
*/
@@ -476,6 +492,11 @@ public void upgrade(CharSequence path, int timeout, CharSequence authorizationHe
476492
sendBuffer.putAscii(qwpClientId);
477493
sendBuffer.putAscii("\r\n");
478494
}
495+
if (qwpAcceptEncoding != null) {
496+
sendBuffer.putAscii("X-QWP-Accept-Encoding: ");
497+
sendBuffer.putAscii(qwpAcceptEncoding);
498+
sendBuffer.putAscii("\r\n");
499+
}
479500
if (authorizationHeader != null) {
480501
sendBuffer.putAscii("Authorization: ");
481502
sendBuffer.putAscii(authorizationHeader);
@@ -958,7 +979,7 @@ private void validateUpgradeResponse(int headerEnd) {
958979
throw new HttpClientException("Invalid Sec-WebSocket-Accept header");
959980
}
960981

961-
// Extract X-QWP-Version (optional defaults to 1 if absent)
982+
// Extract X-QWP-Version (optional, defaults to 1 if absent)
962983
serverQwpVersion = extractQwpVersion(response);
963984
}
964985

0 commit comments

Comments
 (0)