diff --git a/package-lock.json b/package-lock.json index 767a399..b4f1c78 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@libsqlstudio/studio", - "version": "0.6.4", + "version": "0.7.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@libsqlstudio/studio", - "version": "0.6.4", + "version": "0.7.0", "dependencies": { "@aws-sdk/client-s3": "^3.540.0", "@blocknote/core": "^0.12.1", @@ -75,7 +75,6 @@ "react-resizable-panels": "^1.0.9", "sonner": "^1.4.41", "sql-formatter": "^15.3.2", - "sql-query-identifier": "^2.6.0", "tailwind-merge": "^2.2.2", "tailwindcss-animate": "^1.0.7", "zod": "^3.22.4" @@ -21931,14 +21930,6 @@ "sql-formatter": "bin/sql-formatter-cli.cjs" } }, - "node_modules/sql-query-identifier": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/sql-query-identifier/-/sql-query-identifier-2.7.0.tgz", - "integrity": "sha512-MTDRfn0kUv+9ELnt9wt/FATi03Wq1j3YvhE5Up8ToE5Afk5zzBgVHPuIu1bVhqNqfF0aeIPl3vEzZ60H1yv0ag==", - "engines": { - "node": ">= 10.13" - } - }, "node_modules/sql.js": { "version": "1.10.3", "resolved": "https://registry.npmjs.org/sql.js/-/sql.js-1.10.3.tgz", diff --git a/package.json b/package.json index fa5b8e6..bffbbac 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@libsqlstudio/studio", - "version": "0.6.4", + "version": "0.7.0", "private": false, "scripts": { "dev": "next dev -p 3008", @@ -94,7 +94,6 @@ "react-resizable-panels": "^1.0.9", "sonner": "^1.4.41", "sql-formatter": "^15.3.2", - "sql-query-identifier": "^2.6.0", "tailwind-merge": "^2.2.2", "tailwindcss-animate": "^1.0.7", "zod": "^3.22.4" diff --git a/src/app/(public)/databases/mysql/page.tsx b/src/app/(public)/databases/mysql/page.tsx new file mode 100644 index 0000000..ca900db --- /dev/null +++ b/src/app/(public)/databases/mysql/page.tsx @@ -0,0 +1,141 @@ +import { MySQLIcon } from "@/components/icons/outerbase-icon"; +import WebsiteLayout from "@/components/website-layout"; +import { Metadata } from "next"; + +const siteDescription = + "LibSQL Studio is a fully-featured, lightweight GUI client for managing MySQL databases"; + +export const metadata: Metadata = { + title: "MySQL - LibSQL Studio", + keywords: ["mysql", "studio", "browser", "editor", "gui", "online", "client"], + description: siteDescription, + openGraph: { + siteName: "LibSQL Studio", + description: siteDescription, + }, +}; + +function HeroSection() { + return ( +
+
+ +
+
+

+ + MySQL Support +

+

+ LibSQL Studio is a lightweight, fully-featured GUI client for MySQL + databases. It enables you to manage and view your database, or + expose your database interface externally and much more. +

+
+
+
+ ); +} + +export default function DatabaseMySqlPage() { + return ( + + + +
+

+ Connecting +

+ +

+ You can connect to your MySQL database using our command line + interface. +

+ +
+          
+ > npx{" "} + @outerbase/studio + + mysql://root:123@localhost:3306/chinook + +
+
+
{" "}
+
{" Serving! "}
+
{" - Local: http://localhost:4000 "}
+
{" - Network: http://xxx.xxx.xxx.xxx:4000 "}
+
{" "}
+
+
+ +

+ You can also configure the port and secure it with authentication. +

+ +
+          
+
+ > npx{" "} + @outerbase/studio \ +
+
--port=5000 \
+
--user=admin --pass=123 \
+
+ + mysql://root:123@localhost:3306/chinook + +
+
+
+ +

+ Configuration File +

+ +

+ Tired of typing long connection strings repeatedly? Simply save the + configuration to a file and use it whenever needed. Create{" "} + outerbase.json +

+ +
+          {`{
+  "driver": "mysql",
+  "connection": {
+    "database": "chinook",
+    "host": "localhost",
+    "port": 3306,
+    "user": "root",
+    "password": "123456"
+  }
+}
+`}
+        
+ +

+ Next, run our command line tool to start the service. By default, it + will search for the{" "} + outerbase.json{" "} + configuration file. Alternatively, you can specify a custom + configuration using the{" "} + --config flag. +

+ +
+          
+ > npx{" "} + @outerbase/studio +
+
+
+
+ ); +} diff --git a/src/app/(public)/page.tsx b/src/app/(public)/page.tsx index 786fa2f..89d06e9 100644 --- a/src/app/(public)/page.tsx +++ b/src/app/(public)/page.tsx @@ -125,12 +125,9 @@ function SupportDriver() { - + MySQL - - Coming Soon 15th Sep 2024 - diff --git a/src/components/gui/sql-editor/index.tsx b/src/components/gui/sql-editor/index.tsx index febd12e..bcd2340 100644 --- a/src/components/gui/sql-editor/index.tsx +++ b/src/components/gui/sql-editor/index.tsx @@ -1,13 +1,15 @@ import CodeMirror, { EditorView, + Extension, ReactCodeMirrorRef, } from "@uiw/react-codemirror"; +import { LanguageSupport } from "@codemirror/language"; import { acceptCompletion, completionStatus, startCompletion, } from "@codemirror/autocomplete"; -import { sql, SQLNamespace } from "@codemirror/lang-sql"; +import { sql, SQLNamespace, MySQL as MySQLDialect } from "@codemirror/lang-sql"; import { forwardRef, KeyboardEventHandler, useMemo } from "react"; import { defaultKeymap, insertTab } from "@codemirror/commands"; @@ -19,9 +21,12 @@ import { sqliteDialect } from "@/drivers/sqlite/sqlite-dialect"; import { functionTooltip } from "./function-tooltips"; import sqliteFunctionList from "@/drivers/sqlite/function-tooltip.json"; import { toast } from "sonner"; +import SqlStatementHighlightPlugin from "./statement-highlight"; +import { SupportedDialect } from "@/drivers/base-driver"; interface SqlEditorProps { value: string; + dialect: SupportedDialect; readOnly?: boolean; onChange?: (value: string) => void; schema?: SQLNamespace; @@ -38,6 +43,7 @@ interface SqlEditorProps { const SqlEditor = forwardRef( function SqlEditor( { + dialect, value, onChange, schema, @@ -119,6 +125,51 @@ const SqlEditor = forwardRef( ]); }, [fontSize, onFontSizeChanged]); + const extensions = useMemo(() => { + let sqlDialect: LanguageSupport | undefined = undefined; + let tooltipExtension: Extension | undefined = undefined; + + if (dialect === "sqlite") { + sqlDialect = sql({ + dialect: sqliteDialect, + schema, + }); + tooltipExtension = functionTooltip(sqliteFunctionList); + } else { + sqlDialect = sql({ + dialect: MySQLDialect, + schema, + }); + } + + return [ + EditorView.baseTheme({ + "& .cm-line": { + borderLeft: "3px solid transparent", + paddingLeft: "10px", + }, + }), + keyExtensions, + sqlDialect, + tooltipExtension, + tableNameHighlightPlugin, + SqlStatementHighlightPlugin, + EditorView.updateListener.of((state) => { + 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); + }), + ].filter(Boolean) as Extension[]; + }, [ + dialect, + onCursorChange, + keyExtensions, + schema, + tableNameHighlightPlugin, + ]); + return ( ( fontSize: 20, height: "100%", }} - extensions={[ - keyExtensions, - sql({ - dialect: sqliteDialect, - schema, - }), - functionTooltip(sqliteFunctionList), - tableNameHighlightPlugin, - EditorView.updateListener.of((state) => { - 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); - }), - ]} + extensions={extensions} /> ); } diff --git a/src/components/gui/sql-editor/statement-highlight.test.ts b/src/components/gui/sql-editor/statement-highlight.test.ts new file mode 100644 index 0000000..324202a --- /dev/null +++ b/src/components/gui/sql-editor/statement-highlight.test.ts @@ -0,0 +1,155 @@ +import { MySQL, SQLite } from "@codemirror/lang-sql"; +import { EditorState } from "@codemirror/state"; +import { splitSqlQuery } from "./statement-highlight"; + +function sqlite(code: string) { + const state = EditorState.create({ doc: code, extensions: [SQLite] }); + return splitSqlQuery(state).map((p) => p.text); +} + +function mysql(code: string) { + const state = EditorState.create({ doc: code, extensions: [MySQL] }); + return splitSqlQuery(state).map((p) => p.text); +} + +describe("split sql statements", () => { + test("should parse a query with different statements in a single line", () => { + expect( + sqlite( + `INSERT INTO Persons (PersonID, Name) VALUES (1, 'Jack');SELECT * FROM Persons` + ) + ).toEqual([ + `INSERT INTO Persons (PersonID, Name) VALUES (1, 'Jack');`, + `SELECT * FROM Persons`, + ]); + }); + + test("should identify a query with different statements in multiple lines", () => { + expect( + sqlite(` + INSERT INTO Persons (PersonID, Name) VALUES (1, 'Jack'); + SELECT * FROM Persons'; + `) + ).toEqual([ + `INSERT INTO Persons (PersonID, Name) VALUES (1, 'Jack');`, + `SELECT * FROM Persons';\n `, + ]); + }); + + test("sholud be able to split statement with BEGIN and END", () => { + expect( + sqlite(`CREATE TABLE customer( + cust_id INTEGER PRIMARY KEY, + cust_name TEXT, + cust_addr TEXT +); + +-- some comment here that should be ignore + + +CREATE VIEW customer_address AS + SELECT cust_id, cust_addr FROM customer; +CREATE TRIGGER cust_addr_chng +INSTEAD OF UPDATE OF cust_addr ON customer_address +BEGIN + UPDATE customer SET cust_addr=NEW.cust_addr + WHERE cust_id=NEW.cust_id; +END ;`) + ).toEqual([ + `CREATE TABLE customer(\n cust_id INTEGER PRIMARY KEY,\n cust_name TEXT,\n cust_addr TEXT\n);`, + `CREATE VIEW customer_address AS\n SELECT cust_id, cust_addr FROM customer;`, + `CREATE TRIGGER cust_addr_chng\nINSTEAD OF UPDATE OF cust_addr ON customer_address\nBEGIN\n UPDATE customer SET cust_addr=NEW.cust_addr\n WHERE cust_id=NEW.cust_id;\nEND ;`, + ]); + }); + + test("should be able to split statement with BEGIN and END and CONDITION inside", () => { + expect( + mysql(`CREATE TRIGGER upd_check BEFORE UPDATE ON account +FOR EACH ROW +BEGIN + IF NEW.amount < 0 THEN + SET NEW.amount = 0; + ELSEIF NEW.amount > 100 THEN + SET NEW.amount = 100; + END IF; +END; SELECT * FROM hello`) + ).toEqual([ + `CREATE TRIGGER upd_check BEFORE UPDATE ON account\nFOR EACH ROW\nBEGIN\n IF NEW.amount < 0 THEN\n SET NEW.amount = 0;\n ELSEIF NEW.amount > 100 THEN\n SET NEW.amount = 100;\n END IF;\nEND;`, + "SELECT * FROM hello", + ]); + }); + + test("should be able to split statement with BEGIN with no end", () => { + expect( + mysql(`SELECT * FROM outerbase; CREATE TRIGGER upd_check BEFORE UPDATE ON account +FOR EACH ROW +BEGIN + IF NEW.amount < 0 THEN + SET NEW.amount = 0; + ELSEIF NEW.amount > 100 THEN + SET NEW.amount = 100;`) + ).toEqual([ + "SELECT * FROM outerbase;", + `CREATE TRIGGER upd_check BEFORE UPDATE ON account +FOR EACH ROW +BEGIN + IF NEW.amount < 0 THEN + SET NEW.amount = 0; + ELSEIF NEW.amount > 100 THEN + SET NEW.amount = 100;`, + ]); + }); + + test("should be able to split TRIGGER without begin", () => { + expect( + mysql(`create trigger hire_log after insert on employees +for each row insert into hiring values (new.id, current_time()); + +insert into employees (first_name, last_name) values ("Tim", "Sehn");`) + ).toEqual([ + `create trigger hire_log after insert on employees \nfor each row insert into hiring values (new.id, current_time());`, + `insert into employees (first_name, last_name) values ("Tim", "Sehn");`, + ]); + }); + + test("should be able to split nested BEGIN", () => { + expect( + mysql( + `CREATE PROCEDURE procCreateCarTable +IS +BEGIN + BEGIN + EXECUTE IMMEDIATE 'DROP TABLE CARS'; + EXCEPTION WHEN OTHERS THEN NULL; + EXECUTE IMMEDIATE 'CREATE TABLE CARS (ID VARCHAR2(1), NAME VARCHAR2(10), TITLE + VARCHAR2(10))'; + END; + BEGIN + EXECUTE IMMEDIATE 'DROP TABLE TRUCKS'; + EXCEPTION WHEN OTHERS THEN NULL; + EXECUTE IMMEDIATE 'CREATE TABLE TRUCKS (ID VARCHAR2(1), NAME VARCHAR2(10), TITLE + VARCHAR2(10))'; + END; +END; SELECT * FROM outeerbase;` + ) + ).toEqual([ + `CREATE PROCEDURE procCreateCarTable +IS +BEGIN + BEGIN + EXECUTE IMMEDIATE 'DROP TABLE CARS'; + EXCEPTION WHEN OTHERS THEN NULL; + EXECUTE IMMEDIATE 'CREATE TABLE CARS (ID VARCHAR2(1), NAME VARCHAR2(10), TITLE + VARCHAR2(10))'; + END; + BEGIN + EXECUTE IMMEDIATE 'DROP TABLE TRUCKS'; + EXCEPTION WHEN OTHERS THEN NULL; + EXECUTE IMMEDIATE 'CREATE TABLE TRUCKS (ID VARCHAR2(1), NAME VARCHAR2(10), TITLE + VARCHAR2(10))'; + END; +END;`, + "SELECT * FROM outeerbase;", + ]); + }); +}); diff --git a/src/components/gui/sql-editor/statement-highlight.ts b/src/components/gui/sql-editor/statement-highlight.ts new file mode 100644 index 0000000..49cfb85 --- /dev/null +++ b/src/components/gui/sql-editor/statement-highlight.ts @@ -0,0 +1,172 @@ +import { + Decoration, + EditorState, + EditorView, + StateField, + Range, +} from "@uiw/react-codemirror"; +import { syntaxTree } from "@codemirror/language"; +import { SyntaxNode } from "@lezer/common"; + +const statementLineHighlight = Decoration.line({ + class: "cm-highlight-statement", +}); + +export interface StatementSegment { + from: number; + to: number; + text: string; +} + +function toNodeString(state: EditorState, node: SyntaxNode) { + return state.doc.sliceString(node.from, node.to); +} + +function isRequireEndStatement(state: EditorState, node: SyntaxNode): number { + const ptr = node.firstChild; + if (!ptr) return 0; + + // Majority of the query will fall in SELECT, INSERT, UPDATE, DELETE + const firstKeyword = toNodeString(state, ptr).toLowerCase(); + if (firstKeyword === "select") return 0; + if (firstKeyword === "insert") return 0; + if (firstKeyword === "update") return 0; + if (firstKeyword === "delete") return 0; + + const keywords = node.getChildren("Keyword"); + if (keywords.length === 0) return 0; + + return keywords.filter( + (k) => toNodeString(state, k).toLowerCase() === "begin" + ).length; +} + +function isEndStatement(state: EditorState, node: SyntaxNode) { + let ptr = node.firstChild; + if (!ptr) return false; + if (toNodeString(state, ptr).toLowerCase() !== "end") return false; + + ptr = ptr.nextSibling; + if (!ptr) return false; + if (toNodeString(state, ptr) !== ";") return false; + + return true; +} + +export function splitSqlQuery( + state: EditorState, + generateText: boolean = true +): StatementSegment[] { + const topNode = syntaxTree(state).topNode; + + // Get all the statements + let needEndStatementCounter = 0; + const statements = topNode.getChildren("Statement"); + + if (statements.length === 0) return []; + + const statementGroups: SyntaxNode[][] = []; + let accumulateNodes: SyntaxNode[] = []; + let i = 0; + + for (; i < statements.length; i++) { + const statement = statements[i]; + needEndStatementCounter += isRequireEndStatement(state, statement); + + if (needEndStatementCounter) { + accumulateNodes.push(statement); + } else { + statementGroups.push([statement]); + } + + if (needEndStatementCounter && isEndStatement(state, statement)) { + needEndStatementCounter--; + if (needEndStatementCounter === 0) { + statementGroups.push(accumulateNodes); + accumulateNodes = []; + } + } + } + + if (accumulateNodes.length > 0) { + statementGroups.push(accumulateNodes); + } + + return statementGroups.map((r) => ({ + from: r[0].from, + to: r[r.length - 1].to, + text: generateText + ? state.doc.sliceString(r[0].from, r[r.length - 1].to) + : "", + })); +} + +export function resolveToNearestStatement( + state: EditorState +): { from: number; to: number } | null { + // Breakdown and grouping the statement + const cursor = state.selection.main.from; + const statements = splitSqlQuery(state, false); + + if (statements.length === 0) return null; + + // Check if our current cursor is within any statement + let i = 0; + for (; i < statements.length; i++) { + const statement = statements[i]; + if (cursor < statement.from) break; + if (cursor > statement.to) continue; + if (cursor >= statement.from && cursor <= statement.to) return statement; + } + + if (i === 0) return statements[0]; + if (i === statements.length) return statements[i - 1]; + + const cursorLine = state.doc.lineAt(cursor).number; + const topLine = state.doc.lineAt(statements[i - 1].to).number; + const bottomLine = state.doc.lineAt(statements[i].from).number; + + if (cursorLine - topLine >= bottomLine - cursorLine) { + return statements[i]; + } else { + return statements[i - 1]; + } +} +function getDecorationFromState(state: EditorState) { + const statement = resolveToNearestStatement(state); + + if (!statement) return Decoration.none; + + // Get the line of the node + const fromLineNumber = state.doc.lineAt(statement.from).number; + const toLineNumber = state.doc.lineAt(statement.to).number; + + const d: Range[] = []; + for (let i = fromLineNumber; i <= toLineNumber; i++) { + d.push(statementLineHighlight.range(state.doc.line(i).from)); + } + + return Decoration.set(d); +} + +const SqlStatementStateField = StateField.define({ + create(state) { + return getDecorationFromState(state); + }, + + update(_, tr) { + return getDecorationFromState(tr.state); + }, + + provide: (f) => EditorView.decorations.from(f), +}); + +const SqlStatementTheme = EditorView.baseTheme({ + ".cm-highlight-statement": { + borderLeft: "3px solid #ff9ff3 !important", + }, +}); + +const SqlStatementHighlightPlugin = [SqlStatementStateField, SqlStatementTheme]; + +export default SqlStatementHighlightPlugin; diff --git a/src/components/gui/tabs/query-tab.tsx b/src/components/gui/tabs/query-tab.tsx index 329fc25..ef1ae93 100644 --- a/src/components/gui/tabs/query-tab.tsx +++ b/src/components/gui/tabs/query-tab.tsx @@ -1,6 +1,5 @@ import { format } from "sql-formatter"; import { useCallback, useMemo, useRef, useState } from "react"; -import { identify } from "sql-query-identifier"; import { LucideFastForward, LucideGrid, @@ -18,7 +17,6 @@ import { Separator } from "@/components/ui/separator"; import { Button } from "@/components/ui/button"; import { KEY_BINDING } from "@/lib/key-matcher"; import { ReactCodeMirrorRef } from "@uiw/react-codemirror"; -import { selectStatementFromPosition } from "@/drivers/sqlite/sql-helper"; import QueryProgressLog from "../query-progress-log"; import { useDatabaseDriver } from "@/context/driver-provider"; import { @@ -41,6 +39,10 @@ import { TooltipContent, TooltipTrigger, } from "@/components/ui/tooltip"; +import { + resolveToNearestStatement, + splitSqlQuery, +} from "../sql-editor/statement-highlight"; interface QueryWindowProps { initialCode?: string; @@ -89,23 +91,22 @@ export default function QueryWindow({ }; const onRunClicked = (all = false) => { - const statements = identify(code, { - dialect: "sqlite", - strict: false, - }); - let finalStatements: string[] = []; - const editor = editorRef.current; + const editorState = editorRef.current?.view?.state; + + if (!editorState) return; if (all) { - finalStatements = statements.map((s) => s.text); - } else if (editor?.view) { - const position = editor.view.state.selection.main.head; - const statement = selectStatementFromPosition(statements, position); + finalStatements = splitSqlQuery(editorState).map((q) => q.text); + } else { + const segment = resolveToNearestStatement(editorState); + if (!segment) return; + + const statement = editorState.doc.sliceString(segment.from, segment.to); if (statement) { - finalStatements = [statement.text]; + finalStatements = [statement]; } } @@ -227,6 +228,7 @@ export default function QueryWindow({
- +
diff --git a/src/drivers/base-driver.ts b/src/drivers/base-driver.ts index 0f8a392..ace202e 100644 --- a/src/drivers/base-driver.ts +++ b/src/drivers/base-driver.ts @@ -31,6 +31,7 @@ export function describeTableColumnType(type: TableColumnDataType) { } } +export type SupportedDialect = "sqlite" | "mysql"; export type SqlOrder = "ASC" | "DESC"; export type DatabaseRow = Record; @@ -202,6 +203,7 @@ export interface DriverFlags { supportBigInt: boolean; supportCreateUpdateTable: boolean; mismatchDetection: boolean; + dialect: SupportedDialect; } export interface DatabaseTableColumnChange { diff --git a/src/drivers/mysql/mysql-driver.ts b/src/drivers/mysql/mysql-driver.ts index 011c7c2..fadad57 100644 --- a/src/drivers/mysql/mysql-driver.ts +++ b/src/drivers/mysql/mysql-driver.ts @@ -51,6 +51,7 @@ export default abstract class MySQLLikeDriver extends CommonSQLImplement { supportBigInt: false, mismatchDetection: false, supportCreateUpdateTable: false, + dialect: "mysql", }; } diff --git a/src/drivers/sqlite-base-driver.ts b/src/drivers/sqlite-base-driver.ts index e826cef..d83ce17 100644 --- a/src/drivers/sqlite-base-driver.ts +++ b/src/drivers/sqlite-base-driver.ts @@ -30,6 +30,7 @@ export abstract class SqliteLikeBaseDriver extends CommonSQLImplement { optionalSchema: true, mismatchDetection: false, supportCreateUpdateTable: true, + dialect: "sqlite", }; } diff --git a/src/drivers/sqlite/sql-helper.test.ts b/src/drivers/sqlite/sql-helper.test.ts index 1e82104..84dbc6e 100644 --- a/src/drivers/sqlite/sql-helper.test.ts +++ b/src/drivers/sqlite/sql-helper.test.ts @@ -5,10 +5,8 @@ import { escapeSqlBinary, escapeSqlString, escapeSqlValue, - selectStatementFromPosition, unescapeIdentity, } from "./sql-helper"; -import { identify } from "sql-query-identifier"; describe("Escape SQL", () => { it("escape sql string", () => { @@ -85,29 +83,3 @@ describe("Mapping sqlite column type to our table type", () => { }); } }); - -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/drivers/sqlite/sql-helper.ts b/src/drivers/sqlite/sql-helper.ts index 9d16884..19a7a18 100644 --- a/src/drivers/sqlite/sql-helper.ts +++ b/src/drivers/sqlite/sql-helper.ts @@ -1,6 +1,5 @@ import { DatabaseValue, TableColumnDataType } from "@/drivers/base-driver"; import { hex } from "@/lib/bit-operation"; -import type { IdentifyResult } from "sql-query-identifier/lib/defines"; export function escapeIdentity(str: string) { return `"${str.replace(/"/g, `""`)}"`; @@ -63,16 +62,6 @@ export function convertSqliteType( return TableColumnDataType.TEXT; } -export function selectStatementFromPosition( - statements: IdentifyResult[], - pos: number -): IdentifyResult | undefined { - for (const statement of statements) { - if (statement.end + 1 >= pos) return statement; - } - return undefined; -} - export function escapeCsvValue(value: unknown): string { if (value === null || value === undefined) { return ""; diff --git a/src/drivers/sqlite/sql-parse-table.test.ts b/src/drivers/sqlite/sql-parse-table.test.ts index 77d3700..149654d 100644 --- a/src/drivers/sqlite/sql-parse-table.test.ts +++ b/src/drivers/sqlite/sql-parse-table.test.ts @@ -88,6 +88,7 @@ it("parse column constraint", () => { expect(pcc(`references "users" on delete cascade ("id")`)).toEqual({ foreignKey: { + foreignSchemaName: "main", foreignTableName: "users", foreignColumns: ["id"], }, @@ -130,6 +131,7 @@ it("parse create table", () => { type: "integer", constraint: { foreignKey: { + foreignSchemaName: "main", foreignTableName: "product", foreignColumns: ["id"], },