mirror of
https://github.com/stackrender/stackrender.git
synced 2026-09-10 19:25:44 +00:00
Render sql from Diagram initialized
This commit is contained in:
+3
-2
@@ -7,7 +7,7 @@
|
||||
"dev": "vite",
|
||||
"build": "tsc && vite build",
|
||||
"lint": "eslint -c .eslintrc.json ./src/**/**/*.{ts,tsx} --fix",
|
||||
"preview": "vite preview"
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@dnd-kit/core": "^6.3.1",
|
||||
@@ -29,6 +29,7 @@
|
||||
"i18next-browser-languagedetector": "^8.0.5",
|
||||
"lucide-react": "^0.501.0",
|
||||
"next-themes": "^0.4.6",
|
||||
"node-sql-parser": "^5.3.9",
|
||||
"object-hash": "^3.0.0",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
@@ -65,4 +66,4 @@
|
||||
"vite-plugin-top-level-await": "^1.5.0",
|
||||
"vite-tsconfig-paths": "^4.3.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import { Save } from "lucide-react";
|
||||
const RenameDatabase: React.FC = ({ }) => {
|
||||
const { database } = useDatabase();
|
||||
const { editDatabase } = useDatabaseOperations();
|
||||
const [editMode, setEditMode] = useState<boolean>(true);
|
||||
const [editMode, setEditMode] = useState<boolean>(false);
|
||||
const [dbName, setDbName] = useState<string>(database.name);
|
||||
const { t } = useTranslation();
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
@@ -72,7 +72,6 @@ const RenameDatabase: React.FC = ({ }) => {
|
||||
placeholder={database.name}
|
||||
type="text"
|
||||
size="sm"
|
||||
autoFocus
|
||||
radius="sm"
|
||||
variant="faded"
|
||||
onChange={(event: any) => setDbName(event.target.value)}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
|
||||
|
||||
import { useDatabaseOperations } from "@/providers/database-provider/database-provider";
|
||||
import { DatabaseToAst } from "@/utils/render/parsers/database_to_ast";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import { Parser } from "node-sql-parser";
|
||||
import { DatabaseType } from "@/lib/schemas/database-schema";
|
||||
|
||||
const parser = new Parser();
|
||||
|
||||
|
||||
export const useRenderSql = (database: DatabaseType) => {
|
||||
const [sql, setSql] = useState<string>("");
|
||||
const { data_types } = useDatabaseOperations();
|
||||
|
||||
useEffect(() => {
|
||||
const dbAst: any = DatabaseToAst(database, data_types);
|
||||
setSql(
|
||||
parser.sqlify(dbAst)
|
||||
);
|
||||
|
||||
}, [database]) ;
|
||||
|
||||
|
||||
return sql ;
|
||||
|
||||
}
|
||||
@@ -1,16 +1,20 @@
|
||||
import { InferSelectModel, relations } from 'drizzle-orm';
|
||||
import { sqliteTable, text } from 'drizzle-orm/sqlite-core';
|
||||
import { sqliteTable, text } from 'drizzle-orm/sqlite-core';
|
||||
import { fields } from './field-schema';
|
||||
|
||||
export const data_types = sqliteTable('data_types', {
|
||||
id : text("id") ,
|
||||
id: text("id"),
|
||||
name: text('name'),
|
||||
|
||||
dialect: text("dialect", {
|
||||
enum: ["postgres", "mysql", "sqlite", "mariadb"],
|
||||
}).notNull().default("postgres"),
|
||||
});
|
||||
|
||||
|
||||
|
||||
export const dataTypeRelations = relations(fields, ({ many }) => ({
|
||||
fields : many(fields) ,
|
||||
fields: many(fields),
|
||||
}));
|
||||
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ export const databases = sqliteTable('databases', {
|
||||
.unique(),
|
||||
name: text('name').notNull(),
|
||||
dialect: text("dialect", {
|
||||
enum: ["postgres", "mysql", "sqlite"],
|
||||
enum: ["postgres", "mysql", "sqlite" , "mariadb"],
|
||||
}).notNull().default("postgres"),
|
||||
|
||||
numOfTables: integer("numOfTables")
|
||||
|
||||
@@ -37,8 +37,8 @@ import useHighlightedEdges from "@/hooks/use-highlighted-edges";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import useOverlappingTables from "@/hooks/use-overlapping-tables";
|
||||
import { useTheme } from "next-themes";
|
||||
|
||||
|
||||
import { Parser } from "node-sql-parser";
|
||||
const parser = new Parser();
|
||||
const DatabasePage: React.FC = () => {
|
||||
|
||||
const { t } = useTranslation();
|
||||
@@ -55,7 +55,7 @@ const DatabasePage: React.FC = () => {
|
||||
const { setIsConnectionInProgress } = useDiagramOps();
|
||||
|
||||
// Destructure tables and relationships from database
|
||||
const { tables, relationships } = database;
|
||||
const { tables, relationships } = database;
|
||||
|
||||
// Hook to allow zooming and centering the diagram
|
||||
const { fitView } = useReactFlow();
|
||||
@@ -63,7 +63,7 @@ const DatabasePage: React.FC = () => {
|
||||
// Define custom node and edge types
|
||||
const nodeTypes = useMemo(() => ({ table: Table }), []);
|
||||
const edgeTypes = useMemo(() => ({ 'relationship-edge': Relationship }), []);
|
||||
|
||||
|
||||
// Called when a connection is made between fields
|
||||
const onConnect = useCallback((connection: Connection) => {
|
||||
const sourceFieldId: string | undefined = (connection.sourceHandle as string).split("_").pop();
|
||||
@@ -113,11 +113,11 @@ const DatabasePage: React.FC = () => {
|
||||
} as TableInsertType)));
|
||||
|
||||
// Delete tables if removed
|
||||
if (nodeRemoveChanges.length > 0 ) {
|
||||
if (nodeRemoveChanges.length > 0) {
|
||||
deleteMultiTables(nodeRemoveChanges.map((change: NodeRemoveChange) => change.id));
|
||||
}
|
||||
return onNodesChange(changes);
|
||||
}, [onNodesChange ]);
|
||||
}, [onNodesChange]);
|
||||
|
||||
// Called when edges (relationships) change
|
||||
const handleEdgeChanges: OnEdgesChange<any> = useCallback((changes: EdgeChange<any>[]) => {
|
||||
@@ -160,7 +160,6 @@ const DatabasePage: React.FC = () => {
|
||||
useHighlightedEdges(nodes, relationships, edges);
|
||||
const { isOverlapping, puls } = useOverlappingTables(tables);
|
||||
|
||||
|
||||
return (
|
||||
|
||||
<div className="w-full h-screen flex relative overflow-hidden">
|
||||
@@ -205,7 +204,7 @@ const DatabasePage: React.FC = () => {
|
||||
/>
|
||||
</Controls >
|
||||
|
||||
<Background className="bg-default/10 dark:bg-background-100" />
|
||||
<Background className=" dark:bg-background-100" />
|
||||
</ReactFlow>
|
||||
<div
|
||||
className="absolute left-[24px] bottom-[24px] "
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import { useRenderSql } from "@/hooks/user-render-sql";
|
||||
import { useDatabase } from "@/providers/database-provider/database-provider";
|
||||
import React from "react";
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
const SqlPreview : React.FC = ({}) => {
|
||||
|
||||
const {database} = useDatabase() ;
|
||||
const sql = useRenderSql(database) ;
|
||||
|
||||
|
||||
return(
|
||||
<p>
|
||||
{sql
|
||||
}
|
||||
</p>
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
export default React.memo(SqlPreview) ;
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/tooltip/tooltip"
|
||||
import { Accordion, AccordionItem, Button, Input } from "@heroui/react"
|
||||
import { Code, List, Table } from "lucide-react"
|
||||
import { Code, List, Table } from "lucide-react"
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Ref, useCallback, useEffect, useRef, useState } from "react";
|
||||
import TableAccordionHeader from "./table-accordion-item/table-accordion-header";
|
||||
@@ -9,8 +9,10 @@ import { useDatabase, useDatabaseOperations } from "@/providers/database-provide
|
||||
import { TableInsertType, TableType } from "@/lib/schemas/table-schema";
|
||||
import { v4 } from "uuid";
|
||||
import { useDiagram } from "@/providers/diagram-provider/diagram-provider";
|
||||
import { useReactFlow } from "@xyflow/react";
|
||||
import { randomColor } from "@/lib/colors";
|
||||
import { useReactFlow } from "@xyflow/react";
|
||||
import SqlPreview from "../sql-preview";
|
||||
|
||||
|
||||
|
||||
|
||||
interface Props { }
|
||||
@@ -19,14 +21,15 @@ const PADDING_Y = 80;
|
||||
|
||||
const TablesController: React.FC<Props> = ({ }) => {
|
||||
|
||||
const { database } = useDatabase();
|
||||
const { createTable } = useDatabaseOperations();
|
||||
const { database, getDefaultPrimaryKeyType } = useDatabase();
|
||||
const { createTable, data_types } = useDatabaseOperations();
|
||||
const { getViewport } = useReactFlow();
|
||||
const { tables: allTables } = database;
|
||||
const [tables, setTables] = useState<TableType[]>(allTables);
|
||||
|
||||
const { t } = useTranslation();
|
||||
const [selectedTable, setSelectedTable] = useState(new Set([]));
|
||||
const [showSqlPreview, setShowSqlPreview] = useState<boolean>(false);
|
||||
const { focusedTableId } = useDiagram();
|
||||
const nameRef: Ref<HTMLInputElement> = useRef<HTMLInputElement>(null);
|
||||
|
||||
@@ -46,17 +49,19 @@ const TablesController: React.FC<Props> = ({ }) => {
|
||||
name: `table_${tables.length + 1}`,
|
||||
posX,
|
||||
posY,
|
||||
|
||||
|
||||
fields: [{
|
||||
id: v4(),
|
||||
name: "id",
|
||||
isPrimary: true,
|
||||
unique: true
|
||||
unique: true,
|
||||
typeId: getDefaultPrimaryKeyType()?.id
|
||||
|
||||
}]
|
||||
} as TableInsertType);
|
||||
|
||||
setSelectedTable(new Set([newTableId]) as any);
|
||||
}, [tables, getViewport]);
|
||||
}, [tables, getViewport, getDefaultPrimaryKeyType]);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
@@ -71,7 +76,17 @@ const TablesController: React.FC<Props> = ({ }) => {
|
||||
const keyword = nameRef.current?.value;
|
||||
if (keyword !== undefined)
|
||||
setTables(() => allTables.filter((table: TableType) => table.name.toLowerCase().trim().includes(keyword?.toLowerCase().trim())))
|
||||
}, [nameRef, allTables])
|
||||
}, [nameRef, allTables]);
|
||||
|
||||
|
||||
|
||||
const toggleSqlPreview = useCallback(() => {
|
||||
setShowSqlPreview(preview => !preview);
|
||||
}, [])
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<div className="w-full h-full flex flex-col gap-2">
|
||||
@@ -84,12 +99,9 @@ const TablesController: React.FC<Props> = ({ }) => {
|
||||
variant="light"
|
||||
className="size-8 p-0 text-icon hover:text-font/90"
|
||||
isIconOnly
|
||||
onPress={() =>
|
||||
//setShowDBML((value) => !value)
|
||||
console.log("hello world ")
|
||||
}
|
||||
onPressEnd={toggleSqlPreview}
|
||||
>
|
||||
{false ? (
|
||||
{showSqlPreview ? (
|
||||
<List className="size-4" />
|
||||
) : (
|
||||
<Code className="size-4" />
|
||||
@@ -108,15 +120,15 @@ const TablesController: React.FC<Props> = ({ }) => {
|
||||
type="text"
|
||||
size="sm"
|
||||
autoFocus
|
||||
radius="sm"
|
||||
radius="sm"
|
||||
variant="faded"
|
||||
placeholder={t("db_controller.filter")}
|
||||
className="h-8 w-full focus-visible:ring-0 shadow-none "
|
||||
classNames={{
|
||||
inputWrapper : "dark:bg-default border-divider group-hover:border-primary " ,
|
||||
inputWrapper: "dark:bg-default border-divider group-hover:border-primary ",
|
||||
}}
|
||||
onKeyUp={searchTables}
|
||||
|
||||
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
@@ -132,37 +144,43 @@ const TablesController: React.FC<Props> = ({ }) => {
|
||||
> {t("db_controller.add_table")}
|
||||
</Button>
|
||||
</div>
|
||||
<div className=" flex-1 overflow-auto">
|
||||
{
|
||||
!showSqlPreview &&
|
||||
<div className=" flex-1 overflow-auto">
|
||||
|
||||
<Accordion
|
||||
hideIndicator
|
||||
|
||||
selectedKeys={selectedTable}
|
||||
onSelectionChange={setSelectedTable as any}
|
||||
isCompact
|
||||
<Accordion
|
||||
hideIndicator
|
||||
|
||||
>
|
||||
{tables.map((table: TableType) => (
|
||||
<AccordionItem
|
||||
key={table.id}
|
||||
aria-label={table.name}
|
||||
classNames={{
|
||||
trigger: "w-full h-12 hover:bg-default transition-all duration-200 dark:hover:bg-background",
|
||||
base: "rounded-md mb-1 mt-1 p-0 overflow-hidden dark:border-background-100",
|
||||
content : "bg-transparent"
|
||||
}}
|
||||
subtitle={
|
||||
<TableAccordionHeader
|
||||
isOpen={selectedTableId == table.id}
|
||||
table={table}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<TableAccordionBody table={table} />
|
||||
</AccordionItem>
|
||||
))}
|
||||
</Accordion>
|
||||
</div>
|
||||
selectedKeys={selectedTable}
|
||||
onSelectionChange={setSelectedTable as any}
|
||||
isCompact
|
||||
|
||||
>
|
||||
{tables.map((table: TableType) => (
|
||||
<AccordionItem
|
||||
key={table.id}
|
||||
aria-label={table.name}
|
||||
classNames={{
|
||||
trigger: "w-full h-12 hover:bg-default transition-all duration-200 dark:hover:bg-background",
|
||||
base: "rounded-md mb-1 mt-1 p-0 overflow-hidden dark:border-background-100",
|
||||
content: "bg-transparent"
|
||||
}}
|
||||
subtitle={
|
||||
<TableAccordionHeader
|
||||
isOpen={selectedTableId == table.id}
|
||||
table={table}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<TableAccordionBody table={table} />
|
||||
</AccordionItem>
|
||||
))}
|
||||
</Accordion>
|
||||
</div>
|
||||
}
|
||||
{
|
||||
showSqlPreview && <SqlPreview/>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -174,3 +192,4 @@ const TablesController: React.FC<Props> = ({ }) => {
|
||||
export default TablesController
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ interface DatabaseDataContextType {
|
||||
isLoading: boolean,
|
||||
isSwitchingDatabase: boolean,
|
||||
getField: (tableId: string, id: string) => FieldType | undefined,
|
||||
getDefaultPrimaryKeyType : () => DataType | undefined
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ import { getTimestamp } from "@/utils/utils";
|
||||
import { IndexInsertType, indices } from "@/lib/schemas/index-schema";
|
||||
import { field_indices } from "@/lib/schemas/field_index-schema";
|
||||
import { v4 } from "uuid";
|
||||
import { DataType } from "@/lib/schemas/data-type-schema";
|
||||
|
||||
|
||||
interface Props { children: React.ReactNode }
|
||||
@@ -28,10 +29,7 @@ const DatabaseProvider: React.FC<Props> = ({ children }) => {
|
||||
db.query.databases.findMany()
|
||||
));
|
||||
|
||||
// Fetch all data types
|
||||
const { data: data_types, isLoading: loadingDataTypes } = useQuery(toCompilableQuery(
|
||||
db.query.data_types.findMany()
|
||||
));
|
||||
|
||||
|
||||
// Fetch the current database with nested tables, fields, and relationships
|
||||
let { data: database, isLoading: loadingCurrentDatabase, isFetching } = useQuery(
|
||||
@@ -69,7 +67,7 @@ const DatabaseProvider: React.FC<Props> = ({ children }) => {
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
|
||||
const switchDatabase = useCallback((databaseId: string | undefined) => {
|
||||
setCurrentDatabaseId(databaseId);
|
||||
@@ -83,6 +81,13 @@ const DatabaseProvider: React.FC<Props> = ({ children }) => {
|
||||
if (database.length == 1)
|
||||
database = database[0] as any;
|
||||
|
||||
// Fetch all data types
|
||||
const { data: data_types, isLoading: loadingDataTypes } = useQuery(toCompilableQuery(
|
||||
db.query.data_types.findMany({
|
||||
where : (data_types , { eq })=> eq(data_types.dialect, (database as any).dialect)
|
||||
})
|
||||
));
|
||||
|
||||
// Auto-select first database if none is selected
|
||||
useEffect(() => {
|
||||
if (databases.length > 0 && !currentDatabaseId) {
|
||||
@@ -119,7 +124,7 @@ const DatabaseProvider: React.FC<Props> = ({ children }) => {
|
||||
.from(tables)
|
||||
.where(eq(tables.databaseId, databaseId))
|
||||
|
||||
console.log(numOfTables)
|
||||
|
||||
await tx.update(databaseModel).set({
|
||||
numOfTables
|
||||
}).where(eq(databaseModel.id, databaseId))
|
||||
@@ -346,6 +351,11 @@ const DatabaseProvider: React.FC<Props> = ({ children }) => {
|
||||
}
|
||||
}, [db, currentDatabaseId]);
|
||||
|
||||
|
||||
const getDefaultPrimaryKeyType = useCallback(() => {
|
||||
return data_types.find((dataType : DataType) => dataType.name == "bigint" )
|
||||
} , [data_types ]) ;
|
||||
|
||||
const databaseOpsValue = useMemo(() => ({
|
||||
|
||||
createDatabase,
|
||||
@@ -404,8 +414,7 @@ const DatabaseProvider: React.FC<Props> = ({ children }) => {
|
||||
databases: databases as DatabaseType[],
|
||||
isLoading,
|
||||
isSwitchingDatabase,
|
||||
|
||||
|
||||
getDefaultPrimaryKeyType ,
|
||||
getField,
|
||||
}}>
|
||||
<DatabaseOperationsContext.Provider value={databaseOpsValue}>
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
|
||||
|
||||
import { DataType } from "@/lib/schemas/data-type-schema";
|
||||
import { DatabaseType } from "@/lib/schemas/database-schema";
|
||||
import { FieldType } from "@/lib/schemas/field-schema";
|
||||
import { TableType } from "@/lib/schemas/table-schema";
|
||||
|
||||
|
||||
export const DatabaseToAst = (database: DatabaseType , data_types : DataType[]) => {
|
||||
let dbAst: any = [];
|
||||
if ( ! data_types || data_types.length == 0)
|
||||
return dbAst ;
|
||||
for (const table of database.tables) {
|
||||
dbAst.push(TableToAst(table , data_types ))
|
||||
};
|
||||
|
||||
return dbAst;
|
||||
}
|
||||
|
||||
|
||||
export const TableToAst = (table: TableType , data_types : DataType[]) => {
|
||||
return {
|
||||
keyword: "table",
|
||||
type: "create",
|
||||
table: [{
|
||||
table: table.name
|
||||
}],
|
||||
create_definitions: table.fields.map((field: FieldType) => FieldToAst({
|
||||
...field ,
|
||||
type : data_types.find((dataType : DataType) => dataType.id == field.typeId) as DataType
|
||||
} ))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
export const FieldToAst = (field: FieldType ) => {
|
||||
return {
|
||||
column: {
|
||||
type: "column_ref",
|
||||
column: {
|
||||
expr: {
|
||||
type: "default", value: field.name
|
||||
}
|
||||
},
|
||||
},
|
||||
default_val: null,
|
||||
unique: field.unique ? "unique" : null,
|
||||
nullable: {
|
||||
type: field.nullable ? "null" : "not null",
|
||||
value: field.nullable ? "null" : "not null",
|
||||
},
|
||||
definition: {
|
||||
dataType: field.type?.name?.toLocaleUpperCase() ,
|
||||
|
||||
},
|
||||
primary_key: field.isPrimary ? "primary key" : null,
|
||||
resource: "column"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user