From e0f671ab2f978b6e09541be617f4bc2d44da4815 Mon Sep 17 00:00:00 2001 From: Visal In Date: Fri, 27 Dec 2024 21:16:26 +0700 Subject: [PATCH] follow this style and please help correct it back --- package-lock.json | 10 ++ package.json | 1 + src/components/gui/tabs/trigger-tab.tsx | 96 +++++++++++-- src/components/gui/trigger-editor/index.tsx | 134 ++++++++++-------- .../trigger-editor/trigger-save-dialog.tsx | 41 ++++-- src/drivers/base-driver.ts | 20 +-- src/drivers/mysql/mysql-driver.ts | 19 +-- src/drivers/sqlite-base-driver.ts | 11 +- src/drivers/sqlite/sql-parse-trigger.ts | 3 +- 9 files changed, 218 insertions(+), 117 deletions(-) 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/tabs/trigger-tab.tsx b/src/components/gui/tabs/trigger-tab.tsx index d767a32a..ccd7457e 100644 --- a/src/components/gui/tabs/trigger-tab.tsx +++ b/src/components/gui/tabs/trigger-tab.tsx @@ -1,29 +1,103 @@ import { useSchema } from "@/context/schema-provider"; -import TriggerEditor, { TriggerEditorProps } from "../trigger-editor"; +import TriggerEditor from "../trigger-editor"; import { useTabsContext } from "../windows-tab"; import { LucideTableProperties } from "lucide-react"; +import { useEffect, useState } from "react"; +import { DatabaseTriggerSchema } from "@/drivers/base-driver"; +import { useDatabaseDriver } from "@/context/driver-provider"; +import OpacityLoading from "../loading-opacity"; +import { produce } from "immer"; +import { TriggerController } from "../trigger-editor/trigger-controller"; -export default function TriggerTab(props: TriggerEditorProps) { +import { isEqual } from "lodash"; + +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({ + name, + schemaName, + tableName, +}: TriggerTabProps) { + const { databaseDriver } = useDatabaseDriver(); const { refresh: refreshSchema } = useSchema(); const { replaceCurrentTab } = useTabsContext(); - const onSave = (trigger: TriggerEditorProps) => { + // 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 ?? ""; + }); + } + ); + const [value, setValue] = useState(initialValue); + + const hasChanged = !isEqual(initialValue, value); + + // Loading the trigger + useEffect(() => { + if (name && schemaName) { + databaseDriver + .trigger(schemaName, name) + .then((triggerValue) => { + setValue(triggerValue); + setInitialValue(triggerValue); + }) + .finally(() => setLoading(false)); + } + }, [name, schemaName, databaseDriver]); + + const onSave = () => { refreshSchema(); - console.log(trigger) replaceCurrentTab({ component: ( ), - key: 'trigger-' + trigger.name || '', - identifier: 'trigger-' + trigger.name || '', - title: trigger.name || '', + key: "trigger-" + value.name || "", + identifier: "trigger-" + value.name || "", + title: value.name || "", icon: LucideTableProperties, }); + }; + + if (loading) { + return ; } - return + return ( +
+ { + // @adam do something here + }} + onDiscard={() => { + setValue(initialValue); + }} + /> + + +
+ ); } diff --git a/src/components/gui/trigger-editor/index.tsx b/src/components/gui/trigger-editor/index.tsx index fb5c7c3d..4aa8060a 100644 --- a/src/components/gui/trigger-editor/index.tsx +++ b/src/components/gui/trigger-editor/index.tsx @@ -1,66 +1,57 @@ import { useDatabaseDriver } from "@/context/driver-provider"; -import { DatabaseTriggerSchemaChange } from "@/drivers/base-driver"; -import { LucideAlertCircle } from "lucide-react"; -import { useState } from "react"; +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 { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; import SqlEditor from "../sql-editor"; -import { TriggerController } from "./trigger-controller"; -import { TriggerSaveDialog } from "./trigger-save-dialog"; -import { useTriggerState } from "./trigger-state"; +import { produce } from "immer"; -export interface TriggerEditorProps { - name: string; - tableName?: string; - schemaName: string; +interface TriggerEditorProps { + onChange: (value: DatabaseTriggerSchema) => void; + value: DatabaseTriggerSchema; } -interface Props extends TriggerEditorProps { - onSave: (trigger: TriggerEditorProps) => void; -} - -export default function TriggerEditor(props: Props) { - const { name, tableName, schemaName } = props; +export default function TriggerEditor({ value, onChange }: TriggerEditorProps) { const { databaseDriver } = useDatabaseDriver(); - const { trigger, setTriggerField, error, onDiscard, previewScript } = useTriggerState(schemaName, name, tableName ?? ''); - const [isExecuting, setIsExecuting] = useState(false); return ( -
- { - isExecuting && ( - { - props.onSave(value); - setIsExecuting(false); - }} - onClose={() => setIsExecuting(false)} - previewScript={previewScript} - schemaName={schemaName} - trigger={trigger as DatabaseTriggerSchemaChange} - tableName={tableName} - /> - ) - } - setIsExecuting(true)} - onDiscard={onDiscard} - previewScript={previewScript.join('\n')} - disabled={!trigger.name?.new || !schemaName || !trigger.isChange} - isExecuting={isExecuting} - /> + <>
Trigger Name
- setTriggerField('name', { ...trigger.name, new: e.currentTarget.value })} /> + + onChange( + produce(value, (draft) => { + draft.name = e.currentTarget.value; + }) + ) + } + />
On Table
setTriggerField('tableName', value)} + schemaName={value.schemaName} + value={value.tableName} + onChange={(newTableName) => { + onChange( + produce(value, (draft) => { + draft.tableName = newTableName; + }) + ); + }} />
@@ -68,7 +59,16 @@ export default function TriggerEditor(props: Props) {
Event
- + onChange( + produce(value, (draft) => { + draft.when = e as TriggerWhen; + }) + ) + } + > @@ -80,7 +80,16 @@ export default function TriggerEditor(props: Props) {
- { + onChange( + produce(value, (draft) => { + draft.operation = newOperation; + }) + ); + }} + > @@ -93,22 +102,27 @@ export default function TriggerEditor(props: Props) {
- {error && ( -
- -

{error}

-
- )} +
-
Trigger statement: (eg: "SET NEW.columnA = TRIM(OLD.columnA)")
+
+ Trigger statement: (eg: "SET NEW.columnA = + TRIM(OLD.columnA)") +
+ setTriggerField('statement', value)} + onChange={(newStatement) => + onChange( + produce(value, (draft) => { + draft.statement = newStatement; + }) + ) + } />
- - ) -} \ 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 index 04f005b3..4910d048 100644 --- a/src/components/gui/trigger-editor/trigger-save-dialog.tsx +++ b/src/components/gui/trigger-editor/trigger-save-dialog.tsx @@ -1,10 +1,15 @@ -import { AlertDialog, AlertDialogCancel, AlertDialogContent, AlertDialogFooter, AlertDialogTitle } from "@/components/ui/alert-dialog"; +import { + AlertDialog, + AlertDialogCancel, + AlertDialogContent, + AlertDialogFooter, + AlertDialogTitle, +} from "@/components/ui/alert-dialog"; import { LucideAlertCircle, LucideLoader, LucideSave } from "lucide-react"; import { useState } from "react"; import CodePreview from "../code-preview"; import { Button } from "@/components/ui/button"; import { useDatabaseDriver } from "@/context/driver-provider"; -import { TriggerEditorProps } from "."; import { DatabaseTriggerSchemaChange } from "@/drivers/base-driver"; interface Props { @@ -23,21 +28,29 @@ export function TriggerSaveDialog(props: Props) { const onSave = () => { setIsExecuting(true); - const isCreated = !props.trigger.name.old + const isCreated = !props.trigger.name.old; + databaseDriver; databaseDriver - databaseDriver.transaction( - isCreated ? props.previewScript : [`DROP TRIGGER IF EXISTS \`${props.schemaName}\`.\`${props.trigger.name.old}\``, ...props.previewScript], - ).then(() => { - props.onSave({ - tableName: props.tableName, - schemaName: props.schemaName, - name: props.trigger.name.new ?? "" + .transaction( + isCreated + ? props.previewScript + : [ + `DROP TRIGGER IF EXISTS \`${props.schemaName}\`.\`${props.trigger.name.old}\``, + ...props.previewScript, + ] + ) + .then(() => { + props.onSave({ + tableName: props.tableName, + schemaName: props.schemaName, + name: props.trigger.name.new ?? "", + }); }) - }).catch((err) => setErrorMessage((err as Error).message)) + .catch((err) => setErrorMessage((err as Error).message)) .finally(() => { setIsExecuting(false); }); - } + }; return ( @@ -66,5 +79,5 @@ export function TriggerSaveDialog(props: Props) { - ) -} \ No newline at end of file + ); +} diff --git a/src/drivers/base-driver.ts b/src/drivers/base-driver.ts index 1c460b8f..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; @@ -280,19 +281,6 @@ export interface DatabaseSchemaChange { collate?: string; } -export interface DatabaseTriggerSchemaChange { - name: { - old?: string; - new?: string; - }; - operation: TriggerOperation; - when: TriggerWhen; - tableName: string; - whenExpression: string; - statement: string; - schemaName?: string; -} - export abstract class BaseDriver { // Flags abstract getFlags(): DriverFlags; @@ -352,7 +340,7 @@ export abstract class BaseDriver { abstract createUpdateTableSchema(change: DatabaseTableSchemaChange): string[]; abstract createUpdateDatabaseSchema(change: DatabaseSchemaChange): string[]; - abstract createUpdateTriggerSchema( - change: DatabaseTriggerSchemaChange - ): string[]; + + abstract createTrigger(trigger: DatabaseTriggerSchema): string; + abstract dropTrigger(schemaName: string, name: string): string; } diff --git a/src/drivers/mysql/mysql-driver.ts b/src/drivers/mysql/mysql-driver.ts index 3b8117a1..45ce6c67 100644 --- a/src/drivers/mysql/mysql-driver.ts +++ b/src/drivers/mysql/mysql-driver.ts @@ -389,13 +389,13 @@ export default abstract class MySQLLikeDriver extends CommonSQLImplement { foreignKey: constraint.CONSTRAINT_TYPE === "FOREIGN KEY" ? { - columns: columnList.map((c) => c.COLUMN_NAME), - foreignColumns: columnList.map( - (c) => c.REFERENCED_COLUMN_NAME - ), - foreignSchemaName: columnList[0].REFERENCED_TABLE_SCHEMA, - foreignTableName: columnList[0].REFERENCED_TABLE_NAME, - } + columns: columnList.map((c) => c.COLUMN_NAME), + foreignColumns: columnList.map( + (c) => c.REFERENCED_COLUMN_NAME + ), + foreignSchemaName: columnList[0].REFERENCED_TABLE_SCHEMA, + foreignTableName: columnList[0].REFERENCED_TABLE_NAME, + } : undefined, }; } @@ -435,6 +435,7 @@ export default abstract class MySQLLikeDriver extends CommonSQLImplement { return { name: triggerRow.TRIGGER_NAME, tableName: triggerRow.EVENT_OBJECT_TABLE, + schemaName: triggerRow.TRIGGER_SCHEMA, operation: triggerRow.EVENT_MANIPULATION, statement, when: triggerRow.ACTION_TIMING, @@ -451,8 +452,8 @@ export default abstract class MySQLLikeDriver extends CommonSQLImplement { return generateMysqlDatabaseSchema(this, change); } - createUpdateTriggerSchema(change: DatabaseTriggerSchemaChange): string[] { - return generateMysqlTriggerSchema(this, change); + createTrigger(change: DatabaseTriggerSchema): string { + return `CREATE TRIGGER ${this.escapeId(change.schemaName || "")}.${this.escapeId(change.name.new ?? "")} \n${change.when} ${change.operation} ON ${this.escapeId(change.tableName)} \nFOR EACH ROW \nBEGIN \n\t${change.statement} \nEND`, } inferTypeFromHeader(): TableColumnDataType | undefined { diff --git a/src/drivers/sqlite-base-driver.ts b/src/drivers/sqlite-base-driver.ts index bb15023d..50a6a443 100644 --- a/src/drivers/sqlite-base-driver.ts +++ b/src/drivers/sqlite-base-driver.ts @@ -184,7 +184,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 { @@ -310,13 +310,12 @@ export abstract class SqliteLikeBaseDriver extends CommonSQLImplement { const orderPart = options.orderBy && options.orderBy.length > 0 ? options.orderBy - .map((r) => `${this.escapeId(r.columnName)} ${r.by}`) - .join(", ") + .map((r) => `${this.escapeId(r.columnName)} ${r.by}`) + .join(", ") : ""; - const sql = `SELECT ${injectRowIdColumn ? "rowid, " : ""}* FROM ${this.escapeId(schemaName)}.${this.escapeId(tableName)}${ - whereRaw ? ` WHERE ${whereRaw} ` : "" - } ${orderPart ? ` ORDER BY ${orderPart}` : ""} LIMIT ${escapeSqlValue(options.limit)} OFFSET ${escapeSqlValue(options.offset)};`; + const sql = `SELECT ${injectRowIdColumn ? "rowid, " : ""}* FROM ${this.escapeId(schemaName)}.${this.escapeId(tableName)}${whereRaw ? ` WHERE ${whereRaw} ` : "" + } ${orderPart ? ` ORDER BY ${orderPart}` : ""} LIMIT ${escapeSqlValue(options.limit)} OFFSET ${escapeSqlValue(options.offset)};`; return { data: await this.query(sql), 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,