feat: add column sort, SQL formatter, table stats, insert dialog, saved queries & sessions monitor
- Column sort by header click in table view (ASC/DESC/none cycle, server-side) - SQL formatter with Format button and Shift+Alt+F keybinding (sql-formatter) - Table size and row count display in schema tree via pg_class - Insert row dialog with column type hints and auto-skip for identity columns - Saved queries (bookmarks) with CRUD backend, sidebar panel, and save dialog - Active sessions monitor (pg_stat_activity) with auto-refresh, cancel & terminate Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
use crate::error::{TuskError, TuskResult};
|
||||
use crate::models::schema::{ColumnInfo, ConstraintInfo, IndexInfo, SchemaObject};
|
||||
use crate::models::schema::{ColumnDetail, ColumnInfo, ConstraintInfo, IndexInfo, SchemaObject};
|
||||
use crate::state::AppState;
|
||||
use sqlx::Row;
|
||||
use std::collections::HashMap;
|
||||
@@ -61,9 +61,14 @@ pub async fn list_tables(
|
||||
.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",
|
||||
"SELECT t.table_name, \
|
||||
c.reltuples::bigint as row_count, \
|
||||
pg_total_relation_size(quote_ident(t.table_schema) || '.' || quote_ident(t.table_name))::bigint as size_bytes \
|
||||
FROM information_schema.tables t \
|
||||
LEFT JOIN pg_class c ON c.relname = t.table_name \
|
||||
AND c.relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = $1) \
|
||||
WHERE t.table_schema = $1 AND t.table_type = 'BASE TABLE' \
|
||||
ORDER BY t.table_name",
|
||||
)
|
||||
.bind(&schema)
|
||||
.fetch_all(pool)
|
||||
@@ -76,6 +81,8 @@ pub async fn list_tables(
|
||||
name: r.get(0),
|
||||
object_type: "table".to_string(),
|
||||
schema: schema.clone(),
|
||||
row_count: r.get::<Option<i64>, _>(1),
|
||||
size_bytes: r.get::<Option<i64>, _>(2),
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
@@ -107,6 +114,8 @@ pub async fn list_views(
|
||||
name: r.get(0),
|
||||
object_type: "view".to_string(),
|
||||
schema: schema.clone(),
|
||||
row_count: None,
|
||||
size_bytes: None,
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
@@ -138,6 +147,8 @@ pub async fn list_functions(
|
||||
name: r.get(0),
|
||||
object_type: "function".to_string(),
|
||||
schema: schema.clone(),
|
||||
row_count: None,
|
||||
size_bytes: None,
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
@@ -169,6 +180,8 @@ pub async fn list_indexes(
|
||||
name: r.get(0),
|
||||
object_type: "index".to_string(),
|
||||
schema: schema.clone(),
|
||||
row_count: None,
|
||||
size_bytes: None,
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
@@ -200,6 +213,8 @@ pub async fn list_sequences(
|
||||
name: r.get(0),
|
||||
object_type: "sequence".to_string(),
|
||||
schema: schema.clone(),
|
||||
row_count: None,
|
||||
size_bytes: None,
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
@@ -378,3 +393,42 @@ pub async fn get_completion_schema(
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_column_details(
|
||||
state: State<'_, AppState>,
|
||||
connection_id: String,
|
||||
schema: String,
|
||||
table: String,
|
||||
) -> TuskResult<Vec<ColumnDetail>> {
|
||||
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 = 'YES' as is_nullable, \
|
||||
c.column_default, \
|
||||
c.is_identity = 'YES' as is_identity \
|
||||
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| ColumnDetail {
|
||||
column_name: r.get::<String, _>(0),
|
||||
data_type: r.get::<String, _>(1),
|
||||
is_nullable: r.get::<bool, _>(2),
|
||||
column_default: r.get::<Option<String>, _>(3),
|
||||
is_identity: r.get::<bool, _>(4),
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user