diff --git a/package.json b/package.json index e2a250a..7541fbf 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "preview": "vite preview" }, "dependencies": { + "@codemirror/lang-sql": "^6.9.0", "@dnd-kit/core": "^6.3.1", "@dnd-kit/sortable": "^10.0.0", "@heroui/react": "^2.7.6", @@ -20,6 +21,7 @@ "@radix-ui/react-tooltip": "^1.2.3", "@react-aria/visually-hidden": "3.8.21", "@react-types/shared": "3.28.0", + "@uiw/react-codemirror": "^4.23.12", "@xyflow/react": "^12.6.3", "date-fns": "^4.1.0", "drizzle-orm": "^0.43.1", @@ -35,6 +37,7 @@ "react-dom": "18.3.1", "react-i18next": "^15.5.1", "react-router-dom": "6.23.0", + "sql-formatter": "^15.6.3", "tailwind-merge": "^3.2.0", "tailwind-variants": "0.3.0", "tailwindcss": "3.4.16", diff --git a/src/hooks/user-render-sql.tsx b/src/hooks/user-render-sql.tsx index b666727..3160e38 100644 --- a/src/hooks/user-render-sql.tsx +++ b/src/hooks/user-render-sql.tsx @@ -6,6 +6,7 @@ import { useEffect, useState } from "react"; import { Parser } from "node-sql-parser"; import { DatabaseType } from "@/lib/schemas/database-schema"; +import { format } from 'sql-formatter'; const parser = new Parser(); @@ -16,13 +17,14 @@ export const useRenderSql = (database: DatabaseType) => { useEffect(() => { const dbAst: any = DatabaseToAst(database, data_types); + const formattedSqlCode = format(parser.sqlify(dbAst), { language: 'sql' }); // or 'mysql', 'postgresql', etc. setSql( - parser.sqlify(dbAst) + formattedSqlCode ); - }, [database]) ; + }, [database]); - return sql ; + return sql; } \ No newline at end of file diff --git a/src/lib/schemas/field-schema.ts b/src/lib/schemas/field-schema.ts index 24a3c76..118ae10 100644 --- a/src/lib/schemas/field-schema.ts +++ b/src/lib/schemas/field-schema.ts @@ -2,7 +2,7 @@ import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core'; import { tables } from './table-schema'; import { data_types, DataType } from './data-type-schema'; import { InferInsertModel, InferSelectModel, relations } from 'drizzle-orm'; -import { relationships } from './relationship-schema'; +import { relationships, RelationshipType } from './relationship-schema'; import { field_indices } from './field_index-schema'; import { indices } from './index-schema'; @@ -32,8 +32,8 @@ export const fieldsRelations = relations(fields, ({ one, many }) => ({ fields: [fields.typeId], references: [data_types.id] }), - sourceRelations: one(relationships), - targetRelations: one(relationships), + sourceRelations: many(relationships), + targetRelations: many(relationships), fieldIndices: many(field_indices), indices: many(indices) @@ -42,8 +42,12 @@ export const fieldsRelations = relations(fields, ({ one, many }) => ({ export interface FieldType extends InferSelectModel { sequence: number, - type: DataType + type: DataType, + sourceRelations?: RelationshipType[], + targetRelations?: RelationshipType[] }; -export interface FieldInsertType extends InferInsertModel { }; \ No newline at end of file +export interface FieldInsertType extends InferInsertModel { + +}; \ No newline at end of file diff --git a/src/lib/schemas/index-schema.ts b/src/lib/schemas/index-schema.ts index eee4b81..07bc3a2 100644 --- a/src/lib/schemas/index-schema.ts +++ b/src/lib/schemas/index-schema.ts @@ -4,7 +4,7 @@ import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core'; import { tables } from './table-schema'; import { InferInsertModel, InferSelectModel, relations } from 'drizzle-orm'; import { field_indices, FieldIndexInsertType, FieldIndexType } from './field_index-schema'; -import { fields } from './field-schema'; +import { fields, FieldType } from './field-schema'; @@ -32,7 +32,8 @@ export const indicesRlationships = relations(indices, ({ one, many }) => ({ export interface IndexType extends InferSelectModel { - fieldIndices: FieldIndexType[] + fieldIndices: FieldIndexType[] ; + fields : FieldType[] ; }; export interface IndexInsertType extends InferInsertModel { fieldIndices? : FieldIndexInsertType[] diff --git a/src/lib/schemas/table-schema.ts b/src/lib/schemas/table-schema.ts index caf9afa..beb21ea 100644 --- a/src/lib/schemas/table-schema.ts +++ b/src/lib/schemas/table-schema.ts @@ -1,7 +1,7 @@ import { InferInsertModel, InferSelectModel, relations } from 'drizzle-orm'; import { sqliteTable, text, real, integer } from 'drizzle-orm/sqlite-core'; import { fields, FieldType } from './field-schema'; -import { relationships } from './relationship-schema'; +import { relationships, RelationshipType } from './relationship-schema'; import { databases } from './database-schema'; import { IndexType, indices } from './index-schema'; @@ -43,11 +43,14 @@ export const tablesRelations = relations(tables, ({ many, one }) => ({ export interface TableType extends InferSelectModel { fields: FieldType[]; indices : IndexType[] ; + + sourceRelations? :RelationshipType[] ; + targetRelations? : RelationshipType[] ; + }; export interface TableInsertType extends InferInsertModel { fields?: FieldType[]; - }; \ No newline at end of file diff --git a/src/pages/database/db-controller/sql-preview.tsx b/src/pages/database/db-controller/sql-preview.tsx index b2a63f9..f85232b 100644 --- a/src/pages/database/db-controller/sql-preview.tsx +++ b/src/pages/database/db-controller/sql-preview.tsx @@ -1,27 +1,67 @@ import { useRenderSql } from "@/hooks/user-render-sql"; import { useDatabase } from "@/providers/database-provider/database-provider"; -import React from "react"; +import React, { useEffect, useMemo } from "react"; + +import CodeMirror from '@uiw/react-codemirror'; +import { sql } from '@codemirror/lang-sql'; +import { oneDark } from '@codemirror/theme-one-dark'; +import { useTheme } from "next-themes"; +import { Parser } from "node-sql-parser"; + +const parser = new Parser(); +const code = ` + +-- Addresses table with a foreign key to users +CREATE TABLE addresses ( + id INTEGER PRIMARY KEY, + user_id INT NOT NULL, + street VARCHAR(255), + city VARCHAR(100), + state VARCHAR(100), + postal_code VARCHAR(20), + country VARCHAR(100), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE +); + +-- Indexes to improve lookup performance +CREATE UNIQUE INDEX idx_addresses_user_id ON addresses(user_id); +CREATE INDEX idx_addresses_city ON addresses(city , country); + + +` +const SqlPreview: React.FC = ({ }) => { + const { database } = useDatabase(); + const sqlCode = useRenderSql(database); + const {resolvedTheme} = useTheme(); + useEffect(() => { + const ast = parser.astify(code ) + console.log (ast) + }, []) -const SqlPreview : React.FC = ({}) => { - - const {database} = useDatabase() ; - const sql = useRenderSql(database) ; - - - return( -

- {sql + return ( +

+ { + + } -

+
) -} +} -export default React.memo(SqlPreview) ; \ No newline at end of file +export default React.memo(SqlPreview); \ No newline at end of file diff --git a/src/pages/database/db-controller/tables-controller/tables-controller.tsx b/src/pages/database/db-controller/tables-controller/tables-controller.tsx index 937a5f2..66b1345 100644 --- a/src/pages/database/db-controller/tables-controller/tables-controller.tsx +++ b/src/pages/database/db-controller/tables-controller/tables-controller.tsx @@ -79,15 +79,10 @@ const TablesController: React.FC = ({ }) => { }, [nameRef, allTables]); - const toggleSqlPreview = useCallback(() => { setShowSqlPreview(preview => !preview); }, []) - - - - return (
@@ -179,7 +174,11 @@ const TablesController: React.FC = ({ }) => {
} { - showSqlPreview && + showSqlPreview && +
+ + +
}
) diff --git a/src/styles/globals.css b/src/styles/globals.css index c4eb24a..40c9d49 100644 --- a/src/styles/globals.css +++ b/src/styles/globals.css @@ -173,4 +173,9 @@ tbody[role="rowgroup"] tr td:last-child { tbody[role="rowgroup"] tr td:first-child { border-radius: 4px 0px 0px 4px !important; +} + +.cm-editor { + + min-width : 100% !important ; } \ No newline at end of file diff --git a/src/utils/render/parsers/database_to_ast.ts b/src/utils/render/parsers/database_to_ast.ts index bbd9c1b..20be177 100644 --- a/src/utils/render/parsers/database_to_ast.ts +++ b/src/utils/render/parsers/database_to_ast.ts @@ -3,38 +3,94 @@ import { DataType } from "@/lib/schemas/data-type-schema"; import { DatabaseType } from "@/lib/schemas/database-schema"; import { FieldType } from "@/lib/schemas/field-schema"; +import { FieldIndexType } from "@/lib/schemas/field_index-schema"; +import { IndexType } from "@/lib/schemas/index-schema"; +import { RelationshipType } from "@/lib/schemas/relationship-schema"; import { TableType } from "@/lib/schemas/table-schema"; -export const DatabaseToAst = (database: DatabaseType , data_types : DataType[]) => { +export const DatabaseToAst = (database: DatabaseType, data_types: DataType[]) => { let dbAst: any = []; - if ( ! data_types || data_types.length == 0) - return dbAst ; + if (!data_types || data_types.length == 0) + return dbAst; for (const table of database.tables) { - dbAst.push(TableToAst(table , data_types )) + dbAst.push(TableToAst({ + ...table, + sourceRelations: database.relationships.filter((relationship: RelationshipType) => relationship.sourceTableId == table.id), + targetRelations: database.relationships.filter((relationship: RelationshipType) => relationship.targetTableId == table.id), + }, data_types)); + + if (table.indices && table.indices.length > 0) + for (const index of table.indices) + dbAst.push(IndexToAst({ + ...index, + fields: index.fieldIndices.map((fieldIndex: FieldIndexType) => table.fields.find((field: FieldType) => field.id == fieldIndex.fieldId)) as FieldType[] + }, table)); }; return dbAst; } -export const TableToAst = (table: TableType , data_types : DataType[]) => { +export const TableToAst = (table: TableType, data_types: DataType[]) => { + + const constraints: any[] = table.sourceRelations ? table.sourceRelations?.map((relationship: RelationshipType) => { + return { + constraint_type: "FOREIGN KEY", + resource: "constraint", + definition: [ + { + type: "column_ref", + column: relationship.sourceField.name, + + } + ], + reference_definition: { + on_action: [ + { + "type": "on delete", + "value": { + "type": "origin", + "value": "cascade" + } + } + ], + keyword: "references", + table: [{ + table: relationship.targetTable.name, + }], + definition: [ + { + type: "column_ref", + column: relationship.targetField.name, + } + ], + } + } + + }) : []; + + const filed_definitions = table.fields.map((field: FieldType) => FieldToAst({ + ...field, + type: data_types.find((dataType: DataType) => dataType.id == field.typeId) as DataType, + sourceRelations: table.sourceRelations?.filter((relationship: RelationshipType) => relationship.sourceFieldId == field.id), + targetRelations: table.targetRelations?.filter((relationship: RelationshipType) => relationship.targetFieldId == field.id), + + })); 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 - } )) + create_definitions: [...filed_definitions, ...constraints] } } -export const FieldToAst = (field: FieldType ) => { +export const FieldToAst = (field: FieldType) => { + return { column: { type: "column_ref", @@ -51,7 +107,7 @@ export const FieldToAst = (field: FieldType ) => { value: field.nullable ? "null" : "not null", }, definition: { - dataType: field.type?.name?.toLocaleUpperCase() , + dataType: field.type?.name?.toLocaleUpperCase(), }, primary_key: field.isPrimary ? "primary key" : null, @@ -60,3 +116,18 @@ export const FieldToAst = (field: FieldType ) => { } + +export const IndexToAst = (index: IndexType, table: TableType) => { + + return { + index: index.name, + type: "create", + table: { table: table.name }, + keyword: "index", + index_type: index.unique ? "unique" : null, + index_columns: index.fields.map((field: FieldType) => ({ + column: field.name, + type: "column_ref" + })) + } +} \ No newline at end of file