Skip to content

Commit

Permalink
feat: add row read and row write stats
Browse files Browse the repository at this point in the history
  • Loading branch information
invisal committed Apr 27, 2024
1 parent 56f6a88 commit 288a19c
Show file tree
Hide file tree
Showing 11 changed files with 122 additions and 21 deletions.
19 changes: 11 additions & 8 deletions gui/src/components/query-progress-log.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { MultipleQueryProgress } from "@gui/lib/multiple-query";
import { useEffect, useState } from "react";
import CodePreview from "./code-preview";
import ResultStats from "./result-stat";
import isEmptyResultStats from "@gui/lib/empty-stats";

function formatTimeAgo(ms: number) {
if (ms < 1000) {
Expand Down Expand Up @@ -50,14 +52,6 @@ export default function QueryProgressLog({
{last3.map((detail) => {
return (
<div key={detail.order}>
{detail.end && !detail.error && (
<div className="text-sm">
<strong>[Query #{detail.order + 1}]</strong> This query took{" "}
{formatTimeAgo(detail.end - detail.start)} and affected{" "}
{detail.affectedRow} row(s).
</div>
)}

{!!detail.error && (
<div className="mt-2 mb-2 text-red-500">
<pre>{detail.error}</pre>
Expand All @@ -76,6 +70,15 @@ export default function QueryProgressLog({

<div className="mt-3" />
<CodePreview code={detail.sql} />

{detail.end &&
!detail.error &&
detail.stats &&
!isEmptyResultStats(detail.stats) && (
<div className="-ml-4">
<ResultStats stats={detail.stats} />
</div>
)}
</div>
);
})}
Expand Down
34 changes: 34 additions & 0 deletions gui/src/components/result-stat.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { DatabaseResultStat } from "@gui/driver";

export default function ResultStats({ stats }: { stats: DatabaseResultStat }) {
return (
<div className="text-xs p-2 flex">
{stats.queryDurationMs !== null && (
<div className="px-2 border-r">
<span className="font-semibold">Query Duration</span>:{" "}
{stats.queryDurationMs}s
</div>
)}

{!!stats.rowsRead && (
<div className="px-2 border-r">
<span className="font-semibold">Rows Read</span>: {stats.rowsRead}
</div>
)}

{!!stats.rowsWritten && (
<div className="px-2 border-r">
<span className="font-semibold">Rows Written</span>:{" "}
{stats.rowsWritten}
</div>
)}

{!!stats.rowsAffected && (
<div className="px-2 border-r">
<span className="font-semibold">Affected Rows</span>:{" "}
{stats.rowsAffected}
</div>
)}
</div>
);
}
18 changes: 17 additions & 1 deletion gui/src/components/tabs/query-tab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,15 @@ import { useAutoComplete } from "@gui/contexts/auto-complete-provider";
import { useDatabaseDriver } from "@gui/contexts/driver-provider";
import OptimizeTableState from "../table-optimized/OptimizeTableState";
import { MultipleQueryProgress, multipleQuery } from "@gui/lib/multiple-query";
import { DatabaseResultStat } from "@gui/driver";
import ResultStats from "../result-stat";

export default function QueryWindow() {
const { schema } = useAutoComplete();
const { databaseDriver } = useDatabaseDriver();
const [code, setCode] = useState("");
const [data, setData] = useState<OptimizeTableState>();
const [stats, setStats] = useState<DatabaseResultStat>();
const [progress, setProgress] = useState<MultipleQueryProgress>();
const editorRef = useRef<ReactCodeMirrorRef>(null);
const [lineNumber, setLineNumber] = useState(0);
Expand Down Expand Up @@ -63,6 +66,7 @@ export default function QueryWindow() {
const state = OptimizeTableState.createFromResult(last);
state.setReadOnlyMode(true);
setData(state);
setStats(last.stat);
}
})
.catch(console.error);
Expand Down Expand Up @@ -117,7 +121,19 @@ export default function QueryWindow() {
</ResizablePanel>
<ResizableHandle withHandle />
<ResizablePanel defaultSize={50} style={{ position: "relative" }}>
{data && <ResultTable data={data} />}
{data && (
<div className="flex flex-col h-full w-full">
<div className="grow overflow-hidden">
<ResultTable data={data} />
</div>
{stats && (
<div className="shrink-0">
<Separator />
<ResultStats stats={stats} />
</div>
)}
</div>
)}
{!data && progress && (
<div className="w-full h-full overflow-y-auto overflow-x-hidden">
<QueryProgressLog progress={progress} />
Expand Down
12 changes: 12 additions & 0 deletions gui/src/components/tabs/table-data-tab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,15 @@ import {
} from "@gui/components/ui/alert-dialog";
import {
ColumnSortOption,
DatabaseResultStat,
DatabaseTableSchema,
} from "@gui/drivers/base-driver";
import { useAutoComplete } from "@gui/contexts/auto-complete-provider";
import OpacityLoading from "../loading-opacity";
import OptimizeTableState from "../table-optimized/OptimizeTableState";
import { useDatabaseDriver } from "@gui/contexts/driver-provider";
import ResultStats from "../result-stat";
import isEmptyResultStats from "@gui/lib/empty-stats";

interface TableDataContentProps {
tableName: string;
Expand All @@ -47,6 +50,7 @@ export default function TableDataWindow({ tableName }: TableDataContentProps) {
const [loading, setLoading] = useState(false);
const [data, setData] = useState<OptimizeTableState>();
const [tableSchema, setTableSchema] = useState<DatabaseTableSchema>();
const [stat, setStat] = useState<DatabaseResultStat>();
const [sortColumns, setSortColumns] = useState<ColumnSortOption[]>([]);
const [changeNumber, setChangeNumber] = useState(0);

Expand Down Expand Up @@ -77,6 +81,7 @@ export default function TableDataWindow({ tableName }: TableDataContentProps) {
});

setData(OptimizeTableState.createFromResult(dataResult, schemaResult));
setStat(dataResult.stat);
setTableSchema(schemaResult);
updateTableSchema(tableName, schemaResult.columns);
setLastQueryTimestamp(Date.now());
Expand All @@ -95,6 +100,7 @@ export default function TableDataWindow({ tableName }: TableDataContentProps) {
tableName,
sortColumns,
updateTableSchema,
setStat,
where,
finalOffset,
finalLimit,
Expand Down Expand Up @@ -326,6 +332,12 @@ export default function TableDataWindow({ tableName }: TableDataContentProps) {
/>
) : null}
</div>
{stat && !isEmptyResultStats(stat) && (
<div className="shrink-0 grow-0">
<Separator />
<ResultStats stats={stat} />
</div>
)}
</div>
);
}
10 changes: 9 additions & 1 deletion gui/src/drivers/base-driver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,18 @@ export interface DatabaseHeader {
originalType: string | null;
type: TableColumnDataType;
}

export interface DatabaseResultStat {
rowsAffected: number;
rowsRead: number | null;
rowsWritten: number | null;
queryDurationMs: number | null;
}

export interface DatabaseResultSet {
rows: DatabaseRow[];
headers: DatabaseHeader[];
rowsAffected: number;
stat: DatabaseResultStat;
lastInsertRowid?: number;
}

Expand Down
10 changes: 10 additions & 0 deletions gui/src/lib/empty-stats.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { DatabaseResultStat } from "@gui/driver";

export default function isEmptyResultStats(stats: DatabaseResultStat) {
return (
!stats.queryDurationMs &&
!stats.rowsAffected &&
!stats.rowsRead &&
!stats.rowsWritten
);
}
10 changes: 7 additions & 3 deletions gui/src/lib/multiple-query.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import { BaseDriver, DatabaseResultSet } from "@gui/drivers/base-driver";
import {
BaseDriver,
DatabaseResultSet,
DatabaseResultStat,
} from "@gui/drivers/base-driver";

export interface MultipleQueryProgressItem {
order: number;
sql: string;
start: number;
end?: number;
affectedRow?: number;
stats?: DatabaseResultStat;
error?: string;
}

Expand Down Expand Up @@ -43,7 +47,7 @@ export async function multipleQuery(
const r = await driver.query(statement);

log.end = Date.now();
log.affectedRow = r.rowsAffected;
log.stats = r.stat;

if (r.headers.length > 0) {
lastResult = r;
Expand Down
8 changes: 4 additions & 4 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion studio/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@
"dotenv": "^16.4.5",
"drizzle-orm": "^0.30.1",
"eslint-plugin-jest": "^27.6.3",
"libsql-stateless-easy": "^1.6.2",
"libsql-stateless-easy": "^1.6.7",
"lucia": "^3.1.1",
"lucide-react": "^0.309.0",
"magic-bytes.js": "^1.10.0",
Expand Down
10 changes: 8 additions & 2 deletions studio/src/drivers/rqlite-driver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ interface RqliteResult {
values?: unknown[][];
last_insert_id?: number;
rows_affected?: number;
time?: number;
error?: string;
}

Expand Down Expand Up @@ -55,7 +56,12 @@ export function transformRawResult(raw: RqliteResult): DatabaseResultSet {

return {
rows,
rowsAffected: raw?.rows_affected ?? 0,
stat: {
rowsAffected: raw?.rows_affected ?? 0,
rowsRead: null,
rowsWritten: null,
queryDurationMs: raw?.time ?? 0,
},
headers,
lastInsertRowid:
raw.last_insert_id === undefined ? undefined : raw.last_insert_id,
Expand Down Expand Up @@ -87,7 +93,7 @@ export default class RqliteDriver extends SqliteLikeBaseDriver {
}

// https://rqlite.io/docs/api/api/#unified-endpoint
const result = await fetch(this.endpoint + "/db/request", {
const result = await fetch(this.endpoint + "/db/request?timings", {
method: "POST",
headers,
body: JSON.stringify(
Expand Down
10 changes: 9 additions & 1 deletion studio/src/drivers/turso-driver.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,15 @@ export function transformRawResult(raw: ResultSet): DatabaseResultSet {

return {
rows,
rowsAffected: raw.rowsAffected,
stat: {
rowsAffected: raw.rowsAffected,

// This is unique for stateless driver
rowsRead: (raw as any).rowsRead ?? null,
rowsWritten: (raw as any).rowsWritten ?? null,
queryDurationMs: (raw as any).queryDurationMS ?? null,
},

headers,
lastInsertRowid:
raw.lastInsertRowid === undefined
Expand Down

0 comments on commit 288a19c

Please sign in to comment.