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}