diff --git a/src/assets/oraclesql-icon.png b/src/assets/oraclesql-icon.png new file mode 100644 index 00000000..cdeb3a31 Binary files /dev/null and b/src/assets/oraclesql-icon.png differ diff --git a/src/assets/oraclesql.png b/src/assets/oraclesql.png new file mode 100644 index 00000000..cb7686e4 Binary files /dev/null and b/src/assets/oraclesql.png differ diff --git a/src/components/EditorHeader/ControlPanel.jsx b/src/components/EditorHeader/ControlPanel.jsx index c274878b..c824f65f 100644 --- a/src/components/EditorHeader/ControlPanel.jsx +++ b/src/components/EditorHeader/ControlPanel.jsx @@ -29,6 +29,7 @@ import { jsonToSQLite, jsonToMariaDB, jsonToSQLServer, + jsonToOracleSQL, } from "../../utils/exportSQL/generic"; import { ObjectType, @@ -882,6 +883,22 @@ export default function ControlPanel({ })); }, }, + { + OracleSQL: () => { + setModal(MODAL.CODE); + const src = jsonToOracleSQL({ + tables: tables, + references: relationships, + types: types, + database: database, + }); + setExportData((prev) => ({ + ...prev, + data: src, + extension: "sql", + })); + }, + }, ], }), function: () => { diff --git a/src/data/constants.js b/src/data/constants.js index b07605e1..469a7cc7 100644 --- a/src/data/constants.js +++ b/src/data/constants.js @@ -112,5 +112,6 @@ export const DB = { MSSQL: "transactsql", SQLITE: "sqlite", MARIADB: "mariadb", + ORACLESQL: "oraclesql", GENERIC: "generic", }; diff --git a/src/data/databases.js b/src/data/databases.js index 55c9187c..c32a74d6 100644 --- a/src/data/databases.js +++ b/src/data/databases.js @@ -3,6 +3,7 @@ import postgresImage from "../assets/postgres-icon.png"; import sqliteImage from "../assets/sqlite-icon.png"; import mariadbImage from "../assets/mariadb-icon.png"; import mssqlImage from "../assets/mssql-icon.png"; +import oraclesqlImage from "../assets/oraclesql-icon.png"; import i18n from "../i18n/i18n"; import { DB } from "./constants"; @@ -40,6 +41,14 @@ export const databases = new Proxy( image: mssqlImage, hasTypes: false, }, + [DB.ORACLESQL]: { + name: "Oracle SQL", + label: DB.ORACLESQL, + image: oraclesqlImage, + hasTypes: false, + hasEnums: false, + hasArrays: false, + }, [DB.GENERIC]: { name: i18n.t("generic"), label: DB.GENERIC, diff --git a/src/data/datatypes.js b/src/data/datatypes.js index 77c0cd72..7da391be 100644 --- a/src/data/datatypes.js +++ b/src/data/datatypes.js @@ -55,6 +55,16 @@ const defaultTypesBase = { isSized: false, hasPrecision: true, }, + NUMBER: { + type: "NUMBER", + checkDefault: (field) => { + return /^-?\d+(\.\d+)?$/.test(field.default); + }, + hasCheck: true, + isSized: false, + hasPrecision: true, + canIncrement: false, + }, FLOAT: { type: "FLOAT", checkDefault: (field) => { @@ -110,6 +120,20 @@ const defaultTypesBase = { defaultSize: 255, hasQuotes: true, }, + VARCHAR2: { + type: "VARCHAR2", + 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: 225, + hasQuotes: true, + }, TEXT: { type: "TEXT", checkDefault: (field) => true, @@ -223,6 +247,22 @@ const defaultTypesBase = { hasPrecision: false, noDefault: true, }, + CLOB: { + type: "CLOB", + checkDefault: (field) => true, + isSized: false, + hasCheck: false, + hasPrecision: false, + noDefault: true, + }, + NCLOB: { + type: "NCLOB", + checkDefault: (field) => true, + isSized: false, + hasCheck: false, + hasPrecision: false, + noDefault: true, + }, JSON: { type: "JSON", checkDefault: (field) => true, @@ -1616,6 +1656,155 @@ export const mssqlTypes = new Proxy(mssqlTypesBase, { get: (target, prop) => (prop in target ? target[prop] : false), }); +const oraclesqlTypesBase = { + NUMBER: { + type: "NUMBER", + checkDefault: (field) => { + return /^-?\d+(\.\d+)?$/.test(field.default); + }, + hasCheck: true, + isSized: false, + hasPrecision: true, + canIncrement: false, + }, + VARCHAR2: { + type: "VARCHAR2", + 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: 4000, + hasQuotes: 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, + }, + CLOB: { + type: "CLOB", + checkDefault: (field) => true, + isSized: false, + hasCheck: false, + hasPrecision: false, + noDefault: true, + }, + NCLOB: { + type: "NCLOB", + checkDefault: (field) => true, + isSized: false, + hasCheck: false, + hasPrecision: false, + noDefault: true, + }, + BLOB: { + type: "BLOB", + checkDefault: (field) => true, + isSized: false, + hasCheck: false, + hasPrecision: false, + noDefault: true, + }, + DATE: { + type: "DATE", + checkDefault: (field) => { + return /^\d{4}-\d{2}-\d{2}$/.test(field.default); + }, + hasCheck: false, + isSized: false, + hasPrecision: false, + hasQuotes: true, + }, + TIMESTAMP: { + type: "TIMESTAMP", + checkDefault: (field) => { + if (field.default.toUpperCase() === "CURRENT_TIMESTAMP") { + return true; + } + return /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}(?:\.\d+)?$/.test( + field.default, + ); + }, + hasCheck: false, + isSized: false, + hasPrecision: true, + hasQuotes: true, + }, + INTERVAL: { + type: "INTERVAL", + checkDefault: (field) => { + return /^INTERVAL\s'\d+'(\s+DAY|HOUR|MINUTE|SECOND)?$/.test( + field.default, + ); + }, + hasCheck: false, + isSized: false, + hasPrecision: false, + hasQuotes: true, + }, + FLOAT: { + type: "FLOAT", + checkDefault: (field) => { + return /^-?\d+(\.\d+)?$/.test(field.default); + }, + hasCheck: true, + isSized: false, + hasPrecision: true, + }, + DOUBLE: { + type: "DOUBLE", + checkDefault: (field) => { + return /^-?\d+(\.\d+)?$/.test(field.default); + }, + hasCheck: true, + isSized: false, + hasPrecision: true, + }, + BOOLEAN: { + type: "BOOLEAN", + checkDefault: (field) => { + return ( + field.default === "0" || + field.default === "1" || + field.default.toUpperCase() === "TRUE" || + field.default.toUpperCase() === "FALSE" + ); + }, + hasCheck: false, + isSized: false, + hasPrecision: false, + }, + RAW: { + type: "RAW", + checkDefault: (field) => { + return /^[0-9A-Fa-f]+$/.test(field.default); + }, + hasCheck: false, + isSized: true, + hasPrecision: false, + defaultSize: 2000, + hasQuotes: false, + }, +}; + +export const oraclesqlTypes = new Proxy(oraclesqlTypesBase, { + get: (target, prop) => (prop in target ? target[prop] : false), +}); + const dbToTypesBase = { [DB.GENERIC]: defaultTypes, [DB.MYSQL]: mysqlTypes, @@ -1623,6 +1812,7 @@ const dbToTypesBase = { [DB.SQLITE]: sqliteTypes, [DB.MSSQL]: mssqlTypes, [DB.MARIADB]: mysqlTypes, + [DB.ORACLESQL]: oraclesqlTypes, }; export const dbToTypes = new Proxy(dbToTypesBase, { diff --git a/src/pages/LandingPage.jsx b/src/pages/LandingPage.jsx index 46c7f1dd..642a3a2d 100644 --- a/src/pages/LandingPage.jsx +++ b/src/pages/LandingPage.jsx @@ -9,6 +9,7 @@ import mysql_icon from "../assets/mysql.png"; import postgres_icon from "../assets/postgres.png"; import sqlite_icon from "../assets/sqlite.png"; import mariadb_icon from "../assets/mariadb.png"; +import oraclesql_icon from "../assets/oraclesql.png"; import sql_server_icon from "../assets/sql-server.png"; import discord from "../assets/discord.png"; import github from "../assets/github.png"; @@ -178,7 +179,7 @@ export default function LandingPage() {
We support these DBMS
-
+
+
Reach out to us diff --git a/src/utils/exportSQL/generic.js b/src/utils/exportSQL/generic.js index 8aa2a045..78a244ce 100644 --- a/src/utils/exportSQL/generic.js +++ b/src/utils/exportSQL/generic.js @@ -134,6 +134,76 @@ export function getTypeString( } return type; + } else if (dbms === "oraclesql") { + let oracleType; + switch (field.type) { + case "INT": + case "INTEGER": + case "SMALLINT": + case "BIGINT": + case "DECIMAL": + case "NUMERIC": + case "REAL": + case "FLOAT": + case "DOUBLE": + oracleType = "NUMBER"; + break; + case "CHAR": + oracleType = "CHAR"; + break; + case "VARCHAR": + oracleType = "VARCHAR2"; + break; + case "TEXT": + oracleType = "CLOB"; + break; + case "TIME": + oracleType = "TIMESTAMP"; + break; + case "TIMESTAMP": + case "DATE": + case "DATETIME": + oracleType = field.type; + break; + case "BOOLEAN": + oracleType = "NUMBER(1)"; + break; + case "BINARY": + case "VARBINARY": + oracleType = "RAW"; + break; + case "BLOB": + oracleType = "BLOB"; + break; + case "JSON": + oracleType = "JSON"; + break; + case "UUID": + oracleType = "RAW(16)"; + break; + case "ENUM": + case "SET": + oracleType = "VARCHAR2"; + break; + default: + throw new Error(`Unsupported type for Oracle: ${field.type}`); + } + const typeInfo = dbToTypes[currentDb][oracleType]; + if (typeInfo.isSized || typeInfo.hasPrecision) { + if (oracleType === "NUMBER") { + return `${oracleType}${field.size ? `(${field.size})` : "(38,0)"}`; + } else { + return `${oracleType}${field.size ? `(${field.size})` : ""}`; + } + } + + if (field.type === "ENUM" || field.type === "SET") { + oracleType += ` CHECK (${field.name} IN (${field.values + .map((v) => `'${v}'`) + .join(", ")}))`; + } + + return oracleType; } } @@ -386,7 +456,7 @@ export function jsonToMariaDB(obj) { (field) => `${field.comment === "" ? "" : `\t-- ${field.comment}\n`}\t\`${ field.name - }\` ${getTypeString(field, obj.database)}${field.notNull ? " NOT NULL" : ""}${ + }\` ${getTypeString(field, obj.database, "oraclesql")}${field.notNull ? " NOT NULL" : ""}${ field.increment ? " AUTO_INCREMENT" : "" }${field.unique ? " UNIQUE" : ""}${ field.default !== "" @@ -502,3 +572,55 @@ export function jsonToSQLServer(obj) { ) .join("\n")}`; } + +export function jsonToOracleSQL(obj) { + return `${obj.tables + .map( + (table) => + `${ + table.comment === "" ? "" : `/* ${table.comment} */\n` + }CREATE TABLE "${table.name}" (\n${table.fields + .map( + (field) => + `${field.comment === "" ? "" : ` -- ${field.comment}\n`} "${ + field.name + }" ${getTypeString(field, obj.database, "oraclesql")}${field.primary ? "" : field.notNull ? (table.indices.some((index) => index.fields.some((f) => f === field.name)) ? " NOT NULL" : field.unique ? " NOT NULL UNIQUE" : " UNIQUE") : ""}${ + field.default !== "" + ? ` DEFAULT ${parseDefault(field, obj.database)}` + : "" + }${ + field.check === "" || + !dbToTypes[obj.database][field.type].hasCheck + ? "" + : ` CHECK (${field.check})` + }`, + ) + .join(",\n")}${ + table.fields.filter((f) => f.primary).length > 0 + ? `,\n PRIMARY KEY (${table.fields + .filter((f) => f.primary) + .map((f) => `"${f.name}"`) + .join(", ")})` + : "" + }\n);\n${table.indices + .map( + (i) => + `\nCREATE ${i.unique ? "UNIQUE " : ""}INDEX "${i.name}"\n ON "${ + table.name + }" (${i.fields.map((f) => `"${f}"`).join(", ")});`, + ) + .join("\n")}`, + ) + .join("\n\n")}\n${obj.references + .map( + (r) => + `ALTER TABLE "${obj.tables[r.startTableId].name}"\nADD CONSTRAINT fk_${ + r.startTableId + }_${r.endTableId} FOREIGN KEY ("${ + obj.tables[r.startTableId].fields[r.startFieldId].name + }") REFERENCES "${obj.tables[r.endTableId].name}"("${ + obj.tables[r.endTableId].fields[r.endFieldId].name + }");`, + ) + .join("\n")}`; +} diff --git a/src/utils/exportSQL/index.js b/src/utils/exportSQL/index.js index ad877daf..002e7745 100644 --- a/src/utils/exportSQL/index.js +++ b/src/utils/exportSQL/index.js @@ -2,6 +2,7 @@ import { DB } from "../../data/constants"; import { toMariaDB } from "./mariadb"; import { toMSSQL } from "./mssql"; import { toMySQL } from "./mysql"; +import { toOracleSQL } from "./oraclesql"; import { toPostgres } from "./postgres"; import { toSqlite } from "./sqlite"; @@ -17,6 +18,8 @@ export function exportSQL(diagram) { return toMariaDB(diagram); case DB.MSSQL: return toMSSQL(diagram); + case DB.ORACLE: + return toOracleSQL(diagram); default: return ""; } diff --git a/src/utils/exportSQL/oraclesql.js b/src/utils/exportSQL/oraclesql.js new file mode 100644 index 00000000..308caaf3 --- /dev/null +++ b/src/utils/exportSQL/oraclesql.js @@ -0,0 +1,56 @@ +import { dbToTypes } from "../../data/datatypes"; +import { parseDefault } from "./shared"; + +export function toOracleSQL(diagram) { + return `${diagram.tables + .map( + (table) => + `${ + table.comment === "" ? "" : `/* ${table.comment} */\n` + }CREATE TABLE "${table.name}" (\n${table.fields + .map( + (field) => + `${field.comment === "" ? "" : `\t-- ${field.comment}\n`}\t"${ + field.name + }" ${field.type}${field.size !== undefined && field.size !== "" ? "(" + field.size + ")" : ""}${ + field.notNull ? " NOT NULL" : "" + }${ + field.increment ? " GENERATED ALWAYS AS IDENTITY" : "" + }${field.unique ? " UNIQUE" : ""}${ + field.default !== "" + ? ` DEFAULT ${parseDefault(field, diagram.database)}` + : "" + }${ + field.check === "" || + !dbToTypes[diagram.database][field.type].hasCheck + ? "" + : ` CHECK(${field.check})` + }${field.comment ? ` -- ${field.comment}` : ""}`, + ) + .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.comment ? ` -- ${table.comment}` : ""};\n${`\n${table.indices + .map( + (i) => + `\nCREATE ${i.unique ? "UNIQUE " : ""}INDEX "${i.name}"\nON "${table.name}" (${i.fields + .map((f) => `"${f}"`) + .join(", ")});`, + ) + .join("")}`}`, + ) + .join("\n")}\n${diagram.references + .map( + (r) => + `ALTER TABLE "${diagram.tables[r.startTableId].name}"\nADD CONSTRAINT "${r.name}" 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")}`; +}