diff --git a/.gitignore b/.gitignore
index b3bf3411..37c9d999 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,4 +9,5 @@ dvdrental.tar
*.sql
debug.log
coverage/
-release-builds/
\ No newline at end of file
+release-builds/
+UserTableLayouts.json
\ No newline at end of file
diff --git a/README.md b/README.md
index b96cadad..c0412f62 100644
--- a/README.md
+++ b/README.md
@@ -3,16 +3,15 @@
[](https://github.com/oslabs-beta/SeeQR)
-
+


[](https://twitter.com/theseeqr)
[](https://github.com/open-source-labs/SeeQR)
-[](https://github.com/open-source-labs/SeeQR/actions/workflows/test.yml)
[SeeQR.info](http://www.seeqr.info)
-
SeeQR: A database analytic tool to compare the efficiency of different schemas and queries on a granular level so that developers/architects can make better informed architectural decisions regarding SQL databases at various scales.
+
SeeQR is a convenient one-stop shop for efficient SQL database manipulation and performance testing. SeeQR can be used throughout the database life-cycle, from creation to testing.
@@ -33,6 +32,15 @@ To get started on contributing to this project:
1. Download and install [Postgres.app](https://postgresapp.com/)(Mac)/[PGAdmin](https://www.pgadmin.org/download)(Windows) and start it before opening up SeeQR
2. Ensure that psql is available in the `$PATH`
3. Ensure that a 'postgres' role exists
+ - Open a database in Postgres
+ - Use command:
+ - `SET ROLE postgres`
+4. Ensure that your Postgres instance is running on port 5432
+ - If there is currently an instance running on port 5432
+ - Open a new terminal
+ - Use command to end all:
+ - `sudo pkill -u postgres`
+ - Start Postgres instance on port 5432
4. Download the latest version of [SeeQR](https://github.com/open-source-labs/seeqr/releases/latest)
## Built With
@@ -60,23 +68,24 @@ To get started on contributing to this project:
- Copying an existing database (with or without original data)
- Users can export any database onto their local machine
- Users can toggle between the 'DATABASES' view and the 'QUERIES' view
+ - Users can toggle between an 'ER DIAGRAM' view and the 'TABLES' view for each database
- Databases
-
- - In the 'DATABASES' view, users can select a table from a list of all the tables in the schema of the currently selected database
- - Information about the selected table is then displayed
- - The name and size of the selected database are also displayed at the top of the page
- - Users can also generate large amounts of foreign-key compliant dummy data for the selected table in the current database. Currently supported data types are:
- - INT
- - SMALLINT
- - BIGINT
- - VARCHAR
- - BOOLEAN
- - DATE
+ - In the 'DATABASES' view, an interactive Entity Relationship Diagram (`ER DIAGRAM`) is displayed for the selected database
+ - Users can select `TABLE` to see selected database in tabular form
+ - Users can select a table from a list of all the tables in the schema of the currently selected database
+ - Information about the selected table is then displayed
+ - The name and size of the selected database are also displayed at the top of the page
+ - Users can also generate large amounts of foreign-key compliant dummy data for the selected table in the current database. Currently supported data types are:
+ - INT
+ - BIGINT
+ - VARCHAR
+ - BOOLEAN
+ - DATE
@@ -86,14 +95,15 @@ To get started on contributing to this project:
- Users can create a new database from scratch by clicking the `Create New Database` button at the bottom of the sidebar
- Once a the database is given a name, hitting the `Initialize Database` button will create new database on the users PostgreSQL instance
- - Users can then input SQL commands and click `Update Database` to create and drop tables in the database
- - Users have the option to alter any existing databases as well by selecting the database on the sidebar and running any SQL commands they would like.
+ - Users can modify the newly created database as well as any existing databases using the `ER Diagram` to create/change/delete tables and columns
+ - Users also have the option to alter any newly created / existing databases by inputting SQL commands and click `Update Database`
- The `Export` button will write a .sql file on the user's desktop of the selected database
+
- Queries
@@ -164,7 +174,7 @@ We've released SeeQR because it's a useful tool to help optimize SQL databases.
## Core Team
-[Allison Le](https://github.com/allisonle1) | [Brandon Lee](https://github.com/BrandonW-Lee) | [Casey Escovedo](https://github.com/caseyescovedo) | [Casey Walker](https://github.com/cwalker3011) | [Catherine Chiu](https://github.com/catherinechiu) | [Chris Akinrinade](https://github.com/chrisakinrinade) | [Cindy Chau](https://github.com/cindychau) | [Claudio Santos](https://github.com/Claudiohbsantos) | [Eric Han](https://github.com/ericJH92) | [Faraz Akhtar](https://github.com/faraza22) | [Frank Norton](https://github.com/FrankNorton32) | [Harrison Nam](https://github.com/harrynam07) | [James Kolotouros](https://github.com/dkolotouros) | [Jennifer Courtner](https://github.com/jcourtner) | [John Wagner](https://github.com/jwagner988) | [Justin Dury-Agri](https://github.com/justinD-A) | [Justin Hicks](https://github.com/JuiceBawks) | [Katie Klochan](https://github.com/kklochan) | [May Wirapa Boonyasurat](https://github.com/mimiwrp) | [Mercer Stronck](https://github.com/mercerstronck) | [Muhammad Trad](https://github.com/muhammadtrad) | [Richard Guo](https://github.com/richardguoo) | [Richard Lam](https://github.com/rlam108) | [Sam Frakes](https://github.com/frakes413) | [Serena Kuo](https://github.com/serenackuo) | [Timothy Sin](https://github.com/timothysin) | [Vincent Trang](https://github.com/vincentt114)
+[Michelle Chang](https://github.com/mkchang168) | [Jake Bradbeer](https://github.com/JBradbeer) | [Bryan Santos](https://github.com/santosb93) | [William Trey Lewis](https://github.com/treyfrog128) | [Brandon Lee](https://github.com/BrandonW-Lee) | [Casey Escovedo](https://github.com/caseyescovedo) | [Casey Walker](https://github.com/cwalker3011) | [Catherine Chiu](https://github.com/catherinechiu) | [Chris Akinrinade](https://github.com/chrisakinrinade) | [Cindy Chau](https://github.com/cindychau) | [Claudio Santos](https://github.com/Claudiohbsantos) | [Eric Han](https://github.com/ericJH92) | [Faraz Akhtar](https://github.com/faraza22) | [Frank Norton](https://github.com/FrankNorton32) | [Harrison Nam](https://github.com/harrynam07) | [James Kolotouros](https://github.com/dkolotouros) | [Jennifer Courtner](https://github.com/jcourtner) | [John Wagner](https://github.com/jwagner988) | [Justin Dury-Agri](https://github.com/justinD-A) | [Justin Hicks](https://github.com/JuiceBawks) | [Katie Klochan](https://github.com/kklochan) | [May Wirapa Boonyasurat](https://github.com/mimiwrp) | [Mercer Stronck](https://github.com/mercerstronck) | [Muhammad Trad](https://github.com/muhammadtrad) | [Richard Guo](https://github.com/richardguoo) | [Richard Lam](https://github.com/rlam108) | [Sam Frakes](https://github.com/frakes413) | [Serena Kuo](https://github.com/serenackuo) | [Timothy Sin](https://github.com/timothysin) | [Vincent Trang](https://github.com/vincentt114)
## License
diff --git a/assets/readmeImages/gifs/Query_Execution.gif b/assets/readmeImages/gifs/Query_Execution.gif
index e4e8873f..23d6dbc9 100644
Binary files a/assets/readmeImages/gifs/Query_Execution.gif and b/assets/readmeImages/gifs/Query_Execution.gif differ
diff --git a/assets/readmeImages/gifs/create_db.gif b/assets/readmeImages/gifs/create_db.gif
index abbf3f99..fd5615ef 100644
Binary files a/assets/readmeImages/gifs/create_db.gif and b/assets/readmeImages/gifs/create_db.gif differ
diff --git a/assets/readmeImages/gifs/dummy_data.gif b/assets/readmeImages/gifs/dummy_data.gif
index ed7b5254..a20c2be3 100644
Binary files a/assets/readmeImages/gifs/dummy_data.gif and b/assets/readmeImages/gifs/dummy_data.gif differ
diff --git a/assets/readmeImages/gifs/modify_db.gif b/assets/readmeImages/gifs/modify_db.gif
new file mode 100644
index 00000000..dcf3558a
Binary files /dev/null and b/assets/readmeImages/gifs/modify_db.gif differ
diff --git a/assets/readmeImages/gifs/quick_start.gif b/assets/readmeImages/gifs/quick_start.gif
index 3034a0ba..d39d3dad 100644
Binary files a/assets/readmeImages/gifs/quick_start.gif and b/assets/readmeImages/gifs/quick_start.gif differ
diff --git a/backend/BE_types.ts b/backend/BE_types.ts
index c9d9e322..c3114598 100644
--- a/backend/BE_types.ts
+++ b/backend/BE_types.ts
@@ -1,6 +1,9 @@
/**
* This file contains common types that need to be used across the backend
*/
+ import {
+ UpdatesObjType
+} from '../frontend/types';
export interface ColumnObj {
column_name: string;
@@ -28,3 +31,8 @@ export interface DBList {
}
export type DummyRecords = [string[], ...Array<(string | number)[]>];
+
+export type BackendObjType = {
+ database: string;
+ updates: UpdatesObjType;
+};
diff --git a/backend/DummyD/dummyDataMain.ts b/backend/DummyD/dummyDataMain.ts
index 6e3bd95c..1743c0ee 100644
--- a/backend/DummyD/dummyDataMain.ts
+++ b/backend/DummyD/dummyDataMain.ts
@@ -71,7 +71,8 @@ type GenerateDummyData = (tableInfo: ColumnObj[], numRows: number) => Promise {
// assuming primary key is serial, get all the column names except for the column with the primary key
const columnNames = tableInfo.reduce((acc: string[], curr: ColumnObj) => {
- if (curr.constraint_type !== 'PRIMARY KEY') acc.push(curr.column_name);
+ // if (curr.constraint_type !== 'PRIMARY KEY')
+ acc.push(curr.column_name);
return acc;
}, []);
const dummyRecords: DummyRecords = [columnNames];
@@ -83,8 +84,8 @@ const generateDummyData: GenerateDummyData = async (tableInfo: ColumnObj[], numR
for (let j = 0; j < tableInfo.length; j += 1) {
// if column has no foreign key constraint, then generate dummy data based on data type
if (
- tableInfo[j].constraint_type !== 'FOREIGN KEY' &&
- tableInfo[j].constraint_type !== 'PRIMARY KEY'
+ tableInfo[j].constraint_type !== 'FOREIGN KEY'
+ // && tableInfo[j].constraint_type !== 'PRIMARY KEY'
) row.push(generateDataByType(tableInfo[j]));
// if there is a foreign key constraint, grab random key from foreign table
@@ -94,11 +95,15 @@ const generateDummyData: GenerateDummyData = async (tableInfo: ColumnObj[], numR
const foreignTable = tableInfo[j].foreign_table;
const getForeignKeyQuery = `
SELECT ${foreignColumn}
- FROM ${foreignTable} TABLESAMPLE BERNOULLI(50)
+ FROM ${foreignTable} TABLESAMPLE BERNOULLI(100)
LIMIT 1
`;
const foreignKey = await db.query(getForeignKeyQuery);
- if (foreignKey.rows.length) row.push(foreignKey.rows[0]['_id']);
+ const chosenPrimaryValue = foreignKey.rows[0][Object.keys(foreignKey.rows[0])[0]]
+ if (foreignKey.rows.length) {
+ if (typeof chosenPrimaryValue === 'string') row.push(`'${chosenPrimaryValue}'`);
+ else row.push(chosenPrimaryValue);
+ }
else return new Error('There was an error while retrieving a valid foreign key.');
} catch(err) {
return err;
diff --git a/backend/channels.ts b/backend/channels.ts
index 39a06a44..c8505200 100644
--- a/backend/channels.ts
+++ b/backend/channels.ts
@@ -5,6 +5,7 @@ import os from 'os';
import helperFunctions from './helperFunctions';
import generateDummyData from './DummyD/dummyDataMain';
import { ColumnObj, DBList, DummyRecords } from './BE_types';
+import backendObjToQuery from './ertable-functions';
const db = require('./models');
@@ -59,23 +60,26 @@ ipcMain.handle(
);
// Deletes the DB that is passed from the front end and returns an updated DB List
-ipcMain.handle('drop-db', async (event, dbName: string, currDB: boolean): Promise => {
- event.sender.send('async-started');
- try {
- // if deleting currently connected db, disconnect from db
- if (currDB) await db.connectToDB('');
+ipcMain.handle(
+ 'drop-db',
+ async (event, dbName: string, currDB: boolean): Promise => {
+ event.sender.send('async-started');
+ try {
+ // if deleting currently connected db, disconnect from db
+ if (currDB) await db.connectToDB('');
- // drop db
- const dropDBScript = dropDBFunc(dbName);
- await db.query(dropDBScript);
+ // drop db
+ const dropDBScript = dropDBFunc(dbName);
+ await db.query(dropDBScript);
- // send updated db info
- const dbsAndTables: DBList = await db.getLists();
- event.sender.send('db-lists', dbsAndTables);
- } finally {
- event.sender.send('async-complete');
+ // send updated db info
+ const dbsAndTables: DBList = await db.getLists();
+ event.sender.send('db-lists', dbsAndTables);
+ } finally {
+ event.sender.send('async-complete');
+ }
}
-});
+);
interface DuplicatePayload {
newName: string;
@@ -134,7 +138,7 @@ ipcMain.handle(
const dbsAndTableInfo: DBList = await db.getLists();
event.sender.send('db-lists', dbsAndTableInfo);
} finally {
- // //cleanup temp file
+ // //cleanup temp file
try {
fs.unlinkSync(tempFilePath);
} catch (e) {
@@ -201,7 +205,7 @@ interface QueryPayload {
selectedDb: string;
}
-// Run query passed from the front-end, and send back an updated DB List
+// Run query passed from the front-end, and send back an updated DB List
// DB will rollback if query is unsuccessful
ipcMain.handle(
'run-query',
@@ -251,48 +255,38 @@ ipcMain.handle(
}
);
-interface ExportPayload {
+interface ExportPayload {
sourceDb: string;
}
-ipcMain.handle(
- 'export-db',
- async (event, { sourceDb }: ExportPayload) => {
- event.sender.send('async-started');
- // store temporary file in user desktop
- const FilePath = path.resolve(
- os.homedir(),
- 'desktop',
- `${sourceDb}.sql`
- );
+ipcMain.handle('export-db', async (event, { sourceDb }: ExportPayload) => {
+ event.sender.send('async-started');
+ // store temporary file in user desktop
+ const FilePath = path.resolve(os.homedir(), 'desktop', `${sourceDb}.sql`);
let feedback: Feedback = {
- type: '',
- message: '',
- };
+ type: '',
+ message: '',
+ };
try {
// dump database to new file
const dumpCmd = runFullCopyFunc(sourceDb, FilePath);
-
+
try {
await promExecute(dumpCmd);
feedback = {
type: 'success',
- message: `${sourceDb} Schema successfully exported to ${FilePath}`
- }
+ message: `${sourceDb} Schema successfully exported to ${FilePath}`,
+ };
event.sender.send('feedback', feedback);
} catch (e) {
- throw new Error(
- `Failed to dump ${sourceDb} to a file at ${FilePath}`
- );
- }
- }
- finally {
- event.sender.send('async-complete');
- }
+ throw new Error(`Failed to dump ${sourceDb} to a file at ${FilePath}`);
}
-);
+ } finally {
+ event.sender.send('async-complete');
+ }
+});
interface dummyDataRequestPayload {
dbName: string;
@@ -314,8 +308,10 @@ ipcMain.handle(
const tableInfo: ColumnObj[] = await db.getTableInfo(data.tableName);
// generate dummy data
- const dummyArray: DummyRecords = await generateDummyData(tableInfo, data.rows);
-
+ const dummyArray: DummyRecords = await generateDummyData(
+ tableInfo,
+ data.rows
+ );
// generate insert query string to insert dummy records
const columnsStringified = '('
.concat(dummyArray[0].join(', '))
@@ -331,7 +327,6 @@ ipcMain.handle(
.concat(dummyArray[dummyArray.length - 1].join(', '))
.concat(');');
insertQuery = insertQuery.concat(lastRecordStringified);
-
// insert dummy records into DB
await db.query('Begin;');
await db.query(insertQuery);
@@ -354,7 +349,7 @@ ipcMain.handle(
// send feedback back to FE
event.sender.send('feedback', feedback);
-
+
// send notice to FE that DD generation has been completed
event.sender.send('async-complete');
}
@@ -380,13 +375,11 @@ ipcMain.handle(
// update DBList in the sidebar to show this new db
const dbsAndTableInfo: DBList = await db.getLists();
event.sender.send('db-lists', dbsAndTableInfo);
-
} catch (e) {
// in the case of an error, delete the created db
const dropDBScript = dropDBFunc(newDbName);
await db.query(dropDBScript);
throw new Error('Failed to initialize new database');
-
} finally {
event.sender.send('async-complete');
}
@@ -400,7 +393,7 @@ interface UpdatePayload {
selectedDb: string;
}
-// Run query passed from the front-end, and send back an updated DB List
+// Run query passed from the front-end, and send back an updated DB List
// DB will rollback if query is unsuccessful
ipcMain.handle(
'update-db',
@@ -414,15 +407,11 @@ ipcMain.handle(
// Run Query
// let returnedRows;
try {
-
await db.query(sqlString);
-
} catch (e) {
if (e) throw new Error('Failed to update schema');
}
-
} finally {
-
// send updated db info in case query affected table or database information
// must be run after we connect back to the originally selected so tables information is accurate
const dbsAndTables: DBList = await db.getLists();
@@ -431,4 +420,46 @@ ipcMain.handle(
event.sender.send('async-complete');
}
}
-);
\ No newline at end of file
+);
+
+// Generate and run query from react-flow ER diagram
+ipcMain.handle('ertable-schemaupdate', async (event, backendObj) => {
+ // send notice to front end that schema update has started
+ event.sender.send('async-started');
+ let feedback: Feedback = {
+ type: '',
+ message: '',
+ };
+ try {
+ // Generates query from backendObj
+ const query = backendObjToQuery(backendObj);
+ // run sql command
+ await db.query('Begin;');
+ await db.query(query);
+ await db.query('Commit;');
+ feedback = {
+ type: 'success',
+ message: 'Database updated successfully.',
+ };
+ return 'success';
+ } catch (err) {
+ // rollback transaction if there's an error in update and send back feedback to FE
+ await db.query('Rollback;');
+
+ feedback = {
+ type: 'error',
+ message: err,
+ };
+ } finally {
+ // send updated db info
+ const updatedDb: DBList = await db.getLists();
+ event.sender.send('db-lists', updatedDb);
+
+ // send feedback back to FE
+ event.sender.send('feedback', feedback);
+
+ // send notice to FE that schema update has been completed
+ event.sender.send('async-complete');
+
+ }
+});
diff --git a/backend/ertable-functions.ts b/backend/ertable-functions.ts
new file mode 100644
index 00000000..7e2f1b9e
--- /dev/null
+++ b/backend/ertable-functions.ts
@@ -0,0 +1,233 @@
+import {
+ AddTablesObjType,
+ DropTablesObjType,
+ AlterTablesObjType,
+ AlterColumnsObjType,
+ AddConstraintObjType,
+} from '../frontend/types';
+
+import {
+ BackendObjType,
+} from './BE_types';
+
+function backendObjToQuery(backendObj: BackendObjType): string {
+ const outputArray: string[] = [];
+
+ // Add table to database
+ function addTable(addTableArray: AddTablesObjType[]): void {
+ for (let i = 0; i < addTableArray.length; i += 1) {
+ const currTable: AddTablesObjType = addTableArray[i];
+ outputArray.push(
+ `CREATE TABLE ${currTable.table_schema}.${currTable.table_name}(); `
+ );
+ }
+ }
+ // Remove table from database
+ function dropTable(dropTableArray: DropTablesObjType[]): void {
+ for (let i = 0; i < dropTableArray.length; i += 1) {
+ const currTable: DropTablesObjType = dropTableArray[i];
+ outputArray.push(
+ `DROP TABLE ${currTable.table_schema}.${currTable.table_name}; `
+ );
+ }
+ }
+ // Alter existing table in database. All column functions reside under this function
+ function alterTable(alterTableArray: AlterTablesObjType[]): void {
+ // Add column to table
+ function addColumn(currTable: AlterTablesObjType): string {
+ let addColumnString: string = '';
+ if (currTable.addColumns.length) {
+ for (let i = 0; i < currTable.addColumns.length; i += 1) {
+ addColumnString += `ALTER TABLE ${currTable.table_schema}.${currTable.table_name} ADD COLUMN ${currTable.addColumns[i].column_name} ${currTable.addColumns[i].data_type}(${currTable.addColumns[i].character_maximum_length}); `;
+ }
+ }
+ return addColumnString;
+ }
+ // Remove column from table
+ function dropColumn(currTable: AlterTablesObjType): string {
+ let dropColumnString: string = '';
+ if (currTable.dropColumns.length) {
+ for (let i = 0; i < currTable.dropColumns.length; i += 1) {
+ dropColumnString += `ALTER TABLE ${currTable.table_schema}.${currTable.table_name} DROP COLUMN ${currTable.dropColumns[i].column_name}; `;
+ }
+ }
+ return dropColumnString;
+ }
+ // Add/remove constraints from column
+ function alterTableConstraint(currTable: AlterTablesObjType): string {
+ let alterTableConstraintString: string = '';
+ // Add a primary key constraint to column
+ function addPrimaryKey(currConstraint: AddConstraintObjType, currColumn: AlterColumnsObjType): void {
+ let defaultRowValue: number | string;
+ if(currColumn.current_data_type === 'character varying') defaultRowValue = 'A';
+ else defaultRowValue = 1;
+ alterTableConstraintString += `ALTER TABLE ${currTable.table_schema}.${currTable.table_name} ADD CONSTRAINT ${currConstraint.constraint_name} PRIMARY KEY (${currColumn.column_name}); INSERT INTO ${currTable.table_schema}.${currTable.table_name} (${currColumn.column_name}) VALUES ('${defaultRowValue}'); `;
+ }
+ // Add a foreign key constraint to column
+ function addForeignKey(currConstraint: AddConstraintObjType, currColumn: AlterColumnsObjType): void {
+ alterTableConstraintString += `ALTER TABLE ${currTable.table_schema}.${currTable.table_name} ADD CONSTRAINT ${currConstraint.constraint_name} FOREIGN KEY ("${currColumn.column_name}") REFERENCES ${currConstraint.foreign_table}(${currConstraint.foreign_column}); `;
+ }
+ // Add a unique constraint to column
+ function addUnique(currConstraint: AddConstraintObjType, currColumn: AlterColumnsObjType): void {
+ alterTableConstraintString += `ALTER TABLE ${currTable.table_schema}.${currTable.table_name} ADD CONSTRAINT ${currConstraint.constraint_name} UNIQUE (${currColumn.column_name}); `;
+ }
+ // Remove constraint from column
+ function dropConstraint(currDrop): void {
+ alterTableConstraintString += `ALTER TABLE ${currTable.table_schema}.${currTable.table_name} DROP CONSTRAINT ${currDrop}; `;
+ }
+
+ for (let i = 0; i < currTable.alterColumns.length; i += 1) {
+ const currColumn: AlterColumnsObjType = currTable.alterColumns[i];
+ for (let j = 0; j < currColumn.add_constraint.length; j += 1) {
+ const currConstraint: AddConstraintObjType = currColumn.add_constraint[j];
+ if (currConstraint.constraint_type === 'PRIMARY KEY') {
+ addPrimaryKey(currConstraint, currColumn);
+ } else if (currConstraint.constraint_type === 'FOREIGN KEY') {
+ addForeignKey(currConstraint, currColumn);
+ } else if (currConstraint.constraint_type === 'UNIQUE') {
+ addUnique(currConstraint, currColumn);
+ }
+ }
+ for (let j = 0; j < currColumn.drop_constraint.length; j += 1) {
+ const currDrop: string = currColumn.drop_constraint[j];
+ dropConstraint(currDrop);
+ }
+ }
+ return alterTableConstraintString;
+ }
+ // Add/remove not null constraint from column
+ function alterNotNullConstraint(currTable: AlterTablesObjType): string {
+ let notNullConstraintString: string = '';
+ for (let i = 0; i < currTable.alterColumns.length; i += 1) {
+ if (currTable.alterColumns[i].is_nullable === 'NO') {
+ notNullConstraintString += `ALTER TABLE ${currTable.table_schema}.${currTable.table_name} ALTER COLUMN ${currTable.alterColumns[i].column_name} SET NOT NULL; `;
+ }
+ if (currTable.alterColumns[i].is_nullable === 'YES') {
+ notNullConstraintString += `ALTER TABLE ${currTable.table_schema}.${currTable.table_name} ALTER COLUMN ${currTable.alterColumns[i].column_name} DROP NOT NULL; `;
+ }
+ }
+ return notNullConstraintString;
+ }
+ // Change the data type of the column
+ function alterType(currTable: AlterTablesObjType): string {
+ let alterTypeString: string = '';
+ for (let i = 0; i < currTable.alterColumns.length; i += 1) {
+ if (currTable.alterColumns[i].data_type !== null) {
+ if (currTable.alterColumns[i].data_type === 'date') {
+ alterTypeString += `ALTER TABLE ${currTable.table_schema}.${currTable.table_name} ALTER COLUMN ${currTable.alterColumns[i].column_name} TYPE date USING ${currTable.alterColumns[i].column_name}::text::date; `;
+ } else {
+ alterTypeString += `ALTER TABLE ${currTable.table_schema}.${currTable.table_name} ALTER COLUMN ${currTable.alterColumns[i].column_name} TYPE ${currTable.alterColumns[i].data_type} USING ${currTable.alterColumns[i].column_name}::${currTable.alterColumns[i].data_type}; `;
+ }
+ }
+ }
+ return alterTypeString;
+ }
+ // Change the max character length of a varchar
+ function alterMaxCharacterLength(currTable: AlterTablesObjType): string {
+ let alterMaxCharacterLengthString: string = '';
+ for (let i = 0; i < currTable.alterColumns.length; i += 1) {
+ if (currTable.alterColumns[i].character_maximum_length) {
+ alterMaxCharacterLengthString += `ALTER TABLE ${currTable.table_schema}.${currTable.table_name} ALTER COLUMN ${currTable.alterColumns[i].column_name} TYPE varchar(${currTable.alterColumns[i].character_maximum_length}); `;
+ }
+ }
+ return alterMaxCharacterLengthString;
+ }
+
+ for (let i = 0; i < alterTableArray.length; i += 1) {
+ const currTable: AlterTablesObjType = alterTableArray[i];
+ outputArray.push(
+ `${addColumn(currTable)}${dropColumn(currTable)}${alterType(currTable)}${alterTableConstraint(
+ currTable
+ )}${alterNotNullConstraint(currTable)}${alterMaxCharacterLength(currTable)}`
+ );
+ }
+ }
+ // Outer function to rename tables and columns. Will rename columns first, then rename tables
+ function renameTablesColumns(renameTableArray: AlterTablesObjType[]): void {
+ let renameString: string = '';
+ const columnsNames: object = {};
+ const tablesNames: object = {};
+ const constraintsNames: object = {};
+ // Populates the tablesNames object with new table names
+ function renameTable(currTable: AlterTablesObjType): void {
+ if (currTable.new_table_name) {
+ tablesNames[currTable.table_name] = {
+ table_name: currTable.table_name,
+ table_schema: currTable.table_schema,
+ new_table_name: currTable.new_table_name,
+ };
+ }
+ }
+ // Populates the columnsNames object with new column names
+ function renameColumn(currTable: AlterTablesObjType): void {
+ for (let i = 0; i < currTable.alterColumns.length; i+=1) {
+ const currAlterColumn: AlterColumnsObjType = currTable.alterColumns[i];
+ // populates an array of objects with all of the new column names
+ if (currAlterColumn.new_column_name) {
+ columnsNames[currAlterColumn.column_name] = {
+ column_name: currAlterColumn.column_name,
+ table_name: currTable.table_name,
+ table_schema: currTable.table_schema,
+ new_column_name: currAlterColumn.new_column_name,
+ };
+ }
+ }
+ }
+ const renameConstraintCache = {}
+ // Populates the constraintsNAmes object with new constraint names
+ function renameConstraint(currTable): void {
+ for (let i = 0; i < currTable.alterColumns.length; i+=1) {
+ const currAlterColumn: AlterColumnsObjType = currTable.alterColumns[i];
+ // populates an array of objects with all of the new constraint names
+ if (currAlterColumn.rename_constraint) {
+ constraintsNames[currAlterColumn.rename_constraint] = {
+ constraint_type: currAlterColumn.rename_constraint[0] === 'p' ? 'pk' : 'f' ? 'fk' : 'unique',
+ column_name: currAlterColumn.new_column_name ? currAlterColumn.new_column_name : currAlterColumn.column_name,
+ table_name: renameConstraintCache[currTable.table_name] ? renameConstraintCache[currTable.table_name] : currTable.table_name,
+ table_schema: currTable.table_schema,
+ };
+ }
+ }
+ }
+
+ for (let i = 0; i < renameTableArray.length; i+=1) {
+ if (renameTableArray[i].new_table_name) renameConstraintCache[renameTableArray[i].table_name] = renameTableArray[i].new_table_name;
+ }
+
+ for (let i = 0; i < renameTableArray.length; i+=1) {
+ const currTable: AlterTablesObjType = renameTableArray[i];
+ renameConstraint(currTable);
+ renameColumn(currTable);
+ renameTable(currTable);
+ }
+ // Goes through the columnsNames object and adds the query for renaming
+ const columnsToRename: string[] = Object.keys(columnsNames);
+ for (let i = 0; i < columnsToRename.length; i+=1) {
+ const currColumn: AlterColumnsObjType = columnsNames[columnsToRename[i]];
+ // only renames a column with the most recent name that was saved
+ renameString += `ALTER TABLE ${currColumn.table_schema}.${currColumn.table_name} RENAME COLUMN ${currColumn.column_name} TO ${currColumn.new_column_name}; `;
+ }
+ // Goes through the tablesNames object and adds the query for renaming
+ const tablesToRename: string[] = Object.keys(tablesNames);
+ for (let i = 0; i < tablesToRename.length; i+=1) {
+ const currTable: AlterTablesObjType = tablesNames[tablesToRename[i]];
+ // only renames a table with the most recent name that was saved
+ renameString += `ALTER TABLE ${currTable.table_schema}.${currTable.table_name} RENAME TO ${currTable.new_table_name}; `;
+ }
+ // Goes through the constraintsNames object and adds the query for renaming
+ const constraintsToRename: string[] = Object.keys(constraintsNames);
+ for (let i = 0; i < constraintsToRename.length; i+=1) {
+ const currColumn: AlterColumnsObjType = constraintsNames[constraintsToRename[i]];
+ renameString += `ALTER TABLE ${currColumn.table_schema}.${currColumn.table_name} RENAME CONSTRAINT ${constraintsToRename[i]} TO ${currColumn.constraint_type}_${currColumn.table_name}${currColumn.column_name}; `;
+ }
+ outputArray.push(renameString);
+ }
+
+ addTable(backendObj.updates.addTables);
+ dropTable(backendObj.updates.dropTables);
+ alterTable(backendObj.updates.alterTables);
+ renameTablesColumns(backendObj.updates.alterTables);
+ return outputArray.join('');
+}
+
+export default backendObjToQuery;
diff --git a/backend/main.ts b/backend/main.ts
index ff24cae5..f9f77386 100644
--- a/backend/main.ts
+++ b/backend/main.ts
@@ -1,6 +1,8 @@
// eslint-disable-next-line import/no-extraneous-dependencies
import { FamilyRestroomRounded } from '@mui/icons-material';
-import { app, BrowserWindow, Menu } from 'electron';
+import { BrowserWindow, Menu } from 'electron';
+
+const { app } = require('electron');
const dev: boolean = process.env.NODE_ENV === 'development';
@@ -61,17 +63,6 @@ function createWindow() {
});
}
-// Install React Dev Tools Extension
-if (dev) {
- const {
- default: installExtension,
- REACT_DEVELOPER_TOOLS,
- } = require('electron-devtools-installer');
-
- app.on('ready', () => {
- installExtension(REACT_DEVELOPER_TOOLS);
- });
-}
// Invoke createWindow to create browser windows after Electron has been initialized.
app.on('ready', createWindow);
diff --git a/frontend/components/App.tsx b/frontend/components/App.tsx
index 6934d688..9a384d88 100644
--- a/frontend/components/App.tsx
+++ b/frontend/components/App.tsx
@@ -33,7 +33,7 @@ const Main = styled.main<{ $fullwidth: boolean }>`
grid-area: ${({ $fullwidth }) => ($fullwidth ? '1 / 1 / -1 / -1' : 'main')};
background: ${bgColor};
height: calc(100vh - (2 * ${defaultMargin}));
- max-width: ${({ $fullwidth }) => ($fullwidth ? '' : `calc(100vw - ${sidebarWidth} )`)};
+ max-width: ${({ $fullwidth }) => ($fullwidth ? '' : `calc(90vw - ${sidebarWidth} )`)};
padding: ${defaultMargin} ${sidebarShowButtonSize};
margin: 0;
`;
@@ -51,6 +51,8 @@ const App = () => {
const [selectedDb, setSelectedDb] = useState('');
const [sidebarIsHidden, setSidebarHidden] = useState(false);
const [newFilePath, setFilePath] = useState('');
+ const [ERView, setERView] = useState(true);
+
/**
* Hook to create new Query from data
@@ -117,7 +119,8 @@ const App = () => {
setSidebarHidden,
sidebarIsHidden,
setFilePath,
- newFilePath
+ newFilePath,
+ setERView
}}
/>
@@ -125,7 +128,12 @@ const App = () => {
queries={comparedQueries}
show={shownView === 'compareView'}
/>
-
+ {
+}: DbListProps) => {
const [databases, setDatabases] = useState([]);
const [openAdd, setOpenAdd] = useState(false);
const [openDupe, setOpenDupe] = useState(false);
diff --git a/frontend/components/sidebar/Sidebar.tsx b/frontend/components/sidebar/Sidebar.tsx
index 4d91f4fe..a841e029 100644
--- a/frontend/components/sidebar/Sidebar.tsx
+++ b/frontend/components/sidebar/Sidebar.tsx
@@ -63,6 +63,7 @@ const Sidebar = ({
sidebarIsHidden,
setFilePath,
newFilePath,
+ setERView
}: AppState) => {
const toggleOpen = () => setSidebarHidden(!sidebarIsHidden);
@@ -88,7 +89,7 @@ const Sidebar = ({
setSelectedView={setSelectedView}
toggleOpen={toggleOpen}
/>
-
+ ;
+type ViewSelectorProps = Pick;
/**
* Selector for view on sidebar. Updates App state with selected view
*/
-const ViewSelector = ({ selectedView, setSelectedView }: ViewSelectorProps) => (
+const ViewSelector = ({ selectedView, setSelectedView, setERView }: ViewSelectorProps) => (
setSelectedView('queryView')}
@@ -37,7 +37,10 @@ const ViewSelector = ({ selectedView, setSelectedView }: ViewSelectorProps) => (
Queries
setSelectedView('dbView')}
+ onClick={() => {
+ setSelectedView('dbView')
+ if (setERView) setERView(true)
+ }}
$isSelected={
selectedView === 'dbView' || selectedView === 'quickStartView'
}
diff --git a/frontend/components/views/DbView/DatabaseDetails.tsx b/frontend/components/views/DbView/DatabaseDetails.tsx
index 48b3ef9c..bacb74d3 100644
--- a/frontend/components/views/DbView/DatabaseDetails.tsx
+++ b/frontend/components/views/DbView/DatabaseDetails.tsx
@@ -17,10 +17,9 @@ const DatabaseDetails = ({ db }: DatabaseDetailsProps) => {
if (!db) return null;
return (
- {`${db.db_name}`}
- {`${db.db_size}`}
+ {`${db.db_name}`} {`${db.db_size}`}
);
};
-export default DatabaseDetails;
+export default DatabaseDetails;
\ No newline at end of file
diff --git a/frontend/components/views/DbView/DbView.tsx b/frontend/components/views/DbView/DbView.tsx
index 48ab4bce..9d635bac 100644
--- a/frontend/components/views/DbView/DbView.tsx
+++ b/frontend/components/views/DbView/DbView.tsx
@@ -14,6 +14,8 @@ const requestDbListOnce = once(() => ipcRenderer.send('return-db-list'));
interface DbViewProps {
selectedDb: AppState['selectedDb'];
show: boolean;
+ setERView: (boolean) => void;
+ ERView: boolean;
}
const StyledDummyButton = styled(Button)`
@@ -22,7 +24,7 @@ const StyledDummyButton = styled(Button)`
right: ${sidebarShowButtonSize};
`;
-const DbView = ({ selectedDb, show }: DbViewProps) => {
+const DbView = ({ selectedDb, show, setERView, ERView}: DbViewProps) => {
const [dbTables, setTables] = useState([]);
const [selectedTable, setSelectedTable] = useState();
const [databases, setDatabases] = useState([]);
@@ -65,10 +67,12 @@ const DbView = ({ selectedDb, show }: DbViewProps) => {
tables={dbTables}
selectTable={(table: TableInfo) => setSelectedTable(table)}
selectedTable={selectedTable}
+ selectedDb={selectedDb}
+ setERView={setERView}
/>
+ );
+}
+
+export default ERTabling;
diff --git a/frontend/components/views/ERTables/NodeTypes.ts b/frontend/components/views/ERTables/NodeTypes.ts
new file mode 100644
index 00000000..5287dcc5
--- /dev/null
+++ b/frontend/components/views/ERTables/NodeTypes.ts
@@ -0,0 +1,22 @@
+import tableHeader from './TableHeaderNode';
+import tableField from './TableFieldNode';
+/**
+ * This file is required for React-flow
+ * React-flow states:
+ * " You can add a new node type to React Flow by adding it to the nodeTypes prop.
+ * It's important that the nodeTypes are memoized or defined outside of the component.
+ * Otherwise React creates a new object on every render which leads to performance issues and bugs."
+ *
+ * https://reactflow.dev/docs/guides/custom-nodes/
+ *
+ */
+type NodeTypes = {
+ tableHeader: any
+ tableField: any
+}
+const nodeTypes: NodeTypes = {
+ tableHeader,
+ tableField
+}
+
+export default nodeTypes;
\ No newline at end of file
diff --git a/frontend/components/views/ERTables/TableFieldCheckBox.tsx b/frontend/components/views/ERTables/TableFieldCheckBox.tsx
new file mode 100644
index 00000000..9a61b122
--- /dev/null
+++ b/frontend/components/views/ERTables/TableFieldCheckBox.tsx
@@ -0,0 +1,39 @@
+import React from 'react';
+
+type TableFieldCheckBoxProps = {
+ label : string;
+ idName : string;
+ isChecked? : boolean | undefined;
+ onChange? : ((any) => void)[] | ((any) => void);
+}
+
+const TableFieldCheckBox = ({label, idName, isChecked, onChange}: TableFieldCheckBoxProps) => {
+ const onChangeHandler = (e) => {
+ // confirm that onChange is given
+ if (!onChange) return;
+ // if an array of callbacks is given, apply consecutively
+ if (Array.isArray(onChange)) {
+ onChange.forEach(func => func(e.target.checked))
+ } else {
+ // if only one callback is given, apply solely
+ onChange(e.target.checked)
+ }
+ }
+
+ return (
+
+ {`${label}:`}
+
+ {onChangeHandler(e)}}
+ />
+
+
+ )
+}
+
+
+export default TableFieldCheckBox;
\ No newline at end of file
diff --git a/frontend/components/views/ERTables/TableFieldDropDown.tsx b/frontend/components/views/ERTables/TableFieldDropDown.tsx
new file mode 100644
index 00000000..e1844de7
--- /dev/null
+++ b/frontend/components/views/ERTables/TableFieldDropDown.tsx
@@ -0,0 +1,76 @@
+import React, { useEffect, useState } from 'react';
+import TableFieldDropDownOption from './TableFieldDropDownOption';
+
+type OtherTablesType = {
+ table_name: string;
+ column_names: string[];
+};
+
+type TableFieldDropDownProps = {
+ label: string;
+ idName: string;
+ options: string[];
+ defaultValue: string;
+ isDisabled?: boolean;
+ otherTables: OtherTablesType[];
+ setFkOptions?: (fkOptions: string[]) => void;
+ schemaStateCopy: any;
+ setSchemaState: (string) => {};
+};
+
+const TableFieldDropDown = (props: TableFieldDropDownProps) => {
+ const {
+ label,
+ idName,
+ options,
+ defaultValue,
+ isDisabled,
+ otherTables,
+ setFkOptions,
+ schemaStateCopy,
+ setSchemaState,
+ } = props;
+
+ const optionsArray = options.map((option, i) => (
+
+ ));
+
+ const handleChange = (e) => {
+ // if setFKOptions is truthy, the instance of TableFieldDropDown
+ // with Other table names has been changed
+ if (setFkOptions) {
+ // check to see if otherTables is truthy
+ // set the FK options to rerender a new list depending on the table name
+ const newTableFkOptions = otherTables.find(
+ (el) => el.table_name === e.target.value
+ );
+
+ if (newTableFkOptions) {
+ setFkOptions(newTableFkOptions.column_names);
+ }
+ }
+ // setSchemaState(schemaStateCopy)
+ };
+
+ return (
+