questdb/lib.rs
1/*******************************************************************************
2 * ___ _ ____ ____
3 * / _ \ _ _ ___ ___| |_| _ \| __ )
4 * | | | | | | |/ _ \/ __| __| | | | _ \
5 * | |_| | |_| | __/\__ \ |_| |_| | |_) |
6 * \__\_\\__,_|\___||___/\__|____/|____/
7 *
8 * Copyright (c) 2014-2019 Appsicle
9 * Copyright (c) 2019-2025 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#![doc = include_str!("../README.md")]
25
26mod error;
27
28#[cfg(any(feature = "sync-sender-tcp", feature = "sync-sender-qwp-udp"))]
29mod gai;
30
31// Shared RFC 6455 WebSocket plumbing. Compiled whenever either side
32// needs it (ingress QWP/WS sender or egress QWP/WS reader). Each side
33// keeps its own transport-specific state machine on top of these
34// primitives.
35#[cfg(any(feature = "_sender-qwp-ws", feature = "_egress"))]
36mod ws;
37
38// JKS / PKCS#12 trust-store loader for `tls_roots_password`. Pulled
39// in only for the QWP transports — matches the Java reference's
40// `KeyStore.getInstance(...)` surface there. Other ILP transports
41// keep using rustls' native PEM input.
42#[cfg(feature = "_keystore-roots")]
43mod keystore_roots;
44
45pub mod ingress;
46
47// Transport-neutral Arrow field-metadata keys, shared by the ingress encoder
48// and the egress adapter. Homed here so a sender-only `arrow-ingress` build
49// can use them without compiling the egress reader.
50#[cfg(feature = "_arrow")]
51pub mod arrow_metadata;
52
53// Transport-neutral arrow<->polars_arrow FFI bridges, shared by both polars
54// directions.
55#[cfg(feature = "_polars")]
56#[doc(hidden)]
57pub(crate) mod polars_ffi;
58
59#[cfg(feature = "_egress")]
60pub mod egress;
61
62pub use error::*;
63
64// --- Primary entry point -------------------------------------------------
65//
66// `QuestDb` is the connection/pool handle for a QuestDB instance. It spans
67// both directions — it hands out unified ingestion senders (write) and query
68// readers (read) — so it lives in its own top-level `db` module,
69// a peer of `ingress` and `egress` rather than a child of either. Those
70// modules remain the home of the specialised, direction-specific types
71// (`Chunk`, `AckLevel`, `ColumnView`, `Cursor`, `Bind`, …); the common entry
72// path is `use questdb::QuestDb`.
73#[cfg(feature = "sync-sender-qwp-ws")]
74mod db;
75
76#[cfg(feature = "sync-sender-qwp-ws")]
77pub use db::{BorrowedSender, ConnectHandlers, QuestDb};
78// Unstable per-pool connection-count snapshot for soak / leak harnesses.
79// `#[doc(hidden)]` at the definition site; re-exported so `QuestDb`'s
80// `dbg_pool_counts` return type is nameable.
81#[cfg(feature = "sync-sender-qwp-ws")]
82pub use db::{DbgPoolCount, DbgPoolCounts};
83// Internal transport behind `QuestDb::flush_arrow_batch` /
84// `QuestDb::flush_polars_dataframe`. Not part of the public API: hidden from
85// the docs and has no documented constructor. Kept reachable only so the
86// crate's own ingestion entry points can name it.
87#[cfg(feature = "sync-sender-qwp-ws")]
88#[doc(hidden)]
89pub use db::BorrowedDirectColumnSender;
90
91#[cfg(all(feature = "sync-sender-qwp-ws", feature = "_egress"))]
92pub use db::BorrowedReader;
93
94// FFI escape-hatch surface. Hidden and not semver-stable: it exists so the
95// `questdb-rs-ffi` C-ABI crate can borrow owned (lifetime-free) pool handles
96// that C / Python cannot express as Rust lifetimes. Normal Rust users borrow
97// the lifetime-bound handles re-exported above. The `ffi-support` feature
98// implies `sync-sender-qwp-ws`, so the module is always available when enabled.
99#[cfg(feature = "ffi-support")]
100#[doc(hidden)]
101pub use db::ffi_support;
102
103#[cfg(all(test, any(feature = "_sender-qwp-udp", feature = "_sender-qwp-ws")))]
104mod alloc_counter {
105 use std::alloc::{GlobalAlloc, Layout, System};
106 use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
107
108 pub static COUNTING: AtomicBool = AtomicBool::new(false);
109 pub static ALLOC_COUNT: AtomicUsize = AtomicUsize::new(0);
110
111 pub struct CountingAllocator;
112
113 unsafe impl GlobalAlloc for CountingAllocator {
114 unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
115 if COUNTING.load(Ordering::Relaxed) {
116 ALLOC_COUNT.fetch_add(1, Ordering::Relaxed);
117 }
118 unsafe { System.alloc(layout) }
119 }
120
121 unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
122 unsafe { System.dealloc(ptr, layout) }
123 }
124
125 unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
126 if COUNTING.load(Ordering::Relaxed) {
127 ALLOC_COUNT.fetch_add(1, Ordering::Relaxed);
128 }
129 unsafe { System.realloc(ptr, layout, new_size) }
130 }
131 }
132
133 /// Begin counting allocations made through the global allocator.
134 ///
135 /// The counter and the enable flag are process-global, so *any* allocation
136 /// on *any* thread between [`start_counting`] and [`stop_counting`] is
137 /// included. Tests that assert on the result must therefore run
138 /// single-threaded: mark them `#[ignore]` and run with `--test-threads=1`
139 /// (see the existing `qwp_zero_alloc_*` tests for the convention).
140 pub fn start_counting() -> usize {
141 ALLOC_COUNT.store(0, Ordering::SeqCst);
142 COUNTING.store(true, Ordering::SeqCst);
143 0
144 }
145
146 /// Stop counting and return the number of allocations observed since
147 /// [`start_counting`]. Same single-thread constraint as `start_counting`.
148 pub fn stop_counting() -> usize {
149 COUNTING.store(false, Ordering::SeqCst);
150 ALLOC_COUNT.load(Ordering::SeqCst)
151 }
152}
153
154#[cfg(all(test, any(feature = "_sender-qwp-udp", feature = "_sender-qwp-ws")))]
155#[global_allocator]
156static GLOBAL: alloc_counter::CountingAllocator = alloc_counter::CountingAllocator;
157
158#[cfg(test)]
159mod tests;