Skip to main content

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;