Skip to main content

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}