chdb_rust/connection.rs
1//! Connection management for chDB.
2//!
3//! This module provides the [`Connection`] type for managing connections to chDB databases.
4
5use std::ffi::{c_char, CString};
6
7use crate::bindings;
8use crate::error::{Error, Result};
9use crate::format::OutputFormat;
10use crate::query_result::QueryResult;
11
12/// A connection to a chDB database.
13///
14/// A `Connection` represents an active connection to a chDB database instance.
15/// Connections can be created for in-memory databases or persistent databases
16/// stored on disk.
17///
18/// # Thread Safety
19///
20/// `Connection` implements `Send`, meaning it can be safely transferred between threads.
21/// However, the underlying chDB library may have limitations on concurrent access.
22/// It's recommended to use one connection per thread or implement proper synchronization.
23///
24/// # Examples
25///
26/// ```no_run
27/// use chdb_rust::connection::Connection;
28/// use chdb_rust::format::OutputFormat;
29///
30/// // Create an in-memory connection
31/// let conn = Connection::open_in_memory()?;
32///
33/// // Execute a query
34/// let result = conn.query("SELECT 1", OutputFormat::JSONEachRow)?;
35/// println!("{}", result.data_utf8_lossy());
36/// # Ok::<(), chdb_rust::error::Error>(())
37/// ```
38#[derive(Debug)]
39pub struct Connection {
40 // Pointer to chdb_connection (which is *mut chdb_connection_)
41 inner: *mut bindings::chdb_connection,
42}
43
44// Safety: Connection is safe to send between threads
45// The underlying chDB library is thread-safe for query execution
46unsafe impl Send for Connection {}
47
48impl Connection {
49 /// Connect to chDB with the given command-line arguments.
50 ///
51 /// This is a low-level function that allows you to pass arbitrary arguments
52 /// to the chDB connection. For most use cases, prefer [`open_in_memory`](Self::open_in_memory)
53 /// or [`open_with_path`](Self::open_with_path).
54 ///
55 /// # Arguments
56 ///
57 /// * `args` - Array of command-line arguments (e.g., `["clickhouse", "--path=/tmp/db"]`)
58 ///
59 /// # Examples
60 ///
61 /// ```no_run
62 /// use chdb_rust::connection::Connection;
63 ///
64 /// // Connect with custom arguments
65 /// let conn = Connection::open(&["clickhouse", "--path=/tmp/mydb"])?;
66 /// # Ok::<(), chdb_rust::error::Error>(())
67 /// ```
68 ///
69 /// # Errors
70 ///
71 /// Returns [`Error::ConnectionFailed`] if the
72 /// connection cannot be established.
73 pub fn open(args: &[&str]) -> Result<Self> {
74 let c_args: Vec<CString> = args
75 .iter()
76 .map(|s| CString::new(*s))
77 .collect::<std::result::Result<Vec<_>, _>>()?;
78
79 let mut argv: Vec<*mut c_char> = c_args.iter().map(|s| s.as_ptr() as *mut c_char).collect();
80
81 let conn_ptr = unsafe { bindings::chdb_connect(argv.len() as i32, argv.as_mut_ptr()) };
82
83 if conn_ptr.is_null() {
84 return Err(Error::ConnectionFailed);
85 }
86
87 // Check if the connection itself is null
88 let conn = unsafe { *conn_ptr };
89 if conn.is_null() {
90 return Err(Error::ConnectionFailed);
91 }
92
93 Ok(Self { inner: conn_ptr })
94 }
95
96 /// Connect to an in-memory database.
97 ///
98 /// Creates a connection to a temporary in-memory database. Data stored in this
99 /// database will be lost when the connection is closed.
100 ///
101 /// # Examples
102 ///
103 /// ```no_run
104 /// use chdb_rust::connection::Connection;
105 ///
106 /// let conn = Connection::open_in_memory()?;
107 /// # Ok::<(), chdb_rust::error::Error>(())
108 /// ```
109 ///
110 /// # Errors
111 ///
112 /// Returns [`Error::ConnectionFailed`] if the
113 /// connection cannot be established.
114 pub fn open_in_memory() -> Result<Self> {
115 Self::open(&["clickhouse"])
116 }
117
118 /// Connect to a database at the given path.
119 ///
120 /// Creates a connection to a persistent database stored at the specified path.
121 /// The directory will be created if it doesn't exist.
122 ///
123 /// # Arguments
124 ///
125 /// * `path` - The filesystem path where the database should be stored
126 ///
127 /// # Examples
128 ///
129 /// ```no_run
130 /// use chdb_rust::connection::Connection;
131 ///
132 /// let conn = Connection::open_with_path("/tmp/mydb")?;
133 /// # Ok::<(), chdb_rust::error::Error>(())
134 /// ```
135 ///
136 /// # Errors
137 ///
138 /// Returns [`Error::ConnectionFailed`] if the
139 /// connection cannot be established.
140 pub fn open_with_path(path: &str) -> Result<Self> {
141 let path_arg = format!("--path={path}");
142 Self::open(&["clickhouse", &path_arg])
143 }
144
145 /// Execute a query and return the result.
146 ///
147 /// Executes a SQL query against the database and returns the result in the
148 /// specified output format.
149 ///
150 /// # Arguments
151 ///
152 /// * `sql` - The SQL query string to execute
153 /// * `format` - The desired output format for the result
154 ///
155 /// # Returns
156 ///
157 /// Returns a [`QueryResult`] containing the query output, or an [`Error`]
158 /// if the query fails.
159 ///
160 /// # Examples
161 ///
162 /// ```no_run
163 /// use chdb_rust::connection::Connection;
164 /// use chdb_rust::format::OutputFormat;
165 ///
166 /// let conn = Connection::open_in_memory()?;
167 /// let result = conn.query("SELECT 1 + 1 AS sum", OutputFormat::JSONEachRow)?;
168 /// println!("{}", result.data_utf8_lossy());
169 /// # Ok::<(), chdb_rust::error::Error>(())
170 /// ```
171 ///
172 /// # Errors
173 ///
174 /// Returns an error if:
175 /// - The query syntax is invalid
176 /// - The query references non-existent tables or columns
177 /// - The query execution fails for any other reason
178 pub fn query(&self, sql: &str, format: OutputFormat) -> Result<QueryResult> {
179 let query_cstr = CString::new(sql)?;
180 let format_cstr = CString::new(format.as_str())?;
181
182 // chdb_query takes chdb_connection (which is *mut chdb_connection_)
183 let conn = unsafe { *self.inner };
184 let result_ptr =
185 unsafe { bindings::chdb_query(conn, query_cstr.as_ptr(), format_cstr.as_ptr()) };
186
187 if result_ptr.is_null() {
188 return Err(Error::NoResult);
189 }
190
191 let result = QueryResult::new(result_ptr);
192 result.check_error()
193 }
194}
195
196impl Drop for Connection {
197 fn drop(&mut self) {
198 if !self.inner.is_null() {
199 unsafe { bindings::chdb_close_conn(self.inner) };
200 }
201 }
202}