From d8128f50103e62f0f934091eec4e613a34638c6f Mon Sep 17 00:00:00 2001 From: 1ilit Date: Sat, 22 Jun 2024 04:13:44 +0300 Subject: [PATCH] Import from postgres --- src/components/EditorHeader/ControlPanel.jsx | 9 +- src/data/datatypes.js | 459 +++++++++++++++++-- src/utils/exportSQL/index.js | 3 +- src/utils/exportSQL/postgres.js | 91 ++++ src/utils/importSQL/index.js | 3 +- src/utils/importSQL/postgres.js | 245 ++++++++++ 6 files changed, 762 insertions(+), 48 deletions(-) create mode 100644 src/utils/exportSQL/postgres.js create mode 100644 src/utils/importSQL/postgres.js diff --git a/src/components/EditorHeader/ControlPanel.jsx b/src/components/EditorHeader/ControlPanel.jsx index 34f3121..efa781a 100644 --- a/src/components/EditorHeader/ControlPanel.jsx +++ b/src/components/EditorHeader/ControlPanel.jsx @@ -749,9 +749,12 @@ export default function ControlPanel({ setImportDb(DB.MYSQL); }, }, - // { - // PostgreSQL: () => setModal(MODAL.IMPORT_SRC), - // }, + { + PostgreSQL: () => { + setModal(MODAL.IMPORT_SRC); + setImportDb(DB.POSTGRES); + }, + }, { SQLite: () => { setModal(MODAL.IMPORT_SRC); diff --git a/src/data/datatypes.js b/src/data/datatypes.js index 4fd2736..59e6780 100644 --- a/src/data/datatypes.js +++ b/src/data/datatypes.js @@ -680,49 +680,422 @@ export const mysqlTypes = { }; export const postgresTypes = { - SMALLINT: { type: "SMALLINT", checkDefault: (field) => {} }, - INTEGER: { type: "INTEGER", checkDefault: (field) => {} }, - BIGINT: { type: "BIGINT", checkDefault: (field) => {} }, - DECIMAL: { type: "DECIMAL", checkDefault: (field) => {} }, - NUMERIC: { type: "NUMERIC", checkDefault: (field) => {} }, - REAL: { type: "REAL", checkDefault: (field) => {} }, - "DOUBLE PRECISION": { type: "DOUBLE PRECISION", checkDefault: (field) => {} }, - SMALLSERIAL: { type: "SMALLSERIAL", checkDefault: (field) => {} }, - SERIAL: { type: "SERIAL", checkDefault: (field) => {} }, - BIGSERIAL: { type: "BIGSERIAL", checkDefault: (field) => {} }, - MONEY: { type: "MONEY", checkDefault: (field) => {} }, - CHARACTER: { type: "CHARACTER", checkDefault: (field) => {} }, - CHAR: { type: "CHAR", checkDefault: (field) => {} }, - VARCHAR: { type: "VARCHAR", checkDefault: (field) => {} }, - TEXT: { type: "TEXT", checkDefault: (field) => {} }, - BYTEA: { type: "BYTEA", checkDefault: (field) => {} }, - DATE: { type: "DATE", checkDefault: (field) => {} }, - TIME: { type: "TIME", checkDefault: (field) => {} }, - TIMESTAMP: { type: "TIMESTAMP", checkDefault: (field) => {} }, - TIMESTAMPTZ: { type: "TIMESTAMPTZ", checkDefault: (field) => {} }, - INTERVAL: { type: "INTERVAL", checkDefault: (field) => {} }, - BOOLEAN: { type: "BOOLEAN", checkDefault: (field) => {} }, - ENUM: { type: "ENUM", checkDefault: (field) => {} }, - POINT: { type: "POINT", checkDefault: (field) => {} }, - LINE: { type: "LINE", checkDefault: (field) => {} }, - LSEG: { type: "LSEG", checkDefault: (field) => {} }, - BOX: { type: "BOX", checkDefault: (field) => {} }, - PATH: { type: "PATH", checkDefault: (field) => {} }, - POLYGON: { type: "POLYGON", checkDefault: (field) => {} }, - CIRCLE: { type: "CIRCLE", checkDefault: (field) => {} }, - CIDR: { type: "CIDR", checkDefault: (field) => {} }, - INET: { type: "INET", checkDefault: (field) => {} }, - MACADDR: { type: "MACADDR", checkDefault: (field) => {} }, - MACADDR8: { type: "", checkDefault: (field) => {} }, - BIT: { type: "", checkDefault: (field) => {} }, - VARBIT: { type: "", checkDefault: (field) => {} }, - TSVECTOR: { type: "", checkDefault: (field) => {} }, - TSQUERY: { type: "", checkDefault: (field) => {} }, - JSON: { type: "", checkDefault: (field) => {} }, - JSONB: { type: "", checkDefault: (field) => {} }, - UUID: { type: "", checkDefault: (field) => {} }, - XML: { type: "", checkDefault: (field) => {} }, - ARRAY: { type: "", checkDefault: (field) => {} }, + SMALLINT: { + type: "SMALLINT", + checkDefault: (field) => { + return intRegex.test(field.default); + }, + hasCheck: true, + isSized: false, + hasPrecision: false, + canIncrement: true, + }, + INTEGER: { + type: "INTEGER", + checkDefault: (field) => { + return intRegex.test(field.default); + }, + hasCheck: true, + isSized: false, + hasPrecision: false, + canIncrement: true, + }, + BIGINT: { + type: "BIGINT", + checkDefault: (field) => { + return intRegex.test(field.default); + }, + hasCheck: true, + isSized: false, + hasPrecision: false, + canIncrement: true, + }, + DECIMAL: { + type: "DECIMAL", + checkDefault: (field) => { + return doubleRegex.test(field.default); + }, + hasCheck: true, + isSized: false, + hasPrecision: true, + }, + NUMERIC: { + type: "NUMERIC", + checkDefault: (field) => { + return doubleRegex.test(field.default); + }, + hasCheck: true, + isSized: false, + hasPrecision: true, + }, + REAL: { + type: "REAL", + checkDefault: (field) => { + return doubleRegex.test(field.default); + }, + hasCheck: true, + isSized: false, + hasPrecision: true, + }, + "DOUBLE PRECISION": { + type: "DOUBLE PRECISION", + checkDefault: (field) => { + return doubleRegex.test(field.default); + }, + hasCheck: true, + isSized: false, + hasPrecision: true, + }, + SMALLSERIAL: { + type: "SMALLSERIAL", + checkDefault: (field) => { + return intRegex.test(field.default); + }, + hasCheck: true, + isSized: false, + hasPrecision: false, + }, + SERIAL: { + type: "SERIAL", + checkDefault: (field) => { + return intRegex.test(field.default); + }, + hasCheck: true, + isSized: false, + hasPrecision: false, + }, + BIGSERIAL: { + type: "BIGSERIAL", + checkDefault: (field) => { + return intRegex.test(field.default); + }, + hasCheck: true, + isSized: false, + hasPrecision: false, + }, + MONEY: { + type: "MONEY", + checkDefault: (field) => { + return doubleRegex.test(field.default); + }, + hasCheck: true, + isSized: false, + hasPrecision: true, + }, + CHAR: { + type: "CHAR", + 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: 1, + hasQuotes: true, + }, + 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, + }, + BYTEA: { + type: "BYTEA", + checkDefault: (field) => { + return /^[0-9a-fA-F]*$/.test(field.default); + }, + hasCheck: false, + isSized: false, + hasPrecision: false, + defaultSize: null, + hasQuotes: true, + }, + DATE: { + type: "DATE", + checkDefault: (field) => { + return /^\d{4}-\d{2}-\d{2}$/.test(field.default); + }, + hasCheck: false, + isSized: false, + hasPrecision: false, + hasQuotes: true, + }, + TIME: { + type: "TIME", + checkDefault: (field) => { + return /^(?:[01]?\d|2[0-3]):[0-5]?\d:[0-5]?\d$/.test(field.default); + }, + hasCheck: false, + isSized: false, + hasPrecision: false, + hasQuotes: true, + }, + TIMESTAMP: { + type: "TIMESTAMP", + checkDefault: (field) => { + if (field.default.toUpperCase() === "CURRENT_TIMESTAMP") { + return true; + } + if (!/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/.test(field.default)) { + return false; + } + const content = field.default.split(" "); + const date = content[0].split("-"); + return parseInt(date[0]) >= 1970 && parseInt(date[0]) <= 2038; + }, + hasCheck: false, + isSized: false, + hasPrecision: false, + hasQuotes: true, + }, + TIMESTAMPTZ: { + type: "TIMESTAMPTZ", + checkDefault: (field) => { + if (field.default.toUpperCase() === "CURRENT_TIMESTAMP") { + return true; + } + return /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}([+-]\d{2}:\d{2})?$/.test( + field.default, + ); + }, + hasCheck: false, + isSized: false, + hasPrecision: false, + hasQuotes: true, + }, + INTERVAL: { + type: "INTERVAL", + checkDefault: (field) => /^['"\d\s\\-]+$/.test(field.default), + hasCheck: false, + isSized: false, + hasPrecision: false, + hasQuotes: true, + }, + BOOLEAN: { + type: "BOOLEAN", + checkDefault: (field) => /^(true|false)$/i.test(field.default), + hasCheck: false, + isSized: false, + hasPrecision: false, + hasQuotes: false, + }, + ENUM: { + type: "ENUM", + checkDefault: (field) => true, + hasCheck: false, + isSized: false, + hasPrecision: false, + hasQuotes: true, + }, + POINT: { + type: "POINT", + checkDefault: (field) => /^\(\d+,\d+\)$/.test(field.default), + hasCheck: false, + isSized: false, + hasPrecision: false, + hasQuotes: false, + }, + LINE: { + type: "LINE", + checkDefault: (field) => /^(\(\d+,\d+\),)+\(\d+,\d+\)$/.test(field.default), + hasCheck: false, + isSized: false, + hasPrecision: false, + hasQuotes: false, + }, + LSEG: { + type: "LSEG", + checkDefault: (field) => /^(\(\d+,\d+\),)+\(\d+,\d+\)$/.test(field.default), + hasCheck: false, + isSized: false, + hasPrecision: false, + hasQuotes: false, + }, + BOX: { + type: "BOX", + checkDefault: (field) => + /^\(\d+(\.\d+)?,\d+(\.\d+)?\),\(\d+(\.\d+)?,\d+(\.\d+)?\)$/.test( + field.default, + ), + hasCheck: false, + isSized: false, + hasPrecision: false, + hasQuotes: true, + }, + PATH: { + type: "PATH", + checkDefault: (field) => + /^\((\d+(\.\d+)?,\d+(\.\d+)?(,\d+(\.\d+)?,\d+(\.\d+)?)*?)\)$/.test( + field.default, + ), + hasCheck: false, + isSized: false, + hasPrecision: false, + hasQuotes: true, + }, + POLYGON: { + type: "POLYGON", + checkDefault: (field) => + /^\((\d+(\.\d+)?,\d+(\.\d+)?(,\d+(\.\d+)?,\d+(\.\d+)?)*?)\)$/.test( + field.default, + ), + hasCheck: false, + isSized: false, + hasPrecision: false, + hasQuotes: true, + }, + CIRCLE: { + type: "CIRCLE", + checkDefault: (field) => + /^<\(\d+(\.\d+)?,\d+(\.\d+)?\),\d+(\.\d+)?\\>$/.test(field.default), + hasCheck: false, + isSized: false, + hasPrecision: false, + hasQuotes: true, + }, + CIDR: { + type: "CIDR", + checkDefault: (field) => + /^(\d{1,3}\.){3}\d{1,3}\/\d{1,2}$/.test(field.default), + hasCheck: false, + isSized: false, + hasPrecision: false, + hasQuotes: true, + }, + INET: { + type: "INET", + checkDefault: (field) => + /^(\d{1,3}\.){3}\d{1,3}(\/\d{1,2})?$/.test(field.default), + hasCheck: false, + isSized: false, + hasPrecision: false, + hasQuotes: true, + }, + MACADDR: { + type: "MACADDR", + checkDefault: (field) => + /^([A-Fa-f0-9]{2}:){5}[A-Fa-f0-9]{2}$/.test(field.default), + hasCheck: false, + isSized: false, + hasPrecision: false, + hasQuotes: true, + }, + MACADDR8: { + type: "MACADDR8", + checkDefault: (field) => + /^([A-Fa-f0-9]{2}:){7}[A-Fa-f0-9]{2}$/.test(field.default), + hasCheck: false, + isSized: false, + hasPrecision: false, + hasQuotes: true, + }, + BIT: { + type: "BIT", + checkDefault: (field) => /^[01]{1,}$/.test(field.default), + hasCheck: true, + isSized: true, + hasPrecision: false, + defaultSize: 1, + hasQuotes: false, + }, + VARBIT: { + type: "VARBIT", + checkDefault: (field) => /^[01]*$/.test(field.default), + hasCheck: true, + isSized: true, + hasPrecision: false, + defaultSize: 1, + hasQuotes: false, + }, + TSVECTOR: { + type: "TSVECTOR", + checkDefault: (field) => /^[A-Za-z0-9: ]*$/.test(field.default), + hasCheck: false, + isSized: false, + hasPrecision: false, + hasQuotes: false, + }, + TSQUERY: { + type: "TSQUERY", + checkDefault: (field) => /^[A-Za-z0-9: &|!()]*$/.test(field.default), + hasCheck: false, + isSized: false, + hasPrecision: false, + hasQuotes: false, + }, + JSON: { + type: "JSON", + checkDefault: (field) => true, + hasCheck: false, + isSized: false, + hasPrecision: false, + hasQuotes: true, + noDefault: true, + }, + JSONB: { + type: "JSONB", + checkDefault: (field) => true, + hasCheck: false, + isSized: false, + hasPrecision: false, + hasQuotes: true, + noDefault: true, + }, + UUID: { + type: "UUID", + checkDefault: (field) => + /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test( + field.default, + ), + hasCheck: false, + isSized: false, + hasPrecision: false, + hasQuotes: true, + noDefault: true, + }, + XML: { + type: "XML", + checkDefault: (field) => true, + hasCheck: false, + isSized: false, + hasPrecision: false, + hasQuotes: true, + noDefault: true, + }, + ARRAY: { + type: "ARRAY", + checkDefault: (field) => true, + hasCheck: false, + isSized: false, + hasPrecision: false, + hasQuotes: false, + noDefault: true, + }, }; export const sqliteTypes = { diff --git a/src/utils/exportSQL/index.js b/src/utils/exportSQL/index.js index 5eea279..e6b3743 100644 --- a/src/utils/exportSQL/index.js +++ b/src/utils/exportSQL/index.js @@ -1,6 +1,7 @@ import { DB } from "../../data/constants"; import { toMariaDB } from "./mariadb"; import { toMySQL } from "./mysql"; +import { toPostgres } from "./postgres"; import { toSqlite } from "./sqlite"; export function exportSQL(diagram) { @@ -10,7 +11,7 @@ export function exportSQL(diagram) { case DB.MYSQL: return toMySQL(diagram); case DB.POSTGRES: - return "hi from postgres"; + return toPostgres(diagram); case DB.MARIADB: return toMariaDB(diagram); case DB.MSSQL: diff --git a/src/utils/exportSQL/postgres.js b/src/utils/exportSQL/postgres.js new file mode 100644 index 0000000..caf3852 --- /dev/null +++ b/src/utils/exportSQL/postgres.js @@ -0,0 +1,91 @@ +import { dbToTypes } from "../../data/datatypes"; +import { parseDefault } from "./shared"; + +export function toPostgres(diagram) { + return `${diagram.types.map((type) => { + const typeStatements = type.fields + .filter((f) => f.type === "ENUM" || f.type === "SET") + .map( + (f) => + `CREATE TYPE "${f.name}_t" AS ENUM (${f.values + .map((v) => `'${v}'`) + .join(", ")});\n`, + ); + if (typeStatements.length > 0) { + return ( + typeStatements.join("") + + `${ + type.comment === "" ? "" : `/**\n${type.comment}\n*/\n` + }CREATE TYPE ${type.name} AS (\n${type.fields + .map((f) => `\t${f.name} ${f.type}`) + .join("\n")}\n);` + ); + } else { + return `${ + type.comment === "" ? "" : `/**\n${type.comment}\n*/\n` + }CREATE TYPE ${type.name} AS (\n${type.fields + .map((f) => `\t${f.name} ${f.type}`) + .join("\n")}\n);`; + } + })}\n${diagram.tables + .map( + (table) => + `${table.comment === "" ? "" : `/**\n${table.comment}\n*/\n`}${ + table.fields.filter((f) => f.type === "ENUM" || f.type === "SET") + .length > 0 + ? `${table.fields + .filter((f) => f.type === "ENUM" || f.type === "SET") + .map( + (f) => + `CREATE TYPE "${f.name}_t" AS ENUM (${f.values + .map((v) => `'${v}'`) + .join(", ")});\n\n`, + )}` + : "" + }CREATE TABLE "${table.name}" (\n${table.fields + .map( + (field) => + `${field.comment === "" ? "" : `\t-- ${field.comment}\n`}\t"${ + field.name + }" ${field.type}${field.notNull ? " NOT NULL" : ""}${ + 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);\n${ + table.indices.length > 0 + ? `${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 07604e0..59a9b00 100644 --- a/src/utils/importSQL/index.js +++ b/src/utils/importSQL/index.js @@ -6,6 +6,7 @@ import { } from "../../data/constants"; import { fromMariaDB } from "./mariadb"; import { fromMySQL } from "./mysql"; +import { fromPostgres } from "./postgres"; import { fromSQLite } from "./sqlite"; export function importSQL(ast, toDb = DB.MYSQL, diagramDb = DB.GENERIC) { @@ -18,7 +19,7 @@ export function importSQL(ast, toDb = DB.MYSQL, diagramDb = DB.GENERIC) { diagram = fromMySQL(ast, diagramDb); break; case DB.POSTGRES: - diagram = { tables: [], relationships: [] }; + diagram = fromPostgres(ast, diagramDb); break; case DB.MARIADB: diagram = fromMariaDB(ast, diagramDb); diff --git a/src/utils/importSQL/postgres.js b/src/utils/importSQL/postgres.js new file mode 100644 index 0000000..61703a3 --- /dev/null +++ b/src/utils/importSQL/postgres.js @@ -0,0 +1,245 @@ +import { Cardinality, DB } from "../../data/constants"; +import { dbToTypes } from "../../data/datatypes"; +import { buildSQLFromAST } from "./shared"; + +const affinity = { + [DB.POSTGRES]: new Proxy( + { INT: "INTEGER" }, + { get: (target, prop) => (prop in target ? target[prop] : "BLOB") }, + ), + [DB.GENERIC]: new Proxy( + { + INT: "INTEGER", + MEDIUMINT: "INTEGER", + BIT: "BOOLEAN", + }, + { get: (target, prop) => (prop in target ? target[prop] : "BLOB") }, + ), +}; + +export function fromPostgres(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.expr.value; + + 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.expr.value; + const endTable = d.reference_definition.table[0].table; + const endField = + d.reference_definition.definition[0].column.expr.value; + + 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 }; +}