Skip to content

Commit e9ec0f1

Browse files
authored
Added template query buttons to SQL Console in Sync Diagnostics Client (#862)
1 parent eac163a commit e9ec0f1

5 files changed

Lines changed: 150 additions & 7 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@powersync/diagnostics-app': patch
3+
---
4+
5+
Added quick-select template query buttons for common PowerSync internal tables (ps_oplog, ps_crud, ps_buckets, ps_untyped) with tooltips and docs links to both SQL consoles

tools/diagnostics-app/src/app/views/inspector-sql-console.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React from 'react';
22
import { NavigationPage } from '@/components/navigation/NavigationPage';
3-
import { SQLConsoleCore } from '@/app/views/shared/sql-console-core';
3+
import { SQLConsoleCore, POWERSYNC_TEMPLATE_QUERIES, CLIENT_ARCH_DOCS_URL } from '@/app/views/shared/sql-console-core';
44
import { useInspectorDatabase } from '@/library/inspector/InspectorContext';
55

66
const DEFAULT_QUERY = `SELECT name, type FROM sqlite_master ORDER BY type, name`;
@@ -17,7 +17,7 @@ export default function InspectorSQLConsolePage() {
1717

1818
return (
1919
<NavigationPage title="SQL Console">
20-
<SQLConsoleCore executeQuery={executeQuery} defaultQuery={DEFAULT_QUERY} historySource="inspector" />
20+
<SQLConsoleCore executeQuery={executeQuery} defaultQuery={DEFAULT_QUERY} historySource="inspector" templateQueries={POWERSYNC_TEMPLATE_QUERIES} templateDocsUrl={CLIENT_ARCH_DOCS_URL} />
2121
</NavigationPage>
2222
);
2323
}

tools/diagnostics-app/src/app/views/shared/query-history.tsx

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,34 @@ interface QueryHistoryEntry {
1515
executed_at: string;
1616
}
1717

18+
export interface QueryHistoryHandle {
19+
setQuery: (query: string) => void;
20+
}
21+
1822
export interface QueryHistoryDropdownProps {
1923
source: string;
2024
defaultQuery?: string;
2125
ready?: boolean;
2226
error?: string | null;
2327
onQueryChanged: (params: { query: string }) => void;
28+
onReady?: (handle: QueryHistoryHandle) => void;
2429
}
2530

2631
/**
2732
* Inner component that renders the query input, execute button, and history dropdown.
2833
* Must be rendered inside a PowerSyncContext provider for useQuery to work.
2934
*/
30-
function QueryHistoryInput({ source, defaultQuery = '', ready = true, error, onQueryChanged }: QueryHistoryDropdownProps) {
35+
function QueryHistoryInput({ source, defaultQuery = '', ready = true, error, onQueryChanged, onReady }: QueryHistoryDropdownProps) {
3136
const inputRef = React.useRef<HTMLInputElement>(null);
37+
38+
React.useEffect(() => {
39+
onReady?.({
40+
setQuery: (query: string) => {
41+
if (inputRef.current) inputRef.current.value = query;
42+
}
43+
});
44+
}, [onReady]);
45+
3246
const inputWrapperRef = React.useRef<HTMLDivElement>(null);
3347
const [showHistory, setShowHistory] = React.useState(false);
3448
const [dropdownStyle, setDropdownStyle] = React.useState<React.CSSProperties>({});

tools/diagnostics-app/src/app/views/shared/sql-console-core.tsx

Lines changed: 126 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,54 @@
11
import React from 'react';
22
import { Label } from '@/components/ui/label';
3+
import { Button } from '@/components/ui/button';
34
import { Spinner } from '@/components/ui/spinner';
45
import { DataTable, DataTableColumn } from '@/components/ui/data-table';
5-
import { QueryHistoryDropdown } from './query-history';
6+
import { Card, CardContent } from '@/components/ui/card';
7+
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
8+
import { QueryHistoryDropdown, QueryHistoryHandle } from './query-history';
69

710
// ---------------------------------------------------------------------------
811
// SQLConsoleCore - the reusable SQL console UI
912
// ---------------------------------------------------------------------------
1013

1114
const MAX_RESULT_ROWS = 10_000;
15+
const MAX_DISCOVERED_VIEWS = 30;
16+
17+
export interface TemplateQuery {
18+
label: string;
19+
query: string;
20+
tooltip: string;
21+
}
22+
23+
export const CLIENT_ARCH_DOCS_URL =
24+
'https://docs.powersync.com/architecture/client-architecture#client-side-schema-and-sqlite-database-structure';
25+
26+
export const POWERSYNC_TEMPLATE_QUERIES: TemplateQuery[] = [
27+
{
28+
label: 'ps_untyped',
29+
query: 'SELECT * FROM ps_untyped',
30+
tooltip:
31+
'Synced data not matching any table in the client-side schema. Rows migrate to ps_data__<table> once the table is added to the schema.'
32+
},
33+
{
34+
label: 'ps_oplog',
35+
query: 'SELECT * FROM ps_oplog',
36+
tooltip:
37+
'Operation log received from the PowerSync Service, grouped per bucket. Useful for debugging sync state and inspecting individual operations.'
38+
},
39+
{
40+
label: 'ps_crud',
41+
query: 'SELECT * FROM ps_crud',
42+
tooltip:
43+
'Pending local changes waiting to be uploaded. If rows are stuck here, the upload queue may be blocked by a failing operation.'
44+
},
45+
{
46+
label: 'ps_buckets',
47+
query: 'SELECT * FROM ps_buckets',
48+
tooltip:
49+
'Metadata for each sync bucket including last applied op and checkpoint. Helpful for verifying which buckets are actively syncing.'
50+
}
51+
];
1252

1353
export interface SQLConsoleCoreProps {
1454
/** Execute a query and return the results as an array of row objects */
@@ -19,14 +59,27 @@ export interface SQLConsoleCoreProps {
1959
historySource?: string;
2060
/** Whether the target database is ready for queries. Auto-execution is deferred until true. Defaults to true. */
2161
ready?: boolean;
62+
/** Predefined queries shown as quick-select buttons. These don't save to history. */
63+
templateQueries?: TemplateQuery[];
64+
/** URL shown below the Quick Queries heading as a "Learn more" link */
65+
templateDocsUrl?: string;
2266
}
2367

24-
export function SQLConsoleCore({ executeQuery, defaultQuery = '', historySource = 'powersync', ready = true }: SQLConsoleCoreProps) {
68+
export function SQLConsoleCore({ executeQuery, defaultQuery = '', historySource = 'powersync', ready = true, templateQueries, templateDocsUrl }: SQLConsoleCoreProps) {
69+
const historyHandleRef = React.useRef<QueryHistoryHandle | null>(null);
2570
const [results, setResults] = React.useState<Record<string, any>[] | null>(null);
2671
const [totalRowCount, setTotalRowCount] = React.useState(0);
2772
const [isLoading, setIsLoading] = React.useState(false);
2873
const [error, setError] = React.useState<string | null>(null);
2974
const [autoLimited, setAutoLimited] = React.useState(false);
75+
const [discoveredTables, setDiscoveredTables] = React.useState<string[]>([]);
76+
77+
React.useEffect(() => {
78+
if (!ready) return;
79+
executeQuery(`SELECT name FROM sqlite_master WHERE type='view' AND name NOT LIKE 'ps_%' ORDER BY name`)
80+
.then((rows) => setDiscoveredTables(rows.map((r) => r.name as string)))
81+
.catch(() => {});
82+
}, [ready, executeQuery]);
3083

3184
const runQuery = React.useCallback(
3285
async (sql: string) => {
@@ -87,6 +140,76 @@ export function SQLConsoleCore({ executeQuery, defaultQuery = '', historySource
87140

88141
return (
89142
<div className="min-w-0 max-w-full p-5">
143+
{templateQueries && templateQueries.length > 0 && (
144+
<div className="mb-4 space-y-3">
145+
<Label className="mb-1 block">Quick Queries</Label>
146+
<Card>
147+
<CardContent className="p-3 space-y-2">
148+
<p className="text-xs text-muted-foreground">
149+
PowerSync internal tables.{' '}
150+
{templateDocsUrl && (
151+
<a
152+
href={templateDocsUrl}
153+
target="_blank"
154+
rel="noopener noreferrer"
155+
className="hover:text-foreground underline">
156+
Learn more about client architecture
157+
</a>
158+
)}
159+
</p>
160+
<div className="flex flex-wrap gap-2">
161+
<TooltipProvider delayDuration={200}>
162+
{templateQueries.map((tq) => (
163+
<Tooltip key={tq.label}>
164+
<TooltipTrigger asChild>
165+
<Button
166+
variant="outline"
167+
size="sm"
168+
onClick={() => {
169+
historyHandleRef.current?.setQuery(tq.query);
170+
runQuery(tq.query);
171+
}}>
172+
{tq.label}
173+
</Button>
174+
</TooltipTrigger>
175+
<TooltipContent className="max-w-xs">
176+
<p>{tq.tooltip}</p>
177+
</TooltipContent>
178+
</Tooltip>
179+
))}
180+
</TooltipProvider>
181+
</div>
182+
</CardContent>
183+
</Card>
184+
{discoveredTables.length > 0 && (
185+
<Card>
186+
<CardContent className="p-3 space-y-2">
187+
<p className="text-xs text-muted-foreground">
188+
Views discovered from the database schema.
189+
{discoveredTables.length > MAX_DISCOVERED_VIEWS && ` Showing first ${MAX_DISCOVERED_VIEWS} of ${discoveredTables.length}.`}
190+
</p>
191+
<div className="flex flex-wrap gap-2">
192+
{discoveredTables.slice(0, MAX_DISCOVERED_VIEWS).map((name) => {
193+
const query = `SELECT * FROM ${name}`;
194+
return (
195+
<Button
196+
key={name}
197+
variant="outline"
198+
size="sm"
199+
onClick={() => {
200+
historyHandleRef.current?.setQuery(query);
201+
runQuery(query);
202+
}}>
203+
{name}
204+
</Button>
205+
);
206+
})}
207+
</div>
208+
</CardContent>
209+
</Card>
210+
)}
211+
</div>
212+
)}
90213
<div className="flex flex-wrap items-end gap-2.5 mb-4">
91214
<div className="min-w-0 flex-1 basis-0 space-y-1.5 relative">
92215
<Label htmlFor="query-input">Query</Label>
@@ -96,6 +219,7 @@ export function SQLConsoleCore({ executeQuery, defaultQuery = '', historySource
96219
ready={ready}
97220
error={error}
98221
onQueryChanged={handleQueryChanged}
222+
onReady={(handle) => { historyHandleRef.current = handle; }}
99223
/>
100224
{error && <p className="text-sm text-destructive">{error}</p>}
101225
</div>

tools/diagnostics-app/src/app/views/sql-console.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React from 'react';
22
import { NavigationPage } from '@/components/navigation/NavigationPage';
3-
import { SQLConsoleCore } from '@/app/views/shared/sql-console-core';
3+
import { SQLConsoleCore, POWERSYNC_TEMPLATE_QUERIES, CLIENT_ARCH_DOCS_URL } from '@/app/views/shared/sql-console-core';
44
import { db, useSchemaReady } from '@/library/powersync/ConnectionManager';
55

66
const DEFAULT_QUERY = `SELECT name FROM ps_buckets`;
@@ -14,7 +14,7 @@ export default function SQLConsolePage() {
1414

1515
return (
1616
<NavigationPage title="SQL Console">
17-
<SQLConsoleCore executeQuery={executeQuery} defaultQuery={DEFAULT_QUERY} ready={schemaReady} />
17+
<SQLConsoleCore executeQuery={executeQuery} defaultQuery={DEFAULT_QUERY} ready={schemaReady} templateQueries={POWERSYNC_TEMPLATE_QUERIES} templateDocsUrl={CLIENT_ARCH_DOCS_URL} />
1818
</NavigationPage>
1919
);
2020
}

0 commit comments

Comments
 (0)