Skip to content

Commit 211f8ba

Browse files
authored
feat: add mysql driver (#147)
* trying to rework driver * feat: add mysql driver init code * mapping pk and autoincrement * add attach support * fix use sqlite driver * rework auto completion * add trigger with schema * flat the schema if there is only one schema * add schema list on table editor * auto select schema name based on where create table is selected * add disable edit create table flag * everything should use schema name instead of "main" schema * remove all console.log * add proper error for iframe driver * add better logging and mysql ignore system database * fixing test case * fixing the type
1 parent f507746 commit 211f8ba

39 files changed

Lines changed: 1091 additions & 501 deletions

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"version": "0.6.4",
44
"private": false,
55
"scripts": {
6-
"dev": "next dev",
6+
"dev": "next dev -p 3008",
77
"dev-https": "next dev --experimental-https",
88
"build": "next build",
99
"build-with-migrate": "npm run db:migrate && next build",
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
"use client";
2+
import { Studio } from "@/components/gui/studio";
3+
import { IframeMySQLDriver } from "@/drivers/iframe-driver";
4+
import { useSearchParams } from "next/navigation";
5+
import { useEffect, useMemo } from "react";
6+
7+
export default function EmbedPageClient() {
8+
const searchParams = useSearchParams();
9+
const driver = useMemo(() => new IframeMySQLDriver(), []);
10+
11+
useEffect(() => {
12+
return driver.listen();
13+
}, [driver]);
14+
15+
return (
16+
<Studio
17+
driver={driver}
18+
name={searchParams.get("name") || "Unnamed Connection"}
19+
color={searchParams.get("color") || "gray"}
20+
/>
21+
);
22+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import ThemeLayout from "../../theme_layout";
2+
import EmbedPageClient from "./page-client";
3+
4+
export default async function EmbedPage(props: {
5+
searchParams: {
6+
theme?: string;
7+
disableThemeToggle?: string;
8+
[key: string]: any;
9+
};
10+
}) {
11+
let overrideTheme: "dark" | "light" | undefined = undefined;
12+
const disableToggle = props.searchParams.disableThemeToggle === "1";
13+
14+
if (props.searchParams.theme) {
15+
overrideTheme = props.searchParams.theme === "dark" ? "dark" : "light";
16+
}
17+
18+
const overrideThemeVariables: Record<string, string> = {};
19+
20+
for (const key in props.searchParams) {
21+
if (!key.startsWith("themeVariables[")) {
22+
continue;
23+
}
24+
25+
overrideThemeVariables[key.slice(15, -1)] = props.searchParams[key];
26+
}
27+
28+
return (
29+
<ThemeLayout
30+
overrideTheme={overrideTheme}
31+
disableToggle={disableToggle}
32+
overrideThemeVariables={overrideThemeVariables}
33+
>
34+
<EmbedPageClient />
35+
</ThemeLayout>
36+
);
37+
}

src/app/(theme)/embed/sqlite/page-client.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
"use client";
22
import { Studio } from "@/components/gui/studio";
3-
import IframeDriver from "@/drivers/iframe-driver";
3+
import { IframeSQLiteDriver } from "@/drivers/iframe-driver";
44
import { useSearchParams } from "next/navigation";
55
import { useEffect, useMemo } from "react";
66

77
export default function EmbedPageClient() {
88
const searchParams = useSearchParams();
9-
const driver = useMemo(() => new IframeDriver(), []);
9+
const driver = useMemo(() => new IframeSQLiteDriver(), []);
1010

1111
useEffect(() => {
1212
return driver.listen();

src/components/gui/database-gui.tsx

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import SettingSidebar from "./sidebar/setting-sidebar";
2323

2424
import { useDatabaseDriver } from "@/context/driver-provider";
2525
import SavedDocTab from "./sidebar/saved-doc-tab";
26+
import { useSchema } from "@/context/schema-provider";
2627

2728
export default function DatabaseGui() {
2829
const DEFAULT_WIDTH = 300;
@@ -33,8 +34,10 @@ export default function DatabaseGui() {
3334
setDefaultWidthPercentage((DEFAULT_WIDTH / window.innerWidth) * 100);
3435
}, []);
3536

36-
const { collaborationDriver, docDriver } = useDatabaseDriver();
37+
const { databaseDriver, collaborationDriver, docDriver } =
38+
useDatabaseDriver();
3739
const [selectedTabIndex, setSelectedTabIndex] = useState(0);
40+
const { currentSchemaName } = useSchema();
3841
const [tabs, setTabs] = useState<WindowTabItemProps[]>(() => [
3942
{
4043
title: "Query",
@@ -119,14 +122,16 @@ export default function DatabaseGui() {
119122
openTab({ type: "query" });
120123
},
121124
},
122-
{
123-
text: "New Table",
124-
onClick: () => {
125-
openTab({ type: "schema" });
126-
},
127-
},
128-
];
129-
}, []);
125+
databaseDriver.getFlags().supportCreateUpdateTable
126+
? {
127+
text: "New Table",
128+
onClick: () => {
129+
openTab({ type: "schema", schemaName: currentSchemaName });
130+
},
131+
}
132+
: undefined,
133+
].filter(Boolean) as { text: string; onClick: () => void }[];
134+
}, [currentSchemaName, databaseDriver]);
130135

131136
return (
132137
<div className="h-screen w-screen flex flex-col">

src/components/gui/schema-editor/column-fk-popup.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,11 @@ import { Separator } from "../../ui/separator";
1111
export default function ColumnForeignKeyPopup({
1212
constraint,
1313
disabled,
14+
schemaName,
1415
onChange,
1516
}: Readonly<{
1617
constraint: DatabaseForeignKeyClause;
18+
schemaName: string;
1719
disabled: boolean;
1820
onChange: ColumnChangeEvent;
1921
}>) {
@@ -33,6 +35,7 @@ export default function ColumnForeignKeyPopup({
3335
<div className="flex flex-col gap-2 mt-2">
3436
<Label className="text-xs font-normal">Foreign Table Name</Label>
3537
<TableCombobox
38+
schemaName={schemaName}
3639
value={constraint.foreignTableName}
3740
disabled={disabled}
3841
onChange={(newTable) => {
@@ -64,6 +67,7 @@ export default function ColumnForeignKeyPopup({
6467
},
6568
});
6669
}}
70+
schemaName={schemaName}
6771
tableName={constraint.foreignTableName}
6872
/>
6973
</div>

src/components/gui/schema-editor/column-provider.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1+
import { DatabaseTableColumnChange } from "@/drivers/base-driver";
12
import { PropsWithChildren, createContext, useContext } from "react";
2-
import { DatabaseTableColumnChange } from ".";
33

44
const ColumnContext = createContext<{ columns: DatabaseTableColumnChange[] }>({
55
columns: [],

src/components/gui/schema-editor/index.tsx

Lines changed: 38 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -4,39 +4,15 @@ import { Dispatch, SetStateAction, useCallback, useMemo } from "react";
44
import { Button, buttonVariants } from "../../ui/button";
55
import SchemaEditorColumnList from "./schema-editor-column-list";
66
import { Input } from "../../ui/input";
7-
import generateSqlSchemaChange, {
8-
checkSchemaChange,
9-
} from "@/components/lib/sql-generate.schema";
10-
import {
11-
DatabaseTableColumn,
12-
DatabaseTableColumnConstraint,
13-
} from "@/drivers/base-driver";
7+
import { checkSchemaChange } from "@/components/lib/sql-generate.schema";
148
import SchemaEditorConstraintList from "./schema-editor-constraint-list";
159
import { ColumnsProvider } from "./column-provider";
1610
import { Popover, PopoverContent, PopoverTrigger } from "../../ui/popover";
1711
import CodePreview from "../code-preview";
1812
import { toast } from "sonner";
19-
20-
export interface DatabaseTableColumnChange {
21-
old: DatabaseTableColumn | null;
22-
new: DatabaseTableColumn | null;
23-
}
24-
25-
export interface DatabaseTableConstraintChange {
26-
id: string;
27-
old: DatabaseTableColumnConstraint | null;
28-
new: DatabaseTableColumnConstraint | null;
29-
}
30-
31-
export interface DatabaseTableSchemaChange {
32-
name: {
33-
old?: string;
34-
new?: string;
35-
};
36-
columns: DatabaseTableColumnChange[];
37-
constraints: DatabaseTableConstraintChange[];
38-
createScript?: string;
39-
}
13+
import { DatabaseTableSchemaChange } from "@/drivers/base-driver";
14+
import { useDatabaseDriver } from "@/context/driver-provider";
15+
import SchemaNameSelect from "./schema-name-select";
4016

4117
interface Props {
4218
onSave: () => void;
@@ -51,6 +27,7 @@ export default function SchemaEditor({
5127
onSave,
5228
onDiscard,
5329
}: Readonly<Props>) {
30+
const { databaseDriver } = useDatabaseDriver();
5431
const isCreateScript = value.name.old === "";
5532

5633
const onAddColumn = useCallback(() => {
@@ -84,8 +61,8 @@ export default function SchemaEditor({
8461
const hasChange = checkSchemaChange(value);
8562

8663
const previewScript = useMemo(() => {
87-
return generateSqlSchemaChange(value).join("\n");
88-
}, [value]);
64+
return databaseDriver.createUpdateTableSchema(value).join("\n");
65+
}, [value, databaseDriver]);
8966

9067
return (
9168
<div className="w-full h-full flex flex-col">
@@ -94,7 +71,7 @@ export default function SchemaEditor({
9471
<Button
9572
variant="ghost"
9673
onClick={onSave}
97-
disabled={!hasChange || !value.name?.new}
74+
disabled={!hasChange || !value.name?.new || !value.schemaName}
9875
size={"sm"}
9976
>
10077
<LucideSave className="w-4 h-4 mr-2" />
@@ -173,22 +150,34 @@ export default function SchemaEditor({
173150
)}
174151
</div>
175152

176-
<div className="flex items-center mx-3 mt-1 mb-2 ml-5 gap-2">
177-
<div className="text-xs flex items-center justify-center">Name</div>
178-
<Input
179-
placeholder="Table Name"
180-
value={value.name.new ?? value.name.old ?? ""}
181-
onChange={(e) => {
182-
onChange({
183-
...value,
184-
name: {
185-
...value.name,
186-
new: e.currentTarget.value,
187-
},
188-
});
189-
}}
190-
className="w-[200px]"
191-
/>
153+
<div className="flex items-center mx-3 mt-3 mb-4 ml-5 gap-2">
154+
<div>
155+
<div className="text-xs font-medium mb-1">Table Name</div>
156+
<Input
157+
placeholder="Table Name"
158+
value={value.name.new ?? value.name.old ?? ""}
159+
onChange={(e) => {
160+
onChange({
161+
...value,
162+
name: {
163+
...value.name,
164+
new: e.currentTarget.value,
165+
},
166+
});
167+
}}
168+
className="w-[200px]"
169+
/>
170+
</div>
171+
<div>
172+
<div className="text-xs font-medium mb-1">Schema</div>
173+
<SchemaNameSelect
174+
readonly={!isCreateScript}
175+
value={value.schemaName}
176+
onChange={(selectedSchema) => {
177+
onChange({ ...value, schemaName: selectedSchema });
178+
}}
179+
/>
180+
</div>
192181
</div>
193182
<Separator />
194183
</div>
@@ -197,9 +186,11 @@ export default function SchemaEditor({
197186
columns={value.columns}
198187
onChange={onChange}
199188
onAddColumn={onAddColumn}
189+
schemaName={value.schemaName}
200190
/>
201191
<ColumnsProvider value={value.columns}>
202192
<SchemaEditorConstraintList
193+
schemaName={value.schemaName}
203194
constraints={value.constraints}
204195
onChange={onChange}
205196
disabled={!isCreateScript}

src/components/gui/schema-editor/schema-editor-column-list.tsx

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { DatabaseTableColumnChange, DatabaseTableSchemaChange } from ".";
21
import { Dispatch, SetStateAction, useCallback } from "react";
32
import {
43
DropdownMenu,
@@ -22,7 +21,9 @@ import ColumnDefaultValueInput from "./column-default-value-input";
2221
import { checkSchemaColumnChange } from "@/components/lib/sql-generate.schema";
2322
import {
2423
DatabaseTableColumn,
24+
DatabaseTableColumnChange,
2525
DatabaseTableColumnConstraint,
26+
DatabaseTableSchemaChange,
2627
TableColumnDataType,
2728
} from "@/drivers/base-driver";
2829
import { cn } from "@/lib/utils";
@@ -83,10 +84,12 @@ function changeColumnOnIndex(
8384
function ColumnItem({
8485
value,
8586
idx,
87+
schemaName,
8688
onChange,
8789
}: {
8890
value: DatabaseTableColumnChange;
8991
idx: number;
92+
schemaName?: string;
9093
onChange: Dispatch<SetStateAction<DatabaseTableSchemaChange>>;
9194
}) {
9295
const disabled = !!value.old;
@@ -197,11 +200,12 @@ function ColumnItem({
197200
/>
198201
)}
199202

200-
{column.constraint?.foreignKey && (
203+
{column.constraint?.foreignKey && schemaName && (
201204
<ColumnForeignKeyPopup
202205
constraint={column.constraint.foreignKey}
203206
disabled={disabled}
204207
onChange={change}
208+
schemaName={schemaName}
205209
/>
206210
)}
207211

@@ -293,10 +297,12 @@ function ColumnItem({
293297
export default function SchemaEditorColumnList({
294298
columns,
295299
onChange,
300+
schemaName,
296301
onAddColumn,
297302
}: Readonly<{
298303
columns: DatabaseTableColumnChange[];
299304
onChange: Dispatch<SetStateAction<DatabaseTableSchemaChange>>;
305+
schemaName?: string;
300306
onAddColumn: () => void;
301307
}>) {
302308
const headerStyle = "text-xs p-2 text-left bg-secondary border";
@@ -317,7 +323,13 @@ export default function SchemaEditorColumnList({
317323
</thead>
318324
<tbody>
319325
{columns.map((col, idx) => (
320-
<ColumnItem idx={idx} value={col} key={idx} onChange={onChange} />
326+
<ColumnItem
327+
idx={idx}
328+
value={col}
329+
key={idx}
330+
onChange={onChange}
331+
schemaName={schemaName}
332+
/>
321333
))}
322334
</tbody>
323335
<tfoot>

0 commit comments

Comments
 (0)