diff --git a/src/components/EditorHeader/ControlPanel.jsx b/src/components/EditorHeader/ControlPanel.jsx index dbe9397..c782d98 100644 --- a/src/components/EditorHeader/ControlPanel.jsx +++ b/src/components/EditorHeader/ControlPanel.jsx @@ -758,9 +758,12 @@ export default function ControlPanel({ setImportDb(DB.SQLITE); }, }, - // { - // MariaDB: () => setModal(MODAL.IMPORT_SRC), - // }, + { + MariaDB: () => { + setModal(MODAL.IMPORT_SRC); + setImportDb(DB.MARIADB); + }, + }, // { // MSSQL: () => setModal(MODAL.IMPORT_SRC), // }, diff --git a/src/data/datatypes.js b/src/data/datatypes.js index e4f8880..4fd2736 100644 --- a/src/data/datatypes.js +++ b/src/data/datatypes.js @@ -857,49 +857,6 @@ export const sqliteTypes = { }, }; -export const mariadbTypes = { - TINYINT: { type: " ", checkDefault: (field) => {} }, - SMALLINT: { type: "", checkDefault: (field) => {} }, - MEDIUMINT: { type: "", checkDefault: (field) => {} }, - INT: { type: "", checkDefault: (field) => {} }, - INTEGER: { type: "", checkDefault: (field) => {} }, - BIGINT: { type: "", checkDefault: (field) => {} }, - DECIMAL: { type: "", checkDefault: (field) => {} }, - NUMERIC: { type: "", checkDefault: (field) => {} }, - FLOAT: { type: "", checkDefault: (field) => {} }, - DOUBLE: { type: "", checkDefault: (field) => {} }, - BIT: { type: "", checkDefault: (field) => {} }, - BOOLEAN: { type: "", checkDefault: (field) => {} }, - DATE: { type: "", checkDefault: (field) => {} }, - DATETIME: { type: "", checkDefault: (field) => {} }, - TIMESTAMP: { type: "", checkDefault: (field) => {} }, - TIME: { type: "", checkDefault: (field) => {} }, - YEAR: { type: "", checkDefault: (field) => {} }, - CHAR: { type: "", checkDefault: (field) => {} }, - VARCHAR: { type: "", checkDefault: (field) => {} }, - BINARY: { type: "", checkDefault: (field) => {} }, - VARBINARY: { type: "", checkDefault: (field) => {} }, - TINYBLOB: { type: "", checkDefault: (field) => {} }, - BLOB: { type: "", checkDefault: (field) => {} }, - MEDIUMBLOB: { type: "", checkDefault: (field) => {} }, - LONGBLOB: { type: "", checkDefault: (field) => {} }, - TINYTEXT: { type: "", checkDefault: (field) => {} }, - TEXT: { type: "", checkDefault: (field) => {} }, - MEDIUMTEXT: { type: "", checkDefault: (field) => {} }, - LONGTEXT: { type: "", checkDefault: (field) => {} }, - ENUM: { type: "", checkDefault: (field) => {} }, - SET: { type: "", checkDefault: (field) => {} }, - GEOMETRY: { type: "", checkDefault: (field) => {} }, - POINT: { type: "", checkDefault: (field) => {} }, - LINESTRING: { type: "", checkDefault: (field) => {} }, - POLYGON: { type: "", checkDefault: (field) => {} }, - MULTIPOINT: { type: "", checkDefault: (field) => {} }, - MULTILINESTRING: { type: "", checkDefault: (field) => {} }, - MULTIPOLYGON: { type: "", checkDefault: (field) => {} }, - GEOMETRYCOLLECTION: { type: "", checkDefault: (field) => {} }, - JSON: { type: "", checkDefault: (field) => {} }, -}; - export const mssqlTypes = { BIGINT: { type: "", checkDefault: (field) => {} }, INTEGER: { type: "", checkDefault: (field) => {} }, @@ -942,7 +899,7 @@ const dbToTypesBase = { postgresql: postgresTypes, sqlite: sqliteTypes, mssql: mssqlTypes, - mariadb: mariadbTypes, + mariadb: mysqlTypes, }; export const dbToTypes = new Proxy(dbToTypesBase, { diff --git a/src/utils/exportSQL/index.js b/src/utils/exportSQL/index.js index 0782bb2..a75e806 100644 --- a/src/utils/exportSQL/index.js +++ b/src/utils/exportSQL/index.js @@ -1,4 +1,5 @@ import { DB } from "../../data/constants"; +import { toMariaDB } from "./mariadb"; import { toSqlite } from "./sqlite"; export function exportSQL(diagram) { @@ -10,7 +11,7 @@ export function exportSQL(diagram) { case DB.POSTGRES: return "hi from postgres"; case DB.MARIADB: - return "hi from mariadb"; + return toMariaDB(diagram); case DB.MSSQL: return "hi from mssql"; default: diff --git a/src/utils/exportSQL/mariadb.js b/src/utils/exportSQL/mariadb.js new file mode 100644 index 0000000..e8ec85f --- /dev/null +++ b/src/utils/exportSQL/mariadb.js @@ -0,0 +1,60 @@ +import { dbToTypes } from "../../data/datatypes"; +import { parseDefault } from "./shared"; + +export function toMariaDB(diagram) { + return `${diagram.tables + .map( + (table) => + `${ + table.comment === "" ? "" : `/* ${table.comment} */\n` + }CREATE OR REPLACE TABLE \`${table.name}\` (\n${table.fields + .map( + (field) => + `${field.comment === "" ? "" : `\t-- ${field.comment}\n`}\t\`${ + field.name + }\` ${field.type}${field.notNull ? " NOT NULL" : ""}${ + field.increment ? " AUTO_INCREMENT" : "" + }${field.unique ? " UNIQUE" : ""}${ + field.default !== "" + ? ` DEFAULT ${parseDefault(field, diagram.database)}` + : "" + }${ + field.check === "" || + !dbToTypes[diagram.database][field.type].hasCheck + ? "" + : ` CHECK(${field.check})` + }`, + ) + .join(",\n")}${ + table.fields.filter((f) => f.primary).length > 0 + ? `,\n\tPRIMARY KEY(${table.fields + .filter((f) => f.primary) + .map((f) => `\`${f.name}\``) + .join(", ")})` + : "" + }\n);${ + table.indices.length > 0 + ? `\n${table.indices.map( + (i) => + `\nCREATE ${i.unique ? "UNIQUE " : ""}INDEX \`${ + i.name + }\`\nON \`${table.name}\` (${i.fields + .map((f) => `\`${f}\``) + .join(", ")});`, + )}` + : "" + }`, + ) + .join("\n")}\n${diagram.references + .map( + (r) => + `ALTER TABLE \`${ + diagram.tables[r.startTableId].name + }\`\nADD FOREIGN KEY(\`${ + diagram.tables[r.startTableId].fields[r.startFieldId].name + }\`) REFERENCES \`${diagram.tables[r.endTableId].name}\`(\`${ + diagram.tables[r.endTableId].fields[r.endFieldId].name + }\`)\nON UPDATE ${r.updateConstraint.toUpperCase()} ON DELETE ${r.deleteConstraint.toUpperCase()};`, + ) + .join("\n")}`; +} diff --git a/src/utils/importSQL/index.js b/src/utils/importSQL/index.js index 6078bbf..07604e0 100644 --- a/src/utils/importSQL/index.js +++ b/src/utils/importSQL/index.js @@ -4,6 +4,7 @@ import { tableFieldHeight, tableHeaderHeight, } from "../../data/constants"; +import { fromMariaDB } from "./mariadb"; import { fromMySQL } from "./mysql"; import { fromSQLite } from "./sqlite"; @@ -20,7 +21,7 @@ export function importSQL(ast, toDb = DB.MYSQL, diagramDb = DB.GENERIC) { diagram = { tables: [], relationships: [] }; break; case DB.MARIADB: - diagram = { tables: [], relationships: [] }; + diagram = fromMariaDB(ast, diagramDb); break; case DB.MSSQL: diagram = { tables: [], relationships: [] }; diff --git a/src/utils/importSQL/mariadb.js b/src/utils/importSQL/mariadb.js new file mode 100644 index 0000000..e458d84 --- /dev/null +++ b/src/utils/importSQL/mariadb.js @@ -0,0 +1,246 @@ +import { Cardinality, DB } from "../../data/constants"; +import { dbToTypes } from "../../data/datatypes"; +import { buildSQLFromAST } from "./shared"; + +const affinity = { + [DB.MARIADB]: new Proxy( + { INT: "INTEGER" }, + { get: (target, prop) => (prop in target ? target[prop] : "BLOB") }, + ), + [DB.GENERIC]: new Proxy( + { + INT: "INTEGER", + TINYINT: "SMALLINT", + MEDIUMINT: "INTEGER", + BIT: "BOOLEAN", + YEAR: "INTEGER", + }, + { get: (target, prop) => (prop in target ? target[prop] : "BLOB") }, + ), +}; + +export function fromMariaDB(ast, diagramDb = DB.GENERIC) { + const tables = []; + const relationships = []; + + ast.forEach((e) => { + if (e.type === "create") { + if (e.keyword === "table") { + const table = {}; + table.name = e.table[0].table; + table.comment = ""; + table.color = "#175e7a"; + table.fields = []; + table.indices = []; + table.id = tables.length; + e.create_definitions.forEach((d) => { + if (d.resource === "column") { + const field = {}; + field.name = d.column.column; + + let type = d.definition.dataType; + if (!dbToTypes[diagramDb][type]) { + type = affinity[diagramDb][type]; + } + field.type = type; + + if (d.definition.expr && d.definition.expr.type === "expr_list") { + field.values = d.definition.expr.value.map((v) => v.value); + } + field.comment = ""; + field.unique = false; + if (d.unique) field.unique = true; + field.increment = false; + if (d.auto_increment) field.increment = true; + field.notNull = false; + if (d.nullable) field.notNull = true; + field.primary = false; + if (d.primary_key) field.primary = true; + field.default = ""; + if (d.default_val) { + let defaultValue = ""; + if (d.default_val.value.type === "function") { + defaultValue = d.default_val.value.name.name[0].value; + if (d.default_val.value.args) { + defaultValue += + "(" + + d.default_val.value.args.value + .map((v) => { + if ( + v.type === "single_quote_string" || + v.type === "double_quote_string" + ) + return "'" + v.value + "'"; + return v.value; + }) + .join(", ") + + ")"; + } + } else if (d.default_val.value.type === "null") { + defaultValue = "NULL"; + } else { + defaultValue = d.default_val.value.value.toString(); + } + field.default = defaultValue; + } + if (d.definition["length"]) { + if (d.definition.scale) { + field.size = d.definition["length"] + "," + d.definition.scale; + } else { + field.size = d.definition["length"]; + } + } + field.check = ""; + if (d.check) { + field.check = buildSQLFromAST(d.check.definition[0]); + } + + table.fields.push(field); + } else if (d.resource === "constraint") { + if (d.constraint_type === "primary key") { + d.definition.forEach((c) => { + table.fields.forEach((f) => { + if (f.name === c.column && !f.primary) { + f.primary = true; + } + }); + }); + } else if (d.constraint_type === "FOREIGN KEY") { + const relationship = {}; + const startTableId = table.id; + const startTable = e.table[0].table; + const startField = d.definition[0].column; + const endTable = d.reference_definition.table[0].table; + const endField = d.reference_definition.definition[0].column; + + const endTableId = tables.findIndex((t) => t.name === endTable); + if (endTableId === -1) return; + + const endFieldId = tables[endTableId].fields.findIndex( + (f) => f.name === endField, + ); + if (endField === -1) return; + + const startFieldId = table.fields.findIndex( + (f) => f.name === startField, + ); + if (startFieldId === -1) return; + + relationship.name = startTable + "_" + startField + "_fk"; + relationship.startTableId = startTableId; + relationship.endTableId = endTableId; + relationship.endFieldId = endFieldId; + relationship.startFieldId = startFieldId; + let updateConstraint = "No action"; + let deleteConstraint = "No action"; + d.reference_definition.on_action.forEach((c) => { + if (c.type === "on update") { + updateConstraint = c.value.value; + updateConstraint = + updateConstraint[0].toUpperCase() + + updateConstraint.substring(1); + } else if (c.type === "on delete") { + deleteConstraint = c.value.value; + deleteConstraint = + deleteConstraint[0].toUpperCase() + + deleteConstraint.substring(1); + } + }); + + relationship.updateConstraint = updateConstraint; + relationship.deleteConstraint = deleteConstraint; + relationship.cardinality = Cardinality.ONE_TO_ONE; + relationships.push(relationship); + } + } + }); + table.fields.forEach((f, j) => { + f.id = j; + }); + tables.push(table); + } else if (e.keyword === "index") { + const index = {}; + index.name = e.index; + index.unique = false; + if (e.index_type === "unique") index.unique = true; + index.fields = []; + e.index_columns.forEach((f) => index.fields.push(f.column)); + + let found = -1; + tables.forEach((t, i) => { + if (found !== -1) return; + if (t.name === e.table.table) { + t.indices.push(index); + found = i; + } + }); + + if (found !== -1) tables[found].indices.forEach((i, j) => (i.id = j)); + } + } else if (e.type === "alter") { + e.expr.forEach((expr) => { + if ( + expr.action === "add" && + expr.create_definitions.constraint_type === "FOREIGN KEY" + ) { + const relationship = {}; + const startTable = e.table[0].table; + const startField = expr.create_definitions.definition[0].column; + const endTable = + expr.create_definitions.reference_definition.table[0].table; + const endField = + expr.create_definitions.reference_definition.definition[0].column; + let updateConstraint = "No action"; + let deleteConstraint = "No action"; + expr.create_definitions.reference_definition.on_action.forEach( + (c) => { + if (c.type === "on update") { + updateConstraint = c.value.value; + updateConstraint = + updateConstraint[0].toUpperCase() + + updateConstraint.substring(1); + } else if (c.type === "on delete") { + deleteConstraint = c.value.value; + deleteConstraint = + deleteConstraint[0].toUpperCase() + + deleteConstraint.substring(1); + } + }, + ); + + const startTableId = tables.findIndex((t) => t.name === startTable); + if (startTable === -1) return; + + const endTableId = tables.findIndex((t) => t.name === endTable); + if (endTableId === -1) return; + + const endFieldId = tables[endTableId].fields.findIndex( + (f) => f.name === endField, + ); + if (endField === -1) return; + + const startFieldId = tables[startTableId].fields.findIndex( + (f) => f.name === startField, + ); + if (startFieldId === -1) return; + + relationship.name = startTable + "_" + startField + "_fk"; + relationship.startTableId = startTableId; + relationship.startFieldId = startFieldId; + relationship.endTableId = endTableId; + relationship.endFieldId = endFieldId; + relationship.updateConstraint = updateConstraint; + relationship.deleteConstraint = deleteConstraint; + relationship.cardinality = Cardinality.ONE_TO_ONE; + relationships.push(relationship); + + relationships.forEach((r, i) => (r.id = i)); + } + }); + } + }); + + relationships.forEach((r, i) => (r.id = i)); + + return { tables, relationships }; +}