diff --git a/packages/rust/libsql-client/Cargo.toml b/packages/rust/libsql-client/Cargo.toml index f4ba9002..3b5e1219 100644 --- a/packages/rust/libsql-client/Cargo.toml +++ b/packages/rust/libsql-client/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "libsql-client" -version = "0.5.7" +version = "0.5.9" edition = "2021" license = "Apache-2.0" description = "HTTP-based client for libSQL and sqld" diff --git a/packages/rust/libsql-client/examples/connect_from_env.rs b/packages/rust/libsql-client/examples/connect_from_env.rs index 92c6d399..dc6dcf63 100644 --- a/packages/rust/libsql-client/examples/connect_from_env.rs +++ b/packages/rust/libsql-client/examples/connect_from_env.rs @@ -1,33 +1,30 @@ -use libsql_client::{connect, Connection, QueryResult, Statement, Value}; +use anyhow::Result; +use libsql_client::{connect, params, Connection, QueryResult, ResultSet, Statement}; use rand::prelude::SliceRandom; -fn result_to_string(result: QueryResult) -> String { +fn result_to_string(query_result: QueryResult) -> Result { let mut ret = String::new(); - match result { - QueryResult::Error((msg, _)) => return format!("Error: {msg}"), - QueryResult::Success((result, _)) => { - for column in &result.columns { - ret += &format!("| {column:16} |"); - } - ret += "\n| -------------------------------------------------------- |\n"; - for row in result.rows { - for column in &result.columns { - ret += &format!("| {:16} |", row.cells[column].to_string()); - } - ret += "\n"; - } + let ResultSet { columns, rows } = query_result.into_result_set()?; + for column in &columns { + ret += &format!("| {column:16} |"); + } + ret += "\n| -------------------------------------------------------- |\n"; + for row in rows { + for column in &columns { + ret += &format!("| {:16} |", row.cells[column].to_string()); } - }; - ret + ret += "\n"; + } + Ok(ret) } // Bumps a counter for one of the geographic locations picked at random. -async fn bump_counter(db: impl Connection) -> String { +async fn bump_counter(db: impl Connection) -> Result { // Recreate the tables if they do not exist yet db.batch([ "CREATE TABLE IF NOT EXISTS counter(country TEXT, city TEXT, value, PRIMARY KEY(country, city)) WITHOUT ROWID", "CREATE TABLE IF NOT EXISTS coordinates(lat INT, long INT, airport TEXT, PRIMARY KEY (lat, long))" - ]).await.ok(); + ]).await?; // For demo purposes, let's pick a pseudorandom location const FAKE_LOCATIONS: &[(&str, &str, &str, f64, f64)] = &[ @@ -44,6 +41,7 @@ async fn bump_counter(db: impl Connection) -> String { db.transaction([ Statement::with_params( "INSERT OR IGNORE INTO counter VALUES (?, ?, 0)", + // Parameters that have a single type can be passed as a regular slice &[country, city], ), Statement::with_params( @@ -52,29 +50,24 @@ async fn bump_counter(db: impl Connection) -> String { ), Statement::with_params( "INSERT OR IGNORE INTO coordinates VALUES (?, ?, ?)", - &[ - Value::Real(latitude), - Value::Real(longitude), - airport.into(), - ], + // Parameters with different types can be passed to a convenience macro - params!() + params!(latitude, longitude, airport), ), ]) - .await - .ok(); + .await?; - let counter_response = match db.execute("SELECT * FROM counter").await { - Ok(resp) => resp, - Err(e) => return format!("Error: {e}"), - }; - let scoreboard = result_to_string(counter_response); + let counter_response = db.execute("SELECT * FROM counter").await?; + let scoreboard = result_to_string(counter_response)?; let html = format!("Scoreboard:\n{scoreboard}"); - html + Ok(html) } #[tokio::main] async fn main() { let db = connect().unwrap(); - let response = bump_counter(db).await; + let response = bump_counter(db) + .await + .unwrap_or_else(|e| format!("Error: {e}")); println!( "Connection parameters: backend={:?} url={:?}\n{response}", std::env::var("LIBSQL_CLIENT_BACKEND"), diff --git a/packages/rust/libsql-client/examples/select.rs b/packages/rust/libsql-client/examples/select.rs index 1a1e89f7..c954aacf 100644 --- a/packages/rust/libsql-client/examples/select.rs +++ b/packages/rust/libsql-client/examples/select.rs @@ -1,33 +1,30 @@ -use libsql_client::{params, Connection, QueryResult, Statement}; +use anyhow::Result; +use libsql_client::{params, Connection, QueryResult, ResultSet, Statement}; use rand::prelude::SliceRandom; -fn result_to_string(result: QueryResult) -> String { +fn result_to_string(query_result: QueryResult) -> Result { let mut ret = String::new(); - match result { - QueryResult::Error((msg, _)) => return format!("Error: {msg}"), - QueryResult::Success((result, _)) => { - for column in &result.columns { - ret += &format!("| {column:16} |"); - } - ret += "\n| -------------------------------------------------------- |\n"; - for row in result.rows { - for column in &result.columns { - ret += &format!("| {:16} |", row.cells[column].to_string()); - } - ret += "\n"; - } + let ResultSet { columns, rows } = query_result.into_result_set()?; + for column in &columns { + ret += &format!("| {column:16} |"); + } + ret += "\n| -------------------------------------------------------- |\n"; + for row in rows { + for column in &columns { + ret += &format!("| {:16} |", row.cells[column].to_string()); } - }; - ret + ret += "\n"; + } + Ok(ret) } // Bumps a counter for one of the geographic locations picked at random. -async fn bump_counter(db: impl Connection) -> String { +async fn bump_counter(db: impl Connection) -> Result { // Recreate the tables if they do not exist yet db.batch([ "CREATE TABLE IF NOT EXISTS counter(country TEXT, city TEXT, value, PRIMARY KEY(country, city)) WITHOUT ROWID", "CREATE TABLE IF NOT EXISTS coordinates(lat INT, long INT, airport TEXT, PRIMARY KEY (lat, long))" - ]).await.ok(); + ]).await?; // For demo purposes, let's pick a pseudorandom location const FAKE_LOCATIONS: &[(&str, &str, &str, f64, f64)] = &[ @@ -57,24 +54,22 @@ async fn bump_counter(db: impl Connection) -> String { params!(latitude, longitude, airport), ), ]) - .await - .ok(); + .await?; - let counter_response = match db.execute("SELECT * FROM counter").await { - Ok(resp) => resp, - Err(e) => return format!("Error: {e}"), - }; - let scoreboard = result_to_string(counter_response); + let counter_response = db.execute("SELECT * FROM counter").await?; + let scoreboard = result_to_string(counter_response)?; let html = format!("Scoreboard:\n{scoreboard}"); - html + Ok(html) } #[tokio::main] async fn main() { match libsql_client::reqwest::Connection::connect_from_env() { Ok(remote_db) => { - let response = bump_counter(remote_db).await; - println!("Remote:\n{response}"); + match bump_counter(remote_db).await { + Ok(response) => println!("Remote:\n{response}"), + Err(e) => println!("Remote database query failed: {e}"), + }; } Err(e) => println!("Failed to fetch from a remote database: {e}"), } @@ -82,6 +77,8 @@ async fn main() { let mut path_buf = std::env::temp_dir(); path_buf.push("libsql_client_test_db.db"); let local_db = libsql_client::local::Connection::connect(path_buf.as_path()).unwrap(); - let response = bump_counter(local_db).await; - println!("Local:\n{response}"); + match bump_counter(local_db).await { + Ok(response) => println!("Local:\n{response}"), + Err(e) => println!("Local database query failed: {e}"), + }; } diff --git a/packages/rust/libsql-client/src/lib.rs b/packages/rust/libsql-client/src/lib.rs index b12fbbf9..f252745d 100644 --- a/packages/rust/libsql-client/src/lib.rs +++ b/packages/rust/libsql-client/src/lib.rs @@ -59,7 +59,21 @@ pub enum QueryResult { Success((ResultSet, Meta)), } -pub fn parse_columns(columns: Vec, result_idx: usize) -> Result> { +impl QueryResult { + /// Transforms a query result into [`anyhow::Result`] + /// for convenient chaining with the `?` iterator. + pub fn into_result_set(self) -> Result { + match self { + QueryResult::Success((result_set, _)) => Ok(result_set), + QueryResult::Error((msg, _)) => Err(anyhow!("Querying database failed: {msg}")), + } + } +} + +pub(crate) fn parse_columns( + columns: Vec, + result_idx: usize, +) -> Result> { let mut result = Vec::with_capacity(columns.len()); for (idx, column) in columns.into_iter().enumerate() { match column { @@ -74,7 +88,7 @@ pub fn parse_columns(columns: Vec, result_idx: usize) -> Resu Ok(result) } -pub fn parse_value( +pub(crate) fn parse_value( cell: serde_json::Value, result_idx: usize, row_idx: usize, @@ -98,7 +112,7 @@ pub fn parse_value( } } -pub fn parse_rows( +pub(crate) fn parse_rows( rows: Vec, columns: &Vec, result_idx: usize, @@ -127,7 +141,7 @@ pub fn parse_rows( Ok(result) } -pub fn parse_query_result(result: serde_json::Value, idx: usize) -> Result { +pub(crate) fn parse_query_result(result: serde_json::Value, idx: usize) -> Result { match result { serde_json::Value::Object(obj) => { if let Some(err) = obj.get("error") { diff --git a/packages/rust/libsql-client/src/reqwest.rs b/packages/rust/libsql-client/src/reqwest.rs index 37b0efd8..32a2a6b0 100644 --- a/packages/rust/libsql-client/src/reqwest.rs +++ b/packages/rust/libsql-client/src/reqwest.rs @@ -76,7 +76,7 @@ impl Connection { let user = match std::env::var("LIBSQL_CLIENT_USER") { Ok(user) => user, Err(_) => { - return Ok(Connection::connect_from_url(&url::Url::parse(&url)?)?); + return Connection::connect_from_url(&url::Url::parse(&url)?); } }; let pass = std::env::var("LIBSQL_CLIENT_PASS").map_err(|_| {