Skip to content

Commit daedae4

Browse files
committed
Enhance error handling and simplify Result type usage in the library
1 parent 35d4223 commit daedae4

File tree

6 files changed

+72
-56
lines changed

6 files changed

+72
-56
lines changed

Cargo.toml

+3
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,6 @@ thiserror = "1"
99

1010
[build-dependencies]
1111
bindgen = "0.70.1"
12+
13+
[dev-dependencies]
14+
tempdir = "0.3.7"

src/error.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
use std::ffi::NulError;
2+
use std::string::FromUtf8Error;
23

34
#[derive(Debug, thiserror::Error)]
45
pub enum Error {
56
#[error("An unknown error has occurred")]
67
Unknown,
8+
#[error("No result")]
9+
NoResult,
710
#[error("Invalid data: {0}")]
811
InvalidData(String),
912
#[error("Invalid path")]
@@ -15,7 +18,9 @@ pub enum Error {
1518
#[error("Insufficient dir permissions")]
1619
InsufficientPermissions,
1720
#[error("Non UTF-8 sequence: {0}")]
18-
NonUtf8Sequence(String),
21+
NonUtf8Sequence(FromUtf8Error),
1922
#[error("{0}")]
2023
QueryError(String),
2124
}
25+
26+
pub type Result<T, Err = Error> = std::result::Result<T, Err>;

src/lib.rs

+10-7
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,10 @@ use std::ffi::{c_char, CString};
1717

1818
use crate::arg::Arg;
1919
use crate::error::Error;
20+
use crate::error::Result;
2021
use crate::query_result::QueryResult;
2122

22-
pub fn execute(query: &str, query_args: Option<&[Arg]>) -> Result<Option<QueryResult>, Error> {
23+
pub fn execute(query: &str, query_args: Option<&[Arg]>) -> Result<QueryResult> {
2324
let mut argv = Vec::with_capacity(query_args.as_ref().map_or(0, |v| v.len()) + 2);
2425
argv.push(arg_clickhouse()?.into_raw());
2526

@@ -33,26 +34,28 @@ pub fn execute(query: &str, query_args: Option<&[Arg]>) -> Result<Option<QueryRe
3334
call_chdb(argv)
3435
}
3536

36-
fn call_chdb(mut argv: Vec<*mut c_char>) -> Result<Option<QueryResult>, Error> {
37+
fn call_chdb(mut argv: Vec<*mut c_char>) -> Result<QueryResult> {
3738
let argc = argv.len() as i32;
3839
let argv = argv.as_mut_ptr();
3940
let result_ptr = unsafe { bindings::query_stable_v2(argc, argv) };
4041

4142
if result_ptr.is_null() {
42-
return Ok(None);
43+
return Err(Error::NoResult);
4344
}
45+
let result = QueryResult::new(result_ptr);
46+
let result = result.check_error()?;
4447

45-
Ok(Some(QueryResult(result_ptr).check_error()?))
48+
Ok(result)
4649
}
4750

48-
fn arg_clickhouse() -> Result<CString, Error> {
51+
fn arg_clickhouse() -> Result<CString> {
4952
Ok(CString::new("clickhouse")?)
5053
}
5154

52-
fn arg_data_path(value: &str) -> Result<CString, Error> {
55+
fn arg_data_path(value: &str) -> Result<CString> {
5356
Ok(CString::new(format!("--path={}", value))?)
5457
}
5558

56-
fn arg_query(value: &str) -> Result<CString, Error> {
59+
fn arg_query(value: &str) -> Result<CString> {
5760
Ok(CString::new(format!("--query={}", value))?)
5861
}

src/query_result.rs

+28-15
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,23 @@ use std::time::Duration;
55

66
use crate::bindings;
77
use crate::error::Error;
8-
8+
use crate::error::Result;
99
#[derive(Clone)]
10-
pub struct QueryResult(pub(crate) *mut bindings::local_result_v2);
10+
pub struct QueryResult {
11+
inner: *mut bindings::local_result_v2,
12+
}
1113

1214
impl QueryResult {
13-
pub fn data_utf8(&self) -> Result<String, Error> {
14-
String::from_utf8(self.data_ref().to_vec())
15-
.map_err(|e| Error::NonUtf8Sequence(e.to_string()))
15+
pub(crate) fn new(inner: *mut bindings::local_result_v2) -> Self {
16+
Self { inner }
1617
}
18+
pub fn data_utf8(&self) -> Result<String> {
19+
let buf = self.data_ref();
1720

18-
pub fn data_utf8_lossy<'a>(&'a self) -> Cow<'a, str> {
21+
String::from_utf8(buf.to_vec()).map_err(Error::NonUtf8Sequence)
22+
}
23+
24+
pub fn data_utf8_lossy(&self) -> Cow<str> {
1925
String::from_utf8_lossy(self.data_ref())
2026
}
2127

@@ -24,30 +30,37 @@ impl QueryResult {
2430
}
2531

2632
pub fn data_ref(&self) -> &[u8] {
27-
let buf = unsafe { (*self.0).buf };
28-
let len = unsafe { (*self.0).len };
33+
let inner = self.inner;
34+
let buf = unsafe { (*inner).buf };
35+
let len = unsafe { (*inner).len };
2936
let bytes: &[u8] = unsafe { slice::from_raw_parts(buf as *const u8, len) };
3037
bytes
3138
}
3239

3340
pub fn rows_read(&self) -> u64 {
34-
(unsafe { *self.0 }).rows_read
41+
let inner = self.inner;
42+
unsafe { *inner }.rows_read
3543
}
3644

3745
pub fn bytes_read(&self) -> u64 {
38-
unsafe { (*self.0).bytes_read }
46+
let inner = self.inner;
47+
unsafe { *inner }.bytes_read
3948
}
4049

4150
pub fn elapsed(&self) -> Duration {
42-
let elapsed = unsafe { (*self.0).elapsed };
51+
let elapsed = unsafe { (*self.inner).elapsed };
4352
Duration::from_secs_f64(elapsed)
4453
}
4554

46-
pub(crate) fn check_error(self) -> Result<Self, Error> {
47-
let err_ptr = unsafe { (*self.0).error_message };
55+
pub(crate) fn check_error(self) -> Result<Self> {
56+
self.check_error_ref()?;
57+
Ok(self)
58+
}
59+
pub(crate) fn check_error_ref(&self) -> Result<()> {
60+
let err_ptr = unsafe { (*self.inner).error_message };
4861

4962
if err_ptr.is_null() {
50-
return Ok(self);
63+
return Ok(());
5164
}
5265

5366
Err(Error::QueryError(unsafe {
@@ -58,6 +71,6 @@ impl QueryResult {
5871

5972
impl Drop for QueryResult {
6073
fn drop(&mut self) {
61-
unsafe { bindings::free_result_v2(self.0) };
74+
unsafe { bindings::free_result_v2(self.inner) };
6275
}
6376
}

src/session.rs

+1-5
Original file line numberDiff line numberDiff line change
@@ -82,11 +82,7 @@ impl<'a> Default for SessionBuilder<'a> {
8282
}
8383

8484
impl Session {
85-
pub fn execute(
86-
&self,
87-
query: &str,
88-
query_args: Option<&[Arg]>,
89-
) -> Result<Option<QueryResult>, Error> {
85+
pub fn execute(&self, query: &str, query_args: Option<&[Arg]>) -> Result<QueryResult, Error> {
9086
let mut argv = Vec::with_capacity(
9187
self.default_args.len() + query_args.as_ref().map_or(0, |v| v.len()) + 1,
9288
);

tests/examples.rs

+24-28
Original file line numberDiff line numberDiff line change
@@ -1,68 +1,65 @@
11
use chdb_rust::arg::Arg;
2+
use chdb_rust::error::Result;
23
use chdb_rust::execute;
34
use chdb_rust::format::InputFormat;
45
use chdb_rust::format::OutputFormat;
56
use chdb_rust::log_level::LogLevel;
67
use chdb_rust::session::SessionBuilder;
78

89
#[test]
9-
fn stateful() {
10+
fn test_stateful() -> Result<()> {
1011
//
1112
// Create session.
1213
//
13-
14+
let tmp = tempdir::TempDir::new("chdb-rust")?;
1415
let session = SessionBuilder::new()
15-
.with_data_path("/tmp/chdb")
16+
.with_data_path(tmp.path())
1617
.with_arg(Arg::LogLevel(LogLevel::Debug))
1718
.with_arg(Arg::Custom("priority".into(), Some("1".into())))
1819
.with_auto_cleanup(true)
19-
.build()
20-
.unwrap();
20+
.build()?;
2121

2222
//
2323
// Create database.
2424
//
2525

26-
session
27-
.execute("CREATE DATABASE demo; USE demo", Some(&[Arg::MultiQuery]))
28-
.unwrap();
26+
session.execute("CREATE DATABASE demo; USE demo", Some(&[Arg::MultiQuery]))?;
2927

3028
//
3129
// Create table.
3230
//
3331

34-
session
35-
.execute(
36-
"CREATE TABLE logs (id UInt64, msg String) ENGINE = MergeTree ORDER BY id",
37-
None,
38-
)
39-
.unwrap();
32+
session.execute(
33+
"CREATE TABLE logs (id UInt64, msg String) ENGINE = MergeTree() ORDER BY id",
34+
None,
35+
)?;
4036

4137
//
4238
// Insert into table.
4339
//
4440

45-
session
46-
.execute("INSERT INTO logs (id, msg) VALUES (1, 'test')", None)
47-
.unwrap();
41+
session.execute("INSERT INTO logs (id, msg) VALUES (1, 'test')", None)?;
4842

4943
//
5044
// Select from table.
5145
//
46+
let len = session.execute(
47+
"SELECT COUNT(*) FROM logs",
48+
Some(&[Arg::OutputFormat(OutputFormat::JSONEachRow)]),
49+
)?;
5250

53-
let result = session
54-
.execute(
55-
"SELECT * FROM logs",
56-
Some(&[Arg::OutputFormat(OutputFormat::JSONEachRow)]),
57-
)
58-
.unwrap()
59-
.unwrap();
51+
assert_eq!(len.data_utf8_lossy(), "{\"COUNT()\":1}\n");
6052

53+
let result = session.execute(
54+
"SELECT * FROM logs",
55+
Some(&[Arg::OutputFormat(OutputFormat::JSONEachRow)]),
56+
)?;
6157
assert_eq!(result.data_utf8_lossy(), "{\"id\":1,\"msg\":\"test\"}\n");
58+
Ok(())
6259
}
6360

6461
#[test]
65-
fn stateless() {
62+
fn test_stateless() -> Result<()> {
6663
let query = format!(
6764
"SELECT * FROM file('tests/logs.csv', {})",
6865
InputFormat::CSV.as_str()
@@ -71,9 +68,8 @@ fn stateless() {
7168
let result = execute(
7269
&query,
7370
Some(&[Arg::OutputFormat(OutputFormat::JSONEachRow)]),
74-
)
75-
.unwrap()
76-
.unwrap();
71+
)?;
7772

7873
assert_eq!(result.data_utf8_lossy(), "{\"id\":1,\"msg\":\"test\"}\n");
74+
Ok(())
7975
}

0 commit comments

Comments
 (0)