From 02b66803f898823195f2a99c8e5180538b014656 Mon Sep 17 00:00:00 2001 From: Caleb Fahlgren Date: Wed, 26 Jun 2024 00:05:36 -0500 Subject: [PATCH 1/2] add ability to cancel query --- hooks/useDuckDB.ts | 67 +++++++++++++++++++++++++++++----------------- src/content.tsx | 33 ++++++++++++++++++----- 2 files changed, 70 insertions(+), 30 deletions(-) diff --git a/hooks/useDuckDB.ts b/hooks/useDuckDB.ts index 7a17e1a..deb0885 100644 --- a/hooks/useDuckDB.ts +++ b/hooks/useDuckDB.ts @@ -1,10 +1,13 @@ import * as duckdb from "@duckdb/duckdb-wasm" -import { useEffect, useState } from "react" +import { useCallback, useEffect, useState } from "react" export const useDuckDB = () => { const [db, setDb] = useState(null) const [loading, setLoading] = useState(true) const [isQueryRunning, setIsQueryRunning] = useState(false) + const [isCancelling, setIsCancelling] = useState(false) + const [currentConnection, setCurrentConnection] = + useState(null) useEffect(() => { const initializeDuckDB = async () => { @@ -41,34 +44,50 @@ export const useDuckDB = () => { } }, []) - const executeSQL = async ( - query: string, - limit: number = 100000, - params?: any[] - ) => { - if (!db) throw new Error("Database not initialized") + const executeSQL = useCallback( + async (query: string, limit: number = 100000, params?: any[]) => { + if (!db) throw new Error("Database not initialized") - setIsQueryRunning(true) - const connection = await db.connect() + setIsQueryRunning(true) + const connection = await db.connect() + setCurrentConnection(connection) - try { - const resultSet = params?.length - ? await (await connection.prepare(query)).send(...params) - : await connection.send(query) + try { + const resultSet = params?.length + ? await (await connection.prepare(query)).send(...params) + : await connection.send(query) + + const rows = [] - const rows = [] + // read batches until we get to the limit or the end of the result set + // TODO: see if there is a way to set batch size in RecordBatchStreamReader + for await (const batch of resultSet) { + rows.push(...batch.toArray()) + if (rows.length >= limit) break + } - // read batches until we get to the limit or the end of the result set - for await (const batch of resultSet) { - rows.push(...batch.toArray()) - if (rows.length >= limit) break + return { rows: rows.slice(0, limit) } + } finally { + await connection.close() + setCurrentConnection(null) + setIsQueryRunning(false) } + }, + [db] + ) - return { rows: rows.slice(0, limit) } - } finally { - await connection.close() - setIsQueryRunning(false) + // cancel a query in progress + const cancelQuery = useCallback(async () => { + if (currentConnection && isQueryRunning) { + setIsCancelling(true) + try { + await currentConnection.cancelSent() + } finally { + setIsQueryRunning(false) + setIsCancelling(false) + } } - } - return { loading, executeSQL, isQueryRunning } + }, [currentConnection, isQueryRunning]) + + return { loading, executeSQL, isQueryRunning, isCancelling, cancelQuery } } diff --git a/src/content.tsx b/src/content.tsx index ff4db0b..67fc112 100644 --- a/src/content.tsx +++ b/src/content.tsx @@ -33,7 +33,8 @@ const Explorer = () => { const [query, setQuery] = useState("") const [results, setResults] = useState([]) const [error, setError] = useState(null) - const { executeSQL, isQueryRunning, loading } = useDuckDB() + const { executeSQL, isQueryRunning, loading, isCancelling, cancelQuery } = + useDuckDB() const MAX_ROWS = 500 @@ -46,11 +47,21 @@ const Explorer = () => { const { rows } = await executeSQL(query, MAX_ROWS) setResults(rows) } catch (err) { - setError(err.message) + if (err.message !== "Query was cancelled") { + setError(err.message) + } } } - if (loading) return + const handleCancelQuery = async () => { + try { + await cancelQuery() + } catch (err) { + console.error("Error cancelling query:", err) + } + } + + if (loading) return null return (
@@ -65,9 +76,19 @@ const Explorer = () => { placeholder="Enter your SQL query here..." className="w-full p-2 text-sm min-h-[120px] border border-slate-300 rounded resize-none h-24 mb-3" /> - + + {isQueryRunning ? ( + + ) : ( + + )} {error &&

{error}

} {results.length > 0 && (

From 10d206701eba8c2d6302911c2c74b31e92293ee2 Mon Sep 17 00:00:00 2001 From: Caleb Fahlgren Date: Wed, 26 Jun 2024 12:58:12 -0500 Subject: [PATCH 2/2] set indent to 4 on prettier --- .github/workflows/ci.yml | 90 +++++++++++----------- .prettierrc.mjs | 42 +++++------ components.json | 30 ++++---- components/ui/button.tsx | 77 +++++++++---------- components/ui/input.tsx | 28 +++---- components/ui/label.tsx | 18 ++--- components/ui/sonner.tsx | 37 +++++---- components/ui/switch.tsx | 30 ++++---- components/ui/textarea.tsx | 26 +++---- hooks/useDuckDB.ts | 147 ++++++++++++++++++------------------ lib/utils.ts | 2 +- package.json | 134 ++++++++++++++++----------------- postcss.config.js | 8 +- src/content.tsx | 150 +++++++++++++++++++------------------ src/popup.tsx | 115 ++++++++++++++-------------- src/styles.css | 98 ++++++++++++------------ tailwind.config.js | 144 +++++++++++++++++------------------ tsconfig.json | 22 +++--- 18 files changed, 602 insertions(+), 596 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2a7fd0f..73ae7e4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,51 +1,51 @@ name: Node.js CI and Manual Release on: - push: - branches: [main] - pull_request: - branches: [main] - workflow_dispatch: - inputs: - version: - description: "Version to release" - required: true - type: string + push: + branches: [main] + pull_request: + branches: [main] + workflow_dispatch: + inputs: + version: + description: "Version to release" + required: true + type: string jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: pnpm/action-setup@v4 - with: - version: 8 - - uses: actions/setup-node@v4 - with: - node-version: 20 - cache: "pnpm" - - run: pnpm install - - run: pnpm build - - run: zip -r build.zip build - - uses: actions/upload-artifact@v3 - with: - name: build - path: build.zip + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + with: + version: 8 + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: "pnpm" + - run: pnpm install + - run: pnpm build + - run: zip -r build.zip build + - uses: actions/upload-artifact@v3 + with: + name: build + path: build.zip - release: - needs: build - if: github.event_name == 'workflow_dispatch' - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/download-artifact@v3 - with: - name: build - - name: Create Release - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - gh release create v${{ github.event.inputs.version }} \ - --title "Release v${{ github.event.inputs.version }}" \ - --notes "Release notes for version ${{ github.event.inputs.version }}" \ - build.zip + release: + needs: build + if: github.event_name == 'workflow_dispatch' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/download-artifact@v3 + with: + name: build + - name: Create Release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh release create v${{ github.event.inputs.version }} \ + --title "Release v${{ github.event.inputs.version }}" \ + --notes "Release notes for version ${{ github.event.inputs.version }}" \ + build.zip diff --git a/.prettierrc.mjs b/.prettierrc.mjs index 77f84c2..fd4c005 100644 --- a/.prettierrc.mjs +++ b/.prettierrc.mjs @@ -2,25 +2,25 @@ * @type {import('prettier').Options} */ export default { - printWidth: 80, - tabWidth: 2, - useTabs: false, - semi: false, - singleQuote: false, - trailingComma: "none", - bracketSpacing: true, - bracketSameLine: true, - plugins: ["@ianvs/prettier-plugin-sort-imports"], - importOrder: [ - "", // Node.js built-in modules - "", // Imports not matched by other special words or groups. - "", // Empty line - "^@plasmo/(.*)$", - "", - "^@plasmohq/(.*)$", - "", - "^~(.*)$", - "", - "^[./]" - ] + printWidth: 80, + tabWidth: 4, + useTabs: false, + semi: false, + singleQuote: false, + trailingComma: "none", + bracketSpacing: true, + bracketSameLine: true, + plugins: ["@ianvs/prettier-plugin-sort-imports"], + importOrder: [ + "", // Node.js built-in modules + "", // Imports not matched by other special words or groups. + "", // Empty line + "^@plasmo/(.*)$", + "", + "^@plasmohq/(.*)$", + "", + "^~(.*)$", + "", + "^[./]" + ] } diff --git a/components.json b/components.json index 66e660d..4ceef71 100644 --- a/components.json +++ b/components.json @@ -1,17 +1,17 @@ { - "$schema": "https://ui.shadcn.com/schema.json", - "style": "default", - "rsc": false, - "tsx": true, - "tailwind": { - "config": "tailwind.config.js", - "css": "src/styles.css", - "baseColor": "slate", - "cssVariables": true, - "prefix": "" - }, - "aliases": { - "components": "@/components", - "utils": "@/lib/utils" - } + "$schema": "https://ui.shadcn.com/schema.json", + "style": "default", + "rsc": false, + "tsx": true, + "tailwind": { + "config": "tailwind.config.js", + "css": "src/styles.css", + "baseColor": "slate", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils" + } } diff --git a/components/ui/button.tsx b/components/ui/button.tsx index a66c95e..853a5aa 100644 --- a/components/ui/button.tsx +++ b/components/ui/button.tsx @@ -4,51 +4,52 @@ import { cva, type VariantProps } from "class-variance-authority" import * as React from "react" const buttonVariants = cva( - "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", - { - variants: { - variant: { - default: "bg-primary text-primary-foreground hover:bg-primary/90", - destructive: - "bg-destructive text-destructive-foreground hover:bg-destructive/90", - outline: - "border border-input bg-background hover:bg-accent hover:text-accent-foreground", - secondary: - "bg-secondary text-secondary-foreground hover:bg-secondary/80", - ghost: "hover:bg-accent hover:text-accent-foreground", - link: "text-primary underline-offset-4 hover:underline" - }, - size: { - default: "h-10 px-4 py-2", - sm: "h-9 rounded-md px-3", - lg: "h-11 rounded-md px-8", - icon: "h-10 w-10" - } - }, - defaultVariants: { - variant: "default", - size: "default" + "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", + { + variants: { + variant: { + default: + "bg-primary text-primary-foreground hover:bg-primary/90", + destructive: + "bg-destructive text-destructive-foreground hover:bg-destructive/90", + outline: + "border border-input bg-background hover:bg-accent hover:text-accent-foreground", + secondary: + "bg-secondary text-secondary-foreground hover:bg-secondary/80", + ghost: "hover:bg-accent hover:text-accent-foreground", + link: "text-primary underline-offset-4 hover:underline" + }, + size: { + default: "h-10 px-4 py-2", + sm: "h-9 rounded-md px-3", + lg: "h-11 rounded-md px-8", + icon: "h-10 w-10" + } + }, + defaultVariants: { + variant: "default", + size: "default" + } } - } ) export interface ButtonProps - extends React.ButtonHTMLAttributes, - VariantProps { - asChild?: boolean + extends React.ButtonHTMLAttributes, + VariantProps { + asChild?: boolean } const Button = React.forwardRef( - ({ className, variant, size, asChild = false, ...props }, ref) => { - const Comp = asChild ? Slot : "button" - return ( - - ) - } + ({ className, variant, size, asChild = false, ...props }, ref) => { + const Comp = asChild ? Slot : "button" + return ( + + ) + } ) Button.displayName = "Button" diff --git a/components/ui/input.tsx b/components/ui/input.tsx index 3f2d91a..84a7715 100644 --- a/components/ui/input.tsx +++ b/components/ui/input.tsx @@ -2,22 +2,22 @@ import { cn } from "@/lib/utils" import * as React from "react" export interface InputProps - extends React.InputHTMLAttributes {} + extends React.InputHTMLAttributes {} const Input = React.forwardRef( - ({ className, type, ...props }, ref) => { - return ( - - ) - } + ({ className, type, ...props }, ref) => { + return ( + + ) + } ) Input.displayName = "Input" diff --git a/components/ui/label.tsx b/components/ui/label.tsx index a38f3ba..1271cba 100644 --- a/components/ui/label.tsx +++ b/components/ui/label.tsx @@ -4,19 +4,19 @@ import { cva, type VariantProps } from "class-variance-authority" import * as React from "react" const labelVariants = cva( - "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" + "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" ) const Label = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef & - VariantProps + React.ElementRef, + React.ComponentPropsWithoutRef & + VariantProps >(({ className, ...props }, ref) => ( - + )) Label.displayName = LabelPrimitive.Root.displayName diff --git a/components/ui/sonner.tsx b/components/ui/sonner.tsx index 3c5179a..8e52291 100644 --- a/components/ui/sonner.tsx +++ b/components/ui/sonner.tsx @@ -4,26 +4,25 @@ import { Toaster as Sonner } from "sonner" type ToasterProps = React.ComponentProps const Toaster = ({ ...props }: ToasterProps) => { - const { theme = "system" } = useTheme() + const { theme = "system" } = useTheme() - return ( - - ) + return ( + + ) } export { Toaster } diff --git a/components/ui/switch.tsx b/components/ui/switch.tsx index 798d573..09d62a6 100644 --- a/components/ui/switch.tsx +++ b/components/ui/switch.tsx @@ -3,22 +3,22 @@ import * as SwitchPrimitives from "@radix-ui/react-switch" import * as React from "react" const Switch = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( - - - + + + )) Switch.displayName = SwitchPrimitives.Root.displayName diff --git a/components/ui/textarea.tsx b/components/ui/textarea.tsx index a301738..213fae1 100644 --- a/components/ui/textarea.tsx +++ b/components/ui/textarea.tsx @@ -2,21 +2,21 @@ import { cn } from "@/lib/utils" import * as React from "react" export interface TextareaProps - extends React.TextareaHTMLAttributes {} + extends React.TextareaHTMLAttributes {} const Textarea = React.forwardRef( - ({ className, ...props }, ref) => { - return ( -