Skip to content

Commit fc03772

Browse files
committed
fix: handle SQLite virtual tables gracefully
1 parent dfef076 commit fc03772

File tree

2 files changed

+89
-47
lines changed

2 files changed

+89
-47
lines changed

src/main.rs

Lines changed: 84 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -578,10 +578,11 @@ mod sqlite {
578578

579579
let mut row_counts = HashMap::with_capacity(tables as usize);
580580
for name in table_names.iter() {
581-
let count =
582-
conn.query_row(&format!("SELECT count(*) FROM '{name}'"), (), |r| {
581+
let count = conn
582+
.query_row(&format!("SELECT count(*) FROM '{name}'"), (), |r| {
583583
r.get::<_, i32>(0)
584-
})?;
584+
})
585+
.unwrap_or(0);
585586

586587
row_counts.insert(name.to_owned(), count);
587588
}
@@ -595,9 +596,13 @@ mod sqlite {
595596

596597
let mut column_counts = HashMap::with_capacity(tables as usize);
597598
for name in table_names.iter() {
598-
let mut columns = conn.prepare(&format!("PRAGMA table_info('{name}')"))?;
599-
let count =
600-
columns.query_map((), |r| r.get::<_, String>(1))?.count() as i32;
599+
let count = conn
600+
.prepare(&format!("PRAGMA table_info('{name}')"))
601+
.and_then(|mut columns| {
602+
Ok(columns.query_map((), |r| r.get::<_, String>(1))?.count()
603+
as i32)
604+
})
605+
.unwrap_or(0);
601606

602607
column_counts.insert(name.to_owned(), count);
603608
}
@@ -611,16 +616,20 @@ mod sqlite {
611616

612617
let mut index_counts = HashMap::with_capacity(tables as usize);
613618
for name in table_names.iter() {
614-
let count = conn.query_row(
615-
"SELECT count(*) FROM sqlite_master WHERE type='index' AND tbl_name=?1",
616-
[name],
617-
|r| r.get::<_, i32>(0),
618-
)?;
619-
620-
let has_primary_key =
621-
conn.query_row(&format!("PRAGMA table_info('{name}')"), [], |r| {
619+
let count = conn
620+
.query_row(
621+
"SELECT count(*) FROM sqlite_master WHERE type='index' AND tbl_name=?1",
622+
[name],
623+
|r| r.get::<_, i32>(0),
624+
)
625+
.unwrap_or(0);
626+
627+
let has_primary_key = conn
628+
.query_row(&format!("PRAGMA table_info('{name}')"), [], |r| {
622629
r.get::<_, i32>(5)
623-
})? == 1;
630+
})
631+
.map(|v| v == 1)
632+
.unwrap_or(false);
624633

625634
let count = if has_primary_key { count + 1 } else { count };
626635

@@ -675,10 +684,11 @@ mod sqlite {
675684
let mut table_counts = HashMap::with_capacity(table_names.len());
676685
for name in table_names {
677686
let name = name?;
678-
let count =
679-
conn.query_row(&format!("SELECT count(*) FROM '{name}'"), (), |r| {
687+
let count = conn
688+
.query_row(&format!("SELECT count(*) FROM '{name}'"), (), |r| {
680689
r.get::<_, i32>(0)
681-
})?;
690+
})
691+
.unwrap_or(0);
682692

683693
table_counts.insert(name, count);
684694
}
@@ -688,7 +698,7 @@ mod sqlite {
688698
.map(|(name, count)| responses::Count { name, count })
689699
.collect::<Vec<_>>();
690700

691-
counts.sort_by_key(|r| r.count);
701+
counts.sort_by(|a, b| a.count.cmp(&b.count).then(a.name.cmp(&b.name)));
692702

693703
Ok(counts)
694704
})
@@ -713,41 +723,50 @@ mod sqlite {
713723
|r| r.get::<_, String>(0),
714724
)?;
715725

716-
let row_count =
717-
conn.query_row(&format!("SELECT count(*) FROM '{name}'"), (), |r| {
726+
let row_count = conn
727+
.query_row(&format!("SELECT count(*) FROM '{name}'"), (), |r| {
718728
r.get::<_, i32>(0)
719-
})?;
729+
})
730+
.unwrap_or(0);
720731

721732
let table_size = if more_than_five {
722733
"> 5GB".to_owned()
723734
} else {
724-
let table_size = conn.query_row(
735+
conn.query_row(
725736
"SELECT SUM(pgsize) FROM dbstat WHERE name = ?1",
726737
[&name],
727738
|r| r.get::<_, i64>(0),
728-
)?;
729-
helpers::format_size(table_size as f64)
739+
)
740+
.map(|size| helpers::format_size(size as f64))
741+
.unwrap_or_else(|_| "N/A".to_owned())
730742
};
731743

732-
let index_count = conn.query_row(
733-
"SELECT count(*) FROM sqlite_master WHERE type='index' AND tbl_name=?1",
734-
[&name],
735-
|r| r.get::<_, i32>(0),
736-
)?;
744+
let index_count = conn
745+
.query_row(
746+
"SELECT count(*) FROM sqlite_master WHERE type='index' AND tbl_name=?1",
747+
[&name],
748+
|r| r.get::<_, i32>(0),
749+
)
750+
.unwrap_or(0);
737751

738-
let has_primary_key =
739-
conn.query_row(&format!("PRAGMA table_info('{name}')"), [], |r| {
752+
let has_primary_key = conn
753+
.query_row(&format!("PRAGMA table_info('{name}')"), [], |r| {
740754
r.get::<_, i32>(5)
741-
})? == 1;
755+
})
756+
.map(|v| v == 1)
757+
.unwrap_or(false);
742758
let index_count = if has_primary_key {
743759
index_count + 1
744760
} else {
745761
index_count
746762
};
747763

748-
let mut columns = conn.prepare(&format!("PRAGMA table_info('{name}')"))?;
749-
let column_count =
750-
columns.query_map((), |r| r.get::<_, String>(1))?.count() as i32;
764+
let column_count = conn
765+
.prepare(&format!("PRAGMA table_info('{name}')"))
766+
.and_then(|mut columns| {
767+
Ok(columns.query_map((), |r| r.get::<_, String>(1))?.count() as i32)
768+
})
769+
.unwrap_or(0);
751770

752771
Ok(responses::Table {
753772
name,
@@ -770,20 +789,36 @@ mod sqlite {
770789
.conn
771790
.call(move |conn| {
772791
let first_column =
773-
conn.query_row(&format!("PRAGMA table_info('{name}')"), [], |r| {
792+
match conn.query_row(&format!("PRAGMA table_info('{name}')"), [], |r| {
774793
r.get::<_, String>(1)
775-
})?;
794+
}) {
795+
Ok(col) => col,
796+
Err(_) => {
797+
return Ok(responses::TableData {
798+
columns: vec![],
799+
rows: vec![],
800+
});
801+
}
802+
};
776803

777804
let offset = (page - 1) * ROWS_PER_PAGE;
778-
let mut stmt = conn.prepare(&format!(
805+
let mut stmt = match conn.prepare(&format!(
779806
r#"
780807
SELECT *
781808
FROM '{name}'
782809
ORDER BY {first_column}
783810
LIMIT {ROWS_PER_PAGE}
784811
OFFSET {offset}
785812
"#
786-
))?;
813+
)) {
814+
Ok(stmt) => stmt,
815+
Err(_) => {
816+
return Ok(responses::TableData {
817+
columns: vec![],
818+
rows: vec![],
819+
});
820+
}
821+
};
787822
let columns = stmt
788823
.column_names()
789824
.into_iter()
@@ -822,12 +857,15 @@ mod sqlite {
822857
for name in table_names {
823858
let table_name = name?;
824859

825-
let mut columns =
826-
conn.prepare(&format!("PRAGMA table_info('{table_name}')"))?;
827-
let columns = columns
828-
.query_map((), |r| r.get::<_, String>(1))?
829-
.filter_map(|res| res.ok())
830-
.collect::<Vec<_>>();
860+
let columns = conn
861+
.prepare(&format!("PRAGMA table_info('{table_name}')"))
862+
.and_then(|mut columns| {
863+
Ok(columns
864+
.query_map((), |r| r.get::<_, String>(1))?
865+
.filter_map(|res| res.ok())
866+
.collect::<Vec<_>>())
867+
})
868+
.unwrap_or_default();
831869

832870
tables.push(responses::TableWithColumns {
833871
table_name,

ui/src/routes/tables.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
CardHeader,
2020
CardTitle,
2121
} from "@/components/ui/card";
22+
import { Badge } from "@/components/ui/badge";
2223
import { Skeleton } from "@/components/ui/skeleton";
2324
import { useTheme } from "@/provider/theme.provider";
2425
import { fetchTable, fetchTableData, fetchTables } from "@/api";
@@ -104,6 +105,8 @@ function Table({ name }: Props) {
104105

105106
if (!data) return <TableSkeleton />;
106107

108+
const isVirtual = data.sql?.startsWith("CREATE VIRTUAL TABLE") ?? false;
109+
107110
const cards: InfoCardProps[] = [
108111
{
109112
title: "ROW COUNT",
@@ -133,8 +136,9 @@ function Table({ name }: Props) {
133136

134137
return (
135138
<div className="flex flex-1 flex-col gap-4 md:gap-8">
136-
<h2 className="px-2 text-foreground scroll-m-20 border-b pb-2 text-3xl font-semibold tracking-tight first:mt-0">
139+
<h2 className="px-2 text-foreground scroll-m-20 border-b pb-2 text-3xl font-semibold tracking-tight first:mt-0 flex items-center gap-3">
137140
{data.name}
141+
{isVirtual && <Badge variant="secondary">Virtual Table</Badge>}
138142
</h2>
139143

140144
<div className="grid gap-4 md:grid-cols-2 md:gap-8 lg:grid-cols-4">

0 commit comments

Comments
 (0)