Skip to main content

chtypes/
compile.rs

1//! The compile builder. `Library::compile` returns a [`CompileRequest`]; the
2//! terminal `.compile()` call is where the C boundary (`chs_schema_compile`)
3//! is actually crossed. One entry point, one terminal method — chosen over
4//! `Option` arguments at every call site (`compile(ddl, None, 0)` on the
5//! common path) because Rust has no named/optional arguments, and a builder
6//! keeps the settings case reading as a sentence:
7//!
8//! ```no_run
9//! # use chtypes::{CompileMode, Registry};
10//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
11//! # let lib = Registry::from_env_or_default()?.for_version("25.8")?;
12//! let schema = lib.compile("a UInt8, b String").compile()?;                       // common case
13//! let schema = lib.compile("n Nested(a Int64, b String)")
14//!     .settings([("flatten_nested", "0")])
15//!     .mode(CompileMode::Declared)
16//!     .compile()?;
17//! # Ok(()) }
18//! ```
19
20use std::sync::Arc;
21
22use crate::error::Result;
23use crate::ffi::cstring;
24use crate::library::{Library, columns_of};
25use crate::schema::{Schema, settings_json};
26
27/// The compile MODE — mirrors `enum chs_compile_mode` in `chtypes.h`. Numeric
28/// values are part of the ABI, exactly like [`crate::Format`].
29///
30/// [`CompileMode::Declared`] is the only defined mode, so this enum has no
31/// way to construct an out-of-range value through [`CompileRequest`]: a
32/// caller that reached past this builder with a raw escape hatch and passed
33/// one anyway would be refused by the library itself with the `-2`
34/// [`crate::CODE_UNSUPPORTED`] sentinel, never guessed at. This crate exposes
35/// no such escape hatch today.
36#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
37#[repr(i32)]
38pub enum CompileMode {
39    /// Every setting the profile names takes the caller's value; every
40    /// setting it does not name keeps the library's own compile base (build
41    /// defaults plus the derived permissive type-gate list — see
42    /// `spec/c-abi.md` §Compile-time settings).
43    #[default]
44    Declared = 0,
45}
46
47impl CompileMode {
48    /// The `chs_compile_mode` integer this mode crosses the C boundary as.
49    pub fn code(self) -> i32 {
50        self as i32
51    }
52}
53
54/// A compile request, built with [`Library::compile`]. See the module docs
55/// for the common case and the settings-profile case side by side.
56pub struct CompileRequest<'a> {
57    pub(crate) lib: &'a Arc<Library>,
58    pub(crate) columns_sql: String,
59    pub(crate) settings: Vec<(String, String)>,
60    pub(crate) mode: CompileMode,
61}
62
63impl<'a> CompileRequest<'a> {
64    /// A DECLARED settings profile — the settings the deployment's server
65    /// runs, fixed into the handle exactly as a real `CREATE TABLE` fixes
66    /// them into the table. Unset (the default) crosses the C boundary as
67    /// `NULL`/`"{}"`: "compile under this build's own compile base", the
68    /// common case, which takes a code path that makes no `Context` copy at
69    /// all. Replaces any settings from an earlier call.
70    pub fn settings<I, K, V>(mut self, settings: I) -> Self
71    where
72        I: IntoIterator<Item = (K, V)>,
73        K: Into<String>,
74        V: Into<String>,
75    {
76        self.settings = settings
77            .into_iter()
78            .map(|(k, v)| (k.into(), v.into()))
79            .collect();
80        self
81    }
82
83    /// The compile mode. Defaults to [`CompileMode::Declared`], the only
84    /// defined mode.
85    pub fn mode(mut self, mode: CompileMode) -> Self {
86        self.mode = mode;
87        self
88    }
89
90    /// Compile, consuming the request. Crosses the C boundary
91    /// (`chs_schema_compile`) and returns the compiled [`Schema`] handle,
92    /// its columns canonicalised by this build.
93    ///
94    /// # Errors
95    ///
96    /// * [`crate::Error::Schema`] — **ClickHouse itself rejected**, with its
97    ///   own code and message: an unknown setting name in the profile — the
98    ///   `chtypes_*` per-call keys included — is the server's own `115`;
99    ///   a type gate DECLARED at a refusing value fails here exactly as that
100    ///   server's `CREATE` would (`455`, `44`); an invalid type or DEFAULT is
101    ///   the server's own code (`50`, `386`, …); an
102    ///   `Enum … DEFAULT <out-of-domain integer>` is refused with `691` on
103    ///   every line, because older servers accept the DDL and then poison the
104    ///   table (`spec/c-abi.md` §Appendix).
105    /// * [`crate::Error::Unsupported`] — **this build declines**
106    ///   ([`crate::CODE_UNSUPPORTED`]): a DEFAULT that is a property of the
107    ///   server or session (`hostName()`, `currentUser()`), one that would
108    ///   block (`sleep`), one that exceeds the admission budgets, or a `mode`
109    ///   this build does not define. A real server might well have accepted
110    ///   the schema — fall back to it rather than reporting a tenant error.
111    /// * [`crate::Error::Nul`] — the DDL or a setting contained an interior
112    ///   NUL byte.
113    pub fn compile(self) -> Result<Schema> {
114        let ddl = cstring(&self.columns_sql, "column list")?;
115        let json = settings_json(&self.settings)?;
116        let (handle, columns) = {
117            let _guard = self.lib.lock();
118            let handle = self.lib.api().compile(&ddl, &json, self.mode.code())?;
119            // SAFETY: the handle was just returned by this library's compile
120            // and is not shared yet.
121            let columns = unsafe { self.lib.api().columns(handle) };
122            (handle, columns)
123        };
124        Ok(Schema::new(
125            Arc::clone(self.lib),
126            handle,
127            columns_of(columns),
128        ))
129    }
130}