use std::ffi::{CStr, CString, c_char, c_int};
use std::mem::ManuallyDrop;
use std::path::Path;
use libloading::os::unix::{Library as UnixLibrary, RTLD_LOCAL, RTLD_NOW, Symbol};
use crate::error::{ABI_REVISION, Error, Result};
#[repr(C)]
pub(crate) struct ChsSchema {
_opaque: [u8; 0],
}
#[repr(C)]
pub(crate) struct ChsFilter {
_opaque: [u8; 0],
}
#[repr(C)]
pub(crate) struct ChsBlock {
_opaque: [u8; 0],
}
#[repr(C)]
struct ChsBytes {
data: *mut c_char,
len: usize,
}
pub(crate) const EXPORT_NONE: c_int = -1;
type FnVersion = unsafe extern "C" fn() -> *const c_char;
type FnAbiRevision = unsafe extern "C" fn() -> c_int;
type FnInit = unsafe extern "C" fn(*const c_char, *const c_char, *mut *mut c_char) -> c_int;
type FnSetDefaults = unsafe extern "C" fn(*const c_char, *mut *mut c_char) -> c_int;
type FnFree = unsafe extern "C" fn(*mut c_char);
type FnShutdown = unsafe extern "C" fn();
type FnStr = unsafe extern "C" fn() -> *mut c_char;
type FnValidate =
unsafe extern "C" fn(*const c_char, *mut *mut c_char, *mut c_int, *mut *mut c_char) -> c_int;
type FnCompile = unsafe extern "C" fn(
*const c_char,
*const c_char,
c_int,
*mut c_int,
*mut *mut c_char,
) -> *mut ChsSchema;
type FnSchemaFree = unsafe extern "C" fn(*mut ChsSchema);
type FnEngine = unsafe extern "C" fn(
*mut ChsSchema,
*const c_char,
*const c_char,
*const c_char,
*mut *mut c_char,
) -> c_int;
type FnTtl = unsafe extern "C" fn(*mut ChsSchema, *const c_char, *mut *mut c_char) -> c_int;
type FnRow = unsafe extern "C" fn(
*const ChsSchema,
c_int,
*const c_char,
usize,
*const c_char,
) -> *mut c_char;
type FnRows = unsafe extern "C" fn(
*const ChsSchema,
c_int,
*const c_char,
usize,
*const c_char,
c_int,
std::ffi::c_uint,
*mut ChsBytes,
) -> *mut c_char;
type FnFilterCompile = unsafe extern "C" fn(
*const ChsSchema,
*const c_char,
*const c_char,
*mut c_int,
*mut *mut c_char,
) -> *mut ChsFilter;
type FnFilterFree = unsafe extern "C" fn(*mut ChsFilter);
type FnFilterRows = unsafe extern "C" fn(
*const ChsFilter,
c_int,
*const c_char,
usize,
*const c_char,
) -> *mut c_char;
type FnBlockParse = unsafe extern "C" fn(
*const ChsSchema,
c_int,
*const c_char,
usize,
*const c_char,
*mut c_int,
*mut *mut c_char,
) -> *mut ChsBlock;
type FnBlockFree = unsafe extern "C" fn(*mut ChsBlock);
type FnFilterEval = unsafe extern "C" fn(*const ChsFilter, *const ChsBlock) -> *mut c_char;
type FnColCount = unsafe extern "C" fn(*const ChsSchema) -> c_int;
type FnColStr = unsafe extern "C" fn(*const ChsSchema, c_int) -> *const c_char;
type FnColInt = unsafe extern "C" fn(*const ChsSchema, c_int) -> c_int;
type FnReferenceType = unsafe extern "C" fn(*const c_char) -> *mut c_char;
struct ColumnApi {
count: Symbol<FnColCount>,
name: Symbol<FnColStr>,
ty: Symbol<FnColStr>,
default_expr: Symbol<FnColStr>,
default_kind: Symbol<FnColStr>,
is_literal: Symbol<FnColInt>,
}
pub(crate) struct Api {
_lib: ManuallyDrop<UnixLibrary>,
f_version: Symbol<FnVersion>,
abi_revision: c_int,
f_init: Symbol<FnInit>,
f_compile: Symbol<FnCompile>,
f_rows: Symbol<FnRows>,
f_free: Option<Symbol<FnFree>>,
f_schema_free: Option<Symbol<FnSchemaFree>>,
f_shutdown: Option<Symbol<FnShutdown>>,
f_set_defaults: Option<Symbol<FnSetDefaults>>,
f_validate: Option<Symbol<FnValidate>>,
f_row: Option<Symbol<FnRow>>,
f_engine: Option<Symbol<FnEngine>>,
f_ttl: Option<Symbol<FnTtl>>,
f_reference_type: Option<Symbol<FnReferenceType>>,
f_registered_families: Option<Symbol<FnStr>>,
f_function_flags: Option<Symbol<FnStr>>,
f_filter_compile: Option<Symbol<FnFilterCompile>>,
f_filter_free: Option<Symbol<FnFilterFree>>,
f_filter_rows: Option<Symbol<FnFilterRows>>,
f_block_parse: Option<Symbol<FnBlockParse>>,
f_block_free: Option<Symbol<FnBlockFree>>,
f_filter_eval: Option<Symbol<FnFilterEval>>,
cols: Option<ColumnApi>,
}
pub(crate) struct RawColumn {
pub name: String,
pub ty: String,
pub default_kind: String,
pub default_expr: String,
pub default_is_literal: bool,
}
impl Api {
pub(crate) fn open(path: &Path) -> Result<Api> {
let lib = unsafe { UnixLibrary::open(Some(path), RTLD_NOW | RTLD_LOCAL) }.map_err(|e| {
Error::Load {
path: path.to_path_buf(),
message: e.to_string(),
}
})?;
unsafe {
let f_version = require(&lib, b"chs_clickhouse_version\0", path)?;
let f_init = require(&lib, b"chs_init\0", path)?;
let f_compile = require(&lib, b"chs_schema_compile\0", path)?;
let f_rows = require(&lib, b"chs_rows\0", path)?;
let abi_revision = optional::<FnAbiRevision>(&lib, b"chs_abi_revision\0")
.map(|f| f())
.unwrap_or(0);
if abi_revision != 0 && abi_revision != ABI_REVISION {
return Err(Error::Load {
path: path.to_path_buf(),
message: format!(
"artifact reports ABI revision {abi_revision}, this crate speaks \
{ABI_REVISION}; refusing to call through mismatched declarations"
),
});
}
let cols = (|| {
Some(ColumnApi {
count: optional(&lib, b"chs_schema_column_count\0")?,
name: optional(&lib, b"chs_schema_column_name\0")?,
ty: optional(&lib, b"chs_schema_column_type\0")?,
default_expr: optional(&lib, b"chs_schema_column_default_expr\0")?,
default_kind: optional(&lib, b"chs_schema_column_default_kind\0")?,
is_literal: optional(&lib, b"chs_schema_column_default_is_literal\0")?,
})
})();
Ok(Api {
f_version,
abi_revision,
f_init,
f_compile,
f_rows,
f_free: optional(&lib, b"chs_free\0"),
f_schema_free: optional(&lib, b"chs_schema_free\0"),
f_shutdown: optional(&lib, b"chs_shutdown\0"),
f_set_defaults: optional(&lib, b"chs_set_default_settings\0"),
f_validate: optional(&lib, b"chs_validate_type\0"),
f_row: optional(&lib, b"chs_row\0"),
f_engine: optional(&lib, b"chs_schema_engine\0"),
f_ttl: optional(&lib, b"chs_schema_ttl\0"),
f_reference_type: optional(&lib, b"chs_reference_type\0"),
f_registered_families: optional(&lib, b"chs_registered_families\0"),
f_function_flags: optional(&lib, b"chs_function_flags\0"),
f_filter_compile: optional(&lib, b"chs_filter_compile\0"),
f_filter_free: optional(&lib, b"chs_filter_free\0"),
f_filter_rows: optional(&lib, b"chs_filter_rows\0"),
f_block_parse: optional(&lib, b"chs_block_parse\0"),
f_block_free: optional(&lib, b"chs_block_free\0"),
f_filter_eval: optional(&lib, b"chs_filter_eval\0"),
cols,
_lib: ManuallyDrop::new(lib),
})
}
}
pub(crate) fn abi_revision(&self) -> i32 {
self.abi_revision
}
pub(crate) unsafe fn take(&self, p: *mut c_char) -> Option<Vec<u8>> {
unsafe {
if p.is_null() {
return None;
}
let bytes = CStr::from_ptr(p).to_bytes().to_vec();
if let Some(free) = &self.f_free {
free(p);
}
Some(bytes)
}
}
pub(crate) fn clickhouse_version(&self) -> String {
unsafe {
CStr::from_ptr((self.f_version)())
.to_string_lossy()
.into_owned()
}
}
pub(crate) fn init(&self, timezone: &CStr, unsafe_families: &CStr) -> (i32, String) {
let mut err: *mut c_char = std::ptr::null_mut();
unsafe {
let rc = (self.f_init)(timezone.as_ptr(), unsafe_families.as_ptr(), &mut err);
let message = self.take(err);
(rc, string_of(message))
}
}
pub(crate) fn set_default_settings(&self, settings_json: &CStr) -> Option<(i32, String)> {
let f = self.f_set_defaults.as_ref()?;
let mut err: *mut c_char = std::ptr::null_mut();
unsafe {
let rc = f(settings_json.as_ptr(), &mut err);
let message = self.take(err);
Some((rc, string_of(message)))
}
}
pub(crate) fn shutdown(&self) {
if let Some(f) = &self.f_shutdown {
unsafe { f() }
}
}
pub(crate) fn validate_type(&self, expr: &CStr) -> Result<String> {
let Some(f) = self.f_validate.as_ref() else {
return Err(Error::PredatesFeature {
feature: "chs_validate_type",
});
};
let mut canonical: *mut c_char = std::ptr::null_mut();
let mut code: c_int = 0;
let mut err: *mut c_char = std::ptr::null_mut();
unsafe {
let rc = f(expr.as_ptr(), &mut canonical, &mut code, &mut err);
let canon = self.take(canonical);
let message = self.take(err);
if rc == 0 {
Ok(string_of(canon))
} else {
Err(Error::from_code(code, string_of(message)))
}
}
}
pub(crate) fn compile(
&self,
columns_sql: &CStr,
settings_json: &CStr,
mode: i32,
) -> Result<*mut ChsSchema> {
let mut code: c_int = 0;
let mut err: *mut c_char = std::ptr::null_mut();
unsafe {
let handle = (self.f_compile)(
columns_sql.as_ptr(),
settings_json.as_ptr(),
mode as c_int,
&mut code,
&mut err,
);
let message = self.take(err);
if handle.is_null() {
return Err(Error::from_code(code, string_of(message)));
}
Ok(handle)
}
}
pub(crate) fn has_compile_settings(&self) -> bool {
true
}
pub(crate) unsafe fn schema_free(&self, handle: *mut ChsSchema) {
unsafe {
if let Some(f) = &self.f_schema_free {
f(handle);
}
}
}
pub(crate) unsafe fn engine(
&self,
handle: *mut ChsSchema,
engine: &CStr,
order_by: &CStr,
merge_tree_settings_json: &CStr,
) -> Result<()> {
unsafe {
let Some(f) = self.f_engine.as_ref() else {
return Err(Error::PredatesFeature {
feature: "engine support",
});
};
let mut err: *mut c_char = std::ptr::null_mut();
let rc = f(
handle,
engine.as_ptr(),
order_by.as_ptr(),
merge_tree_settings_json.as_ptr(),
&mut err,
);
let message = string_of(self.take(err));
match rc {
0 => Ok(()),
code if code > 0 => Err(Error::Schema {
code,
message,
column: None,
}),
_ => Err(Error::Unsupported { message }),
}
}
}
pub(crate) unsafe fn ttl(&self, handle: *mut ChsSchema, ttl_sql: &CStr) -> Result<()> {
unsafe {
let Some(f) = self.f_ttl.as_ref() else {
return Err(Error::PredatesFeature {
feature: "TTL support",
});
};
let mut err: *mut c_char = std::ptr::null_mut();
let rc = f(handle, ttl_sql.as_ptr(), &mut err);
let message = string_of(self.take(err));
if rc == 0 {
Ok(())
} else {
Err(Error::Unsupported { message })
}
}
}
pub(crate) unsafe fn row(
&self,
handle: *mut ChsSchema,
format: i32,
raw: &[u8],
settings_json: &CStr,
) -> Result<Vec<u8>> {
unsafe {
let Some(f) = self.f_row.as_ref() else {
return Err(Error::PredatesFeature { feature: "chs_row" });
};
let out = f(
handle,
format as c_int,
counted_ptr(raw),
raw.len(),
settings_json.as_ptr(),
);
self.take(out)
.ok_or(Error::PredatesFeature { feature: "chs_row" })
}
}
pub(crate) unsafe fn rows(
&self,
handle: *mut ChsSchema,
format: i32,
body: &[u8],
settings_json: &CStr,
export_format: i32,
doc_flags: u32,
) -> Result<(Vec<u8>, Option<Vec<u8>>)> {
unsafe {
let mut buf = ChsBytes {
data: std::ptr::null_mut(),
len: 0,
};
let want_export = export_format != EXPORT_NONE;
let out = (self.f_rows)(
handle,
format as c_int,
counted_ptr(body),
body.len(),
settings_json.as_ptr(),
export_format as c_int,
doc_flags,
if want_export {
&mut buf
} else {
std::ptr::null_mut()
},
);
let payload = if want_export && !buf.data.is_null() {
let bytes = std::slice::from_raw_parts(buf.data.cast::<u8>(), buf.len).to_vec();
if let Some(free) = &self.f_free {
free(buf.data);
}
Some(bytes)
} else {
None
};
let doc = self.take(out).ok_or(Error::PredatesFeature {
feature: "chs_rows",
})?;
Ok((doc, payload))
}
}
pub(crate) fn has_filter(&self) -> bool {
self.f_filter_compile.is_some()
&& self.f_filter_free.is_some()
&& self.f_filter_rows.is_some()
}
pub(crate) unsafe fn filter_compile(
&self,
handle: *mut ChsSchema,
expr_sql: &CStr,
params_json: &CStr,
) -> Result<*mut ChsFilter> {
if !self.has_filter() {
return Err(Error::PredatesFeature {
feature: "chs_filter_compile",
});
}
let f = self
.f_filter_compile
.as_ref()
.expect("checked by has_filter");
let mut code: c_int = 0;
let mut err: *mut c_char = std::ptr::null_mut();
unsafe {
let filter = f(
handle,
expr_sql.as_ptr(),
params_json.as_ptr(),
&mut code,
&mut err,
);
let message = self.take(err);
if filter.is_null() {
return Err(Error::from_code(code, string_of(message)));
}
Ok(filter)
}
}
pub(crate) unsafe fn filter_free(&self, filter: *mut ChsFilter) {
unsafe {
if let Some(f) = &self.f_filter_free {
f(filter);
}
}
}
pub(crate) unsafe fn filter_rows(
&self,
filter: *mut ChsFilter,
format: i32,
body: &[u8],
settings_json: &CStr,
) -> Result<Vec<u8>> {
let Some(f) = self.f_filter_rows.as_ref() else {
return Err(Error::PredatesFeature {
feature: "chs_filter_rows",
});
};
unsafe {
let out = f(
filter,
format as c_int,
counted_ptr(body),
body.len(),
settings_json.as_ptr(),
);
self.take(out).ok_or(Error::PredatesFeature {
feature: "chs_filter_rows",
})
}
}
pub(crate) fn has_block(&self) -> bool {
self.f_block_parse.is_some() && self.f_block_free.is_some() && self.f_filter_eval.is_some()
}
pub(crate) unsafe fn block_parse(
&self,
handle: *mut ChsSchema,
format: i32,
body: &[u8],
settings_json: &CStr,
) -> Result<*mut ChsBlock> {
if !self.has_block() {
return Err(Error::PredatesFeature {
feature: "chs_block_parse",
});
}
let f = self.f_block_parse.as_ref().expect("checked by has_block");
let mut code: c_int = 0;
let mut err: *mut c_char = std::ptr::null_mut();
unsafe {
let block = f(
handle,
format as c_int,
counted_ptr(body),
body.len(),
settings_json.as_ptr(),
&mut code,
&mut err,
);
let message = self.take(err);
if block.is_null() {
return Err(Error::from_code(code, string_of(message)));
}
Ok(block)
}
}
pub(crate) unsafe fn block_free(&self, block: *mut ChsBlock) {
unsafe {
if let Some(f) = &self.f_block_free {
f(block);
}
}
}
pub(crate) unsafe fn filter_eval(
&self,
filter: *mut ChsFilter,
block: *mut ChsBlock,
) -> Result<Vec<u8>> {
let Some(f) = self.f_filter_eval.as_ref() else {
return Err(Error::PredatesFeature {
feature: "chs_filter_eval",
});
};
unsafe {
let out = f(filter, block);
self.take(out).ok_or(Error::PredatesFeature {
feature: "chs_filter_eval",
})
}
}
pub(crate) unsafe fn columns(&self, handle: *mut ChsSchema) -> Option<Vec<RawColumn>> {
unsafe {
let c = self.cols.as_ref()?;
let n = (c.count)(handle);
if n < 0 {
return None;
}
let mut out = Vec::with_capacity(n as usize);
for i in 0..n {
out.push(RawColumn {
name: borrowed((c.name)(handle, i)),
ty: borrowed((c.ty)(handle, i)),
default_kind: borrowed((c.default_kind)(handle, i)),
default_expr: borrowed((c.default_expr)(handle, i)),
default_is_literal: (c.is_literal)(handle, i) != 0,
});
}
Some(out)
}
}
pub(crate) fn reference_type(&self, type_expr: &CStr) -> Result<Option<String>> {
let Some(f) = self.f_reference_type.as_ref() else {
return Err(Error::PredatesFeature {
feature: "chs_reference_type",
});
};
let out = unsafe { self.take(f(type_expr.as_ptr())) };
let s = string_of(out);
Ok((!s.is_empty()).then_some(s))
}
pub(crate) fn registered_families(&self) -> Result<String> {
let Some(f) = self.f_registered_families.as_ref() else {
return Err(Error::PredatesFeature {
feature: "chs_registered_families",
});
};
Ok(string_of(unsafe { self.take(f()) }))
}
pub(crate) fn function_flags(&self) -> Result<String> {
let Some(f) = self.f_function_flags.as_ref() else {
return Err(Error::PredatesFeature {
feature: "chs_function_flags",
});
};
Ok(string_of(unsafe { self.take(f()) }))
}
}
unsafe fn require<T>(lib: &UnixLibrary, name: &'static [u8], path: &Path) -> Result<Symbol<T>> {
unsafe {
optional(lib, name).ok_or_else(|| Error::NotAnArtifact {
path: path.to_path_buf(),
symbol: std::str::from_utf8(&name[..name.len() - 1]).unwrap_or("chs_?"),
})
}
}
unsafe fn optional<T>(lib: &UnixLibrary, name: &[u8]) -> Option<Symbol<T>> {
unsafe { lib.get(name).ok() }
}
unsafe fn borrowed(p: *const c_char) -> String {
unsafe {
if p.is_null() {
return String::new();
}
CStr::from_ptr(p).to_string_lossy().into_owned()
}
}
fn string_of(bytes: Option<Vec<u8>>) -> String {
match bytes {
Some(b) => String::from_utf8_lossy(&b).into_owned(),
None => String::new(),
}
}
fn counted_ptr(bytes: &[u8]) -> *const c_char {
if bytes.is_empty() {
c"".as_ptr()
} else {
bytes.as_ptr() as *const c_char
}
}
pub(crate) fn cstring(s: &str, what: &'static str) -> Result<CString> {
CString::new(s).map_err(|_| Error::Nul { what })
}