diff --git a/src/components/EditorHeader/ControlPanel.jsx b/src/components/EditorHeader/ControlPanel.jsx index e89dfea..dbe9397 100644 --- a/src/components/EditorHeader/ControlPanel.jsx +++ b/src/components/EditorHeader/ControlPanel.jsx @@ -76,6 +76,7 @@ export default function ControlPanel({ const [sidesheet, setSidesheet] = useState(SIDESHEET.NONE); const [prevTitle, setPrevTitle] = useState(title); const [showEditName, setShowEditName] = useState(false); + const [importDb, setImportDb] = useState(""); const [exportData, setExportData] = useState({ data: null, filename: `${title}_${new Date().toISOString()}`, @@ -743,13 +744,19 @@ export default function ControlPanel({ ...(database === DB.GENERIC && { children: [ { - MySQL: () => setModal(MODAL.IMPORT_SRC), + MySQL: () => { + setModal(MODAL.IMPORT_SRC); + setImportDb(DB.MYSQL); + }, }, // { // PostgreSQL: () => setModal(MODAL.IMPORT_SRC), // }, { - SQLite: () => setModal(MODAL.IMPORT_SRC), + SQLite: () => { + setModal(MODAL.IMPORT_SRC); + setImportDb(DB.SQLITE); + }, }, // { // MariaDB: () => setModal(MODAL.IMPORT_SRC), @@ -762,7 +769,7 @@ export default function ControlPanel({ function: () => { if (database === DB.GENERIC) return; - setModal(MODAL.IMPORT_SRC) + setModal(MODAL.IMPORT_SRC); }, }, export_as: { @@ -1260,6 +1267,7 @@ export default function ControlPanel({ setDiagramId={setDiagramId} setModal={setModal} prevTitle={prevTitle} + importDb={importDb} /> { diff --git a/src/data/datatypes.js b/src/data/datatypes.js index 7af2d9a..f4dae25 100644 --- a/src/data/datatypes.js +++ b/src/data/datatypes.js @@ -366,8 +366,8 @@ export const postgresTypes = { }; export const sqliteTypes = { - INT: { - type: "INT", + INTEGER: { + type: "INTEGER", checkDefault: (field) => { return intRegex.test(field.default); }, @@ -386,23 +386,6 @@ export const sqliteTypes = { hasPrecision: true, defaultSize: null, }, - TEXT: { - type: "TEXT", - checkDefault: (field) => false, - hasCheck: false, - isSized: true, - hasPrecision: false, - defaultSize: 65535, - hasQuotes: true, - }, - BLOB: { - type: "BLOB", - checkDefault: (field) => false, - isSized: false, - hasCheck: false, - hasPrecision: false, - defaultSize: null, - }, NUMERIC: { type: "NUMERIC", checkDefault: (field) => { @@ -426,6 +409,42 @@ export const sqliteTypes = { hasPrecision: false, defaultSize: null, }, + VARCHAR: { + type: "VARCHAR", + checkDefault: (field) => { + if (strHasQuotes(field.default)) { + return field.default.length - 2 <= field.size; + } + return field.default.length <= field.size; + }, + hasCheck: true, + isSized: true, + hasPrecision: false, + defaultSize: 255, + hasQuotes: true, + }, + TEXT: { + type: "TEXT", + checkDefault: (field) => { + if (strHasQuotes(field.default)) { + return field.default.length - 2 <= field.size; + } + return field.default.length <= field.size; + }, hasCheck: true, + isSized: true, + hasPrecision: false, + defaultSize: 65535, + hasQuotes: true, + }, + BLOB: { + type: "BLOB", + checkDefault: (field) => false, + isSized: false, + hasCheck: false, + hasPrecision: false, + defaultSize: null, + }, + TIME: { type: "TIME", checkDefault: (field) => { diff --git a/src/utils/importSQL/index.js b/src/utils/importSQL/index.js index 969f70f..6078bbf 100644 --- a/src/utils/importSQL/index.js +++ b/src/utils/importSQL/index.js @@ -1,19 +1,54 @@ -import { DB } from "../../data/constants"; +import { + DB, + tableColorStripHeight, + tableFieldHeight, + tableHeaderHeight, +} from "../../data/constants"; import { fromMySQL } from "./mysql"; +import { fromSQLite } from "./sqlite"; -export function importSQL(ast, database = DB.MYSQL) { - switch (database) { +export function importSQL(ast, toDb = DB.MYSQL, diagramDb = DB.GENERIC) { + let diagram = { tables: [], relationships: [] }; + switch (toDb) { case DB.SQLITE: - return { tables: [], relationships: [] }; + diagram = fromSQLite(ast, diagramDb); + break; case DB.MYSQL: - return fromMySQL(ast); + diagram = fromMySQL(ast, diagramDb); + break; case DB.POSTGRES: - return { tables: [], relationships: [] }; + diagram = { tables: [], relationships: [] }; + break; case DB.MARIADB: - return { tables: [], relationships: [] }; + diagram = { tables: [], relationships: [] }; + break; case DB.MSSQL: - return { tables: [], relationships: [] }; + diagram = { tables: [], relationships: [] }; + break; default: - return { tables: [], relationships: [] }; + diagram = { tables: [], relationships: [] }; + break; } + + let maxHeight = -1; + const tableWidth = 200; + const gapX = 54; + const gapY = 40; + diagram.tables.forEach((table, i) => { + if (i < diagram.tables.length / 2) { + table.x = i * tableWidth + (i + 1) * gapX; + table.y = gapY; + const height = + table.fields.length * tableFieldHeight + + tableHeaderHeight + + tableColorStripHeight; + maxHeight = Math.max(height, maxHeight); + } else { + const index = diagram.tables.length - i - 1; + table.x = index * tableWidth + (index + 1) * gapX; + table.y = maxHeight + 2 * gapY; + } + }); + + return diagram; } diff --git a/src/utils/importSQL/mysql.js b/src/utils/importSQL/mysql.js index 97283f5..505257d 100644 --- a/src/utils/importSQL/mysql.js +++ b/src/utils/importSQL/mysql.js @@ -1,47 +1,7 @@ -import { - Cardinality, - tableColorStripHeight, - tableFieldHeight, - tableHeaderHeight, -} from "../../data/constants"; +import { Cardinality, DB } from "../../data/constants"; +import { buildSQLFromAST } from "./shared"; -function buildSQLFromAST(ast) { - if (ast.type === "binary_expr") { - const leftSQL = buildSQLFromAST(ast.left); - const rightSQL = buildSQLFromAST(ast.right); - return `${leftSQL} ${ast.operator} ${rightSQL}`; - } - - if (ast.type === "function") { - let expr = ""; - expr = ast.name; - if (ast.args) { - expr += - "(" + - ast.args.value - .map((v) => { - if (v.type === "column_ref") return "`" + v.column + "`"; - if ( - v.type === "single_quote_string" || - v.type === "double_quote_string" - ) - return "'" + v.value + "'"; - return v.value; - }) - .join(", ") + - ")"; - } - return expr; - } else if (ast.type === "column_ref") { - return "`" + ast.column + "`"; - } else if (ast.type === "expr_list") { - return ast.value.map((v) => v.value).join(" AND "); - } else { - return typeof ast.value === "string" ? "'" + ast.value + "'" : ast.value; - } -} - -export function fromMySQL(ast) { +export function fromMySQL(ast, diagramDb = DB.GENERIC) { const tables = []; const relationships = []; @@ -258,25 +218,5 @@ export function fromMySQL(ast) { relationships.forEach((r, i) => (r.id = i)); - let maxHeight = -1; - const tableWidth = 200; - const gapX = 54; - const gapY = 40; - tables.forEach((table, i) => { - if (i < tables.length / 2) { - table.x = i * tableWidth + (i + 1) * gapX; - table.y = gapY; - const height = - table.fields.length * tableFieldHeight + - tableHeaderHeight + - tableColorStripHeight; - maxHeight = Math.max(height, maxHeight); - } else { - const index = tables.length - i - 1; - table.x = index * tableWidth + (index + 1) * gapX; - table.y = maxHeight + 2 * gapY; - } - }); - return { tables, relationships }; } diff --git a/src/utils/importSQL/shared.js b/src/utils/importSQL/shared.js new file mode 100644 index 0000000..0c27310 --- /dev/null +++ b/src/utils/importSQL/shared.js @@ -0,0 +1,35 @@ +export function buildSQLFromAST(ast) { + if (ast.type === "binary_expr") { + const leftSQL = buildSQLFromAST(ast.left); + const rightSQL = buildSQLFromAST(ast.right); + return `${leftSQL} ${ast.operator} ${rightSQL}`; + } + + if (ast.type === "function") { + let expr = ""; + expr = ast.name; + if (ast.args) { + expr += + "(" + + ast.args.value + .map((v) => { + if (v.type === "column_ref") return "`" + v.column + "`"; + if ( + v.type === "single_quote_string" || + v.type === "double_quote_string" + ) + return "'" + v.value + "'"; + return v.value; + }) + .join(", ") + + ")"; + } + return expr; + } else if (ast.type === "column_ref") { + return "`" + ast.column + "`"; + } else if (ast.type === "expr_list") { + return ast.value.map((v) => v.value).join(" AND "); + } else { + return typeof ast.value === "string" ? "'" + ast.value + "'" : ast.value; + } +} diff --git a/src/utils/importSQL/sqlite.js b/src/utils/importSQL/sqlite.js new file mode 100644 index 0000000..3426027 --- /dev/null +++ b/src/utils/importSQL/sqlite.js @@ -0,0 +1,191 @@ +import { Cardinality, DB } from "../../data/constants"; +import { dbToTypes } from "../../data/datatypes"; +import { buildSQLFromAST } from "./shared"; + +export const affinity = new Proxy( + { + INT: "INTEGER", + TINYINT: "INTEGER", + SMALLINT: "INTEGER", + MEDIUMINT: "INTEGER", + BIGINT: "INTEGER", + "UNSIGNED BIG INT": "INTEGER", + INT2: "INTEGER", + INT8: "INTEGER", + CHARACTER: "TEXT", + NCHARACTER: "TEXT", + NVARCHAR: "VARCHAR", + DOUBLE: "REAL", + FLOAT: "REAL", + }, + { + get: (target, prop) => (prop in target ? target[prop] : "BLOB"), + }, +); + +export function fromSQLite(ast, diagramDb = DB.GENERIC) { + console.log(ast); + 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[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)); + } + } + }); + + relationships.forEach((r, i) => (r.id = i)); + + return { tables, relationships }; +}