Skip to content

Commit 9d6538f

Browse files
committed
Support chDB
1 parent 2e61d38 commit 9d6538f

21 files changed

Lines changed: 505 additions & 2 deletions

File tree

Cargo.lock

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src-crates/chdb/examples/chdb.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
use chdb::Connection;
2+
3+
#[tokio::main]
4+
async fn main() {
5+
let conn = Connection::connect(":memory:").await.unwrap();
6+
7+
conn.execute(r"create table if not exists test (id UInt32, name String)")
8+
.unwrap();
9+
conn.execute("insert into test (id, name) values (1, 'Alice')")
10+
.unwrap();
11+
conn.execute("insert into test (id, name) values (2, 'Bob')")
12+
.unwrap();
13+
14+
let query = conn.query(r#"select * from test"#).unwrap();
15+
dbg!(query);
16+
}

src-crates/chdb/src/ffi.rs

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
use dylib::ffi::{ErrorMessage, StringRef, TypedValue};
2+
use std::ffi::c_void;
3+
4+
#[repr(C)]
5+
pub struct Meta {
6+
pub column_count: usize,
7+
pub row_count: usize,
8+
pub duration: u32,
9+
}
10+
11+
#[repr(C)]
12+
pub struct Column {
13+
pub name: StringRef,
14+
pub datatype: StringRef,
15+
}
16+
17+
#[repr(C)]
18+
#[derive(Debug, PartialEq, Eq)]
19+
#[allow(dead_code)]
20+
pub enum DataKind {
21+
Null,
22+
Bool,
23+
I8,
24+
I16,
25+
I32,
26+
I64,
27+
U8,
28+
U16,
29+
U32,
30+
U64,
31+
F32,
32+
F64,
33+
String,
34+
}
35+
36+
#[repr(C)]
37+
pub union Data {
38+
pub null: (),
39+
pub bool: bool,
40+
pub i8: i8,
41+
pub i16: i16,
42+
pub i32: i32,
43+
pub i64: i64,
44+
pub u8: u8,
45+
pub u16: u16,
46+
pub u32: u32,
47+
pub u64: u64,
48+
pub f32: f32,
49+
pub f64: f64,
50+
pub string: StringRef,
51+
}
52+
53+
#[repr(C)]
54+
pub struct ConnectOptions {
55+
pub path: StringRef,
56+
}
57+
58+
pub const CONNECT: &[u8] = b"df_connect";
59+
pub type ConnectFn =
60+
extern "C" fn(options: ConnectOptions, error: *mut ErrorMessage) -> *mut c_void;
61+
62+
pub const CLOSE: &[u8] = b"df_close";
63+
pub type CloseFn = extern "C" fn(conn: *mut c_void);
64+
65+
pub const EXECUTE: &[u8] = b"df_execute";
66+
pub type ExecuteFn = extern "C" fn(conn: *mut c_void, sql: StringRef, error: *mut ErrorMessage);
67+
68+
pub const QUERY: &[u8] = b"df_query";
69+
pub type QueryFn =
70+
extern "C" fn(conn: *mut c_void, sql: StringRef, error: *mut ErrorMessage) -> *mut c_void;
71+
72+
pub const QUERY_META: &[u8] = b"df_query_meta";
73+
pub type QueryMetaFn = extern "C" fn(query: *mut c_void) -> Meta;
74+
75+
pub const QUERY_COLUMN: &[u8] = b"df_query_column";
76+
pub type QueryColumnFn = extern "C" fn(query: *mut c_void, index: usize) -> Column;
77+
78+
pub const QUERY_VALUE: &[u8] = b"df_query_value";
79+
pub type QueryValueFn =
80+
extern "C" fn(query: *mut c_void, row: usize, col: usize) -> TypedValue<DataKind, Data>;
81+
82+
pub const FREE_QUERY: &[u8] = b"df_free_query";
83+
pub type FreeQueryFn = extern "C" fn(query: *mut c_void);
84+
85+
pub const FREE_ERROR: &[u8] = b"df_free_error";
86+
pub type FreeErrorFn = extern "C" fn(error: ErrorMessage);

src-crates/chdb/src/lib.rs

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
mod ffi;
2+
3+
use dylib::Dylib;
4+
use dylib::driver::{Error, Result};
5+
use dylib::ffi::{ErrorMessage, StringRef};
6+
use ffi::*;
7+
use query::{Query, QueryColumn, Value};
8+
use std::{ffi::c_void, sync::Mutex};
9+
10+
// NOTE:
11+
// Do not update manually
12+
// Use `node ./src-dylib/driver-update.mjs` update the sha256 values.
13+
14+
const CHDB_DRIVER_VERSION: &str = "20260603";
15+
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
16+
const CHDB_SHA256: &str = "82af2fa737c04737b0f15f2deb802338117d7c36279f828220dc2955cfa5c8fc";
17+
#[cfg(all(target_os = "macos", target_arch = "x86_64"))]
18+
const CHDB_SHA256: &str = "ecddfd0861931a9a43c623f76882873f08a3d0a312afc6ac893318237445ca78";
19+
#[cfg(all(target_os = "linux", target_arch = "aarch64", target_env = "gnu"))]
20+
const CHDB_SHA256: &str = "b60cf82d932fe24d38144513c9669118949a1003eff0325f8252f3096f06ef57";
21+
#[cfg(all(target_os = "linux", target_arch = "x86_64", target_env = "gnu"))]
22+
const CHDB_SHA256: &str = "6e77f2d46e51c71b9740fe50122e660732d325b4507ed9530c28e1decf311384";
23+
#[cfg(all(target_os = "windows", target_arch = "aarch64", target_env = "msvc"))]
24+
const CHDB_SHA256: &str = ""; // Unsupported platform
25+
#[cfg(all(target_os = "windows", target_arch = "x86_64", target_env = "msvc"))]
26+
const CHDB_SHA256: &str = ""; // Unsupported platform
27+
28+
#[derive(Debug)]
29+
pub struct Connection {
30+
conn: Mutex<*mut c_void>,
31+
dylib: Dylib,
32+
}
33+
34+
unsafe impl Send for Connection {}
35+
unsafe impl Sync for Connection {}
36+
37+
impl Drop for Connection {
38+
fn drop(&mut self) {
39+
let _ = self.close();
40+
}
41+
}
42+
43+
fn free_error(dylib: &Dylib, error: ErrorMessage) -> Result<Option<Error>, Error> {
44+
if !error.is_null() {
45+
let msg = error.as_str().to_string();
46+
dylib.symbol::<FreeErrorFn>(FREE_ERROR)?(error);
47+
return Ok(Some(Error::Message(msg)));
48+
}
49+
Ok(None)
50+
}
51+
52+
impl Connection {
53+
pub async fn connect(path: &str) -> Result<Self> {
54+
if CHDB_SHA256.is_empty() {
55+
return Err(Error::Message(
56+
"chDB is not supported on the current platform.".into(),
57+
));
58+
}
59+
let mut error = ErrorMessage::null();
60+
let dylib = Dylib::try_load("chdb", CHDB_DRIVER_VERSION, CHDB_SHA256).await?;
61+
let options = ConnectOptions {
62+
path: StringRef::new(path),
63+
};
64+
let conn = dylib.symbol::<ConnectFn>(CONNECT)?(options, &mut error);
65+
if let Some(err) = free_error(&dylib, error)? {
66+
return Err(err);
67+
}
68+
Ok(Self {
69+
conn: Mutex::new(conn),
70+
dylib,
71+
})
72+
}
73+
74+
fn close(&self) -> Result<(), Error> {
75+
let conn = self.conn.lock().map_err(|_| Error::Mutex)?;
76+
self.dylib.symbol::<CloseFn>(CLOSE)?(*conn);
77+
Ok(())
78+
}
79+
80+
pub fn execute(&self, sql: &str) -> Result<(), Error> {
81+
let conn = self.conn.lock().map_err(|_| Error::Mutex)?;
82+
let mut error = ErrorMessage::null();
83+
self.dylib.symbol::<ExecuteFn>(EXECUTE)?(*conn, StringRef::new(sql), &mut error);
84+
if let Some(err) = free_error(&self.dylib, error)? {
85+
return Err(err);
86+
}
87+
Ok(())
88+
}
89+
90+
pub fn query(&self, sql: &str) -> Result<Query, Error> {
91+
let conn = self.conn.lock().map_err(|_| Error::Mutex)?;
92+
let mut error = ErrorMessage::null();
93+
let query = self.dylib.symbol::<QueryFn>(QUERY)?(*conn, StringRef::new(sql), &mut error);
94+
if let Some(err) = free_error(&self.dylib, error)? {
95+
return Err(err);
96+
}
97+
let meta = self.dylib.symbol::<QueryMetaFn>(QUERY_META)?(query);
98+
let query_column = self.dylib.symbol::<QueryColumnFn>(QUERY_COLUMN)?;
99+
let query_value = self.dylib.symbol::<QueryValueFn>(QUERY_VALUE)?;
100+
let columns = (0..meta.column_count)
101+
.map(|i| {
102+
let col = query_column(query, i);
103+
QueryColumn {
104+
name: col.name.as_str().to_string(),
105+
datatype: col.datatype.as_str().to_string(),
106+
}
107+
})
108+
.collect::<Vec<_>>();
109+
let mut rows = Vec::with_capacity(meta.row_count);
110+
for y in 0..meta.row_count {
111+
let mut row = Vec::with_capacity(meta.column_count);
112+
for x in 0..meta.column_count {
113+
let data = query_value(query, y, x);
114+
row.push(unsafe {
115+
match data.kind {
116+
DataKind::Null => Value::Null,
117+
DataKind::Bool => Value::Bool(data.value.bool),
118+
DataKind::I8 => Value::I8(data.value.i8),
119+
DataKind::I16 => Value::I16(data.value.i16),
120+
DataKind::I32 => Value::I32(data.value.i32),
121+
DataKind::I64 => Value::I64(data.value.i64),
122+
DataKind::U8 => Value::U8(data.value.u8),
123+
DataKind::U16 => Value::U16(data.value.u16),
124+
DataKind::U32 => Value::U32(data.value.u32),
125+
DataKind::U64 => Value::U64(data.value.u64),
126+
DataKind::F32 => Value::F32(data.value.f32),
127+
DataKind::F64 => Value::F64(data.value.f64),
128+
DataKind::String => Value::String(data.value.string.as_str().to_string()),
129+
}
130+
});
131+
}
132+
rows.push(row);
133+
}
134+
self.dylib.symbol::<FreeQueryFn>(FREE_QUERY)?(query);
135+
Ok(Query {
136+
columns,
137+
rows,
138+
// TODO
139+
rows_affected: None,
140+
duration: meta.duration,
141+
})
142+
}
143+
144+
pub fn select(&self, sql: &str) -> Result<Vec<Vec<Value>>, Error> {
145+
self.query(sql).map(|q| q.rows)
146+
}
147+
}
148+
149+
#[cfg(test)]
150+
mod tests {
151+
use crate::*;
152+
use query::{QueryColumn, Value};
153+
154+
#[tokio::test]
155+
async fn test_query() {
156+
let conn = Connection::connect(":memory:").await.unwrap();
157+
let mut query = conn.query("select 'hello' as hello").unwrap();
158+
assert_eq!(query.columns.len(), 1);
159+
assert_eq!(
160+
query.columns.remove(0),
161+
QueryColumn {
162+
name: "hello".into(),
163+
datatype: "String".into()
164+
}
165+
);
166+
assert_eq!(query.rows.len(), 1);
167+
assert_eq!(query.rows[0].len(), 1);
168+
assert_eq!(
169+
query.rows.remove(0).remove(0),
170+
Value::String("hello".into())
171+
);
172+
assert_eq!(query.rows_affected, None);
173+
}
174+
}

src-crates/connection-config/connection_config.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ pub enum ConnectionConfig {
1818
ManticoreSearch(ManticoreSearchConfig),
1919
MSSQL(MsSqlConfig),
2020
ClickHouse(ClickHouseConfig),
21+
#[serde(rename = "chDB")]
22+
#[strum(serialize = "chDB")]
23+
ChDb(ChDbConfig),
2124
Databend(DatabendConfig),
2225
BigQuery(BigQueryConfig),
2326
Trino(TrinoConfig),
@@ -74,6 +77,8 @@ pub enum SqlDatabaseType {
7477
ManticoreSearch,
7578
MSSQL,
7679
ClickHouse,
80+
#[serde(rename = "chDB")]
81+
ChDb,
7782
Databend,
7883
BigQuery,
7984
Trino,
@@ -117,6 +122,13 @@ pub struct ClickHouseConfig {
117122
pub proxy: Option<ProxyConfig>,
118123
}
119124

125+
#[derive(Debug, Clone, Serialize, Deserialize)]
126+
pub struct ChDbConfig {
127+
pub path: String,
128+
pub readonly: bool,
129+
pub initial: Option<String>,
130+
}
131+
120132
#[derive(Debug, Clone, Serialize, Deserialize)]
121133
pub struct DatabendConfig {
122134
pub protocol: ConnectProtocol,

src-crates/database/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ r2sql = { path = "../r2sql" }
2525
mysql = { path = "../mysql" }
2626
pgsq = { path = "../pgsq" }
2727
duckdb = { path = "../duckdb" }
28+
chdb = { path = "../chdb" }
2829
clickhouse = { path = "../clickhouse" }
2930
databend = { path = "../databend" }
3031
bigquery = { path = "../bigquery" }

0 commit comments

Comments
 (0)