diff --git a/src/components/EditorHeader/ControlPanel.jsx b/src/components/EditorHeader/ControlPanel.jsx index f9a4937..d7c0166 100644 --- a/src/components/EditorHeader/ControlPanel.jsx +++ b/src/components/EditorHeader/ControlPanel.jsx @@ -74,6 +74,7 @@ import { isRtl } from "../../i18n/utils/rtl"; import { jsonToDocumentation } from "../../utils/exportAs/documentation"; import { IdContext } from "../Workspace"; import DatabasesSwitcher from "./DatabasesSwitcher"; +import { convertTableSchema } from "../../utils/typesMappings"; export default function ControlPanel({ diagramId, @@ -109,6 +110,7 @@ export default function ControlPanel({ deleteRelationship, database, } = useDiagram(); + const [prevDatabase, setPrevDatabase] = useState(database); const { enums, setEnums, deleteEnum, addEnum, updateEnum } = useEnums(); const { types, addType, deleteType, updateType, setTypes } = useTypes(); const { notes, setNotes, updateNote, addNote, deleteNote } = useNotes(); @@ -925,8 +927,9 @@ export default function ControlPanel({ function: () => { if (database === DB.GENERIC) return; setModal(MODAL.CODE); + const newTables = tables.map(table => convertTableSchema(table, prevDatabase, database)); const src = exportSQL({ - tables: tables, + tables: newTables, references: relationships, types: types, database: database, @@ -1645,6 +1648,7 @@ export default function ControlPanel({
databases[db].label !== DB.GENERIC); +const databasesWithoutGeneric = Object.keys(databases).filter(db => ![DB.GENERIC, DB.MARIADB, DB.MSSQL].includes(databases[db].label)); -export default function DatabasesSwitcher({ setLastSaved, diagramId }) { +export default function DatabasesSwitcher({ setLastSaved, diagramId, setPrevDatabase }) { const { database, setDatabase } = useDiagram(); const { setSaveState } = useSaveState(); @@ -57,6 +57,7 @@ export default function DatabasesSwitcher({ setLastSaved, diagramId }) { }).then(() => { setSaveState(State.SAVED); setLastSaved(new Date().toLocaleString()); + setPrevDatabase(database); setDatabase(selectedDb); }); }; diff --git a/src/utils/typesMappings.js b/src/utils/typesMappings.js new file mode 100644 index 0000000..7936967 --- /dev/null +++ b/src/utils/typesMappings.js @@ -0,0 +1,220 @@ +import { DB } from "../data/constants"; + +function mapDataTypes(fromDb, toDb) { + if (!fromDb || !toDb) { + throw new Error("Please provide both from/to database names."); + } + + fromDb = fromDb.toLowerCase(); + toDb = toDb.toLowerCase(); + + if (fromDb === toDb || [fromDb, toDb].includes(DB.GENERIC)) { + return ""; + } + + const typeMapping = { + postgresql: { + mysql: { + "SMALLINT": "TINYINT", + "INTEGER": "INT", + "BIGINT": "BIGINT", + "SERIAL": "INT AUTO_INCREMENT", + "BIGSERIAL": "BIGINT AUTO_INCREMENT", + "DECIMAL": "DECIMAL", + "NUMERIC": "DECIMAL", + "REAL": "FLOAT", + "DOUBLE PRECISION": "DOUBLE", + "MONEY": "DECIMAL(19,4)", + "VARCHAR": "VARCHAR", + "CHAR": "CHAR", + "TEXT": "TEXT", + "BYTEA": "BLOB", + "BOOLEAN": "TINYINT(1)", + "DATE": "DATE", + "TIME": "TIME", + "TIMETZ": "TIME", + "TIMESTAMP": "DATETIME", + "TIMESTAMPTZ": "DATETIME", + "INTERVAL": "TIME", + "UUID": "CHAR(36)", + "JSON": "JSON", + "JSONB": "JSON", + "XML": "TEXT", + }, + sqlite: { + "SMALLINT": "INTEGER", + "INTEGER": "INTEGER", + "BIGINT": "INTEGER", + "SERIAL": "INTEGER PRIMARY KEY AUTOINCREMENT", + "BIGSERIAL": "INTEGER", + "DECIMAL": "REAL", + "NUMERIC": "REAL", + "REAL": "REAL", + "DOUBLE PRECISION": "REAL", + "MONEY": "REAL", + "VARCHAR": "TEXT", + "CHAR": "TEXT", + "TEXT": "TEXT", + "BYTEA": "BLOB", + "BOOLEAN": "INTEGER", + "DATE": "TEXT", + "TIME": "TEXT", + "TIMETZ": "TEXT", + "TIMESTAMP": "TEXT", + "TIMESTAMPTZ": "TEXT", + "INTERVAL": "TEXT", + "UUID": "TEXT", + "JSON": "TEXT", + "JSONB": "TEXT", + "XML": "TEXT", + }, + }, + mysql: { + postgresql: { + "TINYINT": "SMALLINT", + "SMALLINT": "SMALLINT", + "MEDIUMINT": "INTEGER", + "INT": "INTEGER", + "BIGINT": "BIGINT", + "FLOAT": "REAL", + "DOUBLE": "DOUBLE PRECISION", + "DECIMAL": "DECIMAL", + "NUMERIC": "NUMERIC", + "VARCHAR": "VARCHAR", + "CHAR": "CHAR", + "TEXT": "TEXT", + "BLOB": "BYTEA", + "TINYBLOB": "BYTEA", + "MEDIUMBLOB": "BYTEA", + "LONGBLOB": "BYTEA", + "BIT": "BOOLEAN", + "BOOLEAN": "BOOLEAN", + "DATE": "DATE", + "TIME": "TIME", + "DATETIME": "TIMESTAMP", + "TIMESTAMP": "TIMESTAMP", + "YEAR": "DATE", + "JSON": "JSON", + "ENUM": "VARCHAR", + "SET": "TEXT", + "POINT": "POINT", + "LINESTRING": "LINE", + "POLYGON": "POLYGON" + }, + sqlite: { + "TINYINT": "INTEGER", + "SMALLINT": "INTEGER", + "MEDIUMINT": "INTEGER", + "INT": "INTEGER", + "BIGINT": "INTEGER", + "FLOAT": "REAL", + "DOUBLE": "REAL", + "DECIMAL": "REAL", + "NUMERIC": "REAL", + "VARCHAR(n)": "TEXT", + "CHAR(n)": "TEXT", + "TEXT": "TEXT", + "BLOB": "BLOB", + "TINYBLOB": "BLOB", + "MEDIUMBLOB": "BLOB", + "LONGBLOB": "BLOB", + "BIT": "INTEGER", + "BOOLEAN": "INTEGER", + "DATE": "TEXT", + "TIME": "TEXT", + "DATETIME": "TEXT", + "TIMESTAMP": "TEXT", + "YEAR": "TEXT", + "JSON": "TEXT", + "ENUM": "TEXT", + "SET": "TEXT", + "POINT": "TEXT", + "LINESTRING": "TEXT", + "POLYGON": "TEXT" + }, + }, + sqlite: { + postgresql: { + "INTEGER": "INTEGER", + "REAL": "DOUBLE PRECISION", + "TEXT": "TEXT", + "BLOB": "BYTEA", + "BOOLEAN": "BOOLEAN", + "DATE": "DATE", + "TIME": "TIME", + "DATETIME": "TIMESTAMP", + "NUMERIC": "NUMERIC", + "VARCHAR": "VARCHAR", + "CHAR": "CHAR", + "JSON": "JSON", + "POINT": "POINT", + "LINESTRING": "LINE", + "POLYGON": "POLYGON", + }, + mysql: { + "INTEGER": "INTEGER", + "REAL": "DOUBLE", + "TEXT": "TEXT", + "BLOB": "BLOB", + "BOOLEAN": "TINYINT(1)", + "DATE": "DATE", + "TIME": "TIME", + "DATETIME": "DATETIME", + "NUMERIC": "DECIMAL", + "VARCHAR": "VARCHAR", + "CHAR": "CHAR", + "JSON": "JSON", + "POINT": "POINT", + "LINESTRING": "LINESTRING", + "POLYGON": "POLYGON", + }, + }, + }; + + if (typeMapping[fromDb] && typeMapping[fromDb][toDb]) { + return typeMapping[fromDb][toDb]; + } else { + return ''; // Unsupported data type mapping + } +} + +export function convertTableSchema(table, fromDb, toDb) { + + return { + ...table, + fields: table.fields.map(field => { + let mappedType = mapDataTypes(fromDb, toDb)?.[field.type]; + + // Handle ENUM values if they exist + if (field.type === "ENUM" && [DB.POSTGRES, DB.SQLITE].includes(toDb)) { + mappedType = toDb === DB.POSTGRES ? "VARCHAR" : "TEXT"; + + if (field.values?.length) { + field.check = `${field.name} IN (${field.values.join(", ")})`; + } + } + + if (field.type === "VARCHAR" && field.check && DB.MYSQL === toDb) { + mappedType = `ENUM`; + const regex = /\(([^)]+)\)/; + const match = field.check.match(regex); + + if (match) { + field.values = match[1].split(',').map(value => value.trim()); + mappedType += `(${field.values.join(", ")})`; + } + } + + if (DB.POSTGRES === toDb && field.increment) { + mappedType = `SERIAL`; + } + + return { + ...field, + type: mappedType || field.type, + // Remove MySQL-specific properties if necessary + values: field.values || undefined + }; + }) + }; +}