generated from alecodes/base-template
feat: add sqlx setup
This commit is contained in:
parent
57074a89ff
commit
07861bcca9
8 changed files with 2158 additions and 15 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -7,3 +7,4 @@ secring.*
|
||||||
# Added by cargo
|
# Added by cargo
|
||||||
|
|
||||||
/target
|
/target
|
||||||
|
.env
|
||||||
|
|
|
||||||
4
.justfile
Normal file
4
.justfile
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
set dotenv-load := true
|
||||||
|
|
||||||
|
dev:
|
||||||
|
cargo run --bin cli -- query "SELECT * FROM sources;"
|
||||||
2011
Cargo.lock
generated
2011
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
10
Cargo.toml
10
Cargo.toml
|
|
@ -4,4 +4,14 @@ version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
chrono = "0.4.39"
|
||||||
clap = { version = "4.5.26", features = ["derive", "env"] }
|
clap = { version = "4.5.26", features = ["derive", "env"] }
|
||||||
|
futures = "0.3.31"
|
||||||
|
sqlx = { version = "0.8", features = [
|
||||||
|
"runtime-tokio",
|
||||||
|
"tls-native-tls",
|
||||||
|
"postgres",
|
||||||
|
"sqlite",
|
||||||
|
"chrono",
|
||||||
|
] }
|
||||||
|
tokio = {version = "1.43.0", features = ["full"]}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,11 @@
|
||||||
|
#![allow(unused, dead_code)]
|
||||||
|
|
||||||
use clap::{Parser, Subcommand};
|
use clap::{Parser, Subcommand};
|
||||||
|
use futures::TryStreamExt;
|
||||||
|
use simple_crud::sql::ColumnType;
|
||||||
|
use sqlx::postgres::PgPool;
|
||||||
|
use sqlx::Column;
|
||||||
|
use sqlx::Row;
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
#[command(version, about, long_about = None)]
|
#[command(version, about, long_about = None)]
|
||||||
|
|
@ -21,14 +28,31 @@ enum Commands {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
let cli = Cli::parse();
|
let cli = Cli::parse();
|
||||||
|
|
||||||
|
let url = cli.db_url.unwrap();
|
||||||
|
|
||||||
// You can check for the existence of subcommands, and if found use their
|
// You can check for the existence of subcommands, and if found use their
|
||||||
// matches just as you would the top level cmd
|
// matches just as you would the top level cmd
|
||||||
match &cli.command {
|
match &cli.command {
|
||||||
Commands::Query { sql } => {
|
Commands::Query { sql } => handle_query(url, sql).await.unwrap(),
|
||||||
println!("Provided query: {sql:?}");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn handle_query(url: String, query: &String) -> Result<(), sqlx::Error> {
|
||||||
|
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).unwrap();
|
||||||
|
// let value = String::from(row.get(col.ordinal()));
|
||||||
|
println!("Column {:?}", column);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
1
src/error.rs
Normal file
1
src/error.rs
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
pub type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
pub mod error;
|
||||||
|
pub mod sql;
|
||||||
112
src/sql.rs
Normal file
112
src/sql.rs
Normal file
|
|
@ -0,0 +1,112 @@
|
||||||
|
use chrono::Utc;
|
||||||
|
use sqlx::postgres::PgRow;
|
||||||
|
use sqlx::Column as _;
|
||||||
|
use sqlx::Row as _;
|
||||||
|
use sqlx::TypeInfo;
|
||||||
|
|
||||||
|
use crate::error;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Column<T> {
|
||||||
|
name: String,
|
||||||
|
sql_type: String,
|
||||||
|
value: Option<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum ColumnType {
|
||||||
|
I8(Column<i8>),
|
||||||
|
String(Column<String>),
|
||||||
|
Bool(Column<bool>),
|
||||||
|
I16(Column<i16>),
|
||||||
|
I32(Column<i32>),
|
||||||
|
I64(Column<i64>),
|
||||||
|
F32(Column<f32>),
|
||||||
|
F64(Column<f64>),
|
||||||
|
DateTime(Column<chrono::DateTime<Utc>>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ColumnType {
|
||||||
|
pub fn new(row: &PgRow, idx: usize) -> error::Result<Self> {
|
||||||
|
let column = row.columns().get(idx).unwrap();
|
||||||
|
let sql_type = column.type_info().name();
|
||||||
|
let name = String::from(column.name());
|
||||||
|
|
||||||
|
match sql_type {
|
||||||
|
"BOOL" => {
|
||||||
|
let value: Option<bool> = row.try_get(idx)?;
|
||||||
|
Ok(ColumnType::Bool(Column {
|
||||||
|
sql_type: String::from(sql_type),
|
||||||
|
value,
|
||||||
|
name,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
"VARCHAR" | "char(n)" | "TEXT" | "NAME" => {
|
||||||
|
let value: Option<String> = row.try_get(idx)?;
|
||||||
|
Ok(ColumnType::String(Column {
|
||||||
|
sql_type: String::from(sql_type),
|
||||||
|
value,
|
||||||
|
name,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
"CHAR" => {
|
||||||
|
let value: Option<i8> = row.try_get(idx)?;
|
||||||
|
Ok(ColumnType::I8(Column {
|
||||||
|
sql_type: String::from(sql_type),
|
||||||
|
value,
|
||||||
|
name,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
"SMALLSERIAL" | "SMALLINT" => {
|
||||||
|
let value: Option<i16> = row.try_get(idx)?;
|
||||||
|
Ok(ColumnType::I16(Column {
|
||||||
|
sql_type: String::from(sql_type),
|
||||||
|
value,
|
||||||
|
name,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
"INT" | "INT4" | "SERIAL" => {
|
||||||
|
let value: Option<i32> = row.try_get(idx)?;
|
||||||
|
Ok(ColumnType::I32(Column {
|
||||||
|
sql_type: String::from(sql_type),
|
||||||
|
value,
|
||||||
|
name,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
"INT8" | "BIGSERIAL" | "BIGINT" => {
|
||||||
|
let value: Option<i64> = row.try_get(idx)?;
|
||||||
|
Ok(ColumnType::I64(Column {
|
||||||
|
sql_type: String::from(sql_type),
|
||||||
|
value,
|
||||||
|
name,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
"FLOAT4" | "REAL" => {
|
||||||
|
let value: Option<f32> = row.try_get(idx)?;
|
||||||
|
Ok(ColumnType::F32(Column {
|
||||||
|
sql_type: String::from(sql_type),
|
||||||
|
value,
|
||||||
|
name,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
"FLOAT8" | "double precision" => {
|
||||||
|
let value: Option<f64> = 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<chrono::DateTime<Utc>> = row.try_get(idx)?;
|
||||||
|
Ok(ColumnType::DateTime(Column {
|
||||||
|
sql_type: String::from(sql_type),
|
||||||
|
value,
|
||||||
|
name,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
&_ => panic!("{} Type not found!", sql_type),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue