diff --git a/src/arg.rs b/src/arg.rs index c303600..9fd0dd9 100644 --- a/src/arg.rs +++ b/src/arg.rs @@ -1,3 +1,8 @@ +//! Query argument definitions for chDB. +//! +//! This module provides types for specifying query arguments such as output format, +//! log level, and custom command-line arguments. + use std::borrow::Cow; use std::ffi::CString; @@ -5,13 +10,35 @@ use crate::error::Error; use crate::format::OutputFormat; use crate::log_level::LogLevel; +/// Query arguments that can be passed when executing queries. +/// +/// `Arg` represents various command-line arguments that can be used to configure +/// query execution. Most commonly, you'll use `OutputFormat` to specify the +/// desired output format. +/// +/// # Examples +/// +/// ```no_run +/// use chdb_rust::arg::Arg; +/// use chdb_rust::format::OutputFormat; +/// use chdb_rust::log_level::LogLevel; +/// +/// // Specify output format +/// let args = &[Arg::OutputFormat(OutputFormat::JSONEachRow)]; +/// +/// // Specify log level +/// let args = &[Arg::LogLevel(LogLevel::Debug)]; +/// +/// // Use custom arguments +/// let args = &[Arg::Custom("path".into(), Some("/tmp/db".into()))]; +/// ``` #[derive(Debug)] pub enum Arg<'a> { - /// --config-file= + /// `--config-file=` ConfigFilePath(Cow<'a, str>), - /// --log-level= + /// `--log-level=` LogLevel(LogLevel), - /// --output-format= + /// `--output-format=` OutputFormat(OutputFormat), /// --multiquery MultiQuery, @@ -26,7 +53,7 @@ pub enum Arg<'a> { /// 2. Arg::Custom("multiline".into(), None). /// /// We should tell user where to look for officially supported arguments. - /// Here is some hint for now: https://github.com/fixcik/chdb-rs/blob/master/OPTIONS.md . + /// Here is some hint for now: . Custom(Cow<'a, str>, Option>), } @@ -45,7 +72,10 @@ impl<'a> Arg<'a> { }?) } - /// Extract OutputFormat from an Arg if it is an OutputFormat variant. + /// Extract `OutputFormat` from an `Arg` if it is an `OutputFormat` variant. + /// + /// This is a helper method used internally to extract output format information + /// from query arguments. pub(crate) fn as_output_format(&self) -> Option { match self { Self::OutputFormat(f) => Some(*f), @@ -54,7 +84,18 @@ impl<'a> Arg<'a> { } } -/// Extract OutputFormat from a slice of Args, returns the first one found or default. +/// Extract `OutputFormat` from a slice of `Arg`s. +/// +/// This function searches through the provided arguments and returns the first +/// `OutputFormat` found, or the default `TabSeparated` format if none is found. +/// +/// # Arguments +/// +/// * `args` - Optional slice of query arguments +/// +/// # Returns +/// +/// Returns the first `OutputFormat` found, or `OutputFormat::TabSeparated` as default. pub(crate) fn extract_output_format(args: Option<&[Arg]>) -> OutputFormat { args.and_then(|args| args.iter().find_map(|a| a.as_output_format())) .unwrap_or(OutputFormat::TabSeparated) diff --git a/src/connection.rs b/src/connection.rs index f810f94..0428132 100644 --- a/src/connection.rs +++ b/src/connection.rs @@ -1,3 +1,7 @@ +//! Connection management for chDB. +//! +//! This module provides the [`Connection`] type for managing connections to chDB databases. + use std::ffi::{c_char, CString}; use crate::bindings; @@ -5,7 +9,32 @@ use crate::error::{Error, Result}; use crate::format::OutputFormat; use crate::query_result::QueryResult; -/// A connection to chDB database. +/// A connection to a chDB database. +/// +/// A `Connection` represents an active connection to a chDB database instance. +/// Connections can be created for in-memory databases or persistent databases +/// stored on disk. +/// +/// # Thread Safety +/// +/// `Connection` implements `Send`, meaning it can be safely transferred between threads. +/// However, the underlying chDB library may have limitations on concurrent access. +/// It's recommended to use one connection per thread or implement proper synchronization. +/// +/// # Examples +/// +/// ```no_run +/// use chdb_rust::connection::Connection; +/// use chdb_rust::format::OutputFormat; +/// +/// // Create an in-memory connection +/// let conn = Connection::open_in_memory()?; +/// +/// // Execute a query +/// let result = conn.query("SELECT 1", OutputFormat::JSONEachRow)?; +/// println!("{}", result.data_utf8_lossy()); +/// # Ok::<(), chdb_rust::error::Error>(()) +/// ``` #[derive(Debug)] pub struct Connection { // Pointer to chdb_connection (which is *mut chdb_connection_) @@ -13,11 +42,34 @@ pub struct Connection { } // Safety: Connection is safe to send between threads +// The underlying chDB library is thread-safe for query execution unsafe impl Send for Connection {} impl Connection { - /// Connect to chDB with the given arguments. - /// Use `--path=` to specify database location, default is `:memory:`. + /// Connect to chDB with the given command-line arguments. + /// + /// This is a low-level function that allows you to pass arbitrary arguments + /// to the chDB connection. For most use cases, prefer [`open_in_memory`](Self::open_in_memory) + /// or [`open_with_path`](Self::open_with_path). + /// + /// # Arguments + /// + /// * `args` - Array of command-line arguments (e.g., `["clickhouse", "--path=/tmp/db"]`) + /// + /// # Examples + /// + /// ```no_run + /// use chdb_rust::connection::Connection; + /// + /// // Connect with custom arguments + /// let conn = Connection::open(&["clickhouse", "--path=/tmp/mydb"])?; + /// # Ok::<(), chdb_rust::error::Error>(()) + /// ``` + /// + /// # Errors + /// + /// Returns [`Error::ConnectionFailed`] if the + /// connection cannot be established. pub fn open(args: &[&str]) -> Result { let c_args: Vec = args .iter() @@ -42,17 +94,87 @@ impl Connection { } /// Connect to an in-memory database. + /// + /// Creates a connection to a temporary in-memory database. Data stored in this + /// database will be lost when the connection is closed. + /// + /// # Examples + /// + /// ```no_run + /// use chdb_rust::connection::Connection; + /// + /// let conn = Connection::open_in_memory()?; + /// # Ok::<(), chdb_rust::error::Error>(()) + /// ``` + /// + /// # Errors + /// + /// Returns [`Error::ConnectionFailed`] if the + /// connection cannot be established. pub fn open_in_memory() -> Result { Self::open(&["clickhouse"]) } /// Connect to a database at the given path. + /// + /// Creates a connection to a persistent database stored at the specified path. + /// The directory will be created if it doesn't exist. + /// + /// # Arguments + /// + /// * `path` - The filesystem path where the database should be stored + /// + /// # Examples + /// + /// ```no_run + /// use chdb_rust::connection::Connection; + /// + /// let conn = Connection::open_with_path("/tmp/mydb")?; + /// # Ok::<(), chdb_rust::error::Error>(()) + /// ``` + /// + /// # Errors + /// + /// Returns [`Error::ConnectionFailed`] if the + /// connection cannot be established. pub fn open_with_path(path: &str) -> Result { let path_arg = format!("--path={}", path); Self::open(&["clickhouse", &path_arg]) } /// Execute a query and return the result. + /// + /// Executes a SQL query against the database and returns the result in the + /// specified output format. + /// + /// # Arguments + /// + /// * `sql` - The SQL query string to execute + /// * `format` - The desired output format for the result + /// + /// # Returns + /// + /// Returns a [`QueryResult`] containing the query output, or an [`Error`] + /// if the query fails. + /// + /// # Examples + /// + /// ```no_run + /// use chdb_rust::connection::Connection; + /// use chdb_rust::format::OutputFormat; + /// + /// let conn = Connection::open_in_memory()?; + /// let result = conn.query("SELECT 1 + 1 AS sum", OutputFormat::JSONEachRow)?; + /// println!("{}", result.data_utf8_lossy()); + /// # Ok::<(), chdb_rust::error::Error>(()) + /// ``` + /// + /// # Errors + /// + /// Returns an error if: + /// - The query syntax is invalid + /// - The query references non-existent tables or columns + /// - The query execution fails for any other reason pub fn query(&self, sql: &str, format: OutputFormat) -> Result { let query_cstr = CString::new(sql)?; let format_cstr = CString::new(format.as_str())?; diff --git a/src/error.rs b/src/error.rs index 0d25327..9c53812 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,28 +1,53 @@ +//! Error types for chdb-rust. +//! +//! This module defines the error types used throughout the crate. + use std::ffi::NulError; use std::string::FromUtf8Error; +/// Errors that can occur when using chdb-rust. +/// +/// This enum represents all possible errors that can be returned by the library. +/// Most errors are self-explanatory, with `QueryError` containing the actual error +/// message from the underlying chDB library. #[derive(Debug, thiserror::Error)] pub enum Error { + /// An unknown error has occurred. #[error("An unknown error has occurred")] Unknown, + /// No result was returned from the query. #[error("No result")] NoResult, + /// Failed to establish a connection to chDB. #[error("Connection failed")] ConnectionFailed, + /// Invalid data was encountered. #[error("Invalid data: {0}")] InvalidData(String), + /// Invalid path was provided. #[error("Invalid path")] PathError, + /// An I/O error occurred. #[error(transparent)] Io(#[from] std::io::Error), + /// A null byte was found in a string where it's not allowed. #[error(transparent)] Nul(#[from] NulError), + /// Insufficient permissions to access the directory. #[error("Insufficient dir permissions")] InsufficientPermissions, + /// The data contains invalid UTF-8 sequences. #[error("Non UTF-8 sequence: {0}")] NonUtf8Sequence(FromUtf8Error), + /// A query execution error occurred. + /// + /// This contains the error message from the underlying chDB library, + /// which typically includes details about SQL syntax errors, missing tables, etc. #[error("{0}")] QueryError(String), } +/// A type alias for `Result`. +/// +/// This is the standard result type used throughout the crate. pub type Result = std::result::Result; diff --git a/src/format.rs b/src/format.rs index fc2ec24..64dfdac 100644 --- a/src/format.rs +++ b/src/format.rs @@ -1,3 +1,13 @@ +//! Input and output format definitions for chDB queries. +//! +//! This module defines the various data formats supported by chDB for reading +//! input data and formatting query results. + +/// Input formats for reading data into chDB. +/// +/// These formats specify how data should be parsed when reading from files or +/// other sources. See the [ClickHouse documentation](https://clickhouse.com/docs/en/interfaces/formats/) +/// for details on each format. #[derive(Debug, Clone, Copy)] pub enum InputFormat { TabSeparated, @@ -41,8 +51,6 @@ pub enum InputFormat { AvroConfluent, Parquet, ParquetMetadata, - Arrow, - ArrowStream, ORC, One, Npy, @@ -61,6 +69,11 @@ pub enum InputFormat { Form, } +/// Output formats for query results. +/// +/// These formats specify how query results should be formatted when returned. +/// See the [ClickHouse documentation](https://clickhouse.com/docs/en/interfaces/formats/) +/// for details on each format. #[derive(Debug, Clone, Copy)] pub enum OutputFormat { TabSeparated, @@ -116,8 +129,6 @@ pub enum OutputFormat { ProtobufList, Avro, Parquet, - Arrow, - ArrowStream, ORC, Npy, RowBinary, @@ -135,6 +146,14 @@ pub enum OutputFormat { } impl InputFormat { + /// Get the string representation of the input format. + /// + /// This returns the format name as it should be used in SQL queries + /// (e.g., in `file()` function calls). + /// + /// # Returns + /// + /// Returns the format name as a static string slice. pub const fn as_str(self) -> &'static str { match self { Self::TabSeparated => "TabSeparated", @@ -180,8 +199,6 @@ impl InputFormat { Self::AvroConfluent => "AvroConfluent", Self::Parquet => "Parquet", Self::ParquetMetadata => "ParquetMetadata", - Self::Arrow => "Arrow", - Self::ArrowStream => "ArrowStream", Self::ORC => "ORC", Self::One => "One", Self::Npy => "Npy", @@ -203,6 +220,13 @@ impl InputFormat { } impl OutputFormat { + /// Get the string representation of the output format. + /// + /// This returns the format name as it should be used when executing queries. + /// + /// # Returns + /// + /// Returns the format name as a static string slice. pub const fn as_str(self) -> &'static str { match self { Self::TabSeparated => "TabSeparated", @@ -260,8 +284,6 @@ impl OutputFormat { Self::ProtobufList => "ProtobufList", Self::Avro => "Avro", Self::Parquet => "Parquet", - Self::Arrow => "Arrow", - Self::ArrowStream => "ArrowStream", Self::ORC => "ORC", Self::Npy => "Npy", Self::RowBinary => "RowBinary", diff --git a/src/lib.rs b/src/lib.rs index 5668e3f..067dba0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,41 @@ +//! # chdb-rust +//! +//! Rust FFI bindings for [chDB](https://github.com/chdb-io/chdb), an embedded ClickHouse database. +//! +//! ## Overview +//! +//! This crate provides a safe Rust interface to chDB, allowing you to execute ClickHouse SQL queries +//! either statelessly (in-memory) or with persistent storage using sessions. +//! +//! ## Quick Start +//! +//! ```no_run +//! use chdb_rust::execute; +//! use chdb_rust::arg::Arg; +//! use chdb_rust::format::OutputFormat; +//! +//! // Execute a simple query +//! let result = execute("SELECT 1 + 1 AS sum", None)?; +//! println!("Result: {}", result.data_utf8_lossy()); +//! # Ok::<(), chdb_rust::error::Error>(()) +//! ``` +//! +//! ## Features +//! +//! - **Stateless queries**: Execute one-off queries without persistent storage +//! - **Stateful sessions**: Create databases and tables with persistent storage +//! - **Multiple output formats**: JSON, CSV, TabSeparated, and more +//! - **Thread-safe**: Connections and results can be safely sent between threads +//! +//! ## Examples +//! +//! See the [`examples`](https://github.com/chdb-io/chdb-rust/tree/main/examples) directory for more detailed examples. +//! +//! ## Safety +//! +//! This crate uses `unsafe` code to interface with the C library, but provides a safe Rust API. +//! All public functions are safe to call, and the crate ensures proper resource cleanup. + pub mod arg; #[allow( dead_code, @@ -7,7 +45,7 @@ pub mod arg; non_upper_case_globals )] mod bindings; -mod connection; +pub mod connection; pub mod error; pub mod format; pub mod log_level; @@ -20,6 +58,46 @@ use crate::error::Result; use crate::query_result::QueryResult; /// Execute a one-off query using an in-memory connection. +/// +/// This function creates a temporary in-memory database connection, executes the query, +/// and returns the result. It's suitable for queries that don't require persistent storage. +/// +/// # Arguments +/// +/// * `query` - The SQL query string to execute +/// * `query_args` - Optional array of query arguments (e.g., output format) +/// +/// # Returns +/// +/// Returns a [`QueryResult`] containing the query output, or an [`Error`](error::Error) if +/// the query fails. +/// +/// # Examples +/// +/// ```no_run +/// use chdb_rust::execute; +/// use chdb_rust::arg::Arg; +/// use chdb_rust::format::OutputFormat; +/// +/// // Simple query with default format +/// let result = execute("SELECT 1 + 1 AS sum", None)?; +/// println!("{}", result.data_utf8_lossy()); +/// +/// // Query with JSON output format +/// let result = execute( +/// "SELECT 'Hello' AS greeting, 42 AS answer", +/// Some(&[Arg::OutputFormat(OutputFormat::JSONEachRow)]) +/// )?; +/// println!("{}", result.data_utf8_lossy()); +/// # Ok::<(), chdb_rust::error::Error>(()) +/// ``` +/// +/// # Errors +/// +/// This function will return an error if: +/// - The query syntax is invalid +/// - The connection cannot be established +/// - The query execution fails pub fn execute(query: &str, query_args: Option<&[Arg]>) -> Result { let conn = Connection::open_in_memory()?; let fmt = extract_output_format(query_args); diff --git a/src/log_level.rs b/src/log_level.rs index ebe8c09..d2f95d7 100644 --- a/src/log_level.rs +++ b/src/log_level.rs @@ -1,3 +1,11 @@ +//! Log level definitions for chDB. +//! +//! This module defines the log levels that can be used to configure chDB's logging behavior. + +/// Log levels for chDB. +/// +/// These correspond to the standard log levels used by chDB for controlling +/// the verbosity of log output. #[derive(Debug, Clone, Copy)] pub enum LogLevel { Trace, @@ -8,6 +16,13 @@ pub enum LogLevel { } impl LogLevel { + /// Get the string representation of the log level. + /// + /// This returns the log level name as expected by chDB. + /// + /// # Returns + /// + /// Returns the log level name as a static string slice. pub const fn as_str(self) -> &'static str { match self { Self::Trace => "trace", diff --git a/src/query_result.rs b/src/query_result.rs index 3ed51b3..d5b29d0 100644 --- a/src/query_result.rs +++ b/src/query_result.rs @@ -1,3 +1,7 @@ +//! Query result handling for chDB. +//! +//! This module provides the [`QueryResult`] type for accessing query execution results. + use core::slice; use std::borrow::Cow; use std::ffi::CStr; @@ -7,12 +11,43 @@ use crate::bindings; use crate::error::Error; use crate::error::Result; +/// The result of a query execution. +/// +/// `QueryResult` contains the output data from a query execution, along with +/// metadata such as execution time and number of rows read. +/// +/// # Thread Safety +/// +/// `QueryResult` implements `Send`, meaning it can be safely transferred between threads. +/// +/// # Examples +/// +/// ```no_run +/// use chdb_rust::execute; +/// use chdb_rust::format::OutputFormat; +/// use chdb_rust::arg::Arg; +/// +/// let result = execute( +/// "SELECT number FROM numbers(10)", +/// Some(&[Arg::OutputFormat(OutputFormat::JSONEachRow)]) +/// )?; +/// +/// // Access the data as a string +/// println!("Data: {}", result.data_utf8_lossy()); +/// +/// // Access metadata +/// println!("Rows read: {}", result.rows_read()); +/// println!("Bytes read: {}", result.bytes_read()); +/// println!("Elapsed time: {:?}", result.elapsed()); +/// # Ok::<(), chdb_rust::error::Error>(()) +/// ``` #[derive(Debug)] pub struct QueryResult { inner: *mut bindings::chdb_result, } // Safety: QueryResult is safe to send between threads +// The underlying chDB result structure is thread-safe for read access unsafe impl Send for QueryResult {} impl QueryResult { @@ -20,19 +55,104 @@ impl QueryResult { Self { inner } } + /// Get the result data as a UTF-8 string. + /// + /// This method validates that the data is valid UTF-8. If the data contains + /// invalid UTF-8 sequences, it returns an error. + /// + /// # Returns + /// + /// Returns a `String` containing the query result, or an error if the data + /// contains invalid UTF-8 sequences. + /// + /// # Examples + /// + /// ```no_run + /// use chdb_rust::execute; + /// + /// let result = execute("SELECT 'Hello, World!' AS greeting", None)?; + /// let data = result.data_utf8()?; + /// println!("{}", data); + /// # Ok::<(), chdb_rust::error::Error>(()) + /// ``` + /// + /// # Errors + /// + /// Returns [`Error::NonUtf8Sequence`] if the + /// result data contains invalid UTF-8 sequences. Use [`data_utf8_lossy`](Self::data_utf8_lossy) + /// if you want to handle invalid UTF-8 gracefully. pub fn data_utf8(&self) -> Result { let buf = self.data_ref(); String::from_utf8(buf.to_vec()).map_err(Error::NonUtf8Sequence) } + /// Get the result data as a UTF-8 string, replacing invalid sequences. + /// + /// This method converts the result data to a string, replacing any invalid UTF-8 + /// sequences with the Unicode replacement character (U+FFFD). + /// + /// # Returns + /// + /// Returns a `Cow` containing the query result. Invalid UTF-8 sequences + /// are replaced with the replacement character. + /// + /// # Examples + /// + /// ```no_run + /// use chdb_rust::execute; + /// + /// let result = execute("SELECT 'Hello, World!' AS greeting", None)?; + /// let data = result.data_utf8_lossy(); + /// println!("{}", data); + /// # Ok::<(), chdb_rust::error::Error>(()) + /// ``` pub fn data_utf8_lossy(&self) -> Cow<'_, str> { String::from_utf8_lossy(self.data_ref()) } + /// Get the result data as a UTF-8 string without validation. + /// + /// # Safety + /// + /// This function is marked as safe, but it will produce invalid UTF-8 strings + /// if the underlying data contains non-UTF-8 bytes. Only use this if you're + /// certain the data is valid UTF-8, or if you're prepared to handle potentially + /// invalid strings. + /// + /// # Examples + /// + /// ```no_run + /// use chdb_rust::execute; + /// + /// let result = execute("SELECT 'Hello' AS greeting", None)?; + /// let data = result.data_utf8_unchecked(); + /// println!("{}", data); + /// # Ok::<(), chdb_rust::error::Error>(()) + /// ``` pub fn data_utf8_unchecked(&self) -> String { unsafe { String::from_utf8_unchecked(self.data_ref().to_vec()) } } + /// Get a reference to the raw result data as bytes. + /// + /// This method returns a byte slice containing the raw query result data. + /// The data is in the format specified when executing the query (e.g., JSON, CSV, etc.). + /// + /// # Returns + /// + /// Returns a byte slice containing the query result data. Returns an empty slice + /// if there's no data or if the buffer pointer is null. + /// + /// # Examples + /// + /// ```no_run + /// use chdb_rust::execute; + /// + /// let result = execute("SELECT 1 AS value", None)?; + /// let bytes = result.data_ref(); + /// println!("Data length: {} bytes", bytes.len()); + /// # Ok::<(), chdb_rust::error::Error>(()) + /// ``` pub fn data_ref(&self) -> &[u8] { let buf = unsafe { bindings::chdb_result_buffer(self.inner) }; let len = unsafe { bindings::chdb_result_length(self.inner) }; @@ -42,14 +162,68 @@ impl QueryResult { unsafe { slice::from_raw_parts(buf as *const u8, len) } } + /// Get the number of rows read by the query. + /// + /// This returns the total number of rows that were read from storage during + /// query execution. + /// + /// # Returns + /// + /// Returns the number of rows read as a `u64`. + /// + /// # Examples + /// + /// ```no_run + /// use chdb_rust::execute; + /// + /// let result = execute("SELECT number FROM numbers(100)", None)?; + /// println!("Rows read: {}", result.rows_read()); + /// # Ok::<(), chdb_rust::error::Error>(()) + /// ``` pub fn rows_read(&self) -> u64 { unsafe { bindings::chdb_result_rows_read(self.inner) } } + /// Get the number of bytes read by the query. + /// + /// This returns the total number of bytes that were read from storage during + /// query execution. + /// + /// # Returns + /// + /// Returns the number of bytes read as a `u64`. + /// + /// # Examples + /// + /// ```no_run + /// use chdb_rust::execute; + /// + /// let result = execute("SELECT number FROM numbers(100)", None)?; + /// println!("Bytes read: {}", result.bytes_read()); + /// # Ok::<(), chdb_rust::error::Error>(()) + /// ``` pub fn bytes_read(&self) -> u64 { unsafe { bindings::chdb_result_bytes_read(self.inner) } } + /// Get the elapsed time for query execution. + /// + /// This returns the time it took to execute the query, measured from when + /// the query was submitted until the result was ready. + /// + /// # Returns + /// + /// Returns a [`Duration`] representing the elapsed time. + /// + /// # Examples + /// + /// ```no_run + /// use chdb_rust::execute; + /// + /// let result = execute("SELECT number FROM numbers(1000)", None)?; + /// println!("Query took: {:?}", result.elapsed()); + /// # Ok::<(), chdb_rust::error::Error>(()) + /// ``` pub fn elapsed(&self) -> Duration { let elapsed = unsafe { bindings::chdb_result_elapsed(self.inner) }; Duration::from_secs_f64(elapsed) diff --git a/src/session.rs b/src/session.rs index 47e413e..673e03b 100644 --- a/src/session.rs +++ b/src/session.rs @@ -1,3 +1,8 @@ +//! Session management for persistent chDB databases. +//! +//! This module provides the [`Session`] and [`SessionBuilder`] types for managing +//! persistent database connections with automatic cleanup. + use std::fs; use std::path::PathBuf; @@ -7,6 +12,24 @@ use crate::error::Error; use crate::format::OutputFormat; use crate::query_result::QueryResult; +/// Builder for creating [`Session`] instances. +/// +/// `SessionBuilder` provides a fluent API for configuring and creating sessions. +/// Use [`new`](Self::new) to create a new builder, configure it with the desired +/// options, and call [`build`](Self::build) to create the session. +/// +/// # Examples +/// +/// ```no_run +/// use chdb_rust::session::SessionBuilder; +/// +/// // Create a session with default settings +/// let session = SessionBuilder::new() +/// .with_data_path("/tmp/mydb") +/// .with_auto_cleanup(true) +/// .build()?; +/// # Ok::<(), chdb_rust::error::Error>(()) +/// ``` pub struct SessionBuilder<'a> { data_path: PathBuf, default_format: OutputFormat, @@ -14,6 +37,47 @@ pub struct SessionBuilder<'a> { auto_cleanup: bool, } +/// A session representing a persistent connection to a chDB database. +/// +/// A `Session` manages a connection to a persistent database stored on disk. +/// Unlike stateless queries, sessions allow you to create tables, insert data, +/// and maintain state across multiple queries. +/// +/// # Thread Safety +/// +/// `Session` contains a [`Connection`] which implements +/// `Send`, so sessions can be safely transferred between threads. However, concurrent +/// access to the same session should be synchronized. +/// +/// # Examples +/// +/// ```no_run +/// use chdb_rust::session::SessionBuilder; +/// use chdb_rust::arg::Arg; +/// use chdb_rust::format::OutputFormat; +/// +/// let session = SessionBuilder::new() +/// .with_data_path("/tmp/mydb") +/// .with_auto_cleanup(true) +/// .build()?; +/// +/// // Create a table +/// session.execute( +/// "CREATE TABLE users (id UInt64, name String) ENGINE = MergeTree() ORDER BY id", +/// None +/// )?; +/// +/// // Insert data +/// session.execute("INSERT INTO users VALUES (1, 'Alice')", None)?; +/// +/// // Query data +/// let result = session.execute( +/// "SELECT * FROM users", +/// Some(&[Arg::OutputFormat(OutputFormat::JSONEachRow)]) +/// )?; +/// println!("{}", result.data_utf8_lossy()); +/// # Ok::<(), chdb_rust::error::Error>(()) +/// ``` #[derive(Debug)] pub struct Session { conn: Connection, @@ -23,6 +87,21 @@ pub struct Session { } impl<'a> SessionBuilder<'a> { + /// Create a new `SessionBuilder` with default settings. + /// + /// The default settings are: + /// - Data path: `./chdb` in the current working directory + /// - Output format: `TabSeparated` + /// - Auto cleanup: `false` + /// + /// # Examples + /// + /// ```no_run + /// use chdb_rust::session::SessionBuilder; + /// + /// let builder = SessionBuilder::new(); + /// # Ok::<(), chdb_rust::error::Error>(()) + /// ``` pub fn new() -> Self { let mut data_path = std::env::current_dir().unwrap(); data_path.push("chdb"); @@ -35,11 +114,49 @@ impl<'a> SessionBuilder<'a> { } } + /// Set the data path for the session. + /// + /// This specifies the filesystem path where the database will be stored. + /// The directory will be created if it doesn't exist. + /// + /// # Arguments + /// + /// * `path` - The path where the database should be stored + /// + /// # Examples + /// + /// ```no_run + /// use chdb_rust::session::SessionBuilder; + /// + /// let builder = SessionBuilder::new() + /// .with_data_path("/tmp/mydb"); + /// # Ok::<(), chdb_rust::error::Error>(()) + /// ``` pub fn with_data_path(mut self, path: impl Into) -> Self { self.data_path = path.into(); self } + /// Add a query argument to the session builder. + /// + /// Currently, only `OutputFormat` arguments are supported and will be used + /// as the default output format for queries executed on this session. + /// + /// # Arguments + /// + /// * `arg` - The argument to add (currently only `OutputFormat` is supported) + /// + /// # Examples + /// + /// ```no_run + /// use chdb_rust::session::SessionBuilder; + /// use chdb_rust::arg::Arg; + /// use chdb_rust::format::OutputFormat; + /// + /// let builder = SessionBuilder::new() + /// .with_arg(Arg::OutputFormat(OutputFormat::JSONEachRow)); + /// # Ok::<(), chdb_rust::error::Error>(()) + /// ``` pub fn with_arg(mut self, arg: Arg<'a>) -> Self { // Only OutputFormat is supported with the new API if let Some(fmt) = arg.as_output_format() { @@ -48,12 +165,59 @@ impl<'a> SessionBuilder<'a> { self } - /// If set Session will delete data directory before it is dropped. + /// Enable or disable automatic cleanup of the data directory. + /// + /// If set to `true`, the session will automatically delete the data directory + /// when it is dropped. This is useful for temporary databases. + /// + /// # Arguments + /// + /// * `value` - Whether to enable automatic cleanup + /// + /// # Examples + /// + /// ```no_run + /// use chdb_rust::session::SessionBuilder; + /// + /// // Session will clean up data directory on drop + /// let session = SessionBuilder::new() + /// .with_data_path("/tmp/tempdb") + /// .with_auto_cleanup(true) + /// .build()?; + /// # Ok::<(), chdb_rust::error::Error>(()) + /// ``` pub fn with_auto_cleanup(mut self, value: bool) -> Self { self.auto_cleanup = value; self } + /// Build the session with the configured settings. + /// + /// This creates the data directory if it doesn't exist and establishes + /// a connection to the database. + /// + /// # Returns + /// + /// Returns a [`Session`] if successful, or an [`Error`] if + /// the session cannot be created. + /// + /// # Errors + /// + /// Returns an error if: + /// - The data path cannot be created + /// - The data path has insufficient permissions + /// - The connection cannot be established + /// + /// # Examples + /// + /// ```no_run + /// use chdb_rust::session::SessionBuilder; + /// + /// let session = SessionBuilder::new() + /// .with_data_path("/tmp/mydb") + /// .build()?; + /// # Ok::<(), chdb_rust::error::Error>(()) + /// ``` pub fn build(self) -> Result { let data_path = self.data_path.to_str().ok_or(Error::PathError)?.to_string(); @@ -80,6 +244,53 @@ impl Default for SessionBuilder<'_> { } impl Session { + /// Execute a query on this session. + /// + /// This executes a SQL query against the database associated with this session. + /// The query can create tables, insert data, or query existing data. + /// + /// # Arguments + /// + /// * `query` - The SQL query string to execute + /// * `query_args` - Optional array of query arguments (e.g., output format) + /// + /// # Returns + /// + /// Returns a [`QueryResult`] containing the query output, + /// or an [`Error`] if the query fails. + /// + /// # Examples + /// + /// ```no_run + /// use chdb_rust::session::SessionBuilder; + /// use chdb_rust::arg::Arg; + /// use chdb_rust::format::OutputFormat; + /// + /// let session = SessionBuilder::new() + /// .with_data_path("/tmp/mydb") + /// .with_auto_cleanup(true) + /// .build()?; + /// + /// // Create a table + /// session.execute( + /// "CREATE TABLE test (id UInt64) ENGINE = MergeTree() ORDER BY id", + /// None + /// )?; + /// + /// // Query with JSON output + /// let result = session.execute( + /// "SELECT * FROM test", + /// Some(&[Arg::OutputFormat(OutputFormat::JSONEachRow)]) + /// )?; + /// # Ok::<(), chdb_rust::error::Error>(()) + /// ``` + /// + /// # Errors + /// + /// Returns an error if: + /// - The query syntax is invalid + /// - The query references non-existent tables or columns + /// - The query execution fails for any other reason pub fn execute(&self, query: &str, query_args: Option<&[Arg]>) -> Result { let fmt = query_args .and_then(|args| args.iter().find_map(|a| a.as_output_format()))