Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# SQLite Studio

SQLite database explorer

## How To Run It

If you are using [Nix](https://nixos.org/).

```bash
nix shell github:frectonz/sqlite-studio
sqlite-studio
```
16 changes: 13 additions & 3 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
src = pkgs.lib.cleanSourceWith {
src = ./.;
filter = path: type:
(pkgs.lib.hasSuffix "\.sql" path) ||
(pkgs.lib.hasSuffix "\.css" path) ||
(pkgs.lib.hasSuffix "\.js" path) ||
(pkgs.lib.hasSuffix "\.svg" path) ||
Expand All @@ -39,9 +38,22 @@
};
commonArgs = { inherit src; };

ui = pkgs.buildNpmPackage {
pname = "ui";
version = "0.0.0";
src = ./ui;
npmDepsHash = "sha256-fIz4WWvzPzQS4CCXvXtf87+ailmDDri1ACnzaB5TP7Y=";
installPhase = ''
cp -pr --reflink=auto -- dist "$out/"
'';
};

cargoArtifacts = craneLib.buildDepsOnly commonArgs;
bin = craneLib.buildPackage (commonArgs // {
inherit cargoArtifacts;
preBuild = ''
cp -pr --reflink=auto -- ${ui} ui/dist
'';
});
in
{
Expand All @@ -52,7 +64,6 @@
devShells.default = pkgs.mkShell {
buildInputs = [
pkgs.emmet-ls
pkgs.sqlx-cli
pkgs.cargo-watch
pkgs.rust-analyzer
rustToolchain
Expand All @@ -62,7 +73,6 @@
pkgs.nodePackages."@tailwindcss/language-server"

pkgs.nodejs
pkgs.nodePackages.pnpm

pkgs.httpie
];
Expand Down
83 changes: 62 additions & 21 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,11 +193,13 @@ impl TheDB {
table_counts.insert(name, count);
}

let counts = table_counts
let mut counts = table_counts
.into_iter()
.map(|(name, count)| responses::RowCount { name, count })
.collect::<Vec<_>>();

counts.sort_by(|a, b| b.count.cmp(&a.count));

color_eyre::eyre::Ok((tables, indexes, triggers, views, counts))
})
.await??;
Expand All @@ -216,9 +218,9 @@ impl TheDB {
})
}

async fn table_names(&self) -> color_eyre::Result<responses::TableNames> {
async fn tables(&self) -> color_eyre::Result<responses::Tables> {
let conn = self.conn.clone();
let names = tokio::task::spawn_blocking(move || {
let tables = tokio::task::spawn_blocking(move || {
let conn = conn.lock().or_else(|e| {
tracing::error!("could not get lock: {e}");
color_eyre::eyre::bail!("could not get lock: {e}")
Expand All @@ -227,19 +229,36 @@ impl TheDB {
let mut stmt = conn.prepare(r#"SELECT name FROM sqlite_master WHERE type="table""#)?;
let table_names = stmt
.query_map([], |row| row.get::<_, String>(0))?
.filter_map(|r| r.ok())
.collect::<Vec<_>>();

color_eyre::eyre::Ok(table_names)
let mut table_counts = HashMap::with_capacity(table_names.len());
for name in table_names {
let name = name?;
let count = conn.query_row(&format!("SELECT count(*) FROM {name}"), (), |r| {
r.get::<_, i32>(0)
})?;

table_counts.insert(name, count);
}

let mut counts = table_counts
.into_iter()
.map(|(name, count)| responses::RowCount { name, count })
.collect::<Vec<_>>();

counts.sort_by_key(|r| r.count);

color_eyre::eyre::Ok(counts)
})
.await??;

Ok(responses::TableNames { names })
Ok(responses::Tables { tables })
}

async fn get_table(&self, name: String) -> color_eyre::Result<responses::Table> {
let conn = self.conn.clone();
let (name, sql, columns, rows) = tokio::task::spawn_blocking(move || {

tokio::task::spawn_blocking(move || {
let conn = conn.lock().or_else(|e| {
tracing::error!("could not get lock: {e}");
color_eyre::eyre::bail!("could not get lock: {e}")
Expand All @@ -253,15 +272,32 @@ impl TheDB {
|r| r.get::<_, String>(0),
)?;

let row_count = conn.query_row(&format!("SELECT count(*) FROM {name}"), (), |r| {
r.get::<_, i32>(0)
})?;

let mut stmt = conn.prepare(&format!("SELECT * FROM {name}"))?;
let columns = stmt
.column_names()
.into_iter()
.map(ToOwned::to_owned)
.collect::<Vec<_>>();

let columns_len = columns.len();
let table_size = conn.query_row(
"SELECT SUM(pgsize) FROM dbstat WHERE name = ?1",
[&name],
|r| r.get::<_, i32>(0),
)?;
let table_size = helpers::format_size(table_size as u64);

let mut indexes_query =
conn.prepare("SELECT name FROM sqlite_master WHERE type='index' AND tbl_name=?1")?;
let indexes = indexes_query
.query_map([&name], |r| r.get::<_, String>(0))?
.filter_map(|r| r.ok())
.collect::<Vec<_>>();

let columns_len = columns.len();
let rows = stmt
.query_map((), |r| {
let mut rows = Vec::with_capacity(columns_len);
Expand All @@ -274,16 +310,18 @@ impl TheDB {
.filter_map(|x| x.ok())
.collect::<Vec<_>>();

color_eyre::eyre::Ok((name, sql, columns, rows))
})
.await??;

Ok(responses::Table {
name,
sql,
columns,
rows,
let resp = responses::Table {
name,
sql,
row_count,
table_size,
indexes,
columns,
rows,
};
color_eyre::eyre::Ok(resp)
})
.await?
}
}

Expand Down Expand Up @@ -339,14 +377,17 @@ mod responses {
}

#[derive(Serialize)]
pub struct TableNames {
pub names: Vec<String>,
pub struct Tables {
pub tables: Vec<RowCount>,
}

#[derive(Serialize)]
pub struct Table {
pub name: String,
pub sql: String,
pub row_count: i32,
pub table_size: String,
pub indexes: Vec<String>,
pub columns: Vec<String>,
pub rows: Vec<Vec<serde_json::Value>>,
}
Expand Down Expand Up @@ -392,8 +433,8 @@ mod handlers {
}

async fn tables(db: TheDB) -> Result<impl warp::Reply, warp::Rejection> {
let tables = db.table_names().await.map_err(|e| {
tracing::error!("error while getting table names: {e}");
let tables = db.tables().await.map_err(|e| {
tracing::error!("error while getting tables: {e}");
warp::reject::custom(rejections::InternalServerError)
})?;
Ok(warp::reply::json(&tables))
Expand Down
Loading