diff --git a/package-lock.json b/package-lock.json index 52c80dcb..1cc846ef 100644 --- a/package-lock.json +++ b/package-lock.json @@ -81,6 +81,7 @@ "sql-formatter": "^15.3.2", "tailwind-merge": "^2.2.2", "tailwindcss-animate": "^1.0.7", + "use-immer": "^0.11.0", "xlsx": "^0.18.5", "zod": "^3.22.4" }, @@ -21208,6 +21209,15 @@ } } }, + "node_modules/use-immer": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/use-immer/-/use-immer-0.11.0.tgz", + "integrity": "sha512-RNAqi3GqsWJ4bcCd4LMBgdzvPmTABam24DUaFiKfX9s3MSorNRz9RDZYJkllJoMHUxVLMDetwAuCDeyWNrp1yA==", + "peerDependencies": { + "immer": ">=8.0.0", + "react": "^16.8.0 || ^17.0.1 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/use-sidecar": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.2.tgz", diff --git a/package.json b/package.json index c47bc9fa..b9692fb2 100644 --- a/package.json +++ b/package.json @@ -100,6 +100,7 @@ "sql-formatter": "^15.3.2", "tailwind-merge": "^2.2.2", "tailwindcss-animate": "^1.0.7", + "use-immer": "^0.11.0", "xlsx": "^0.18.5", "zod": "^3.22.4" }, diff --git a/src/components/gui/query-explanation-diagram/index.tsx b/src/components/gui/query-explanation-diagram/index.tsx index e1652deb..a916d4e7 100644 --- a/src/components/gui/query-explanation-diagram/index.tsx +++ b/src/components/gui/query-explanation-diagram/index.tsx @@ -34,7 +34,7 @@ function QueryExplanationFlow(props: LayoutFlowProps) { setEdges(build.edges as Edge[]) setLoading(false) } - }, [props, loading]) + }, [props, loading, setEdges, setNodes]) return ( void; }) { const { databaseDriver } = useDatabaseDriver(); const { schema, refresh: refreshSchema } = useSchema(); - const [loading, setLoading] = useState(false); - const [currentCollate, setCurrentCollate] = useState(''); const [isExecuting, setIsExecuting] = useState(false); const [errorMessage, setErrorMessage] = useState(""); const [value, setValue] = useState({ @@ -27,40 +24,6 @@ export function SchemaDatabaseCreateForm({ schemaName, onClose }: { schemaName?: return databaseDriver.createUpdateDatabaseSchema(value).join(";\n"); }, [databaseDriver, value]); - const fetchData = useCallback(async () => { - setLoading(true); - - try { - const { rows } = await databaseDriver.query(` - SELECT \`DEFAULT_COLLATION_NAME\` FROM \`information_schema\`.\`SCHEMATA\` WHERE \`SCHEMA_NAME\`='${schemaName}'; - `) - - if (rows.length > 0) { - setValue({ - ...value, - name: { - new: schemaName, - old: schemaName - }, - collate: String(rows[0].DEFAULT_COLLATION_NAME) - }) - setCurrentCollate(String(rows[0].DEFAULT_COLLATION_NAME)) - } - } catch (error) { - // - } finally { - setLoading(false); - } - }, [databaseDriver, schemaName, value]) - - useEffect(() => { - if (schemaName) { - fetchData().then().catch(console.error); - } - }, []) - - // const toggleSave = useCallback(() => setSaving(!isSaving), [isSaving]) - const onSave = useCallback(() => { { setIsExecuting(true); @@ -75,7 +38,6 @@ export function SchemaDatabaseCreateForm({ schemaName, onClose }: { schemaName?: const schemaNames = Object.keys(schema).filter(s => s !== schemaName).map(s => s); const schemaNameExists = schemaNames.includes(value.name.new || ''); - const isChange = value.name.new !== value.name.old || currentCollate !== value.collate return (
@@ -100,32 +62,20 @@ export function SchemaDatabaseCreateForm({ schemaName, onClose }: { schemaName?: } }) }} - disabled={loading || !!schemaName} + disabled={!!schemaName} className={`w-full ${schemaNameExists ? 'border-red-600' : ''}`} /> { schemaNameExists && The schema name `{value.name.new}` already exists. }
-
-
Collation
- { - setValue({ - ...value, - collate: selectedSchema - }) - }} - /> -
- ) + ); } return ( - - + + - - - - - { - databaseDriver.getFlags().supportCreateUpdateDatabase && New schema/database - } - { - databaseDriver.getFlags().supportCreateUpdateTable && onNewTable()}>New table - } - - - - - ) - } - + + + {contentMenu.map((menu) => { + return ( + + {menu.name} + + ); + })} + + + ); + }, [contentMenu]); return (
- {isCreateSchema && } + {isCreateSchema && ( + { + setIsCreateSchema(false); + }} + /> + )} +

Tables

- + {activatorButton}
diff --git a/src/components/gui/tabs/trigger-tab.tsx b/src/components/gui/tabs/trigger-tab.tsx index 1f7c83ac..3576b83d 100644 --- a/src/components/gui/tabs/trigger-tab.tsx +++ b/src/components/gui/tabs/trigger-tab.tsx @@ -1,88 +1,100 @@ +import TriggerEditor from "../trigger-editor"; +import { useCallback, useEffect, useMemo, useState } from "react"; import { DatabaseTriggerSchema } from "@/drivers/base-driver"; -import { useEffect, useState } from "react"; -import { Input } from "@/components/ui/input"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; import { useDatabaseDriver } from "@/context/driver-provider"; -import SqlEditor from "../sql-editor"; -import TableCombobox from "../table-combobox/TableCombobox"; -import { noop } from "@/lib/utils"; +import OpacityLoading from "../loading-opacity"; +import { produce } from "immer"; +import { TriggerController } from "../trigger-editor/trigger-controller"; + +import { isEqual } from "lodash"; +import { TriggerSaveDialog } from "../trigger-editor/trigger-save-dialog"; + +export interface TriggerTabProps { + name: string; + tableName?: string; + schemaName: string; +} + +const EMPTY_DEFAULT_TRIGGER: DatabaseTriggerSchema = { + name: "", + operation: "INSERT", + when: "BEFORE", + tableName: "", + whenExpression: "", + statement: "", + schemaName: "", +}; export default function TriggerTab({ - schemaName, name, -}: { - schemaName: string; - name: string; -}) { + schemaName, + tableName, +}: TriggerTabProps) { const { databaseDriver } = useDatabaseDriver(); - const [trigger, setTrigger] = useState(); - const [error, setError] = useState(); + const [isSaving, setIsSaving] = useState(false); - useEffect(() => { - databaseDriver - .trigger(schemaName, name) - .then(setTrigger) - .catch((e: Error) => { - setError(e.message); + // If name is specified, it means the trigger is already exist + const [loading, setLoading] = useState(!!name); + + // Loading the inital value + const [initialValue, setInitialValue] = useState( + () => { + return produce(EMPTY_DEFAULT_TRIGGER, (draft) => { + draft.tableName = tableName ?? ""; + draft.schemaName = schemaName ?? ""; }); - }, [databaseDriver, schemaName, name]); + } + ); + const [value, setValue] = useState(initialValue); + + const hasChanged = !isEqual(initialValue, value); + + const previewScript = useMemo(() => { + const drop = databaseDriver.dropTrigger(value.schemaName, name); + const create = databaseDriver.createTrigger(value); + return name ? [drop, create] : [create]; + }, [value, databaseDriver, name]); + + // Loading the trigger + useEffect(() => { + if (name && schemaName) { + databaseDriver + .trigger(schemaName, name) + .then((triggerValue) => { + setValue(triggerValue); + setInitialValue(triggerValue); + }) + .finally(() => setLoading(false)); + } + }, [name, schemaName, databaseDriver]); + + const toggleSaving = useCallback(() => { + setIsSaving(!isSaving); + }, [isSaving]); - if (error) { - return
{error}
; + if (loading) { + return ; } return (
-
-
Trigger Name
- + {isSaving && ( + + )} + { + setValue(initialValue); + }} + disabled={!hasChanged} + previewScript={previewScript.join(";\n")} + /> -
-
- -
-
- -
- -
-
-
-
- -
-
+
); } diff --git a/src/components/gui/trigger-editor/index.tsx b/src/components/gui/trigger-editor/index.tsx new file mode 100644 index 00000000..964e74a4 --- /dev/null +++ b/src/components/gui/trigger-editor/index.tsx @@ -0,0 +1,162 @@ +import { useDatabaseDriver } from "@/context/driver-provider"; +import { + DatabaseTriggerSchema, + TriggerOperation, + TriggerWhen, +} from "@/drivers/base-driver"; +import TableCombobox from "../table-combobox/TableCombobox"; +import { Input } from "@/components/ui/input"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import SqlEditor from "../sql-editor"; +import { produce } from "immer"; +import SchemaNameSelect from "../schema-editor/schema-name-select"; +import { useSchema } from "@/context/schema-provider"; +import { useMemo } from "react"; + +export interface TriggerEditorProps { + onChange: (value: DatabaseTriggerSchema) => void; + value: DatabaseTriggerSchema; +} + +export default function TriggerEditor({ value, onChange }: TriggerEditorProps) { + const { databaseDriver } = useDatabaseDriver(); + const { autoCompleteSchema, schema } = useSchema(); + + const extendedAutoCompleteSchema = useMemo(() => { + const currentSchema = schema[value.schemaName]; + if (!currentSchema) return autoCompleteSchema; + + const currentTable = currentSchema.find( + (t) => t.name.toLowerCase() === value.tableName.toLowerCase() + )?.tableSchema; + if (!currentTable) return autoCompleteSchema; + + // Extend OLD and NEW + const currentTableColumns = currentTable.columns.map((c) => c.name); + + return { + ...autoCompleteSchema, + NEW: currentTableColumns, + OLD: currentTableColumns, + }; + }, [autoCompleteSchema, value.schemaName, value.tableName, schema]); + + return ( + <> +
+ + onChange( + produce(value, (draft) => { + draft.name = e.currentTarget.value; + }) + ) + } + /> +
+
+
+
+ +
+ +
+ +
+ +
+ { + onChange( + produce(value, (draft) => { + draft.schemaName = schemaName; + }) + ); + }} + /> +
+
+ { + onChange( + produce(value, (draft) => { + draft.tableName = newTableName; + }) + ); + }} + /> +
+
+
+ +
+
+
+ Trigger statement: (eg: "SET NEW.columnA = + TRIM(OLD.columnA)") +
+ + + onChange( + produce(value, (draft) => { + draft.statement = newStatement; + }) + ) + } + /> +
+
+ + ); +} diff --git a/src/components/gui/trigger-editor/trigger-controller.tsx b/src/components/gui/trigger-editor/trigger-controller.tsx new file mode 100644 index 00000000..2df4483b --- /dev/null +++ b/src/components/gui/trigger-editor/trigger-controller.tsx @@ -0,0 +1,62 @@ +import { Button, buttonVariants } from "@/components/ui/button"; +import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; +import { Separator } from "@/components/ui/separator"; +import { LucideCode, LucideLoader, LucideSave } from "lucide-react"; +import CodePreview from "../code-preview"; + +interface Props { + onSave: () => void; + onDiscard: () => void; + previewScript: string; + isExecuting?: boolean; + disabled?: boolean; +} + +export function TriggerController(props: Props) { + const { onSave, onDiscard, isExecuting, disabled, previewScript } = props; + return ( +
+ + + +
+ +
+ + + +
+ + SQL Preview +
+
+ +
SQL Preview
+
+ +
+
+
+
+ ) +} \ No newline at end of file diff --git a/src/components/gui/trigger-editor/trigger-save-dialog.tsx b/src/components/gui/trigger-editor/trigger-save-dialog.tsx new file mode 100644 index 00000000..cc3c56dc --- /dev/null +++ b/src/components/gui/trigger-editor/trigger-save-dialog.tsx @@ -0,0 +1,88 @@ +import { + AlertDialog, + AlertDialogCancel, + AlertDialogContent, + AlertDialogFooter, + AlertDialogTitle, +} from "@/components/ui/alert-dialog"; +import { LucideAlertCircle, LucideLoader, LucideSave, LucideTableProperties } from "lucide-react"; +import { useCallback, useState } from "react"; +import CodePreview from "../code-preview"; +import { Button } from "@/components/ui/button"; +import { useDatabaseDriver } from "@/context/driver-provider"; +import TriggerTab from "../tabs/trigger-tab"; +import { useTabsContext } from "../windows-tab"; +import { useSchema } from "@/context/schema-provider"; +import { DatabaseTriggerSchema } from "@/drivers/base-driver"; + +interface Props { + onClose: () => void; + previewScript: string[]; + trigger: DatabaseTriggerSchema; +} + +export function TriggerSaveDialog(props: Props) { + const { replaceCurrentTab } = useTabsContext(); + const { refresh: refreshSchema } = useSchema(); + const { databaseDriver } = useDatabaseDriver(); + const [isExecuting, setIsExecuting] = useState(false); + const [errorMessage, setErrorMessage] = useState(""); + + const onSave = useCallback(() => { + setIsExecuting(true); + databaseDriver + .transaction( + props.previewScript + ) + .then(() => { + refreshSchema(); + replaceCurrentTab({ + component: ( + + ), + key: "trigger-" + props.trigger.name || "", + identifier: "trigger-" + props.trigger.name || "", + title: props.trigger.name || "", + icon: LucideTableProperties, + }); + props.onClose(); + }) + .catch((err) => setErrorMessage((err as Error).message)) + .finally(() => { + setIsExecuting(false); + }); + }, [databaseDriver, props, refreshSchema, replaceCurrentTab]) + + return ( + + + Preview + + {errorMessage && ( +
+ +

{errorMessage}

+
+ )} + +

Are you sure you want to run this change?

+ + + Cancel + + +
+
+ ); +} diff --git a/src/drivers/base-driver.ts b/src/drivers/base-driver.ts index e629bca4..7b5aa6ab 100644 --- a/src/drivers/base-driver.ts +++ b/src/drivers/base-driver.ts @@ -171,6 +171,7 @@ export interface DatabaseTriggerSchema { name: string; operation: TriggerOperation; when: TriggerWhen; + schemaName: string; tableName: string; columnNames?: string[]; whenExpression: string; @@ -245,6 +246,7 @@ export interface DriverFlags { supportUpdateReturning: boolean; supportRowId: boolean; supportCreateUpdateDatabase: boolean; + supportCreateUpdateTrigger: boolean; } export interface DatabaseTableColumnChange { @@ -338,4 +340,7 @@ export abstract class BaseDriver { abstract createUpdateTableSchema(change: DatabaseTableSchemaChange): string[]; abstract createUpdateDatabaseSchema(change: DatabaseSchemaChange): string[]; + + abstract createTrigger(trigger: DatabaseTriggerSchema): string; + abstract dropTrigger(schemaName: string, name: string): string; } diff --git a/src/drivers/mysql/generate-schema.ts b/src/drivers/mysql/generate-schema.ts index 14becbca..14c61627 100644 --- a/src/drivers/mysql/generate-schema.ts +++ b/src/drivers/mysql/generate-schema.ts @@ -4,6 +4,7 @@ import { DatabaseTableColumn, DatabaseTableColumnConstraint, DatabaseTableSchemaChange, + DatabaseTriggerSchema, } from "../base-driver"; import { omit, isEqual } from "lodash"; @@ -101,7 +102,7 @@ function generateCreateColumn( [ "REFERENCES", driver.escapeId(foreignTableName) + - `(${driver.escapeId(foreignColumnName)})`, + `(${driver.escapeId(foreignColumnName)})`, ].join(" ") ); } @@ -128,6 +129,15 @@ function generateConstraintScript( } } +export function generateMysqlTriggerSchema( + driver: BaseDriver, + change: DatabaseTriggerSchema +): string[] { + return [ + `CREATE TRIGGER ${driver.escapeId(change.schemaName || "")}.${driver.escapeId(change.name ?? "")} \n${change.when} ${change.operation} ON ${driver.escapeId(change.tableName)} \nFOR EACH ROW \nBEGIN \n\t${change.statement} \nEND`, + ]; +} + export function generateMysqlDatabaseSchema( driver: BaseDriver, change: DatabaseSchemaChange diff --git a/src/drivers/mysql/mysql-driver.ts b/src/drivers/mysql/mysql-driver.ts index e40050a8..b86af2fd 100644 --- a/src/drivers/mysql/mysql-driver.ts +++ b/src/drivers/mysql/mysql-driver.ts @@ -10,6 +10,8 @@ import { ColumnTypeSelector, DatabaseTableColumnConstraint, DatabaseSchemaChange, + TriggerOperation, + TriggerWhen, } from "../base-driver"; import CommonSQLImplement from "../common-sql-imp"; import { escapeSqlValue } from "../sqlite/sql-helper"; @@ -68,6 +70,16 @@ export interface MySQLConstraintColumnResult { REFERENCED_COLUMN_NAME: string; } +export interface MySQLTriggerResult { + TRIGGER_NAME: string; + EVENT_OBJECT_SCHEMA: string; + EVENT_OBJECT_TABLE: string; + ACTION_TIMING: TriggerWhen; + ACTION_STATEMENT: string; + TRIGGER_SCHEMA: string; + EVENT_MANIPULATION: TriggerOperation; +} + function mapColumn(column: MySqlColumn): DatabaseTableColumn { const result: DatabaseTableColumn = { name: column.COLUMN_NAME, @@ -136,6 +148,7 @@ export default abstract class MySQLLikeDriver extends CommonSQLImplement { supportRowId: false, supportInsertReturning: false, supportUpdateReturning: false, + supportCreateUpdateTrigger: true, }; } @@ -176,6 +189,11 @@ export default abstract class MySQLLikeDriver extends CommonSQLImplement { const constraintColumnsResult = (await this.query(constraintColumnsSql)) .rows as unknown as MySQLConstraintColumnResult[]; + const triggerSql = + "SELECT * from information_schema.triggers WHERE TRIGGER_SCHEMA NOT IN ('mysql', 'information_schema', 'performance_schema', 'sys')"; + const triggerResult = (await this.query(triggerSql)) + .rows as unknown as MySQLTriggerResult[]; + // Hash table of schema const schemaRecord: Record = {}; for (const s of schemaResult) { @@ -293,6 +311,26 @@ export default abstract class MySQLLikeDriver extends CommonSQLImplement { } } + // Add triggers + for (const schema of schemaResult) { + const triggers = triggerResult + .filter((f) => f.TRIGGER_SCHEMA === schema.SCHEMA_NAME) + .map((t) => { + return { + name: t.TRIGGER_NAME, + tableName: t.EVENT_OBJECT_TABLE, + schemaName: t.EVENT_OBJECT_SCHEMA, + timing: t.ACTION_TIMING, + statement: t.ACTION_STATEMENT, + type: "trigger", + }; + }); + + schemaRecord[schema.SCHEMA_NAME] = schemaRecord[ + schema.SCHEMA_NAME + ].concat(triggers as DatabaseSchemaItem[]); + } + return schemaRecord; } @@ -375,8 +413,33 @@ export default abstract class MySQLLikeDriver extends CommonSQLImplement { }; } - trigger(): Promise { - throw new Error("Not implemented"); + async trigger( + schemaName: string, + name: string + ): Promise { + const result = await this.query( + `SELECT * from information_schema.triggers WHERE TRIGGER_SCHEMA=${this.escapeValue(schemaName)} AND TRIGGER_NAME=${this.escapeValue(name)}` + ); + + const triggerRow = result.rows[0] as unknown as + | MySQLTriggerResult + | undefined; + if (!triggerRow) throw new Error("Trigger does not exist"); + + const statement = triggerRow.ACTION_STATEMENT.replace(/begin/i, "") + .replace(/end/i, "") + .trim(); + + return { + name: triggerRow.TRIGGER_NAME, + tableName: triggerRow.EVENT_OBJECT_TABLE, + schemaName: triggerRow.TRIGGER_SCHEMA, + operation: triggerRow.EVENT_MANIPULATION, + statement, + when: triggerRow.ACTION_TIMING, + whenExpression: "", + columnNames: [], + }; } createUpdateTableSchema(change: DatabaseTableSchemaChange): string[] { @@ -387,6 +450,14 @@ export default abstract class MySQLLikeDriver extends CommonSQLImplement { return generateMysqlDatabaseSchema(this, change); } + createTrigger(change: DatabaseTriggerSchema): string { + return `CREATE TRIGGER ${this.escapeId(change.schemaName || "")}.${this.escapeId(change.name ?? "")} \n${change.when} ${change.operation} ON ${this.escapeId(change.tableName)} \nFOR EACH ROW \nBEGIN \n\t${change.statement} \nEND`; + } + + dropTrigger(schemaName: string, name: string): string { + return `DROP TRIGGER IF EXISTS ${this.escapeId(schemaName)}.${this.escapeId(name)}`; + } + inferTypeFromHeader(): TableColumnDataType | undefined { return undefined; } diff --git a/src/drivers/postgres/postgres-driver.ts b/src/drivers/postgres/postgres-driver.ts index d850c308..a892c4c3 100644 --- a/src/drivers/postgres/postgres-driver.ts +++ b/src/drivers/postgres/postgres-driver.ts @@ -80,6 +80,7 @@ export default abstract class PostgresLikeDriver extends CommonSQLImplement { supportCreateUpdateDatabase: false, supportInsertReturning: true, supportUpdateReturning: true, + supportCreateUpdateTrigger: false, }; } @@ -358,6 +359,14 @@ WHERE throw new Error("Not implemented"); } + createTrigger(): string { + throw new Error("Not implemented") + } + + dropTrigger(): string { + throw new Error("Not implemented") + } + inferTypeFromHeader(): TableColumnDataType | undefined { return undefined; } diff --git a/src/drivers/sqlite-base-driver.ts b/src/drivers/sqlite-base-driver.ts index f4a83698..789c3415 100644 --- a/src/drivers/sqlite-base-driver.ts +++ b/src/drivers/sqlite-base-driver.ts @@ -12,7 +12,11 @@ import type { SelectFromTableOptions, TableColumnDataType, } from "./base-driver"; -import { convertSqliteType, escapeSqlValue } from "@/drivers/sqlite/sql-helper"; +import { + convertSqliteType, + escapeIdentity, + escapeSqlValue, +} from "@/drivers/sqlite/sql-helper"; import { parseCreateTableScript } from "@/drivers/sqlite/sql-parse-table"; import { parseCreateTriggerScript } from "@/drivers/sqlite/sql-parse-trigger"; @@ -51,6 +55,7 @@ export abstract class SqliteLikeBaseDriver extends CommonSQLImplement { optionalSchema: true, supportCreateUpdateTable: true, supportCreateUpdateDatabase: false, + supportCreateUpdateTrigger: true, dialect: "sqlite", }; } @@ -178,7 +183,7 @@ export abstract class SqliteLikeBaseDriver extends CommonSQLImplement { const triggerRow = result.rows[0] as { sql: string } | undefined; if (!triggerRow) throw new Error("Trigger does not exist"); - return parseCreateTriggerScript(triggerRow.sql); + return parseCreateTriggerScript(schemaName, triggerRow.sql); } close(): void { @@ -234,6 +239,14 @@ export abstract class SqliteLikeBaseDriver extends CommonSQLImplement { throw new Error("Not implemented"); } + createTrigger(change: DatabaseTriggerSchema): string { + return `CREATE TRIGGER ${escapeIdentity(change.name ?? "")} \n${change.when} ${change.operation} ON ${escapeIdentity(change.tableName)} \nFOR EACH ROW \nBEGIN \n\t${change.statement} \nEND`; + } + + dropTrigger(schemaName: string, name: string): string { + return `DROP TRIGGER IF EXISTS ${this.escapeId(schemaName)}.${this.escapeId(name)}`; + } + override async findFirst( schemaName: string, tableName: string, diff --git a/src/drivers/sqlite/sql-parse-trigger.test.ts b/src/drivers/sqlite/sql-parse-trigger.test.ts index e6e745d3..9f171e3c 100644 --- a/src/drivers/sqlite/sql-parse-trigger.test.ts +++ b/src/drivers/sqlite/sql-parse-trigger.test.ts @@ -31,6 +31,7 @@ describe("parse trigger", () => { const statement = `UPDATE customer SET cust_addr=NEW.cust_addr WHERE cust_id=NEW.cust_id;`; it("when: BEFORE", () => { const deleteOutput = parseCreateTriggerScript( + "main", generateSql({ name, operation: "DELETE", tableName, statement }) ); expect(deleteOutput).toMatchObject({ @@ -42,6 +43,7 @@ describe("parse trigger", () => { }); const insert = parseCreateTriggerScript( + "main", generateSql({ name, operation: "INSERT", tableName, statement }) ); expect(insert).toMatchObject({ @@ -53,6 +55,7 @@ describe("parse trigger", () => { }); const updateOf = parseCreateTriggerScript( + "main", generateSql({ name, operation: "UPDATE OF", @@ -73,6 +76,7 @@ describe("parse trigger", () => { it("when: AFTER", () => { const deleteOutput = parseCreateTriggerScript( + "main", generateSql({ name, when: "AFTER", @@ -90,6 +94,7 @@ describe("parse trigger", () => { }); const insert = parseCreateTriggerScript( + "main", generateSql({ name, when: "AFTER", @@ -107,6 +112,7 @@ describe("parse trigger", () => { }); const updateOf = parseCreateTriggerScript( + "main", generateSql({ name, when: "AFTER", @@ -128,6 +134,7 @@ describe("parse trigger", () => { it("when: INSTEAD OF", () => { const deleteOutput = parseCreateTriggerScript( + "main", generateSql({ name, when: "INSTEAD OF", @@ -145,6 +152,7 @@ describe("parse trigger", () => { }); const insert = parseCreateTriggerScript( + "main", generateSql({ name, when: "INSTEAD OF", @@ -162,6 +170,7 @@ describe("parse trigger", () => { }); const updateOf = parseCreateTriggerScript( + "main", generateSql({ name, when: "INSTEAD OF", diff --git a/src/drivers/sqlite/sql-parse-trigger.ts b/src/drivers/sqlite/sql-parse-trigger.ts index 22a54642..8041c193 100644 --- a/src/drivers/sqlite/sql-parse-trigger.ts +++ b/src/drivers/sqlite/sql-parse-trigger.ts @@ -6,7 +6,7 @@ import { TriggerOperation, } from "@/drivers/base-driver"; -export function parseCreateTriggerScript(sql: string): DatabaseTriggerSchema { +export function parseCreateTriggerScript(schemaName: string, sql: string): DatabaseTriggerSchema { const tree = sqliteDialect.language.parser.parse(sql); const ptr = tree.cursor(); ptr.firstChild(); @@ -96,6 +96,7 @@ export function parseCreateTriggerScript(sql: string): DatabaseTriggerSchema { operation, when, tableName, + schemaName, columnNames, whenExpression, statement, diff --git a/src/messages/open-tab.tsx b/src/messages/open-tab.tsx index 65d82874..1d87c0c2 100644 --- a/src/messages/open-tab.tsx +++ b/src/messages/open-tab.tsx @@ -39,7 +39,7 @@ interface OpenUserTab { } interface ToolsTab { - type: "mass-drop-table" | "import-sqlite" | "import-csv" | 'erd'; + type: "mass-drop-table" | "import-sqlite" | "import-csv" | "erd"; } interface OpenTriggerTab { @@ -81,7 +81,7 @@ function generateKeyFromTab(tab: OpenTabsProps) { : "schema-" + tab.schemaName + "-" + tab.tableName; if (tab.type === "user") return "user"; - if (tab.type === 'erd') return 'erd'; + if (tab.type === "erd") return "erd"; if (tab.type === "mass-drop-table") return "mass-drop-table"; if (tab.type === "import-sqlite") return "import-sqlite"; if (tab.type === "import-csv") return "import-csv"; @@ -98,7 +98,7 @@ function generateIconFromTab(tab: OpenTabsProps) { if (tab.type === "table") return Table; if (tab.type === "schema") return LucideTableProperties; if (tab.type === "user") return LucideUser; - if (tab.type === 'erd') return TreeStructure; + if (tab.type === "erd") return TreeStructure; if (tab.type === "mass-drop-table") return StackMinus; return LucideCog; @@ -115,9 +115,9 @@ function generateTitle(tab: OpenTabsProps) { if (tab.type === "user") return "User & Permission"; if (tab.type === "import-csv") return "Import from CSV"; if (tab.type === "import-sqlite") return "Import from SQLite"; - if (tab.type === 'erd') return 'Relational Diagram'; + if (tab.type === "erd") return "Relational Diagram"; if (tab.type === "mass-drop-table") return "Mass Drop Tables"; - if (tab.type === "trigger") return tab.name ?? ""; + if (tab.type === "trigger") return tab.name ?? "New Trigger"; return "Unnamed"; } @@ -147,7 +147,13 @@ function generateComponent(tab: OpenTabsProps, title: string) { if (tab.type === "erd") return ; if (tab.type === "mass-drop-table") return ; if (tab.type === "trigger") - return ; + return ( + + ); return
Unknown Tab
; }