Skip to content

Commit 9a3896d

Browse files
committed
feat: migrate to postgres
1 parent 0aa52c9 commit 9a3896d

File tree

9 files changed

+99
-51
lines changed

9 files changed

+99
-51
lines changed

src-tauri/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,9 @@ tauri-build = { version = "1.5", features = [] }
1616
tauri = { version = "1.5.2", features = ["shell-open"] }
1717
serde = { version = "1.0.192", features = ["derive"] }
1818
serde_json = "1.0.108"
19-
sqlx = { version = "0.7.2", features = ["runtime-tokio", "tls-rustls", "postgres"] }
2019
tokio = "1.34.0"
20+
tokio-postgres = "0.7.10"
21+
chrono = "0.4.31"
2122

2223

2324

src-tauri/src/main.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,19 @@
22
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
33

44
mod postgres;
5+
mod utils;
56

67
use postgres::{get_schema_tables, get_sql_result, pg_connector};
7-
use sqlx::PgPool;
88
use std::sync::Arc;
99
#[cfg(debug_assertions)]
1010
use tauri::Manager;
1111
use tokio::sync::Mutex;
12+
use tokio_postgres::Client;
1213

1314
#[derive(Default)]
1415
pub struct AppState {
1516
pub connection_strings: Arc<Mutex<String>>,
16-
pub pool: Arc<Mutex<Option<PgPool>>>,
17+
pub client: Arc<Mutex<Option<Client>>>,
1718
}
1819

1920
fn main() {

src-tauri/src/postgres.rs

Lines changed: 39 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,35 @@
1-
use sqlx::{postgres::PgPoolOptions, Column, Row};
21
use tauri::{Result, State};
2+
use tokio_postgres::{connect, NoTls};
33

4-
use crate::AppState;
4+
use crate::{utils::reflective_get, AppState};
55

66
#[tauri::command]
77
pub async fn pg_connector(key: &str, app_state: State<'_, AppState>) -> Result<Vec<String>> {
88
if key.is_empty() {
99
return Ok(Vec::new());
1010
}
11+
let (client, connection) = connect(key, NoTls).await.expect("connection error");
1112

12-
let pool = PgPoolOptions::new()
13-
.max_connections(5)
14-
.connect(key)
15-
.await
16-
.unwrap();
13+
tokio::spawn(async move {
14+
if let Err(e) = connection.await {
15+
eprintln!("connection error: {}", e);
16+
}
17+
});
1718

18-
let schemas: Vec<(String,)> = sqlx::query_as(
19-
r#"
19+
let schemas = client
20+
.query(
21+
r#"
2022
SELECT schema_name
2123
FROM information_schema.schemata;
2224
"#,
23-
)
24-
.fetch_all(&pool)
25-
.await
26-
.unwrap();
27-
let schemas = schemas.iter().map(|r| r.0.clone()).collect();
25+
&[],
26+
)
27+
.await
28+
.unwrap();
29+
let schemas = schemas.iter().map(|r| r.get(0)).collect();
2830

2931
*app_state.connection_strings.lock().await = key.to_string();
30-
*app_state.pool.lock().await = Some(pool);
32+
*app_state.client.lock().await = Some(client);
3133

3234
Ok(schemas)
3335
}
@@ -37,19 +39,20 @@ pub async fn get_schema_tables(
3739
schema: &str,
3840
app_state: State<'_, AppState>,
3941
) -> Result<Vec<String>> {
40-
let pool = app_state.pool.lock().await;
41-
let tables: Vec<(String,)> = sqlx::query_as(
42-
r#"
42+
let client = app_state.client.lock().await;
43+
let client = client.as_ref().unwrap();
44+
let tables = client
45+
.query(
46+
r#"
4347
SELECT table_name
4448
FROM information_schema.tables
4549
WHERE table_schema = $1
4650
"#,
47-
)
48-
.bind(schema)
49-
.fetch_all(pool.as_ref().unwrap())
50-
.await
51-
.unwrap();
52-
let tables = tables.iter().map(|r| r.0.clone()).collect();
51+
&[&schema],
52+
)
53+
.await
54+
.unwrap();
55+
let tables = tables.iter().map(|r| r.get(0)).collect();
5356

5457
Ok(tables)
5558
}
@@ -59,30 +62,26 @@ pub async fn get_sql_result(
5962
sql: String,
6063
app_state: State<'_, AppState>,
6164
) -> Result<(Vec<String>, Vec<Vec<String>>)> {
62-
let pool = app_state.pool.lock().await;
65+
let client = app_state.client.lock().await;
66+
let client = client.as_ref().unwrap();
6367

64-
let query = sqlx::query(&sql)
65-
.fetch_all(pool.as_ref().unwrap())
66-
.await
67-
.unwrap();
68-
// get columns
69-
let columns = query
70-
.get(0)
68+
let rows = client.query(sql.as_str(), &[]).await.unwrap();
69+
let columns = rows
70+
.first()
7171
.unwrap()
7272
.columns()
7373
.iter()
7474
.map(|c| c.name().to_string())
7575
.collect::<Vec<String>>();
76-
// get rows
77-
let rows = query
76+
let rows = rows
7877
.iter()
79-
.map(|r| {
80-
let mut row = Vec::new();
81-
for i in 0..r.len() {
82-
let value = r.try_get::<String, _>(i).unwrap_or(String::from("null"));
83-
row.push(value);
78+
.map(|row| {
79+
let mut row_values = Vec::new();
80+
for i in 0..row.len() {
81+
let value = reflective_get(row, i);
82+
row_values.push(value);
8483
}
85-
row
84+
row_values
8685
})
8786
.collect::<Vec<Vec<String>>>();
8887

src-tauri/src/utils.rs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
use tokio_postgres::Row;
2+
3+
/// The postgres-crate does not provide a default mapping to fallback to String for all
4+
/// types: row.get is generic and without a type assignment the FromSql-Trait cannot be inferred.
5+
/// This function matches over the current column-type and does a manual conversion
6+
pub fn reflective_get(row: &Row, index: usize) -> String {
7+
let column_type = row.columns().get(index).map(|c| c.type_().name()).unwrap();
8+
// see https://docs.rs/sqlx/0.4.0-beta.1/sqlx/postgres/types/index.html
9+
10+
let value = match column_type {
11+
"bool" => {
12+
let v = row.get::<_, Option<bool>>(index);
13+
v.map(|v| v.to_string())
14+
}
15+
"varchar" | "char(n)" | "text" | "name" => row.get(index),
16+
// "char" => {
17+
// let v: i8 = row.get(index);
18+
// }
19+
"int2" | "smallserial" | "smallint" => {
20+
let v = row.get::<_, Option<i16>>(index);
21+
v.map(|v| v.to_string())
22+
}
23+
"int" | "int4" | "serial" => {
24+
let v = row.get::<_, Option<i32>>(index);
25+
v.map(|v| v.to_string())
26+
}
27+
"int8" | "bigserial" | "bigint" => {
28+
let v = row.get::<_, Option<i64>>(index);
29+
v.map(|v| v.to_string())
30+
}
31+
"float4" | "real" => {
32+
let v = row.get::<_, Option<f32>>(index);
33+
v.map(|v| v.to_string())
34+
}
35+
"float8" | "double precision" => {
36+
let v = row.get::<_, Option<f64>>(index);
37+
v.map(|v| v.to_string())
38+
}
39+
// "timestamp" | "timestamptz" => {
40+
// // with-chrono feature is needed for this
41+
// let v: Option<chrono::DateTime<chrono::Utc>> = row.get(index);
42+
// v.map(|v| v.to_string())
43+
// }
44+
&_ => Some("CANNOT PARSE".to_string()),
45+
};
46+
value.unwrap_or("null".to_string())
47+
}
48+

src/db_connector.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@ use leptos::{html::*, *};
22

33
use crate::store::{db::DBStore, query::QueryState};
44

5-
#[component]
6-
pub fn DBConnector() -> impl IntoView {
5+
pub fn db_connector() -> impl IntoView {
76
let db = use_context::<DBStore>().unwrap();
87
let connect = create_action(move |db: &DBStore| {
98
let mut db_clone = *db;

src/layout.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
use crate::{db_connector::DBConnector, sidebar::sidebar};
1+
use crate::{db_connector::db_connector, sidebar::sidebar};
22
use leptos::html::{div, main};
33
use leptos::*;
44

55
pub fn layout(children: Children) -> impl IntoView {
66
div().attr("class", "flex h-screen").child(sidebar()).child(
77
div()
88
.attr("class", "flex flex-col flex-1 overflow-hidden")
9-
.child(DBConnector())
9+
.child(db_connector())
1010
.child(
1111
main()
1212
.attr("class", "flex-1 overflow-y-scroll")

src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,6 @@ use app::*;
1313
use leptos::*;
1414

1515
fn main() {
16-
mount_to_body(|| app())
16+
mount_to_body(app)
1717
}
1818

src/sidebar.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ pub fn sidebar() -> impl IntoView {
1515
children: ChildrenFn::to_children(move || {
1616
Fragment::new(vec![p().child("Loading...").into_view()])
1717
}),
18-
fallback: ViewFn::from(|| div()),
18+
fallback: ViewFn::from(div),
1919
}))
2020
.child(move || {
2121
db.schemas
@@ -48,7 +48,7 @@ pub fn sidebar() -> impl IntoView {
4848
children: ChildrenFn::to_children(move || {
4949
Fragment::new(vec![tables(schema.clone()).into_view()])
5050
}),
51-
fallback: ViewFn::from(|| div()),
51+
fallback: ViewFn::from(div),
5252
}))
5353
})
5454
.collect_view()

src/store/db.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ impl DBStore {
3737

3838
pub fn create_connection_string(&self) -> String {
3939
format!(
40-
"postgresql://{}:{}@{}:{}",
40+
"user={} password={} host={} port={}",
4141
self.db_user.get_untracked(),
4242
self.db_password.get_untracked(),
4343
self.db_host.get_untracked(),

0 commit comments

Comments
 (0)