questdb 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.cargo/audit.toml +17 -9
- data/CHANGELOG.md +7 -0
- data/README.md +1 -1
- data/THIRD_PARTY_NOTICES.md +2 -2
- data/docs/configuration.md +3 -2
- data/docs/design.md +1 -1
- data/docs/python-parity.md +1 -1
- data/docs/releasing.md +1 -1
- data/ext/questdb_ingress/extconf.rb +2 -0
- data/ext/questdb_ingress/questdb_ingress.c +2 -1
- data/ext/questdb_ingress/unlink_build_ruby.rb +58 -0
- data/lib/questdb/arrow.rb +2 -0
- data/lib/questdb/client.rb +6 -0
- data/lib/questdb/config.rb +19 -0
- data/lib/questdb/pooled_sender.rb +4 -1
- data/lib/questdb/sender.rb +10 -1
- data/lib/questdb/version.rb +2 -2
- data/script/check-native-linkage +115 -0
- data/test/client_test.rb +30 -0
- data/test/extconf_link_flags_test.rb +160 -0
- data/vendor/c-questdb-client/.bumpversion.toml +1 -1
- data/vendor/c-questdb-client/CMakeLists.txt +1 -1
- data/vendor/c-questdb-client/cpp_test/qwp_mock_server.hpp +10 -1
- data/vendor/c-questdb-client/cpp_test/test_arrow_c.c +39 -0
- data/vendor/c-questdb-client/cpp_test/test_reader_mock.cpp +428 -0
- data/vendor/c-questdb-client/doc/COMPATIBILITY.md +1 -1
- data/vendor/c-questdb-client/doc/CONSIDERATIONS.md +6 -3
- data/vendor/c-questdb-client/doc/RELEASE_RUNBOOK.md +7 -7
- data/vendor/c-questdb-client/doc/RELEASING.md +6 -6
- data/vendor/c-questdb-client/include/questdb/egress/qwp_reader.h +48 -1
- data/vendor/c-questdb-client/include/questdb/egress/qwp_reader.hpp +52 -0
- data/vendor/c-questdb-client/include/questdb/ingress/line_sender.h +46 -4
- data/vendor/c-questdb-client/include/questdb/ingress/line_sender.hpp +7 -4
- data/vendor/c-questdb-client/include/questdb/ingress/line_sender_core.hpp +1 -0
- data/vendor/c-questdb-client/questdb-rs/Cargo.toml +1 -1
- data/vendor/c-questdb-client/questdb-rs/README.md +13 -13
- data/vendor/c-questdb-client/questdb-rs/src/egress/config.rs +78 -0
- data/vendor/c-questdb-client/questdb-rs/src/egress/mod.rs +2 -2
- data/vendor/c-questdb-client/questdb-rs/src/egress/query_request.rs +174 -1
- data/vendor/c-questdb-client/questdb-rs/src/egress/reader.rs +264 -14
- data/vendor/c-questdb-client/questdb-rs/src/egress/wire/capabilities.rs +69 -0
- data/vendor/c-questdb-client/questdb-rs/src/egress/wire/msg_kind.rs +33 -0
- data/vendor/c-questdb-client/questdb-rs/src/error.rs +33 -5
- data/vendor/c-questdb-client/questdb-rs/src/ingress/buffer/qwp.rs +96 -11
- data/vendor/c-questdb-client/questdb-rs/src/ingress/buffer.rs +6 -3
- data/vendor/c-questdb-client/questdb-rs/src/ingress/column_sender/sender.rs +1 -16
- data/vendor/c-questdb-client/questdb-rs/src/ingress/sender/qwp_ws.rs +19 -13
- data/vendor/c-questdb-client/questdb-rs/src/ingress/sender/qwp_ws_driver.rs +2 -9
- data/vendor/c-questdb-client/questdb-rs/src/ingress/sender/qwp_ws_publisher.rs +3 -3
- data/vendor/c-questdb-client/questdb-rs/src/ingress/sender.rs +101 -17
- data/vendor/c-questdb-client/questdb-rs/src/ingress/tests.rs +32 -0
- data/vendor/c-questdb-client/questdb-rs/src/ingress.rs +3 -2
- data/vendor/c-questdb-client/questdb-rs/src/tests/qwp_ws.rs +96 -16
- data/vendor/c-questdb-client/questdb-rs/tests/egress_failover.rs +219 -1
- data/vendor/c-questdb-client/questdb-rs/tests/egress_live_server.rs +34 -0
- data/vendor/c-questdb-client/questdb-rs-ffi/Cargo.lock +2 -2
- data/vendor/c-questdb-client/questdb-rs-ffi/Cargo.toml +1 -1
- data/vendor/c-questdb-client/questdb-rs-ffi/src/egress.rs +31 -0
- data/vendor/c-questdb-client/questdb-rs-ffi/src/lib.rs +70 -3
- data/vendor/c-questdb-client/system_test/arrow_ffi.py +1 -0
- data/vendor/c-questdb-client/system_test/enterprise_e2e/tests/test_failover.py +12 -6
- data/vendor/c-questdb-client/system_test/enterprise_e2e/tests/test_orphan_drainer_bindings.py +12 -4
- data/vendor/c-questdb-client/system_test/failover_clients/Cargo.lock +1 -1
- data/vendor/c-questdb-client/system_test/soak/workload_rs/Cargo.lock +1 -1
- data/vendor/c-questdb-client.version +1 -1
- metadata +5 -2
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Compatibility
|
|
2
2
|
|
|
3
|
-
This page records the supported versions and toolchains for the 7.
|
|
3
|
+
This page records the supported versions and toolchains for the 7.2.0 client
|
|
4
4
|
release. It is the repository source of truth; language guides should link here
|
|
5
5
|
instead of inventing their own compatibility claims.
|
|
6
6
|
|
|
@@ -93,9 +93,12 @@ Flushing every row increases overhead, while unbounded buffers increase memory
|
|
|
93
93
|
use and recovery time.
|
|
94
94
|
|
|
95
95
|
For ILP, the buffer length is the exact pending encoded byte length. For
|
|
96
|
-
QWP/UDP it is a size hint rather than an eventual datagram size.
|
|
97
|
-
|
|
98
|
-
store-and-forward
|
|
96
|
+
QWP/UDP it is a size hint rather than an eventual datagram size. For a
|
|
97
|
+
QWP/WebSocket row buffer it is at least the size of the frame a flush encodes,
|
|
98
|
+
unless store-and-forward has fallen back to full symbol dictionaries, so a
|
|
99
|
+
buffer whose length is within the frame limit fits in one frame.
|
|
100
|
+
QWP/WebSocket column and row senders encode frames at publication time and
|
|
101
|
+
apply their own store-and-forward limits.
|
|
99
102
|
|
|
100
103
|
Pool limits (`sender_pool_max`, `query_pool_max`) bound concurrent connections.
|
|
101
104
|
At capacity, borrow operations wait up to `acquire_timeout_ms` or fail
|
|
@@ -206,7 +206,7 @@ git fetch origin --tags --prune
|
|
|
206
206
|
git switch main
|
|
207
207
|
git pull --ff-only
|
|
208
208
|
test -z "$(git status --porcelain=v1 --untracked-files=all)"
|
|
209
|
-
git switch -c release/7.
|
|
209
|
+
git switch -c release/7.2.0
|
|
210
210
|
```
|
|
211
211
|
|
|
212
212
|
Do not prepare a release from a dirty feature-branch checkout.
|
|
@@ -216,7 +216,7 @@ Do not prepare a release from a dirty feature-branch checkout.
|
|
|
216
216
|
For a future version change, preview before modifying files:
|
|
217
217
|
|
|
218
218
|
```bash
|
|
219
|
-
NEW_VERSION=7.
|
|
219
|
+
NEW_VERSION=7.2.0
|
|
220
220
|
bump-my-version replace --new-version "$NEW_VERSION" --dry-run
|
|
221
221
|
bump-my-version replace --new-version "$NEW_VERSION"
|
|
222
222
|
```
|
|
@@ -326,7 +326,7 @@ git switch main
|
|
|
326
326
|
git pull --ff-only
|
|
327
327
|
test -z "$(git status --porcelain=v1 --untracked-files=all)"
|
|
328
328
|
|
|
329
|
-
VERSION=7.
|
|
329
|
+
VERSION=7.2.0
|
|
330
330
|
RELEASE_SHA="$(git rev-parse HEAD)"
|
|
331
331
|
|
|
332
332
|
git show --no-patch --format=fuller "$RELEASE_SHA"
|
|
@@ -487,7 +487,7 @@ git switch main
|
|
|
487
487
|
git pull --ff-only
|
|
488
488
|
test -z "$(git status --porcelain=v1 --untracked-files=all)"
|
|
489
489
|
|
|
490
|
-
VERSION=7.
|
|
490
|
+
VERSION=7.2.0
|
|
491
491
|
RELEASE_SHA="$(git rev-parse HEAD)"
|
|
492
492
|
|
|
493
493
|
git show --no-patch --format=fuller "$RELEASE_SHA"
|
|
@@ -572,7 +572,7 @@ can hide packaging mistakes:
|
|
|
572
572
|
SMOKE_ROOT="$(mktemp -d)"
|
|
573
573
|
cargo new "$SMOKE_ROOT/qdb-rs-release-smoke"
|
|
574
574
|
cd "$SMOKE_ROOT/qdb-rs-release-smoke"
|
|
575
|
-
cargo add questdb-rs@=7.
|
|
575
|
+
cargo add questdb-rs@=7.2.0
|
|
576
576
|
cargo check
|
|
577
577
|
```
|
|
578
578
|
|
|
@@ -627,11 +627,11 @@ A crates.io version cannot be overwritten or deleted.
|
|
|
627
627
|
To yank without deleting the already published package:
|
|
628
628
|
|
|
629
629
|
```bash
|
|
630
|
-
cargo yank --version 7.
|
|
630
|
+
cargo yank --version 7.2.0 questdb-rs
|
|
631
631
|
```
|
|
632
632
|
|
|
633
633
|
Yanking does not break consumers whose lockfiles already select the version.
|
|
634
|
-
Never reuse 7.
|
|
634
|
+
Never reuse 7.2.0 after yanking it.
|
|
635
635
|
|
|
636
636
|
## Final release checklist
|
|
637
637
|
|
|
@@ -18,10 +18,10 @@ git fetch origin --tags --prune
|
|
|
18
18
|
git switch main
|
|
19
19
|
git pull --ff-only
|
|
20
20
|
test -z "$(git status --porcelain=v1 --untracked-files=all)"
|
|
21
|
-
git switch -c release/7.
|
|
21
|
+
git switch -c release/7.2.0
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
-
Replace `7.
|
|
24
|
+
Replace `7.2.0` throughout these examples with the intended version.
|
|
25
25
|
|
|
26
26
|
Confirm publisher access, a crates.io token configured with `cargo login` or a
|
|
27
27
|
protected environment variable, a release verifier, and a merge-ready
|
|
@@ -38,13 +38,13 @@ uv tool install bump-my-version
|
|
|
38
38
|
Preview and then apply the version update:
|
|
39
39
|
|
|
40
40
|
```bash
|
|
41
|
-
bump-my-version replace --new-version 7.
|
|
42
|
-
bump-my-version replace --new-version 7.
|
|
41
|
+
bump-my-version replace --new-version 7.2.0 --dry-run
|
|
42
|
+
bump-my-version replace --new-version 7.2.0
|
|
43
43
|
```
|
|
44
44
|
|
|
45
45
|
Review every changed version and update the release notes, compatibility
|
|
46
46
|
statements, and this repository's documentation. The QWP/WebSocket floor for
|
|
47
|
-
7.
|
|
47
|
+
7.2.0 is QuestDB 10.0 and the Rust MSRV is 1.91.0.
|
|
48
48
|
|
|
49
49
|
Refresh every tracked lockfile containing this client:
|
|
50
50
|
|
|
@@ -85,7 +85,7 @@ Enterprise check, and release-candidate soak.
|
|
|
85
85
|
From a clean checkout of the approved `main` commit:
|
|
86
86
|
|
|
87
87
|
```bash
|
|
88
|
-
VERSION=7.
|
|
88
|
+
VERSION=7.2.0
|
|
89
89
|
RELEASE_SHA="$(git rev-parse HEAD)"
|
|
90
90
|
|
|
91
91
|
cd questdb-rs
|
|
@@ -977,6 +977,35 @@ QUESTDB_CLIENT_API void qwp_reader_query_initial_credit(
|
|
|
977
977
|
QUESTDB_CLIENT_API void qwp_reader_query_set_reset_symbol_dict(
|
|
978
978
|
qwp_reader_query*, bool reset);
|
|
979
979
|
|
|
980
|
+
/**
|
|
981
|
+
* Run this query under its own timeout, in milliseconds, instead of the
|
|
982
|
+
* server-wide `query.timeout`. Overrides the connect string's
|
|
983
|
+
* `query_timeout_ms` for this query; `0` clears it (no client timeout).
|
|
984
|
+
* Mirrors `ReaderQuery::timeout`.
|
|
985
|
+
*
|
|
986
|
+
* Requires a server advertising `CAP_QUERY_TIMEOUT` (`0x08` in
|
|
987
|
+
* `qwp_reader_server_info_capabilities`). Unlike
|
|
988
|
+
* `qwp_reader_query_set_reset_symbol_dict`, this does NOT silently degrade:
|
|
989
|
+
* against an older server `qwp_reader_query_execute` fails with
|
|
990
|
+
* `line_sender_error_unsupported_server`, before anything is written, instead
|
|
991
|
+
* of running the query under the server default while the caller believes
|
|
992
|
+
* its timeout applied. The connection is untouched by that refusal. Raise
|
|
993
|
+
* the server's `query.timeout` instead, or clear the timeout here.
|
|
994
|
+
*
|
|
995
|
+
* The server applies no ceiling, so a value above `query.timeout` is
|
|
996
|
+
* honoured — which is the point for a long transform.
|
|
997
|
+
*
|
|
998
|
+
* On expiry the cursor terminates with `line_sender_error_query_timeout` and
|
|
999
|
+
* the connection stays open and authenticated, so the next query runs on it
|
|
1000
|
+
* without reconnecting. DO NOT RETRY A WRITE on that error: a statement that
|
|
1001
|
+
* completes past its timeout is answered with `EXEC_DONE` and its row count
|
|
1002
|
+
* rather than a timeout (so a retry cannot apply it twice), and a DDL /
|
|
1003
|
+
* INSERT / UPDATE that timed out waiting for the table writer may still be
|
|
1004
|
+
* applied by that writer afterwards.
|
|
1005
|
+
*/
|
|
1006
|
+
QUESTDB_CLIENT_API void qwp_reader_query_set_timeout_ms(
|
|
1007
|
+
qwp_reader_query*, uint64_t timeout_ms);
|
|
1008
|
+
|
|
980
1009
|
/**
|
|
981
1010
|
* Install a failover-reset callback on the query. Replaces any previously
|
|
982
1011
|
* installed callback. `user_data` is opaque to the library; pass NULL if
|
|
@@ -1368,13 +1397,31 @@ QUESTDB_CLIENT_API bool qwp_reader_cursor_terminal_end(
|
|
|
1368
1397
|
/**
|
|
1369
1398
|
* If the terminal is `EXEC_DONE`, fill the output parameters and return
|
|
1370
1399
|
* true; otherwise zeroes both outputs and returns false.
|
|
1400
|
+
*
|
|
1401
|
+
* `*out_rows_affected` is the row count of an `INSERT` / `UPDATE`. For a
|
|
1402
|
+
* statement the server executes at parse time (`TRUNCATE`, `RENAME TABLE`,
|
|
1403
|
+
* `SET`, ...) it reports no count: it sends `-1`, which arrives as
|
|
1404
|
+
* `QWP_READER_ROWS_AFFECTED_UNKNOWN` (`UINT64_MAX`). `*out_op_type` is the
|
|
1405
|
+
* server's operation-type byte, carried through opaquely.
|
|
1371
1406
|
*/
|
|
1372
1407
|
QUESTDB_CLIENT_API bool qwp_reader_cursor_terminal_exec_done(
|
|
1373
1408
|
const qwp_reader_cursor* cursor,
|
|
1374
1409
|
uint8_t* out_op_type,
|
|
1375
1410
|
uint64_t* out_rows_affected);
|
|
1376
1411
|
|
|
1377
|
-
/**
|
|
1412
|
+
/** `*out_rows_affected` of `qwp_reader_cursor_terminal_exec_done` for a
|
|
1413
|
+
* statement that reports no row count. */
|
|
1414
|
+
#define QWP_READER_ROWS_AFFECTED_UNKNOWN UINT64_MAX
|
|
1415
|
+
|
|
1416
|
+
/**
|
|
1417
|
+
* Whether freeing this cursor leaves its reader reusable; false for NULL.
|
|
1418
|
+
*
|
|
1419
|
+
* True only once the stream reached a terminal frame (or was cancelled to
|
|
1420
|
+
* one) with no query still active and the transport up. A cursor that is
|
|
1421
|
+
* terminal only because a client-side error ended it mid-stream still has
|
|
1422
|
+
* frames in flight; freeing it sends `CANCEL` and closes the transport, so
|
|
1423
|
+
* this reports false for it.
|
|
1424
|
+
*/
|
|
1378
1425
|
QUESTDB_CLIENT_API bool qwp_reader_cursor_connection_reusable(
|
|
1379
1426
|
const qwp_reader_cursor* cursor);
|
|
1380
1427
|
|
|
@@ -27,6 +27,8 @@
|
|
|
27
27
|
#include "qwp_reader.h"
|
|
28
28
|
|
|
29
29
|
#include <array>
|
|
30
|
+
#include <chrono>
|
|
31
|
+
#include <cmath>
|
|
30
32
|
#include <cstdint>
|
|
31
33
|
#include <cstring>
|
|
32
34
|
#include <functional>
|
|
@@ -1195,6 +1197,56 @@ public:
|
|
|
1195
1197
|
return *this;
|
|
1196
1198
|
}
|
|
1197
1199
|
|
|
1200
|
+
/**
|
|
1201
|
+
* Run this query under its own timeout instead of the server-wide
|
|
1202
|
+
* `query.timeout`. Overrides the connect string's `query_timeout_ms`;
|
|
1203
|
+
* `0` clears it. Requires a server advertising `CAP_QUERY_TIMEOUT`
|
|
1204
|
+
* (`0x08`) — against an older one `execute()` throws
|
|
1205
|
+
* `error_code::unsupported_server`, before anything is written, rather
|
|
1206
|
+
* than silently running under the server default; the connection is
|
|
1207
|
+
* untouched. The server applies no ceiling, so a value above
|
|
1208
|
+
* `query.timeout` is honoured.
|
|
1209
|
+
*
|
|
1210
|
+
* On expiry the cursor terminates with `error_code::query_timeout` and
|
|
1211
|
+
* the connection stays usable. Do not retry a write on it: a statement
|
|
1212
|
+
* that outlives its timeout is reported as `EXEC_DONE` instead, and a
|
|
1213
|
+
* DDL / INSERT / UPDATE that timed out waiting for the table writer may
|
|
1214
|
+
* still be applied afterwards.
|
|
1215
|
+
*/
|
|
1216
|
+
query& timeout_ms(uint64_t timeout_ms)
|
|
1217
|
+
{
|
|
1218
|
+
ensure_impl();
|
|
1219
|
+
::qwp_reader_query_set_timeout_ms(_impl, timeout_ms);
|
|
1220
|
+
return *this;
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
/** `timeout_ms` taking a `std::chrono` duration. A non-zero duration
|
|
1224
|
+
* below 1 ms rounds up to 1 ms, never down to "no timeout"; one too
|
|
1225
|
+
* large for the wire saturates at its maximum. Only zero clears the
|
|
1226
|
+
* timeout: a negative, NaN or infinite duration throws
|
|
1227
|
+
* `error_code::invalid_api_call`. */
|
|
1228
|
+
template <typename Rep, typename Period>
|
|
1229
|
+
query& timeout(std::chrono::duration<Rep, Period> d)
|
|
1230
|
+
{
|
|
1231
|
+
// Convert in double: `duration_cast` to an integral rep is UB for
|
|
1232
|
+
// NaN and for values outside the target range.
|
|
1233
|
+
const double ms = std::chrono::duration<double, std::milli>{d}.count();
|
|
1234
|
+
if (std::isnan(ms) || std::isinf(ms))
|
|
1235
|
+
throw ::questdb::error{
|
|
1236
|
+
error_code::invalid_api_call,
|
|
1237
|
+
"query::timeout(): duration must be finite"};
|
|
1238
|
+
if (ms < 0.0)
|
|
1239
|
+
throw ::questdb::error{
|
|
1240
|
+
error_code::invalid_api_call,
|
|
1241
|
+
"query::timeout(): duration must not be negative"};
|
|
1242
|
+
if (ms == 0.0)
|
|
1243
|
+
return timeout_ms(0);
|
|
1244
|
+
if (ms >= 18446744073709551616.0) // 2^64
|
|
1245
|
+
return timeout_ms(UINT64_MAX);
|
|
1246
|
+
const auto as_u64 = static_cast<uint64_t>(ms);
|
|
1247
|
+
return timeout_ms(as_u64 == 0 ? 1 : as_u64);
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1198
1250
|
/**
|
|
1199
1251
|
* Install a failover-reset callback. Replaces any previously installed
|
|
1200
1252
|
* callback. The closure is stored on the heap and remains alive for
|
|
@@ -168,7 +168,11 @@ typedef enum line_sender_error_code
|
|
|
168
168
|
/** HTTP-upgrade or WebSocket handshake failure. */
|
|
169
169
|
line_sender_error_handshake_error = 20,
|
|
170
170
|
|
|
171
|
-
/** Server returned an unsupported QWP version, encoding, or capability.
|
|
171
|
+
/** Server returned an unsupported QWP version, encoding, or capability.
|
|
172
|
+
* Also raised client-side, before anything is written, when a query
|
|
173
|
+
* carries a per-query timeout and the server does not advertise
|
|
174
|
+
* `CAP_QUERY_TIMEOUT` (see `qwp_reader_query_set_timeout_ms`); the
|
|
175
|
+
* connection is untouched by that refusal. */
|
|
172
176
|
line_sender_error_unsupported_server = 21,
|
|
173
177
|
|
|
174
178
|
/** Wire-format violation: bad magic, truncated frame, unknown
|
|
@@ -290,6 +294,24 @@ typedef enum line_sender_error_code
|
|
|
290
294
|
* queued prefix already carried are duplicated.
|
|
291
295
|
*/
|
|
292
296
|
line_sender_error_symbol_dict_full = 37,
|
|
297
|
+
|
|
298
|
+
/** The query ran past the timeout it carried: server-reported QWP
|
|
299
|
+
* `QUERY_TIMEOUT` (status `0x0E`), produced only for a request that
|
|
300
|
+
* supplied `timeout_ms` (see `qwp_reader_query_set_timeout_ms`). A
|
|
301
|
+
* timeout requested against a server that does not advertise
|
|
302
|
+
* `CAP_QUERY_TIMEOUT` is refused with
|
|
303
|
+
* `line_sender_error_unsupported_server` instead, so retrying a timeout
|
|
304
|
+
* with a larger budget cannot loop against a server that ignores it.
|
|
305
|
+
*
|
|
306
|
+
* Per-query, not per-connection: the connection stays open and
|
|
307
|
+
* authenticated, so the next query runs on it without reconnecting.
|
|
308
|
+
*
|
|
309
|
+
* DO NOT RETRY A WRITE ON THIS ERROR. A statement that completes past
|
|
310
|
+
* its timeout is reported as `EXEC_DONE` with its row count rather than
|
|
311
|
+
* as a timeout, precisely so a retrying client cannot apply it twice;
|
|
312
|
+
* and a DDL / INSERT / UPDATE that timed out waiting for the table
|
|
313
|
+
* writer may still be applied by that writer afterwards. */
|
|
314
|
+
line_sender_error_query_timeout = 38,
|
|
293
315
|
} line_sender_error_code;
|
|
294
316
|
|
|
295
317
|
/**
|
|
@@ -352,6 +374,7 @@ typedef line_sender_error_code questdb_error_code;
|
|
|
352
374
|
#define questdb_error_store_resend_required \
|
|
353
375
|
line_sender_error_store_resend_required
|
|
354
376
|
#define questdb_error_symbol_dict_full line_sender_error_symbol_dict_full
|
|
377
|
+
#define questdb_error_query_timeout line_sender_error_query_timeout
|
|
355
378
|
|
|
356
379
|
/** The protocol used to connect with. */
|
|
357
380
|
typedef enum line_sender_protocol
|
|
@@ -833,9 +856,12 @@ void line_sender_buffer_clear(line_sender_buffer* buffer);
|
|
|
833
856
|
/**
|
|
834
857
|
* The current encoded size of the buffered data.
|
|
835
858
|
*
|
|
836
|
-
* For ILP buffers this is the exact pending byte length. For QWP buffers
|
|
837
|
-
* is a buffered size hint, not the exact size of an eventual
|
|
838
|
-
* WebSocket
|
|
859
|
+
* For ILP buffers this is the exact pending byte length. For QWP/UDP buffers
|
|
860
|
+
* this is a buffered size hint, not the exact size of an eventual datagram.
|
|
861
|
+
* For QWP/WebSocket buffers it is at least the size of the frame that
|
|
862
|
+
* flushing the buffer encodes, unless store-and-forward has fallen back to
|
|
863
|
+
* sending full symbol dictionaries because its symbol side-file could not be
|
|
864
|
+
* opened or rolled back.
|
|
839
865
|
*/
|
|
840
866
|
QUESTDB_CLIENT_API
|
|
841
867
|
size_t line_sender_buffer_size(const line_sender_buffer* buffer);
|
|
@@ -2193,6 +2219,22 @@ bool line_sender_qwpws_errors_dropped(
|
|
|
2193
2219
|
uint64_t* dropped_out,
|
|
2194
2220
|
line_sender_error** err_out);
|
|
2195
2221
|
|
|
2222
|
+
/**
|
|
2223
|
+
* Return the current hard per-frame cap of a QWP/WebSocket sender: the
|
|
2224
|
+
* smallest of `max_buf_size`, the server's advertised `X-QWP-Max-Batch-Size`
|
|
2225
|
+
* and the store-and-forward segment payload capacity. A flush whose encoded
|
|
2226
|
+
* frame exceeds it fails with `line_sender_error_batch_too_large` and
|
|
2227
|
+
* publishes nothing. `*server_cap_known_out` is true when the current
|
|
2228
|
+
* connection advertised `X-QWP-Max-Batch-Size`, whether or not that value is
|
|
2229
|
+
* the binding limit. The value can change after reconnect.
|
|
2230
|
+
*/
|
|
2231
|
+
QUESTDB_CLIENT_API
|
|
2232
|
+
bool line_sender_qwpws_effective_frame_cap(
|
|
2233
|
+
const line_sender* sender,
|
|
2234
|
+
size_t* cap_out,
|
|
2235
|
+
bool* server_cap_known_out,
|
|
2236
|
+
line_sender_error** err_out);
|
|
2237
|
+
|
|
2196
2238
|
/**
|
|
2197
2239
|
* Stop accepting new QWP/WebSocket publications and wait for already-published
|
|
2198
2240
|
* frames to resolve. The timeout is configured with the QWP/WebSocket
|
|
@@ -224,9 +224,12 @@ public:
|
|
|
224
224
|
/**
|
|
225
225
|
* The current encoded size of the buffered data.
|
|
226
226
|
*
|
|
227
|
-
* For ILP buffers this is the exact pending byte length. For QWP
|
|
228
|
-
* this is a buffered size hint, not the exact size of any
|
|
229
|
-
* datagram.
|
|
227
|
+
* For ILP buffers this is the exact pending byte length. For QWP/UDP
|
|
228
|
+
* buffers this is a buffered size hint, not the exact size of any
|
|
229
|
+
* eventual datagram. For QWP/WebSocket buffers it is at least the size of
|
|
230
|
+
* the frame that flushing the buffer encodes, unless store-and-forward
|
|
231
|
+
* has fallen back to sending full symbol dictionaries because its symbol
|
|
232
|
+
* side-file could not be opened or rolled back.
|
|
230
233
|
*/
|
|
231
234
|
size_t size() const noexcept
|
|
232
235
|
{
|
|
@@ -1259,7 +1262,7 @@ private:
|
|
|
1259
1262
|
static inline ::line_sender_utf8 name()
|
|
1260
1263
|
{
|
|
1261
1264
|
// Maintained by .bumpversion.cfg
|
|
1262
|
-
static const char user_agent[] = "questdb/c++/7.
|
|
1265
|
+
static const char user_agent[] = "questdb/c++/7.2.0";
|
|
1263
1266
|
::line_sender_utf8 utf8 =
|
|
1264
1267
|
::line_sender_utf8_assert(sizeof(user_agent) - 1, user_agent);
|
|
1265
1268
|
return utf8;
|
|
@@ -104,6 +104,7 @@ enum class error_code : int
|
|
|
104
104
|
batch_too_large = ::line_sender_error_batch_too_large,
|
|
105
105
|
store_resend_required = ::line_sender_error_store_resend_required,
|
|
106
106
|
symbol_dict_full = ::line_sender_error_symbol_dict_full,
|
|
107
|
+
query_timeout = ::line_sender_error_query_timeout,
|
|
107
108
|
};
|
|
108
109
|
// clang-format on
|
|
109
110
|
|
|
@@ -24,7 +24,7 @@ also supported for ingestion.
|
|
|
24
24
|
* [Repository compatibility matrix](../doc/COMPATIBILITY.md)
|
|
25
25
|
* [InfluxDB Line Protocol reference](https://questdb.com/docs/reference/api/ilp/overview/)
|
|
26
26
|
|
|
27
|
-
Version 7.
|
|
27
|
+
Version 7.2.0 requires Rust 1.91.0. QWP over WebSocket requires QuestDB 10.0
|
|
28
28
|
or newer.
|
|
29
29
|
|
|
30
30
|
## Transports
|
|
@@ -178,21 +178,21 @@ then see the
|
|
|
178
178
|
|
|
179
179
|
## Examples
|
|
180
180
|
|
|
181
|
-
A selection of usage examples is available in the [examples directory](https://github.com/questdb/c-questdb-client/tree/7.
|
|
181
|
+
A selection of usage examples is available in the [examples directory](https://github.com/questdb/c-questdb-client/tree/7.2.0/questdb-rs/examples):
|
|
182
182
|
|
|
183
183
|
| Example | Description |
|
|
184
184
|
|---------|-------------|
|
|
185
|
-
| [`basic.rs`](https://github.com/questdb/c-questdb-client/blob/7.
|
|
186
|
-
| [`auth.rs`](https://github.com/questdb/c-questdb-client/blob/7.
|
|
187
|
-
| [`auth_tls.rs`](https://github.com/questdb/c-questdb-client/blob/7.
|
|
188
|
-
| [`from_conf.rs`](https://github.com/questdb/c-questdb-client/blob/7.
|
|
189
|
-
| [`from_env.rs`](https://github.com/questdb/c-questdb-client/blob/7.
|
|
190
|
-
| [`http.rs`](https://github.com/questdb/c-questdb-client/blob/7.
|
|
191
|
-
| [`protocol_version.rs`](https://github.com/questdb/c-questdb-client/blob/7.
|
|
192
|
-
| [`qwp_ws_chunk_and_query.rs`](https://github.com/questdb/c-questdb-client/blob/7.
|
|
193
|
-
| [`qwp_ws_l1_quotes.rs`](https://github.com/questdb/c-questdb-client/blob/7.
|
|
194
|
-
| [`qwp_egress_read.rs`](https://github.com/questdb/c-questdb-client/blob/7.
|
|
195
|
-
| [`polars.rs`](https://github.com/questdb/c-questdb-client/blob/7.
|
|
185
|
+
| [`basic.rs`](https://github.com/questdb/c-questdb-client/blob/7.2.0/questdb-rs/examples/basic.rs) | Minimal TCP ingestion example; shows basic row and array ingestion. |
|
|
186
|
+
| [`auth.rs`](https://github.com/questdb/c-questdb-client/blob/7.2.0/questdb-rs/examples/auth.rs) | Adds authentication (user/password, token) to basic ingestion. |
|
|
187
|
+
| [`auth_tls.rs`](https://github.com/questdb/c-questdb-client/blob/7.2.0/questdb-rs/examples/auth_tls.rs) | Like `auth.rs`, but uses TLS for encrypted TCP connections. |
|
|
188
|
+
| [`from_conf.rs`](https://github.com/questdb/c-questdb-client/blob/7.2.0/questdb-rs/examples/from_conf.rs) | Configures client via connection string instead of builder pattern. |
|
|
189
|
+
| [`from_env.rs`](https://github.com/questdb/c-questdb-client/blob/7.2.0/questdb-rs/examples/from_env.rs) | Reads config from `QDB_CLIENT_CONF` environment variable. |
|
|
190
|
+
| [`http.rs`](https://github.com/questdb/c-questdb-client/blob/7.2.0/questdb-rs/examples/http.rs) | Uses HTTP transport and demonstrates array ingestion with `ndarray`. |
|
|
191
|
+
| [`protocol_version.rs`](https://github.com/questdb/c-questdb-client/blob/7.2.0/questdb-rs/examples/protocol_version.rs) | Shows protocol version selection and feature differences (e.g. arrays). |
|
|
192
|
+
| [`qwp_ws_chunk_and_query.rs`](https://github.com/questdb/c-questdb-client/blob/7.2.0/questdb-rs/examples/qwp_ws_chunk_and_query.rs) | Shares one `QuestDb` pool between concurrent columnar ingestion and query workers. |
|
|
193
|
+
| [`qwp_ws_l1_quotes.rs`](https://github.com/questdb/c-questdb-client/blob/7.2.0/questdb-rs/examples/qwp_ws_l1_quotes.rs) | Columnar ingestion over QWP/WebSocket via the `QuestDb` connection pool. |
|
|
194
|
+
| [`qwp_egress_read.rs`](https://github.com/questdb/c-questdb-client/blob/7.2.0/questdb-rs/examples/qwp_egress_read.rs) | Runs a SQL query and streams the result set over QWP/WebSocket. |
|
|
195
|
+
| [`polars.rs`](https://github.com/questdb/c-questdb-client/blob/7.2.0/questdb-rs/examples/polars.rs) | Round trip: ingests a Polars `DataFrame` and queries it back as one. |
|
|
196
196
|
|
|
197
197
|
## Crate features
|
|
198
198
|
|
|
@@ -88,6 +88,11 @@ pub const DEFAULT_PATH: &str = "/read/v1";
|
|
|
88
88
|
pub const HIGHEST_KNOWN_VERSION: u8 = crate::egress::wire::PROTOCOL_VERSION;
|
|
89
89
|
|
|
90
90
|
/// Default WS port (matches QuestDB HTTP / ILP-HTTP convention).
|
|
91
|
+
/// Default `query_timeout_ms`: `0`, i.e. no client-supplied per-query
|
|
92
|
+
/// timeout, so each query runs under the server-wide `query.timeout`.
|
|
93
|
+
/// Matches the Java client's `query_timeout_ms` default.
|
|
94
|
+
pub const DEFAULT_QUERY_TIMEOUT_MS: u64 = 0;
|
|
95
|
+
|
|
91
96
|
const DEFAULT_PLAIN_PORT: &str = "9000";
|
|
92
97
|
const DEFAULT_TLS_PORT: &str = "9000";
|
|
93
98
|
|
|
@@ -489,6 +494,19 @@ pub struct ReaderConfig {
|
|
|
489
494
|
/// handshake, the WS upgrade (see `auth_timeout_ms`), or the
|
|
490
495
|
/// `SERVER_INFO` read (see `server_info_timeout_ms`).
|
|
491
496
|
pub connect_timeout_ms: u64,
|
|
497
|
+
/// Default per-query timeout in milliseconds, applied to every query
|
|
498
|
+
/// this reader runs unless the query overrides it with
|
|
499
|
+
/// [`ReaderQuery::timeout`](crate::egress::ReaderQuery::timeout). `0`
|
|
500
|
+
/// (the default) means no client timeout: each query runs under the
|
|
501
|
+
/// server-wide `query.timeout`.
|
|
502
|
+
///
|
|
503
|
+
/// A non-zero value requires a server advertising `CAP_QUERY_TIMEOUT`;
|
|
504
|
+
/// against an older server every query is refused before anything is
|
|
505
|
+
/// sent, with [`crate::ErrorCode::UnsupportedServer`], rather than
|
|
506
|
+
/// silently running under the server default. The server applies no
|
|
507
|
+
/// ceiling, so this may legitimately exceed `query.timeout` — which is
|
|
508
|
+
/// the point for long transforms. Connect-string key: `query_timeout_ms`.
|
|
509
|
+
pub query_timeout_ms: u64,
|
|
492
510
|
/// Client's zone identifier — opaque case-insensitive string (e.g.
|
|
493
511
|
/// `eu-west-1a`, `dc-amsterdam`). When set, the host-health tracker
|
|
494
512
|
/// prefers endpoints whose server-advertised `zone_id` matches
|
|
@@ -770,6 +788,7 @@ impl ReaderConfig {
|
|
|
770
788
|
let mut auth_timeout_ms: u64 = DEFAULT_AUTH_TIMEOUT_MS;
|
|
771
789
|
let server_info_timeout_ms: u64 = DEFAULT_SERVER_INFO_TIMEOUT_MS;
|
|
772
790
|
let mut connect_timeout_ms: u64 = 0;
|
|
791
|
+
let mut query_timeout_ms: u64 = DEFAULT_QUERY_TIMEOUT_MS;
|
|
773
792
|
let mut zone: Option<String> = None;
|
|
774
793
|
let mut tls_verify = TlsVerify::On;
|
|
775
794
|
let mut tls_ca = default_tls_ca();
|
|
@@ -914,6 +933,9 @@ impl ReaderConfig {
|
|
|
914
933
|
"connect_timeout" => {
|
|
915
934
|
connect_timeout_ms = parse_value("connect_timeout", val)?;
|
|
916
935
|
}
|
|
936
|
+
"query_timeout_ms" => {
|
|
937
|
+
query_timeout_ms = parse_value("query_timeout_ms", val)?;
|
|
938
|
+
}
|
|
917
939
|
"zone" => {
|
|
918
940
|
// Empty / whitespace-only is treated as unset
|
|
919
941
|
// (zone-blind). Reject CR/LF — these are headers /
|
|
@@ -1044,6 +1066,7 @@ impl ReaderConfig {
|
|
|
1044
1066
|
auth_timeout_ms,
|
|
1045
1067
|
server_info_timeout_ms,
|
|
1046
1068
|
connect_timeout_ms,
|
|
1069
|
+
query_timeout_ms,
|
|
1047
1070
|
zone,
|
|
1048
1071
|
auth,
|
|
1049
1072
|
tls_verify,
|
|
@@ -1986,6 +2009,61 @@ mod tests {
|
|
|
1986
2009
|
assert!(err.msg().contains("connect_timeout"), "msg: {}", err.msg());
|
|
1987
2010
|
}
|
|
1988
2011
|
|
|
2012
|
+
// --- query_timeout_ms (per-query timeout default) ---
|
|
2013
|
+
|
|
2014
|
+
#[test]
|
|
2015
|
+
fn query_timeout_defaults_to_none() {
|
|
2016
|
+
let c = ReaderConfig::from_conf("ws::addr=h:1").unwrap();
|
|
2017
|
+
assert_eq!(
|
|
2018
|
+
c.query_timeout_ms, 0,
|
|
2019
|
+
"default must leave queries under the server-wide query.timeout"
|
|
2020
|
+
);
|
|
2021
|
+
assert_eq!(DEFAULT_QUERY_TIMEOUT_MS, 0);
|
|
2022
|
+
}
|
|
2023
|
+
|
|
2024
|
+
#[test]
|
|
2025
|
+
fn query_timeout_parses_from_connect_string() {
|
|
2026
|
+
let c = ReaderConfig::from_conf("ws::addr=h:1;query_timeout_ms=600000").unwrap();
|
|
2027
|
+
assert_eq!(c.query_timeout_ms, 600_000);
|
|
2028
|
+
}
|
|
2029
|
+
|
|
2030
|
+
#[test]
|
|
2031
|
+
fn query_timeout_zero_is_no_timeout() {
|
|
2032
|
+
// `0` is the documented sentinel for "send no timeout_ms". The
|
|
2033
|
+
// server rejects a zero on the wire, so the client must express
|
|
2034
|
+
// "none" by omitting the field, never by sending 0.
|
|
2035
|
+
let c = ReaderConfig::from_conf("ws::addr=h:1;query_timeout_ms=0").unwrap();
|
|
2036
|
+
assert_eq!(c.query_timeout_ms, 0);
|
|
2037
|
+
}
|
|
2038
|
+
|
|
2039
|
+
#[test]
|
|
2040
|
+
fn query_timeout_has_no_ceiling() {
|
|
2041
|
+
// Deliberate: the server applies no ceiling either, and a dbt-style
|
|
2042
|
+
// transform legitimately wants longer than `query.timeout`. A cap
|
|
2043
|
+
// here would silently cut such a query short.
|
|
2044
|
+
let c = ReaderConfig::from_conf("ws::addr=h:1;query_timeout_ms=86400000").unwrap();
|
|
2045
|
+
assert_eq!(c.query_timeout_ms, 86_400_000);
|
|
2046
|
+
}
|
|
2047
|
+
|
|
2048
|
+
#[test]
|
|
2049
|
+
fn query_timeout_non_numeric_rejected() {
|
|
2050
|
+
let err = ReaderConfig::from_conf("ws::addr=h:1;query_timeout_ms=soon").unwrap_err();
|
|
2051
|
+
assert_eq!(err.code(), ErrorCode::ConfigError);
|
|
2052
|
+
assert!(
|
|
2053
|
+
err.msg().contains("query_timeout_ms"),
|
|
2054
|
+
"msg must name the key: {}",
|
|
2055
|
+
err.msg()
|
|
2056
|
+
);
|
|
2057
|
+
}
|
|
2058
|
+
|
|
2059
|
+
#[test]
|
|
2060
|
+
fn query_timeout_negative_rejected() {
|
|
2061
|
+
// The field is an unsigned varint on the wire; a negative value is
|
|
2062
|
+
// a `STATUS_PARSE_ERROR` at the server, so reject it at parse time.
|
|
2063
|
+
let err = ReaderConfig::from_conf("ws::addr=h:1;query_timeout_ms=-1").unwrap_err();
|
|
2064
|
+
assert_eq!(err.code(), ErrorCode::ConfigError);
|
|
2065
|
+
}
|
|
2066
|
+
|
|
1989
2067
|
#[test]
|
|
1990
2068
|
fn failover_max_duration_defaults_to_30s() {
|
|
1991
2069
|
let c = ReaderConfig::from_conf("ws::addr=h:1").unwrap();
|
|
@@ -90,8 +90,8 @@ pub use config::{
|
|
|
90
90
|
};
|
|
91
91
|
#[cfg(feature = "sync-reader-qwp-ws")]
|
|
92
92
|
pub use reader::{
|
|
93
|
-
BatchView, Cursor, FailoverPhase, FailoverProgressEvent, FailoverResetEvent,
|
|
94
|
-
ReaderQuery, ReaderStats, Terminal,
|
|
93
|
+
BatchView, Cursor, FailoverPhase, FailoverProgressEvent, FailoverResetEvent,
|
|
94
|
+
ROWS_AFFECTED_UNKNOWN, Reader, ReaderQuery, ReaderStats, Terminal,
|
|
95
95
|
};
|
|
96
96
|
pub use server_event::{ServerInfo, ServerRole, UpgradeReject};
|
|
97
97
|
pub use symbol_dict::{SymbolDict, SymbolEntry};
|