feat: add Tauri v2 Rust backend with PostgreSQL support

Add Rust backend: AppState with connection pool management,
TuskError handling, ConnectionConfig model, and all commands
for connections, schema browsing, query execution, data CRUD,
and CSV/JSON export via sqlx.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-11 19:06:27 +03:00
parent 27bdbf0112
commit 9b675babd5
36 changed files with 6918 additions and 0 deletions

View File

@@ -0,0 +1,342 @@
use crate::error::{TuskError, TuskResult};
use crate::models::schema::{ColumnInfo, ConstraintInfo, IndexInfo, SchemaObject};
use crate::state::AppState;
use sqlx::Row;
use tauri::State;
#[tauri::command]
pub async fn list_databases(
state: State<'_, AppState>,
connection_id: String,
) -> TuskResult<Vec<String>> {
let pools = state.pools.read().await;
let pool = pools
.get(&connection_id)
.ok_or(TuskError::NotConnected(connection_id))?;
let rows = sqlx::query(
"SELECT datname FROM pg_database \
WHERE datistemplate = false \
ORDER BY datname",
)
.fetch_all(pool)
.await
.map_err(TuskError::Database)?;
Ok(rows.iter().map(|r| r.get::<String, _>(0)).collect())
}
#[tauri::command]
pub async fn list_schemas(
state: State<'_, AppState>,
connection_id: String,
) -> TuskResult<Vec<String>> {
let pools = state.pools.read().await;
let pool = pools
.get(&connection_id)
.ok_or(TuskError::NotConnected(connection_id))?;
let rows = sqlx::query(
"SELECT schema_name FROM information_schema.schemata \
WHERE schema_name NOT IN ('pg_catalog', 'information_schema', 'pg_toast') \
ORDER BY schema_name",
)
.fetch_all(pool)
.await
.map_err(TuskError::Database)?;
Ok(rows.iter().map(|r| r.get::<String, _>(0)).collect())
}
#[tauri::command]
pub async fn list_tables(
state: State<'_, AppState>,
connection_id: String,
schema: String,
) -> TuskResult<Vec<SchemaObject>> {
let pools = state.pools.read().await;
let pool = pools
.get(&connection_id)
.ok_or(TuskError::NotConnected(connection_id))?;
let rows = sqlx::query(
"SELECT table_name FROM information_schema.tables \
WHERE table_schema = $1 AND table_type = 'BASE TABLE' \
ORDER BY table_name",
)
.bind(&schema)
.fetch_all(pool)
.await
.map_err(TuskError::Database)?;
Ok(rows
.iter()
.map(|r| SchemaObject {
name: r.get(0),
object_type: "table".to_string(),
schema: schema.clone(),
})
.collect())
}
#[tauri::command]
pub async fn list_views(
state: State<'_, AppState>,
connection_id: String,
schema: String,
) -> TuskResult<Vec<SchemaObject>> {
let pools = state.pools.read().await;
let pool = pools
.get(&connection_id)
.ok_or(TuskError::NotConnected(connection_id))?;
let rows = sqlx::query(
"SELECT table_name FROM information_schema.views \
WHERE table_schema = $1 \
ORDER BY table_name",
)
.bind(&schema)
.fetch_all(pool)
.await
.map_err(TuskError::Database)?;
Ok(rows
.iter()
.map(|r| SchemaObject {
name: r.get(0),
object_type: "view".to_string(),
schema: schema.clone(),
})
.collect())
}
#[tauri::command]
pub async fn list_functions(
state: State<'_, AppState>,
connection_id: String,
schema: String,
) -> TuskResult<Vec<SchemaObject>> {
let pools = state.pools.read().await;
let pool = pools
.get(&connection_id)
.ok_or(TuskError::NotConnected(connection_id))?;
let rows = sqlx::query(
"SELECT routine_name FROM information_schema.routines \
WHERE routine_schema = $1 AND routine_type = 'FUNCTION' \
ORDER BY routine_name",
)
.bind(&schema)
.fetch_all(pool)
.await
.map_err(TuskError::Database)?;
Ok(rows
.iter()
.map(|r| SchemaObject {
name: r.get(0),
object_type: "function".to_string(),
schema: schema.clone(),
})
.collect())
}
#[tauri::command]
pub async fn list_indexes(
state: State<'_, AppState>,
connection_id: String,
schema: String,
) -> TuskResult<Vec<SchemaObject>> {
let pools = state.pools.read().await;
let pool = pools
.get(&connection_id)
.ok_or(TuskError::NotConnected(connection_id))?;
let rows = sqlx::query(
"SELECT indexname FROM pg_indexes \
WHERE schemaname = $1 \
ORDER BY indexname",
)
.bind(&schema)
.fetch_all(pool)
.await
.map_err(TuskError::Database)?;
Ok(rows
.iter()
.map(|r| SchemaObject {
name: r.get(0),
object_type: "index".to_string(),
schema: schema.clone(),
})
.collect())
}
#[tauri::command]
pub async fn list_sequences(
state: State<'_, AppState>,
connection_id: String,
schema: String,
) -> TuskResult<Vec<SchemaObject>> {
let pools = state.pools.read().await;
let pool = pools
.get(&connection_id)
.ok_or(TuskError::NotConnected(connection_id))?;
let rows = sqlx::query(
"SELECT sequence_name FROM information_schema.sequences \
WHERE sequence_schema = $1 \
ORDER BY sequence_name",
)
.bind(&schema)
.fetch_all(pool)
.await
.map_err(TuskError::Database)?;
Ok(rows
.iter()
.map(|r| SchemaObject {
name: r.get(0),
object_type: "sequence".to_string(),
schema: schema.clone(),
})
.collect())
}
#[tauri::command]
pub async fn get_table_columns(
state: State<'_, AppState>,
connection_id: String,
schema: String,
table: String,
) -> TuskResult<Vec<ColumnInfo>> {
let pools = state.pools.read().await;
let pool = pools
.get(&connection_id)
.ok_or(TuskError::NotConnected(connection_id))?;
let rows = sqlx::query(
"SELECT \
c.column_name, \
c.data_type, \
c.is_nullable, \
c.column_default, \
c.ordinal_position::int, \
c.character_maximum_length::int, \
COALESCE(( \
SELECT true FROM information_schema.table_constraints tc \
JOIN information_schema.key_column_usage kcu \
ON tc.constraint_name = kcu.constraint_name \
AND tc.table_schema = kcu.table_schema \
WHERE tc.constraint_type = 'PRIMARY KEY' \
AND tc.table_schema = $1 \
AND tc.table_name = $2 \
AND kcu.column_name = c.column_name \
LIMIT 1 \
), false) as is_pk \
FROM information_schema.columns c \
WHERE c.table_schema = $1 AND c.table_name = $2 \
ORDER BY c.ordinal_position",
)
.bind(&schema)
.bind(&table)
.fetch_all(pool)
.await
.map_err(TuskError::Database)?;
Ok(rows
.iter()
.map(|r| ColumnInfo {
name: r.get::<String, _>(0),
data_type: r.get::<String, _>(1),
is_nullable: r.get::<String, _>(2) == "YES",
column_default: r.get::<Option<String>, _>(3),
ordinal_position: r.get::<i32, _>(4),
character_maximum_length: r.get::<Option<i32>, _>(5),
is_primary_key: r.get::<bool, _>(6),
})
.collect())
}
#[tauri::command]
pub async fn get_table_constraints(
state: State<'_, AppState>,
connection_id: String,
schema: String,
table: String,
) -> TuskResult<Vec<ConstraintInfo>> {
let pools = state.pools.read().await;
let pool = pools
.get(&connection_id)
.ok_or(TuskError::NotConnected(connection_id))?;
let rows = sqlx::query(
"SELECT \
tc.constraint_name, \
tc.constraint_type, \
array_agg(kcu.column_name ORDER BY kcu.ordinal_position)::text[] as columns \
FROM information_schema.table_constraints tc \
JOIN information_schema.key_column_usage kcu \
ON tc.constraint_name = kcu.constraint_name \
AND tc.table_schema = kcu.table_schema \
WHERE tc.table_schema = $1 AND tc.table_name = $2 \
GROUP BY tc.constraint_name, tc.constraint_type \
ORDER BY tc.constraint_type, tc.constraint_name",
)
.bind(&schema)
.bind(&table)
.fetch_all(pool)
.await
.map_err(TuskError::Database)?;
Ok(rows
.iter()
.map(|r| ConstraintInfo {
name: r.get::<String, _>(0),
constraint_type: r.get::<String, _>(1),
columns: r.get::<Vec<String>, _>(2),
})
.collect())
}
#[tauri::command]
pub async fn get_table_indexes(
state: State<'_, AppState>,
connection_id: String,
schema: String,
table: String,
) -> TuskResult<Vec<IndexInfo>> {
let pools = state.pools.read().await;
let pool = pools
.get(&connection_id)
.ok_or(TuskError::NotConnected(connection_id))?;
let rows = sqlx::query(
"SELECT \
i.relname as index_name, \
pg_get_indexdef(i.oid) as index_def, \
ix.indisunique as is_unique, \
ix.indisprimary as is_primary \
FROM pg_index ix \
JOIN pg_class i ON i.oid = ix.indexrelid \
JOIN pg_class t ON t.oid = ix.indrelid \
JOIN pg_namespace n ON n.oid = t.relnamespace \
WHERE n.nspname = $1 AND t.relname = $2 \
ORDER BY i.relname",
)
.bind(&schema)
.bind(&table)
.fetch_all(pool)
.await
.map_err(TuskError::Database)?;
Ok(rows
.iter()
.map(|r| IndexInfo {
name: r.get::<String, _>(0),
definition: r.get::<String, _>(1),
is_unique: r.get::<bool, _>(2),
is_primary: r.get::<bool, _>(3),
})
.collect())
}