diff --git a/src/bin/cli/main.rs b/src/bin/cli/main.rs index db45bf7..f52bf1f 100644 --- a/src/bin/cli/main.rs +++ b/src/bin/cli/main.rs @@ -1,13 +1,9 @@ -#![allow(unused, dead_code)] +#![allow(dead_code, unused)] use clap::{Parser, Subcommand}; -use futures::TryStreamExt; use simple_crud::error::Error; use simple_crud::error::Result; -use simple_crud::sql::ColumnType; -use sqlx::postgres::PgPool; -use sqlx::Column; -use sqlx::Row; +use simple_crud::sql; #[derive(Parser)] #[command(version, about, long_about = None)] @@ -44,7 +40,7 @@ async fn main() -> Result<()> { // You can check for the existence of subcommands, and if found use their // matches just as you would the top level cmd let result = match &cli.command { - Commands::Query { sql } => handle_query(url, sql).await, + Commands::Query { sql } => sql::handle_query(url, sql).await, }; if let Err(err) = result { @@ -53,19 +49,3 @@ async fn main() -> Result<()> { Ok(()) } - -async fn handle_query(url: String, query: &String) -> Result<()> { - let pool = PgPool::connect(url.as_str()).await?; - - let mut rows = sqlx::query(query.as_str()).fetch(&pool); - - while let Some(row) = rows.try_next().await? { - for idx in 0..row.len() { - let column = ColumnType::new(&row, idx)?; - // let value = String::from(row.get(col.ordinal())); - println!("Column {:?}", column); - } - } - - return Ok(()); -} diff --git a/src/sql.rs b/src/sql.rs index cfa208e..394ba2f 100644 --- a/src/sql.rs +++ b/src/sql.rs @@ -1,116 +1,15 @@ -use chrono::Utc; -use sqlx::postgres::PgRow; -use sqlx::Column as _; -use sqlx::Row as _; -use sqlx::TypeInfo; +use crate::error::Result; -use crate::error; -use crate::error::Error; +mod postgres; -#[derive(Debug)] -pub struct Column { - name: String, - sql_type: String, - value: Option, -} +pub async fn handle_query(url: String, query: &String) -> Result<()> { + let connector = postgres::PgConnector::new(url).await?; -#[derive(Debug)] -pub enum ColumnType { - I8(Column), - String(Column), - Bool(Column), - I16(Column), - I32(Column), - I64(Column), - F32(Column), - F64(Column), - DateTime(Column>), -} + let rows = connector.query(query).await?; -impl ColumnType { - pub fn new(row: &PgRow, idx: usize) -> error::Result { - let column = row - .columns() - .get(idx) - .ok_or_else(|| Error::ColumnParse(String::from("Could not get column")))?; - let sql_type = column.type_info().name(); - let name = String::from(column.name()); - - match sql_type { - "BOOL" => { - let value: Option = row.try_get(idx)?; - Ok(ColumnType::Bool(Column { - sql_type: String::from(sql_type), - value, - name, - })) - } - "VARCHAR" | "char(n)" | "TEXT" | "NAME" => { - let value: Option = row.try_get(idx)?; - Ok(ColumnType::String(Column { - sql_type: String::from(sql_type), - value, - name, - })) - } - "CHAR" => { - let value: Option = row.try_get(idx)?; - Ok(ColumnType::I8(Column { - sql_type: String::from(sql_type), - value, - name, - })) - } - "SMALLSERIAL" | "SMALLINT" => { - let value: Option = row.try_get(idx)?; - Ok(ColumnType::I16(Column { - sql_type: String::from(sql_type), - value, - name, - })) - } - "INT" | "INT4" | "SERIAL" => { - let value: Option = row.try_get(idx)?; - Ok(ColumnType::I32(Column { - sql_type: String::from(sql_type), - value, - name, - })) - } - "INT8" | "BIGSERIAL" | "BIGINT" => { - let value: Option = row.try_get(idx)?; - Ok(ColumnType::I64(Column { - sql_type: String::from(sql_type), - value, - name, - })) - } - "FLOAT4" | "REAL" => { - let value: Option = row.try_get(idx)?; - Ok(ColumnType::F32(Column { - sql_type: String::from(sql_type), - value, - name, - })) - } - "FLOAT8" | "double precision" => { - let value: Option = row.try_get(idx)?; - Ok(ColumnType::F64(Column { - sql_type: String::from(sql_type), - value, - name, - })) - } - "TIMESTAMP" | "TIMESTAMPTZ" => { - // with-chrono feature is needed for this - let value: Option> = row.try_get(idx)?; - Ok(ColumnType::DateTime(Column { - sql_type: String::from(sql_type), - value, - name, - })) - } - &_ => Err(Error::ColumnParse(format!("{} type not found!", sql_type))), - } + for (idx, row) in rows.iter().enumerate() { + println!("Row {}: {:?}", idx, row); } + + return Ok(()); } diff --git a/src/sql/postgres.rs b/src/sql/postgres.rs new file mode 100644 index 0000000..40ad68c --- /dev/null +++ b/src/sql/postgres.rs @@ -0,0 +1,183 @@ +use std::collections::HashMap; +use std::fmt; + +use chrono::Utc; +use sqlx::postgres::PgRow; +use sqlx::Column; +use sqlx::PgPool; +use sqlx::Row; +use sqlx::TypeInfo; + +use futures::TryStreamExt; + +use crate::error::Error; +use crate::error::Result; + +#[derive(Debug)] +pub struct CellData { + name: String, + sql_type: String, + value: Option, +} + +impl fmt::Display for CellData { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let value = match &self.value { + Some(value) => value.to_string(), + None => String::from("NULL"), + }; + write!(f, "{}", value) + } +} + +#[derive(Debug)] +pub enum Cell { + I8(CellData), + String(CellData), + Bool(CellData), + I16(CellData), + I32(CellData), + I64(CellData), + F32(CellData), + F64(CellData), + DateTime(CellData>), +} + +impl Cell { + pub fn new(row: &PgRow, idx: usize) -> Result { + let column = row + .columns() + .get(idx) + .ok_or_else(|| Error::ColumnParse(String::from("Could not get column")))?; + let sql_type = column.type_info().name(); + let name = String::from(column.name()); + + match sql_type { + "BOOL" => { + let value: Option = row.try_get(idx)?; + Ok(Cell::Bool(CellData { + sql_type: String::from(sql_type), + value, + name, + })) + } + "VARCHAR" | "char(n)" | "TEXT" | "NAME" => { + let value: Option = row.try_get(idx)?; + Ok(Cell::String(CellData { + sql_type: String::from(sql_type), + value, + name, + })) + } + "CHAR" => { + let value: Option = row.try_get(idx)?; + Ok(Cell::I8(CellData { + sql_type: String::from(sql_type), + value, + name, + })) + } + "SMALLSERIAL" | "SMALLINT" => { + let value: Option = row.try_get(idx)?; + Ok(Cell::I16(CellData { + sql_type: String::from(sql_type), + value, + name, + })) + } + "INT" | "INT4" | "SERIAL" => { + let value: Option = row.try_get(idx)?; + Ok(Cell::I32(CellData { + sql_type: String::from(sql_type), + value, + name, + })) + } + "INT8" | "BIGSERIAL" | "BIGINT" => { + let value: Option = row.try_get(idx)?; + Ok(Cell::I64(CellData { + sql_type: String::from(sql_type), + value, + name, + })) + } + "FLOAT4" | "REAL" => { + let value: Option = row.try_get(idx)?; + Ok(Cell::F32(CellData { + sql_type: String::from(sql_type), + value, + name, + })) + } + "FLOAT8" | "double precision" => { + let value: Option = row.try_get(idx)?; + Ok(Cell::F64(CellData { + sql_type: String::from(sql_type), + value, + name, + })) + } + "TIMESTAMP" | "TIMESTAMPTZ" => { + // With-chrono feature is needed for this + let value: Option> = row.try_get(idx)?; + Ok(Cell::DateTime(CellData { + sql_type: String::from(sql_type), + value, + name, + })) + } + &_ => Err(Error::ColumnParse(format!("{} type not found!", sql_type))), + } + } +} + +impl fmt::Display for Cell { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let value = match self { + Cell::I8(cell_data) => cell_data.to_string(), + Cell::String(cell_data) => cell_data.to_string(), + Cell::Bool(cell_data) => cell_data.to_string(), + Cell::I16(cell_data) => cell_data.to_string(), + Cell::I32(cell_data) => cell_data.to_string(), + Cell::I64(cell_data) => cell_data.to_string(), + Cell::F32(cell_data) => cell_data.to_string(), + Cell::F64(cell_data) => cell_data.to_string(), + Cell::DateTime(cell_data) => cell_data.to_string(), + }; + write!(f, "{}", value) + } +} + +type PgRows = Vec>; + +pub(crate) struct PgConnector { + pool: PgPool, +} + +impl PgConnector { + pub async fn new(url: String) -> Result { + let pool = PgPool::connect(url.as_str()).await?; + + Ok(Self { pool }) + } + + pub async fn query(&self, query: &String) -> Result { + let mut query = sqlx::query(query.as_str()).fetch(&self.pool); + + let mut rows = vec![]; + + while let Some(row) = query.try_next().await? { + let mut cells = HashMap::new(); + for idx in 0..row.len() { + let col = row.column(idx); + let cell = Cell::new(&row, idx)?; + + cells.insert(col.name().into(), cell); + } + + rows.push(cells); + } + + return Ok(rows); + } +}