Import Mysql from Sql feature implemented

This commit is contained in:
KarimTamani
2025-06-23 23:22:37 +01:00
parent ea275f0a92
commit 3ddb1f4e39
3 changed files with 311 additions and 77 deletions
+1
View File
@@ -199,6 +199,7 @@ const DatabasePage: React.FC = () => {
snapGrid={[20, 20]}
onConnectStart={onConnectStart}
onConnectEnd={onConnectEnd}
>
<Controls
+21 -3
View File
@@ -35,6 +35,25 @@ const options: ImportDatabaseOption[] = [{
logo: "/postgresql_logo.png",
type: ImportMethodType.DB_CLIENT
}]
}, {
dialect: DatabaseDialect.MYSQL,
methods: [{
id: "mysql_dump",
name: "mysqldump",
icon: <Code className="size-4 text-font/90" />,
type: ImportMethodType.DUMP
}]
}
, {
dialect: DatabaseDialect.MARIADB,
methods: [{
id: "mysql_dump",
name: "mysqldump",
icon: <Code className="size-4 text-font/90" />,
type: ImportMethodType.DUMP
}]
}]
const ImportDatabaseModal: React.FC<ModalProps> = ({ isOpen, onOpenChange }) => {
@@ -44,11 +63,11 @@ const ImportDatabaseModal: React.FC<ModalProps> = ({ isOpen, onOpenChange }) =>
const { database } = useDatabase();
const { data_types, importDatabase } = useDatabaseOperations();
const [sqlCode, setSqlCode] = useState<string>("");
let currentOption: ImportDatabaseOption | undefined = useMemo(() => {
return options.find((option: ImportDatabaseOption) => option.dialect == database?.dialect)
}, [database]);
const [selectedMethodId, setSelectedMethodId] = useState<string[]>(currentOption ? [currentOption.methods[0].id] : []);
if (!currentOption) {
@@ -67,6 +86,7 @@ const ImportDatabaseModal: React.FC<ModalProps> = ({ isOpen, onOpenChange }) =>
const onImport = useCallback(async () => {
const { tables, relationships, indices } = SqlToDatabase(sqlCode, data_types, database?.dialect as DatabaseDialect);
return await importDatabase(tables, relationships, indices)
}, [sqlCode, database?.dialect, data_types]);
@@ -145,7 +165,6 @@ const ImportDatabaseModal: React.FC<ModalProps> = ({ isOpen, onOpenChange }) =>
</ul>
</div>
}
{
selectedImportMethod.type == ImportMethodType.DB_CLIENT &&
<div className="space-y-2">
@@ -171,7 +190,6 @@ const ImportDatabaseModal: React.FC<ModalProps> = ({ isOpen, onOpenChange }) =>
</ul>
</div>
}
</div>
<div className="flex flex-1 ">
{
+289 -74
View File
@@ -1,11 +1,11 @@
import { Modifiers, TimeDefaultValues } from "@/lib/field";
import { DataTypes, Modifiers, TimeDefaultValues } from "@/lib/field";
import { DataType } from "@/lib/schemas/data-type-schema";
import { FieldInsertType, FieldType } from "@/lib/schemas/field-schema";
import { TableInsertType } from "@/lib/schemas/table-schema";
import { Parser } from "node-sql-parser";
import { v4 } from "uuid";
import { parse } from 'pgsql-ast-parser';
import { DatabaseDialect } from "@/lib/database";
import { DatabaseDialect, getDatabaseByDialect } from "@/lib/database";
import { randomColor } from "@/lib/colors";
import { Cardinality, RelationshipInsertType } from "@/lib/schemas/relationship-schema";
import { IndexInsertType } from "@/lib/schemas/index-schema";
@@ -15,6 +15,7 @@ import { DatabaseInsertType } from "@/lib/schemas/database-schema";
export const SqlToDatabase = (sql: string, data_types: DataType[], dialect: DatabaseDialect) => {
const parser = new Parser();
const createTableStatements: string[] = [];
const alterTableStatements: string[] = [];
const createIndexStatements: string[] = [];
@@ -52,7 +53,8 @@ export const SqlToDatabase = (sql: string, data_types: DataType[], dialect: Data
}
}
if (dialect == DatabaseDialect.POSTGRES)
if (dialect == DatabaseDialect.POSTGRES) {
for (const postgresType of createPostgresTypesStatements) {
try {
const instructionAst = parse(postgresType);
@@ -60,11 +62,11 @@ export const SqlToDatabase = (sql: string, data_types: DataType[], dialect: Data
postgresTypes.push(instructionAst[0]);
}
} catch (error) {
console.log(error);
continue;
}
}
if (dialect == DatabaseDialect.POSTGRES)
for (const createTable of createTableStatements) {
try {
const instructionAst = parse(createTable);
@@ -74,7 +76,7 @@ export const SqlToDatabase = (sql: string, data_types: DataType[], dialect: Data
if ((instructionAst[0] as any).constraints && (instructionAst[0] as any).constraints.length > 0) {
const relationshipAst: any = foreignKeyConstraintToAlterTableAst((instructionAst[0] as any).constraints, table)
try {
const newRelationships = astToRelationship(relationshipAst, tables);
const newRelationships = postgresAstToRelationship(relationshipAst, tables);
relationships = relationships.concat(newRelationships);
} catch (error) {
if ((error as any).relationships && (error as any).relationships.length > 0)
@@ -83,35 +85,104 @@ export const SqlToDatabase = (sql: string, data_types: DataType[], dialect: Data
}
}
} catch (error) {
console.log(error);
continue;
}
}
for (const createIndex of createIndexStatements) {
try {
const instructionAst = parse(createIndex);
if (Array.isArray(instructionAst) && instructionAst.length > 0) {
indices.push(postgresAstToIndex(instructionAst[0], tables));
}
} catch (error) {
continue;
}
}
for (const alterTable of alterTableStatements) {
try {
const instructionAst = parse(alterTable);
if (Array.isArray(instructionAst) && instructionAst.length > 0) {
const extractedRelationships: RelationshipInsertType[] = postgresAstToRelationship((instructionAst[0] as any), tables);
relationships = relationships.concat(extractedRelationships);
}
} catch (error) {
if ((error as any).relationships && (error as any).relationships.length > 0)
relationships = relationships.concat((error as any).relationships);
}
}
} else {
for (const createTable of createTableStatements) {
try {
const instructionAst = parser.astify(createTable, {
database: getDatabaseByDialect(dialect).name
});
if (Array.isArray(instructionAst) && instructionAst.length > 0) {
console.log(instructionAst[0])
const table: TableInsertType = astToTable(instructionAst[0], data_types);
tables.push(table);
const foreignKeyConstraints = (instructionAst[0] as any).create_definitions.filter((definition: any) => definition.constraint_type == "FOREIGN KEY");
const referenceDefinitions = (instructionAst[0] as any).create_definitions.filter((definition: any) => definition.resource == "column" && definition.reference_definition);
for (const foreignKeyConstraint of foreignKeyConstraints) {
relationships.push(astToRelationship(tables, table, foreignKeyConstraint) as RelationshipInsertType);
}
for (const referenceDefinition of referenceDefinitions) {
relationships.push(astToRelationship(tables, table, undefined, referenceDefinition) as RelationshipInsertType);
}
}
} catch (error) {
// console.log (error)
continue;
}
}
for (const createIndex of createIndexStatements) {
try {
const instructionAst = parse(createIndex);
if (Array.isArray(instructionAst) && instructionAst.length > 0) {
indices.push(astToIndex(instructionAst[0], tables));
for (const alterTable of alterTableStatements) {
try {
const instructionAst = parser.astify(alterTable, {
database: getDatabaseByDialect(dialect).name
});
if (instructionAst) {
const targetTable: TableInsertType | undefined = tables.find((table: TableInsertType) => table.name == (instructionAst as any).table?.[0].table)
const extractedRelationships: RelationshipInsertType[] = astToRelationship(tables, targetTable, undefined, undefined, instructionAst) as RelationshipInsertType[];
relationships = relationships.concat(extractedRelationships)
}
} catch (error) {
if ((error as any).relationships && (error as any).relationships.length > 0)
relationships = relationships.concat((error as any).relationships);
}
}
for (const createIndex of createIndexStatements) {
try {
let instructionAst = parser.astify(createIndex, {
database: getDatabaseByDialect(dialect).name
});
if (Array.isArray(instructionAst) && instructionAst.length > 0) {
instructionAst = instructionAst[0];
}
indices.push(astToIndex(instructionAst, tables));
} catch (error) {
continue;
}
} catch (error) {
continue;
}
}
for (const alterTable of alterTableStatements) {
try {
const instructionAst = parse(alterTable);
if (Array.isArray(instructionAst) && instructionAst.length > 0) {
const extractedRelationships: RelationshipInsertType[] = astToRelationship((instructionAst[0] as any), tables);
relationships = relationships.concat(extractedRelationships);
}
} catch (error) {
if ((error as any).relationships && (error as any).relationships.length > 0)
relationships = relationships.concat((error as any).relationships);
}
}
return { tables, relationships, indices };
}
@@ -120,8 +191,169 @@ export const SqlToDatabase = (sql: string, data_types: DataType[], dialect: Data
export const astToRelationship = (tables: TableInsertType[], targetTable?: TableInsertType, constrainttAst?: any, columnAst?: any, alterTableAst?: any): RelationshipInsertType | RelationshipInsertType[] => {
let targetField: FieldInsertType | undefined;
let sourceTable: TableInsertType | undefined;
let sourceField: FieldInsertType | undefined;
let relationships: RelationshipInsertType[] = [];
if (constrainttAst) {
targetField = targetTable?.fields?.find((field: FieldInsertType) => field.name == constrainttAst.definition?.[0].column);
sourceTable = tables.find((table: TableInsertType) => table.name == constrainttAst.reference_definition?.table?.[0].table);
sourceField = sourceTable?.fields?.find((field: FieldInsertType) => field.name == constrainttAst.reference_definition?.definition?.[0].column);
}
if (columnAst) {
targetField = targetTable?.fields?.find((field: FieldInsertType) => field.name == columnAst.column?.column);
sourceTable = tables.find((table: TableInsertType) => table.name == columnAst.reference_definition?.table?.[0].table);
sourceField = sourceTable?.fields?.find((field: FieldInsertType) => field.name == columnAst.reference_definition?.definition?.[0].column);
}
if (alterTableAst) {
const expressions = alterTableAst.expr;
const foreignKeyExpressions = expressions.filter((expression: any) => expression.resource == "constraint" && expression.create_definitions?.constraint_type == "FOREIGN KEY")
for (const expression of foreignKeyExpressions) {
const targetField: FieldInsertType | undefined = targetTable?.fields?.find((field: FieldInsertType) => field.name == expression.create_definitions?.definition?.[0].column);
const sourceTable: TableInsertType | undefined = tables.find((table: TableInsertType) => table.name == expression.create_definitions?.reference_definition?.table?.[0].table)
const sourceField: FieldInsertType | undefined = sourceTable?.fields?.find((field: FieldInsertType) => field.name == expression.create_definitions?.reference_definition?.definition?.[0].column);
if (!targetField || !sourceField || !sourceTable)
continue;
relationships.push({
id: v4(),
targetTableId: targetTable?.id,
targetFieldId: targetField.id,
sourceTableId: sourceTable.id,
sourceFieldId: sourceField.id,
cardinality: targetField.unique ? Cardinality.one_to_one : Cardinality.one_to_many
} as RelationshipInsertType)
}
if (relationships.length == foreignKeyExpressions.length)
return relationships;
else
throw Error({
success: false,
message: "Failed to Extract all relationships",
relationships
} as any)
}
if (!targetField || !sourceField || !sourceTable)
throw Error("Failed to extract relationship");
return {
id: v4(),
targetTableId: targetTable?.id,
targetFieldId: targetField.id,
sourceTableId: sourceTable.id,
sourceFieldId: sourceField.id,
cardinality: targetField.unique ? Cardinality.one_to_one : Cardinality.one_to_many
} as RelationshipInsertType;
}
const astToTable = (ast: any, data_types: DataType[]): TableInsertType => {
console.log(ast)
return {
id: v4(),
name: ast.table[0]?.table,
fields: ast.create_definitions.filter((column: any) => column.resource == "column")
.map((fieldAst: any, index: number) => astToField(fieldAst, data_types, index)),
color: randomColor()
} as TableInsertType;
}
export const astToField = (ast: any, data_types: DataType[], sequence: number): FieldInsertType => {
const dataType: DataType | undefined = data_types.find((dataType: DataType) => {
const synonyms: string[] = dataType.synonyms ? JSON.parse(dataType.synonyms) : [];
return dataType.name == ast.definition.dataType?.toLowerCase() || synonyms.includes(ast.definition.dataType?.toLowerCase())
});
let values: string | undefined = undefined;
const modifiers: string[] = dataType?.modifiers ? JSON.parse(dataType.modifiers) : [];
const { length, scale } = ast.definition;
const { character_set, collate: collation } = ast;
let charset: string | undefined;
let collate: string | undefined;
let defaultValue: string | undefined = ast.default_val?.value?.value ? ast.default_val?.value?.value : undefined;
let maxLength: number | null = null;
let precision: number | null = null;
if (modifiers.includes(Modifiers.LENGTH) && length)
maxLength = length;
if (modifiers.includes(Modifiers.PRECISION) && length)
precision = length;
if (modifiers.includes(Modifiers.CHARSET) && character_set)
charset = character_set.value?.value;
if (modifiers.includes(Modifiers.COLLATE) && collation)
collate = collation.collate?.name;
if (modifiers.includes(Modifiers.VALUES)) {
const value = ast.definition?.expr?.value.map((value: any) => value.value);
if (value)
values = JSON.stringify(value);
}
if (dataType?.type == DataTypes.TIME &&
ast.default_val?.value?.type == "function" &&
ast.default_val?.value?.name?.name?.length > 0 &&
ast.default_val?.value?.name?.name[0].value == "CURRENT_TIMESTAMP")
defaultValue = TimeDefaultValues.NOW;
return {
id: v4(),
name: ast.column.column,
defaultValue,
typeId: dataType?.id,
nullable: ast.nullable?.value != "not null",
unique: ast.unique == "unique",
maxLength,
precision,
scale,
sequence,
autoIncrement: ast.auto_increment == "auto_increment",
isPrimary: ast.primary_key == "primary key",
values,
charset,
collate,
} as FieldInsertType;
}
export const postgresAstToTable = (ast: any, data_types: DataType[], postgresTypes: any[]): TableInsertType => {
// console.log(ast);
return {
id: v4(),
@@ -133,6 +365,7 @@ export const postgresAstToTable = (ast: any, data_types: DataType[], postgresTyp
}
export const postgresAstToField = (ast: any, data_types: DataType[], sequence: number, postgresTypes: any[]): FieldInsertType => {
let dataType: DataType | undefined = data_types.find((dataType: DataType) => {
@@ -152,12 +385,12 @@ export const postgresAstToField = (ast: any, data_types: DataType[], sequence: n
}
if (ast.dataType.name?.toLowerCase().includes("serial")) {
let baseType: string = ast.dataType.name?.toLowerCase() == "serial" ? "integer" : ast.dataType.name?.toLowerCase().replace("serial", "int");
dataType = data_types.find((dataType: DataType) => {
return dataType.name == baseType;
});
autoIncrement = true;
autoIncrement = true;
}
const modifiers: string[] = dataType?.modifiers ? JSON.parse(dataType.modifiers) : [];
@@ -165,6 +398,7 @@ export const postgresAstToField = (ast: any, data_types: DataType[], sequence: n
let scale: number | undefined;
let unique: boolean = false;
let primaryKey: boolean = false;
if (ast.dataType.config && ast.dataType.config.length > 0) {
if (ast.dataType.config.length >= 1)
length = ast.dataType.config[0];
@@ -230,52 +464,8 @@ export const postgresAstToField = (ast: any, data_types: DataType[], sequence: n
}
const astToTable = (ast: any, data_types: DataType[]): TableInsertType => {
return {
id: v4(),
name: ast.table[0]?.table,
fields: ast.create_definitions.filter((column: any) => column.resource == "column")
.map((fieldAst: any) => astToField(fieldAst, data_types))
} as TableInsertType;
}
export const astToField = (ast: any, data_types: DataType[]): FieldInsertType => {
const dataType: DataType | undefined = data_types.find((dataType: DataType) => dataType.name == ast.definition.dataType?.toLowerCase());
const modifiers: string[] = dataType?.modifiers ? JSON.parse(dataType.modifiers) : [];
const { length, scale } = ast.definition;
let maxLength: number | null = null;
let precision: number | null = null;
if (modifiers.includes(Modifiers.LENGTH) && length)
maxLength = length;
if (modifiers.includes(Modifiers.PRECISION) && length)
precision = length;
return {
id: v4(),
name: ast.column.column,
defaultValue: ast.default_val,
typeId: dataType?.id,
nullable: ast.nullable?.value == "not null",
unique: ast.unique,
maxLength,
precision,
scale: scale,
} as FieldInsertType;
}
export const astToRelationship = (ast: any, tables: TableInsertType[]): RelationshipInsertType[] => {
export const postgresAstToRelationship = (ast: any, tables: TableInsertType[]): RelationshipInsertType[] => {
const relationships: RelationshipInsertType[] = [];
@@ -357,6 +547,31 @@ const foreignKeyConstraintToAlterTableAst = (constraints: any[], table: TableIns
}
export const astToIndex = (ast: any, tables: TableInsertType[]): IndexInsertType => {
const table: TableInsertType | undefined = tables.find((table: TableInsertType) => table.name == ast.table.table);
if (!table)
throw Error("table not found");
const fieldNames: string[] = ast.index_columns.map((column: any) => column.column);
const fieldIds: string[] | undefined = table.fields?.filter((field: FieldInsertType) => fieldNames.includes(field.name))
.map((field: FieldInsertType) => field.id);
return {
id: v4(),
name: ast.index,
tableId: table.id,
unique: ast.index_type == "unique",
fieldIndices: fieldIds?.map((id: string) => ({
id: v4(),
fieldId: id
}))
} as IndexInsertType
}
export const postgresAstToIndex = (ast: any, tables: TableInsertType[]): IndexInsertType => {
const table: TableInsertType | undefined = tables.find((table: TableInsertType) => table.name == ast.table.name);
if (!table)