From db5d6c93eeecb513beac14d0325cdf29660d35a5 Mon Sep 17 00:00:00 2001 From: "Visal .In" Date: Tue, 20 Feb 2024 17:10:11 +0700 Subject: [PATCH] feat: run current and run all with cursor line number and column number (#9) * feat: add insert value menu * feat: run current and run all --- package-lock.json | 25 +++---- package.json | 2 +- src/app/(windows)/QueryWindow.tsx | 79 +++++++++++++++------- src/components/SqlEditor.tsx | 106 +++++++++++++++++------------- src/lib/sql-helper.test.ts | 28 ++++++++ src/lib/sql-helper.ts | 11 ++++ 6 files changed, 173 insertions(+), 78 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5751e317..45462115 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,13 +32,13 @@ "class-variance-authority": "^0.7.0", "clsx": "^2.1.0", "cmdk": "^0.2.0", - "dbgate-query-splitter": "^4.9.3", "eslint-plugin-jest": "^27.6.3", "lucide-react": "^0.309.0", "next": "14.0.4", "react": "^18", "react-dom": "^18", "react-resizable-panels": "^1.0.9", + "sql-query-identifier": "^2.6.0", "tailwind-merge": "^2.2.0", "tailwindcss-animate": "^1.0.7" }, @@ -4837,11 +4837,6 @@ "node": ">=12" } }, - "node_modules/dbgate-query-splitter": { - "version": "4.9.3", - "resolved": "https://registry.npmjs.org/dbgate-query-splitter/-/dbgate-query-splitter-4.9.3.tgz", - "integrity": "sha512-QMppAy3S6NGQMawNokmhbpZURvLCETyu/8yTfqWUHGdlK963fdSpmoX1A+9SjCDp62sX0vYntfD7uzd6jVSRcw==" - }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -9358,6 +9353,14 @@ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", "devOptional": true }, + "node_modules/sql-query-identifier": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/sql-query-identifier/-/sql-query-identifier-2.6.0.tgz", + "integrity": "sha512-gxlT1LX+BZi1NR8qWDtiU8umJf53OYuSKZN4xIufl6apD3OAVoBc/06FYBWPc+cHKrdqwvvs6wkiGfhvtqZwRw==", + "engines": { + "node": ">= 10.13" + } + }, "node_modules/stack-utils": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", @@ -13963,11 +13966,6 @@ } } }, - "dbgate-query-splitter": { - "version": "4.9.3", - "resolved": "https://registry.npmjs.org/dbgate-query-splitter/-/dbgate-query-splitter-4.9.3.tgz", - "integrity": "sha512-QMppAy3S6NGQMawNokmhbpZURvLCETyu/8yTfqWUHGdlK963fdSpmoX1A+9SjCDp62sX0vYntfD7uzd6jVSRcw==" - }, "debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -17165,6 +17163,11 @@ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", "devOptional": true }, + "sql-query-identifier": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/sql-query-identifier/-/sql-query-identifier-2.6.0.tgz", + "integrity": "sha512-gxlT1LX+BZi1NR8qWDtiU8umJf53OYuSKZN4xIufl6apD3OAVoBc/06FYBWPc+cHKrdqwvvs6wkiGfhvtqZwRw==" + }, "stack-utils": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", diff --git a/package.json b/package.json index decc2b10..a77371cd 100644 --- a/package.json +++ b/package.json @@ -36,13 +36,13 @@ "class-variance-authority": "^0.7.0", "clsx": "^2.1.0", "cmdk": "^0.2.0", - "dbgate-query-splitter": "^4.9.3", "eslint-plugin-jest": "^27.6.3", "lucide-react": "^0.309.0", "next": "14.0.4", "react": "^18", "react-dom": "^18", "react-resizable-panels": "^1.0.9", + "sql-query-identifier": "^2.6.0", "tailwind-merge": "^2.2.0", "tailwindcss-animate": "^1.0.7" }, diff --git a/src/app/(windows)/QueryWindow.tsx b/src/app/(windows)/QueryWindow.tsx index d0da2c6e..448365a5 100644 --- a/src/app/(windows)/QueryWindow.tsx +++ b/src/app/(windows)/QueryWindow.tsx @@ -1,5 +1,5 @@ -import { useState } from "react"; -import { splitQuery, sqliteSplitterOptions } from "dbgate-query-splitter"; +import { useRef, useState } from "react"; +import { identify } from "sql-query-identifier"; import { LucidePlay } from "lucide-react"; import SqlEditor from "@/components/SqlEditor"; import { @@ -16,6 +16,8 @@ import { MultipleQueryProgress, multipleQuery } from "@/lib/multiple-query"; import QueryProgressLog from "../(components)/QueryProgressLog"; import OptimizeTableState from "../(components)/OptimizeTable/OptimizeTableState"; import { KEY_BINDING } from "@/lib/key-matcher"; +import { ReactCodeMirrorRef } from "@uiw/react-codemirror"; +import { selectStatementFromPosition } from "@/lib/sql-helper"; export default function QueryWindow() { const { schema } = useAutoComplete(); @@ -23,39 +25,62 @@ export default function QueryWindow() { const [code, setCode] = useState(""); const [data, setData] = useState(); const [progress, setProgress] = useState(); + const editorRef = useRef(null); + const [lineNumber, setLineNumber] = useState(0); + const [columnNumber, setColumnNumber] = useState(0); - const onRunClicked = () => { - const statements = splitQuery(code, { - ...sqliteSplitterOptions, - adaptiveGoSplit: true, - }).map((statement) => statement.toString()); + const onRunClicked = (all = false) => { + const statements = identify(code, { + dialect: "sqlite", + strict: false, + }); - // Reset the result and make a new query - setData(undefined); - setProgress(undefined); + let finalStatements: string[] = []; - multipleQuery(databaseDriver, statements, (currentProgrss) => { - setProgress(currentProgrss); - }) - .then(({ last }) => { - if (last) { - setData(OptimizeTableState.createFromResult(last)); - } + const editor = editorRef.current; + + if (all) { + finalStatements = statements.map((s) => s.text); + } else if (editor && editor.view) { + const position = editor.view.state.selection.main.head; + const statement = selectStatementFromPosition(statements, position); + + if (statement) { + finalStatements = [statement.text]; + } + } + + if (finalStatements.length > 0) { + // Reset the result and make a new query + setData(undefined); + setProgress(undefined); + + multipleQuery(databaseDriver, finalStatements, (currentProgrss) => { + setProgress(currentProgrss); }) - .catch(console.error); + .then(({ last }) => { + if (last) { + setData(OptimizeTableState.createFromResult(last)); + } + }) + .catch(console.error); + } }; - console.log(KEY_BINDING.run.toCodeMirrorKey()); - return (
{ + setLineNumber(line); + setColumnNumber(col); + }} onKeyDown={(e) => { if (KEY_BINDING.run.match(e)) { onRunClicked(); @@ -67,13 +92,23 @@ export default function QueryWindow() {
- + + + +
+
Ln {lineNumber}
+
Col {columnNumber + 1}
+
diff --git a/src/components/SqlEditor.tsx b/src/components/SqlEditor.tsx index 8fe153e7..5f17390a 100644 --- a/src/components/SqlEditor.tsx +++ b/src/components/SqlEditor.tsx @@ -1,8 +1,11 @@ import { tags as t } from "@lezer/highlight"; import { createTheme } from "@uiw/codemirror-themes"; -import CodeMirror from "@uiw/react-codemirror"; +import CodeMirror, { + EditorView, + ReactCodeMirrorRef, +} from "@uiw/react-codemirror"; import { sql, SQLite } from "@codemirror/lang-sql"; -import { KeyboardEventHandler, useMemo } from "react"; +import { forwardRef, KeyboardEventHandler, useMemo } from "react"; import { defaultKeymap } from "@codemirror/commands"; import { keymap } from "@codemirror/view"; @@ -45,48 +48,63 @@ interface SqlEditorProps { onChange: (value: string) => void; schema?: Record; onKeyDown?: KeyboardEventHandler; + onCursorChange?: ( + pos: number, + lineNumber: number, + columnNumber: number + ) => void; } -export default function SqlEditor({ - value, - onChange, - schema, - onKeyDown, -}: SqlEditorProps) { - const keyExtensions = useMemo(() => { - return keymap.of([ - { - key: KEY_BINDING.run.toCodeMirrorKey(), - preventDefault: true, - run: () => true, - }, - ...defaultKeymap, - ]); - }, []); +const SqlEditor = forwardRef( + function SqlEditor( + { value, onChange, schema, onKeyDown, onCursorChange }: SqlEditorProps, + ref + ) { + const keyExtensions = useMemo(() => { + return keymap.of([ + { + key: KEY_BINDING.run.toCodeMirrorKey(), + preventDefault: true, + run: () => true, + }, + ...defaultKeymap, + ]); + }, []); - return ( - - ); -} + return ( + { + const pos = state.state.selection.main.head; + const line = state.state.doc.lineAt(pos); + const lineNumber = line.number; + const columnNumber = pos - line.from; + if (onCursorChange) onCursorChange(pos, lineNumber, columnNumber); + }), + ]} + /> + ); + } +); + +export default SqlEditor; diff --git a/src/lib/sql-helper.test.ts b/src/lib/sql-helper.test.ts index d5415274..4aea1e23 100644 --- a/src/lib/sql-helper.test.ts +++ b/src/lib/sql-helper.test.ts @@ -9,8 +9,10 @@ import { generateDeleteStatement, generateInsertStatement, generateUpdateStatement, + selectStatementFromPosition, unescapeIdentity, } from "./sql-helper"; +import { identify } from "sql-query-identifier"; describe("Escape SQL", () => { it("escape sql string", () => { @@ -136,3 +138,29 @@ describe("Mapping sqlite column type to our table type", () => { expect(convertSqliteType(type)).toBe(TableColumnDataType.REAL)); } }); + +function ss(sql: string) { + const pos = sql.indexOf("|"); + const statements = identify(sql.replace("|", "")); + return selectStatementFromPosition(statements, pos); +} + +describe("Select current query", () => { + it("select current query", () => { + expect(ss("select * from |t1; update t1 set name='visal';")?.text).toBe( + "select * from t1;" + ); + + expect(ss("select * from t1|; update t1 set name='visal';")?.text).toBe( + "select * from t1;" + ); + + expect(ss("select * from t1;| update t1 set name='visal';")?.text).toBe( + "select * from t1;" + ); + + expect(ss("select * from t1; update| t1 set name='visal';")?.text).toBe( + "update t1 set name='visal';" + ); + }); +}); diff --git a/src/lib/sql-helper.ts b/src/lib/sql-helper.ts index b5010288..6b5cd254 100644 --- a/src/lib/sql-helper.ts +++ b/src/lib/sql-helper.ts @@ -1,5 +1,6 @@ import { TableColumnDataType } from "@/app/(components)/OptimizeTable"; import { hex } from "./bit-operation"; +import { IdentifyResult } from "sql-query-identifier/lib/defines"; export function escapeIdentity(str: string) { return `"${str.replace(/"/g, `""`)}"`; @@ -123,3 +124,13 @@ export function generateUpdateStatement( tableName )} SET ${setPart} WHERE ${wherePart};`; } + +export function selectStatementFromPosition( + statements: IdentifyResult[], + pos: number +): IdentifyResult | undefined { + for (const statement of statements) { + if (statement.end + 1 >= pos) return statement; + } + return undefined; +}