From 290dfe303cf5d3ca56937db4c618db79cc4fd498 Mon Sep 17 00:00:00 2001 From: Benjamin Toby Date: Sat, 3 Jan 2026 05:34:24 +0100 Subject: [PATCH] Updates --- dist/package-shared/data/app-data.d.ts | 5 + dist/package-shared/data/app-data.js | 7 + dist/package-shared/data/app-data.ts | 5 + .../functions/api/query/post.js | 1 + .../functions/backend/db/addDbEntry.d.ts | 29 +- .../functions/backend/db/addDbEntry.js | 18 +- .../createDbFromSchema/handle-indexes.js | 61 +-- .../handle-unique-constraints.d.ts | 14 + .../handle-unique-constraints.js | 69 +++ .../shell/createDbFromSchema/index.js | 8 +- .../shell/utils/createTable.d.ts | 8 +- .../package-shared/shell/utils/createTable.js | 61 ++- .../shell/utils/drop-all-foreign-keys.d.ts | 9 - .../shell/utils/drop-all-foreign-keys.js | 52 --- .../utils/handle-dsql-schema-fields.d.ts | 16 + .../shell/utils/handle-dsql-schema-fields.js | 72 +++ .../handle-mariadb-existing-columns.d.ts | 18 + .../utils/handle-mariadb-existing-columns.js | 94 ++++ .../shell/utils/handle-table-foreign-key.d.ts | 14 +- .../shell/utils/handle-table-foreign-key.js | 45 +- .../shell/utils/update-table-init.d.ts | 17 + .../shell/utils/update-table-init.js | 136 ++++++ .../shell/utils/updateTable.d.ts | 5 +- .../package-shared/shell/utils/updateTable.js | 345 ++------------- dist/package-shared/types/index.d.ts | 59 ++- dist/package-shared/types/index.js | 3 + .../utils/data-fetching/crud.js | 5 +- ...-schema-children-handle-children-tables.js | 4 + .../utils/grab-sql-key-name.d.ts | 2 +- .../package-shared/utils/grab-sql-key-name.js | 6 +- dist/package-shared/utils/parse-env.d.ts | 20 +- package-shared/data/app-data.ts | 5 + package-shared/functions/api/query/post.ts | 1 + .../functions/backend/db/addDbEntry.ts | 48 +- .../createDbFromSchema/handle-indexes.ts | 82 ++-- .../handle-unique-constraints.ts | 80 ++++ .../shell/createDbFromSchema/index.ts | 9 +- package-shared/shell/utils/createTable.ts | 73 ++- .../shell/utils/drop-all-foreign-keys.ts | 53 --- .../shell/utils/handle-dsql-schema-fields.ts | 85 ++++ .../utils/handle-mariadb-existing-columns.ts | 126 ++++++ .../shell/utils/handle-table-foreign-key.ts | 67 +-- .../shell/utils/update-table-init.ts | 166 +++++++ package-shared/shell/utils/updateTable.ts | 416 +++--------------- package-shared/types/index.ts | 66 +++ package-shared/utils/data-fetching/crud.ts | 6 +- ...-schema-children-handle-children-tables.ts | 10 + package-shared/utils/grab-sql-key-name.ts | 10 +- package.json | 2 +- 49 files changed, 1497 insertions(+), 1016 deletions(-) create mode 100644 dist/package-shared/data/app-data.d.ts create mode 100644 dist/package-shared/data/app-data.js create mode 100644 dist/package-shared/data/app-data.ts create mode 100644 dist/package-shared/shell/createDbFromSchema/handle-unique-constraints.d.ts create mode 100644 dist/package-shared/shell/createDbFromSchema/handle-unique-constraints.js delete mode 100644 dist/package-shared/shell/utils/drop-all-foreign-keys.d.ts delete mode 100644 dist/package-shared/shell/utils/drop-all-foreign-keys.js create mode 100644 dist/package-shared/shell/utils/handle-dsql-schema-fields.d.ts create mode 100644 dist/package-shared/shell/utils/handle-dsql-schema-fields.js create mode 100644 dist/package-shared/shell/utils/handle-mariadb-existing-columns.d.ts create mode 100644 dist/package-shared/shell/utils/handle-mariadb-existing-columns.js create mode 100644 dist/package-shared/shell/utils/update-table-init.d.ts create mode 100644 dist/package-shared/shell/utils/update-table-init.js create mode 100644 package-shared/data/app-data.ts create mode 100644 package-shared/shell/createDbFromSchema/handle-unique-constraints.ts delete mode 100644 package-shared/shell/utils/drop-all-foreign-keys.ts create mode 100644 package-shared/shell/utils/handle-dsql-schema-fields.ts create mode 100644 package-shared/shell/utils/handle-mariadb-existing-columns.ts create mode 100644 package-shared/shell/utils/update-table-init.ts diff --git a/dist/package-shared/data/app-data.d.ts b/dist/package-shared/data/app-data.d.ts new file mode 100644 index 0000000..0f6f93b --- /dev/null +++ b/dist/package-shared/data/app-data.d.ts @@ -0,0 +1,5 @@ +declare const AppData: { + readonly UniqueConstraintComment: "dsql_schema_unique_constraint"; + readonly IndexComment: "dsql_schema_index"; +}; +export default AppData; diff --git a/dist/package-shared/data/app-data.js b/dist/package-shared/data/app-data.js new file mode 100644 index 0000000..4391d4c --- /dev/null +++ b/dist/package-shared/data/app-data.js @@ -0,0 +1,7 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const AppData = { + UniqueConstraintComment: `dsql_schema_unique_constraint`, + IndexComment: `dsql_schema_index`, +}; +exports.default = AppData; diff --git a/dist/package-shared/data/app-data.ts b/dist/package-shared/data/app-data.ts new file mode 100644 index 0000000..f1ba1ae --- /dev/null +++ b/dist/package-shared/data/app-data.ts @@ -0,0 +1,5 @@ +const AppData = { + UniqueConstraintComment: `dsql_schema_unique_constraint`, + IndexComment: `dsql_schema_index`, +} as const; +export default AppData; diff --git a/dist/package-shared/functions/api/query/post.js b/dist/package-shared/functions/api/query/post.js index 43decfe..33d4dab 100644 --- a/dist/package-shared/functions/api/query/post.js +++ b/dist/package-shared/functions/api/query/post.js @@ -70,6 +70,7 @@ function apiPost(_a) { delete clonedTargetTable.childrenTables; delete clonedTargetTable.updateData; delete clonedTargetTable.indexes; + delete clonedTargetTable.uniqueConstraints; tableSchema = clonedTargetTable; } } diff --git a/dist/package-shared/functions/backend/db/addDbEntry.d.ts b/dist/package-shared/functions/backend/db/addDbEntry.d.ts index 50d4ee5..dfcf1e5 100644 --- a/dist/package-shared/functions/backend/db/addDbEntry.d.ts +++ b/dist/package-shared/functions/backend/db/addDbEntry.d.ts @@ -1,32 +1,7 @@ -import { DbContextsArray } from "./runQuery"; -import { APIResponseObject, DSQL_TableSchemaType, PostInsertReturn } from "../../../types"; -import { ConnectionConfig } from "mariadb"; -export type AddDbEntryParam = { - dbContext?: (typeof DbContextsArray)[number]; - paradigm?: "Read Only" | "Full Access"; - dbFullName?: string; - tableName: K; - data?: T; - batchData?: T[]; - tableSchema?: DSQL_TableSchemaType; - duplicateColumnName?: keyof T; - duplicateColumnValue?: string | number; - /** - * Update Entry if a duplicate is found. - * Requires `duplicateColumnName` and `duplicateColumnValue` parameters - */ - update?: boolean; - encryptionKey?: string; - encryptionSalt?: string; - forceLocal?: boolean; - debug?: boolean; - dbConfig?: ConnectionConfig; -}; +import { AddDbEntryParam, APIResponseObject, PostInsertReturn } from "../../../types"; /** * Add a db Entry Function */ export default function addDbEntry({ dbContext, paradigm, dbFullName, tableName, data, batchData, tableSchema, duplicateColumnName, duplicateColumnValue, update, encryptionKey, encryptionSalt, forceLocal, debug, dbConfig, }: AddDbEntryParam): Promise>; +} = any, K extends string = string>({ dbContext, paradigm, dbFullName, tableName, data, batchData, tableSchema, duplicateColumnName, duplicateColumnValue, update, encryptionKey, encryptionSalt, forceLocal, debug, dbConfig, onDuplicate, }: AddDbEntryParam): Promise>; diff --git a/dist/package-shared/functions/backend/db/addDbEntry.js b/dist/package-shared/functions/backend/db/addDbEntry.js index 129c20c..05b1a70 100644 --- a/dist/package-shared/functions/backend/db/addDbEntry.js +++ b/dist/package-shared/functions/backend/db/addDbEntry.js @@ -25,7 +25,7 @@ const grab_parsed_value_1 = __importDefault(require("./grab-parsed-value")); * Add a db Entry Function */ function addDbEntry(_a) { - return __awaiter(this, arguments, void 0, function* ({ dbContext, paradigm, dbFullName, tableName, data, batchData, tableSchema, duplicateColumnName, duplicateColumnValue, update, encryptionKey, encryptionSalt, forceLocal, debug, dbConfig, }) { + return __awaiter(this, arguments, void 0, function* ({ dbContext, paradigm, dbFullName, tableName, data, batchData, tableSchema, duplicateColumnName, duplicateColumnValue, update, encryptionKey, encryptionSalt, forceLocal, debug, dbConfig, onDuplicate, }) { const isMaster = forceLocal ? true : (0, check_if_is_master_1.default)({ dbContext, dbFullName }); @@ -141,8 +141,14 @@ function addDbEntry(_a) { } if (newData) { const { insertKeysArray, insertValuesArray, queryValuesArray } = generateQuery(newData); - const query = `INSERT INTO ${isMaster && !dbFullName ? "" : `\`${dbFullName}\`.`}\`${tableName}\` (${insertKeysArray.join(",")}) VALUES (${grabQueryValuesString(insertValuesArray)})`; + let query = `INSERT INTO ${isMaster && !dbFullName ? "" : `\`${dbFullName}\`.`}\`${tableName}\` (${insertKeysArray.join(",")}) VALUES (${grabQueryValuesString(insertValuesArray)})`; const finalQueryValues = grabFinalQueryValuesArr(queryValuesArray); + if (onDuplicate) { + query += ` ON DUPLICATE KEY ${onDuplicate.sql}`; + if (onDuplicate.values) { + finalQueryValues.push(...onDuplicate.values); + } + } const newInsert = yield (0, conn_db_handler_1.default)({ query, values: finalQueryValues, @@ -171,10 +177,16 @@ function addDbEntry(_a) { batchInsertValuesArray.push(insertValuesArray); batchQueryValuesArray.push(queryValuesArray); } - const query = `INSERT INTO ${isMaster && !dbFullName ? "" : `\`${dbFullName}\`.`}\`${tableName}\` (${batchInsertKeysArray === null || batchInsertKeysArray === void 0 ? void 0 : batchInsertKeysArray.join(",")}) VALUES ${batchInsertValuesArray + let query = `INSERT INTO ${isMaster && !dbFullName ? "" : `\`${dbFullName}\`.`}\`${tableName}\` (${batchInsertKeysArray === null || batchInsertKeysArray === void 0 ? void 0 : batchInsertKeysArray.join(",")}) VALUES ${batchInsertValuesArray .map((vl) => `(${grabQueryValuesString(vl)})`) .join(",")}`; const finalQueryValues = grabFinalQueryValuesArr(batchQueryValuesArray.flat()); + if (onDuplicate) { + query += ` ON DUPLICATE KEY ${onDuplicate.sql}`; + if (onDuplicate.values) { + finalQueryValues.push(...onDuplicate.values); + } + } const newInsert = yield (0, conn_db_handler_1.default)({ query, values: finalQueryValues, diff --git a/dist/package-shared/shell/createDbFromSchema/handle-indexes.js b/dist/package-shared/shell/createDbFromSchema/handle-indexes.js index 5692415..c5c9063 100644 --- a/dist/package-shared/shell/createDbFromSchema/handle-indexes.js +++ b/dist/package-shared/shell/createDbFromSchema/handle-indexes.js @@ -13,8 +13,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) { }; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = handleIndexescreateDbFromSchema; -const grab_dsql_schema_index_comment_1 = __importDefault(require("../utils/grab-dsql-schema-index-comment")); const dbHandler_1 = __importDefault(require("../../functions/backend/dbHandler")); +const app_data_1 = __importDefault(require("../../data/app-data")); /** * Handle DATASQUIREL Table Indexes * =================================================== @@ -23,36 +23,47 @@ const dbHandler_1 = __importDefault(require("../../functions/backend/dbHandler") */ function handleIndexescreateDbFromSchema(_a) { return __awaiter(this, arguments, void 0, function* ({ dbFullName, tableName, indexes, }) { + /** + * Handle MYSQL Table Indexes + * =================================================== + * @description Iterate through each table index(if available) + * and perform operations + */ const allExistingIndexes = (yield (0, dbHandler_1.default)({ - query: `SHOW INDEXES FROM \`${dbFullName}\`.\`${tableName}\``, + query: `SHOW INDEXES FROM \`${dbFullName}\`.\`${tableName}\` WHERE Index_comment LIKE '%${app_data_1.default["IndexComment"]}%'`, })); + if (allExistingIndexes) { + for (let f = 0; f < allExistingIndexes.length; f++) { + const { Key_name } = allExistingIndexes[f]; + try { + const existingKeyInSchema = indexes === null || indexes === void 0 ? void 0 : indexes.find((indexObject) => indexObject.alias === Key_name); + if (!existingKeyInSchema) + throw new Error(`This Index(${Key_name}) Has been Deleted!`); + } + catch (error) { + /** + * @description Drop Index: This happens when the MYSQL index is not + * present in the datasquirel DB schema + */ + yield (0, dbHandler_1.default)({ + query: `ALTER TABLE \`${dbFullName}\`.\`${tableName}\` DROP INDEX \`${Key_name}\``, + }); + } + } + } + /** + * # Re-Add New Indexes + */ for (let g = 0; g < indexes.length; g++) { const { indexType, indexName, indexTableFields, alias } = indexes[g]; if (!(alias === null || alias === void 0 ? void 0 : alias.match(/./))) continue; - /** - * @description Check for existing Index in MYSQL db - */ - try { - const existingKeyInDb = allExistingIndexes.filter((indexObject) => indexObject.Key_name === alias); - if (!existingKeyInDb[0]) - throw new Error("This Index Does not Exist"); - } - catch (error) { - /** - * @description Create new index if determined that it - * doesn't exist in MYSQL db - */ - const queryString = `CREATE${indexType == "full_text" - ? " FULLTEXT" - : indexType == "vector" - ? " VECTOR" - : ""} INDEX \`${alias}\` ON \`${dbFullName}\`.\`${tableName}\`(${indexTableFields === null || indexTableFields === void 0 ? void 0 : indexTableFields.map((nm) => nm.value).map((nm) => `\`${nm}\``).join(",")}) COMMENT '${(0, grab_dsql_schema_index_comment_1.default)()} ${indexName}'`; - const addIndex = yield (0, dbHandler_1.default)({ query: queryString }); - } + const queryString = `CREATE${indexType == "full_text" + ? " FULLTEXT" + : indexType == "vector" + ? " VECTOR" + : ""} INDEX \`${alias}\` ON \`${dbFullName}\`.\`${tableName}\`(${indexTableFields === null || indexTableFields === void 0 ? void 0 : indexTableFields.map((nm) => nm.value).map((nm) => `\`${nm}\``).join(",")}) COMMENT '${app_data_1.default["IndexComment"]} ${indexName}'`; + const addIndex = yield (0, dbHandler_1.default)({ query: queryString }); } - const allExistingIndexesAfterUpdate = (yield (0, dbHandler_1.default)({ - query: `SHOW INDEXES FROM \`${dbFullName}\`.\`${tableName}\``, - })); }); } diff --git a/dist/package-shared/shell/createDbFromSchema/handle-unique-constraints.d.ts b/dist/package-shared/shell/createDbFromSchema/handle-unique-constraints.d.ts new file mode 100644 index 0000000..8ecfd10 --- /dev/null +++ b/dist/package-shared/shell/createDbFromSchema/handle-unique-constraints.d.ts @@ -0,0 +1,14 @@ +import { DSQL_UniqueConstraintSchemaType } from "../../types"; +type Param = { + tableName: string; + dbFullName: string; + tableUniqueConstraints: DSQL_UniqueConstraintSchemaType[]; +}; +/** + * Handle DATASQUIREL Table Unique Constraints + * =================================================== + * @description Iterate through each datasquirel schema + * table unique constraint(if available), and perform operations + */ +export default function handleUniqueConstraintsCreateDbFromSchema({ dbFullName, tableName, tableUniqueConstraints, }: Param): Promise; +export {}; diff --git a/dist/package-shared/shell/createDbFromSchema/handle-unique-constraints.js b/dist/package-shared/shell/createDbFromSchema/handle-unique-constraints.js new file mode 100644 index 0000000..81ccc37 --- /dev/null +++ b/dist/package-shared/shell/createDbFromSchema/handle-unique-constraints.js @@ -0,0 +1,69 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.default = handleUniqueConstraintsCreateDbFromSchema; +const dbHandler_1 = __importDefault(require("../../functions/backend/dbHandler")); +const app_data_1 = __importDefault(require("../../data/app-data")); +/** + * Handle DATASQUIREL Table Unique Constraints + * =================================================== + * @description Iterate through each datasquirel schema + * table unique constraint(if available), and perform operations + */ +function handleUniqueConstraintsCreateDbFromSchema(_a) { + return __awaiter(this, arguments, void 0, function* ({ dbFullName, tableName, tableUniqueConstraints, }) { + /** + * # Delete All Existing Unique Constraints + */ + // const allExistingUniqueConstraints = (await dbHandler({ + // query: `SHOW INDEXES FROM \`${dbFullName}\`.\`${tableName}\` WHERE Index_comment LIKE '%${AppData["UniqueConstraintComment"]}%'`, + // })) as DSQL_MYSQL_SHOW_INDEXES_Type[] | null; + // if (allExistingUniqueConstraints?.[0]) { + // for (let f = 0; f < allExistingUniqueConstraints.length; f++) { + // const { Key_name } = allExistingUniqueConstraints[f]; + // try { + // const existingKeyInSchema = tableUniqueConstraints?.find( + // (indexObject) => indexObject.alias === Key_name + // ); + // if (!existingKeyInSchema) + // throw new Error( + // `This Index(${Key_name}) Has been Deleted!` + // ); + // } catch (error) { + // /** + // * @description Drop Index: This happens when the MYSQL index is not + // * present in the datasquirel DB schema + // */ + // await dbHandler({ + // query: `ALTER TABLE \`${dbFullName}\`.\`${tableName}\` DROP INDEX \`${Key_name}\``, + // }); + // } + // } + // } + /** + * # Re-Add New Constraints + */ + for (let g = 0; g < tableUniqueConstraints.length; g++) { + const { constraintName, alias, constraintTableFields } = tableUniqueConstraints[g]; + if (!(alias === null || alias === void 0 ? void 0 : alias.match(/./))) + continue; + /** + * @description Create new index if determined that it + * doesn't exist in MYSQL db + */ + const queryString = `CREATE UNIQUE INDEX \`${alias}\` ON \`${dbFullName}\`.\`${tableName}\`(${constraintTableFields === null || constraintTableFields === void 0 ? void 0 : constraintTableFields.map((nm) => nm.value).map((nm) => `\`${nm}\``).join(",")}) COMMENT '${app_data_1.default["UniqueConstraintComment"]} ${constraintName}'`; + const addIndex = yield (0, dbHandler_1.default)({ query: queryString }); + } + }); +} diff --git a/dist/package-shared/shell/createDbFromSchema/index.js b/dist/package-shared/shell/createDbFromSchema/index.js index d7508e1..a57210b 100644 --- a/dist/package-shared/shell/createDbFromSchema/index.js +++ b/dist/package-shared/shell/createDbFromSchema/index.js @@ -104,7 +104,7 @@ function createDbFromSchema(_a) { */ for (let t = 0; t < tables.length; t++) { const table = tables[t]; - const { tableName, fields, indexes } = table; + const { tableName, fields, indexes, uniqueConstraints } = table; if (targetTable && tableName !== targetTable) continue; console.log(`Handling table => ${tableName}`); @@ -139,6 +139,7 @@ function createDbFromSchema(_a) { recordedDbEntry, tableSchema: table, isMain, + tableUniqueConstraints: uniqueConstraints, }); if (table.childrenTables && table.childrenTables[0]) { for (let ch = 0; ch < table.childrenTables.length; ch++) { @@ -159,6 +160,7 @@ function createDbFromSchema(_a) { userId, dbSchema: childTableParentDbSchema, tableIndexes: childTableSchema.indexes, + tableUniqueConstraints: childTableSchema.uniqueConstraints, clone: true, recordedDbEntry, tableSchema: table, @@ -173,11 +175,13 @@ function createDbFromSchema(_a) { */ const createNewTable = yield (0, createTable_1.default)({ tableName: tableName, - tableInfoArray: fields, + fields, dbFullName: dbFullName, tableSchema: table, recordedDbEntry, isMain, + indexes, + uniqueConstraints, }); /** * Handle DATASQUIREL Table Indexes diff --git a/dist/package-shared/shell/utils/createTable.d.ts b/dist/package-shared/shell/utils/createTable.d.ts index 193336d..f8acd21 100644 --- a/dist/package-shared/shell/utils/createTable.d.ts +++ b/dist/package-shared/shell/utils/createTable.d.ts @@ -1,15 +1,17 @@ -import { DSQL_FieldSchemaType, DSQL_TableSchemaType } from "../../types"; +import { DSQL_FieldSchemaType, DSQL_IndexSchemaType, DSQL_TableSchemaType, DSQL_UniqueConstraintSchemaType } from "../../types"; import { DSQL_DATASQUIREL_USER_DATABASES } from "../../types/dsql"; type Param = { dbFullName: string; tableName: string; - tableInfoArray: DSQL_FieldSchemaType[]; + fields: DSQL_FieldSchemaType[]; tableSchema?: DSQL_TableSchemaType; recordedDbEntry?: DSQL_DATASQUIREL_USER_DATABASES; isMain?: boolean; + indexes?: DSQL_IndexSchemaType[]; + uniqueConstraints?: DSQL_UniqueConstraintSchemaType[]; }; /** * # Create Table Functions */ -export default function createTable({ dbFullName, tableName, tableInfoArray, tableSchema, recordedDbEntry, isMain, }: Param): Promise; +export default function createTable({ dbFullName, tableName, fields: passedFields, tableSchema, recordedDbEntry, isMain, indexes, uniqueConstraints, }: Param): Promise; export {}; diff --git a/dist/package-shared/shell/utils/createTable.js b/dist/package-shared/shell/utils/createTable.js index 467c01c..bb3f46b 100644 --- a/dist/package-shared/shell/utils/createTable.js +++ b/dist/package-shared/shell/utils/createTable.js @@ -18,12 +18,14 @@ const supplementTable_1 = __importDefault(require("./supplementTable")); const handle_table_foreign_key_1 = __importDefault(require("./handle-table-foreign-key")); const create_table_handle_table_record_1 = __importDefault(require("./create-table-handle-table-record")); const dbHandler_1 = __importDefault(require("../../functions/backend/dbHandler")); +const handle_indexes_1 = __importDefault(require("../createDbFromSchema/handle-indexes")); +const handle_unique_constraints_1 = __importDefault(require("../createDbFromSchema/handle-unique-constraints")); /** * # Create Table Functions */ function createTable(_a) { - return __awaiter(this, arguments, void 0, function* ({ dbFullName, tableName, tableInfoArray, tableSchema, recordedDbEntry, isMain, }) { - const finalTable = (0, supplementTable_1.default)({ tableInfoArray: tableInfoArray }); + return __awaiter(this, arguments, void 0, function* ({ dbFullName, tableName, fields: passedFields, tableSchema, recordedDbEntry, isMain, indexes, uniqueConstraints, }) { + const fields = (0, supplementTable_1.default)({ tableInfoArray: passedFields }); let tableId = yield (0, create_table_handle_table_record_1.default)({ recordedDbEntry, tableSchema, @@ -34,15 +36,15 @@ function createTable(_a) { const createTableQueryArray = []; createTableQueryArray.push(`CREATE TABLE IF NOT EXISTS \`${dbFullName}\`.\`${tableName}\` (`); let primaryKeySet = false; - for (let i = 0; i < finalTable.length; i++) { - const column = finalTable[i]; + for (let i = 0; i < fields.length; i++) { + const column = fields[i]; let { fieldEntryText, newPrimaryKeySet } = (0, generateColumnDescription_1.default)({ columnData: column, primaryKeySet: primaryKeySet, }); primaryKeySet = newPrimaryKeySet; const comma = (() => { - if (i === finalTable.length - 1) + if (i === fields.length - 1) return ""; return ","; })(); @@ -53,19 +55,42 @@ function createTable(_a) { const newTable = yield (0, dbHandler_1.default)({ query: createTableQuery, }); - for (let i = 0; i < finalTable.length; i++) { - const column = finalTable[i]; - const { foreignKey, fieldName } = column; - if (!fieldName) - continue; - if (foreignKey) { - yield (0, handle_table_foreign_key_1.default)({ - dbFullName, - foreignKey, - tableName, - fieldName, - }); - } + /** + * Handle MYSQL Foreign Keys + * =================================================== + * @description Iterate through each datasquirel schema + * table index(if available), and perform operations + */ + yield (0, handle_table_foreign_key_1.default)({ + dbFullName, + fields, + tableName, + }); + /** + * Handle DATASQUIREL Table Indexes + * =================================================== + * @description Iterate through each datasquirel schema + * table index(if available), and perform operations + */ + if (indexes === null || indexes === void 0 ? void 0 : indexes[0]) { + (0, handle_indexes_1.default)({ + dbFullName, + indexes, + tableName, + }); + } + /** + * Handle DATASQUIREL Table Unique Indexes + * =================================================== + * @description Iterate through each datasquirel schema + * table unique constraint(if available), and perform operations + */ + if (uniqueConstraints === null || uniqueConstraints === void 0 ? void 0 : uniqueConstraints[0]) { + (0, handle_unique_constraints_1.default)({ + dbFullName, + tableUniqueConstraints: uniqueConstraints, + tableName, + }); } return tableId; }); diff --git a/dist/package-shared/shell/utils/drop-all-foreign-keys.d.ts b/dist/package-shared/shell/utils/drop-all-foreign-keys.d.ts deleted file mode 100644 index 1412eac..0000000 --- a/dist/package-shared/shell/utils/drop-all-foreign-keys.d.ts +++ /dev/null @@ -1,9 +0,0 @@ -type Param = { - dbFullName: string; - tableName: string; -}; -/** - * # Drop All Foreign Keys - */ -export default function dropAllForeignKeys({ dbFullName, tableName, }: Param): Promise; -export {}; diff --git a/dist/package-shared/shell/utils/drop-all-foreign-keys.js b/dist/package-shared/shell/utils/drop-all-foreign-keys.js deleted file mode 100644 index b07eaae..0000000 --- a/dist/package-shared/shell/utils/drop-all-foreign-keys.js +++ /dev/null @@ -1,52 +0,0 @@ -"use strict"; -var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); -}; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.default = dropAllForeignKeys; -const grab_sql_key_name_1 = __importDefault(require("../../utils/grab-sql-key-name")); -const dbHandler_1 = __importDefault(require("../../functions/backend/dbHandler")); -/** - * # Drop All Foreign Keys - */ -function dropAllForeignKeys(_a) { - return __awaiter(this, arguments, void 0, function* ({ dbFullName, tableName, }) { - try { - // const rows = await varDatabaseDbHandler({ - // queryString: `SELECT CONSTRAINT_NAME FROM information_schema.REFERENTIAL_CONSTRAINTS WHERE TABLE_NAME = '${tableName}' AND CONSTRAINT_SCHEMA = '${dbFullName}'`, - // }); - // console.log("rows", rows); - // console.log("dbFullName", dbFullName); - // console.log("tableName", tableName); - // for (const row of rows) { - // await varDatabaseDbHandler({ - // queryString: `ALTER TABLE \`${dbFullName}\`.\`${tableName}\` DROP FOREIGN KEY \`${row.CONSTRAINT_NAME}\` - // `, - // }); - // } - const foreignKeys = (yield (0, dbHandler_1.default)({ - query: `SHOW INDEXES FROM \`${dbFullName}\`.\`${tableName}\` WHERE Key_name LIKE '${(0, grab_sql_key_name_1.default)({ type: "foreign_key" })}%'`, - })); - for (const fk of foreignKeys) { - if (fk.Key_name.match(new RegExp((0, grab_sql_key_name_1.default)({ type: "foreign_key" })))) { - yield (0, dbHandler_1.default)({ - query: `ALTER TABLE \`${dbFullName}\`.\`${tableName}\` DROP INDEX \`${fk.Key_name}\` - `, - }); - } - } - } - catch (error) { - console.log(`dropAllForeignKeys ERROR => ${error.message}`); - } - }); -} diff --git a/dist/package-shared/shell/utils/handle-dsql-schema-fields.d.ts b/dist/package-shared/shell/utils/handle-dsql-schema-fields.d.ts new file mode 100644 index 0000000..b21b35d --- /dev/null +++ b/dist/package-shared/shell/utils/handle-dsql-schema-fields.d.ts @@ -0,0 +1,16 @@ +import { DSQL_FieldSchemaType, DSQL_MYSQL_SHOW_COLUMNS_Type } from "../../types"; +type Param = { + dbFullName: string; + tableName: string; + fields: DSQL_FieldSchemaType[]; + clone?: boolean; + allExistingColumns: DSQL_MYSQL_SHOW_COLUMNS_Type[]; +}; +/** + * Handle DATASQUIREL schema fields for current table + * =================================================== + * @description Iterate through each field object and + * perform operations + */ +export default function handleDSQLSchemaFields({ dbFullName, tableName, fields, allExistingColumns, }: Param): Promise; +export {}; diff --git a/dist/package-shared/shell/utils/handle-dsql-schema-fields.js b/dist/package-shared/shell/utils/handle-dsql-schema-fields.js new file mode 100644 index 0000000..77ac691 --- /dev/null +++ b/dist/package-shared/shell/utils/handle-dsql-schema-fields.js @@ -0,0 +1,72 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.default = handleDSQLSchemaFields; +const dbHandler_1 = __importDefault(require("../../functions/backend/dbHandler")); +const default_fields_regexp_1 = __importDefault(require("../../functions/dsql/default-fields-regexp")); +const generateColumnDescription_1 = __importDefault(require("./generateColumnDescription")); +/** + * Handle DATASQUIREL schema fields for current table + * =================================================== + * @description Iterate through each field object and + * perform operations + */ +function handleDSQLSchemaFields(_a) { + return __awaiter(this, arguments, void 0, function* ({ dbFullName, tableName, fields, allExistingColumns, }) { + let sql = `ALTER TABLE \`${dbFullName}\`.\`${tableName}\``; + for (let i = 0; i < fields.length; i++) { + const column = fields[i]; + // const prevColumn = fields[i - 1]; + // const nextColumn = fields[i + 1]; + const { fieldName, dataType, foreignKey } = column; + if (!fieldName) + continue; + if (default_fields_regexp_1.default.test(fieldName)) + continue; + let updateText = ""; + const existingColumnIndex = allExistingColumns === null || allExistingColumns === void 0 ? void 0 : allExistingColumns.findIndex((_column, _index) => _column.Field === fieldName); + const existingColumn = existingColumnIndex >= 0 + ? allExistingColumns[existingColumnIndex] + : undefined; + let { fieldEntryText } = (0, generateColumnDescription_1.default)({ + columnData: column, + }); + /** + * @description Modify Column(Field) if it already exists + * in MYSQL database + */ + if (existingColumn === null || existingColumn === void 0 ? void 0 : existingColumn.Field) { + const { Field, Type } = existingColumn; + updateText += ` MODIFY COLUMN ${fieldEntryText}`; + } + else { + /** + * @description Append new column to the end of existing columns + */ + updateText += ` ADD COLUMN ${fieldEntryText}`; + } + /** + * @description Pust SQL code snippet to updateTableQueryArray Array + * Add a comma(,) to separate from the next snippet + */ + if (updateText.match(/./)) { + sql += " " + updateText + ","; + } + } + const finalSQL = sql.replace(/\,$/, ""); + const updateTable = yield (0, dbHandler_1.default)({ + query: finalSQL, + }); + }); +} diff --git a/dist/package-shared/shell/utils/handle-mariadb-existing-columns.d.ts b/dist/package-shared/shell/utils/handle-mariadb-existing-columns.d.ts new file mode 100644 index 0000000..fdaef5d --- /dev/null +++ b/dist/package-shared/shell/utils/handle-mariadb-existing-columns.d.ts @@ -0,0 +1,18 @@ +import { DSQL_DatabaseSchemaType, DSQL_FieldSchemaType, DSQL_MYSQL_SHOW_COLUMNS_Type } from "../../types"; +type Param = { + dbFullName: string; + tableName: string; + fields: DSQL_FieldSchemaType[]; + dbSchema: DSQL_DatabaseSchemaType; + userId?: number | string | null; +}; +/** + * Handle MYSQL Columns (Fields) + * =================================================== + * @description Now handle all fields/columns + */ +export default function handleMariaDBExistingColumns({ dbFullName, tableName, fields, dbSchema, userId, }: Param): Promise<{ + upToDateTableFieldsArray: DSQL_FieldSchemaType[]; + allExistingColumns: DSQL_MYSQL_SHOW_COLUMNS_Type[]; +}>; +export {}; diff --git a/dist/package-shared/shell/utils/handle-mariadb-existing-columns.js b/dist/package-shared/shell/utils/handle-mariadb-existing-columns.js new file mode 100644 index 0000000..984bee8 --- /dev/null +++ b/dist/package-shared/shell/utils/handle-mariadb-existing-columns.js @@ -0,0 +1,94 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.default = handleMariaDBExistingColumns; +const dbHandler_1 = __importDefault(require("../../functions/backend/dbHandler")); +const default_fields_regexp_1 = __importDefault(require("../../functions/dsql/default-fields-regexp")); +const grab_required_database_schemas_1 = require("../createDbFromSchema/grab-required-database-schemas"); +const lodash_1 = __importDefault(require("lodash")); +/** + * Handle MYSQL Columns (Fields) + * =================================================== + * @description Now handle all fields/columns + */ +function handleMariaDBExistingColumns(_a) { + return __awaiter(this, arguments, void 0, function* ({ dbFullName, tableName, fields, dbSchema, userId, }) { + let upToDateTableFieldsArray = lodash_1.default.cloneDeep(fields); + let allExistingColumns = (yield (0, dbHandler_1.default)({ + query: `SHOW COLUMNS FROM \`${dbFullName}\`.\`${tableName}\``, + })); + /** + * @description Iterate through every existing column + */ + for (let e = 0; e < allExistingColumns.length; e++) { + const { Field } = allExistingColumns[e]; + if (Field.match(default_fields_regexp_1.default)) + continue; + /** + * @description This finds out whether the fieldName corresponds with the MSQL Field name + * if the fildName doesn't match any MYSQL Field name, the field is deleted. + */ + let existingEntry = upToDateTableFieldsArray.find((column) => column.fieldName === Field || column.originName === Field); + if (!existingEntry) { + yield (0, dbHandler_1.default)({ + query: `ALTER TABLE \`${dbFullName}\`.\`${tableName}\` DROP COLUMN \`${Field}\``, + }); + continue; + } + if (existingEntry) { + /** + * @description Check if Field name has been updated + */ + if (existingEntry.updatedField && existingEntry.fieldName) { + yield (0, dbHandler_1.default)({ + query: `ALTER TABLE \`${dbFullName}\`.\`${tableName}\` RENAME COLUMN \`${existingEntry.originName}\` TO \`${existingEntry.fieldName}\``, + }); + console.log(`Column Renamed from "${existingEntry.originName}" to "${existingEntry.fieldName}"`); + /** + * Update Db Schema + * =================================================== + * @description Update Db Schema after renaming column + */ + try { + const updatedSchemaData = lodash_1.default.cloneDeep(dbSchema); + const targetTableIndex = updatedSchemaData.tables.findIndex((table) => table.tableName === tableName); + const targetFieldIndex = updatedSchemaData.tables[targetTableIndex].fields.findIndex((field) => field.fieldName === existingEntry.fieldName); + delete updatedSchemaData.tables[targetTableIndex].fields[targetFieldIndex]["originName"]; + delete updatedSchemaData.tables[targetTableIndex].fields[targetFieldIndex]["updatedField"]; + /** + * @description Set New Table Fields Array + */ + upToDateTableFieldsArray = + updatedSchemaData.tables[targetTableIndex].fields; + if (userId) { + (0, grab_required_database_schemas_1.writeUpdatedDbSchema)({ + dbSchema: updatedSchemaData, + userId, + }); + } + allExistingColumns = (yield (0, dbHandler_1.default)({ + query: `SHOW COLUMNS FROM \`${dbFullName}\`.\`${tableName}\``, + })); + } + catch (error) { + console.log("Update table error =>", error.message); + } + //////////////////////////////////////// + } + continue; + } + } + return { upToDateTableFieldsArray, allExistingColumns }; + }); +} diff --git a/dist/package-shared/shell/utils/handle-table-foreign-key.d.ts b/dist/package-shared/shell/utils/handle-table-foreign-key.d.ts index 89d5afc..fc42046 100644 --- a/dist/package-shared/shell/utils/handle-table-foreign-key.d.ts +++ b/dist/package-shared/shell/utils/handle-table-foreign-key.d.ts @@ -1,13 +1,15 @@ -import { DSQL_ForeignKeyType } from "../../types"; +import { DSQL_FieldSchemaType } from "../../types"; type Param = { dbFullName: string; tableName: string; - foreignKey: DSQL_ForeignKeyType; - fieldName: string; - errorLogs?: any[]; + fields: DSQL_FieldSchemaType[]; + clone?: boolean; }; /** - * # Update table function + * Handle MYSQL Foreign Keys + * =================================================== + * @description Iterate through each datasquirel schema + * table index(if available), and perform operations */ -export default function handleTableForeignKey({ dbFullName, tableName, foreignKey, errorLogs, fieldName, }: Param): Promise; +export default function handleTableForeignKey({ dbFullName, tableName, fields, clone, }: Param): Promise; export {}; diff --git a/dist/package-shared/shell/utils/handle-table-foreign-key.js b/dist/package-shared/shell/utils/handle-table-foreign-key.js index 3b8d16d..7e268fc 100644 --- a/dist/package-shared/shell/utils/handle-table-foreign-key.js +++ b/dist/package-shared/shell/utils/handle-table-foreign-key.js @@ -15,27 +15,34 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.default = handleTableForeignKey; const dbHandler_1 = __importDefault(require("../../functions/backend/dbHandler")); /** - * # Update table function + * Handle MYSQL Foreign Keys + * =================================================== + * @description Iterate through each datasquirel schema + * table index(if available), and perform operations */ function handleTableForeignKey(_a) { - return __awaiter(this, arguments, void 0, function* ({ dbFullName, tableName, foreignKey, errorLogs, fieldName, }) { - const { destinationTableName, destinationTableColumnName, cascadeDelete, cascadeUpdate, foreignKeyName, } = foreignKey; - let finalQueryString = `ALTER TABLE \`${dbFullName}\`.\`${tableName}\``; - finalQueryString += ` ADD CONSTRAINT \`${foreignKeyName}\` FOREIGN KEY (\`${fieldName}\`)`; - finalQueryString += ` REFERENCES \`${destinationTableName}\`(\`${destinationTableColumnName}\`)`; - if (cascadeDelete) - finalQueryString += ` ON DELETE CASCADE`; - if (cascadeUpdate) - finalQueryString += ` ON UPDATE CASCADE`; - // let foreinKeyText = `ADD CONSTRAINT \`${foreignKeyName}\` FOREIGN KEY (\`${destinationTableColumnType}\`) REFERENCES \`${destinationTableName}\`(\`${destinationTableColumnName}\`)${ - // cascadeDelete ? " ON DELETE CASCADE" : "" - // }${cascadeUpdate ? " ON UPDATE CASCADE" : ""}`; - // let finalQueryString = `ALTER TABLE \`${dbFullName}\`.\`${tableName}\` ${foreinKeyText}`; - const addForeignKey = (yield (0, dbHandler_1.default)({ - query: finalQueryString, - })); - if (!(addForeignKey === null || addForeignKey === void 0 ? void 0 : addForeignKey.serverStatus)) { - errorLogs === null || errorLogs === void 0 ? void 0 : errorLogs.push(addForeignKey); + return __awaiter(this, arguments, void 0, function* ({ dbFullName, tableName, fields, clone, }) { + let addFkSQL = `ALTER TABLE \`${dbFullName}\`.\`${tableName}\``; + for (let i = 0; i < fields.length; i++) { + const { fieldName, foreignKey } = fields[i]; + if (!clone && foreignKey && fieldName) { + const { destinationTableName, destinationTableColumnName, cascadeDelete, cascadeUpdate, foreignKeyName, } = foreignKey; + addFkSQL += ` ADD CONSTRAINT \`${foreignKeyName}\` FOREIGN KEY (\`${fieldName}\`)`; + addFkSQL += ` REFERENCES \`${destinationTableName}\`(\`${destinationTableColumnName}\`)`; + if (cascadeDelete) + addFkSQL += ` ON DELETE CASCADE`; + if (cascadeUpdate) + addFkSQL += ` ON UPDATE CASCADE`; + addFkSQL += `,`; + } + } + const finalAddFKSQL = addFkSQL.endsWith(",") + ? addFkSQL.replace(/\,$/, "") + : undefined; + if (finalAddFKSQL) { + const addForeignKey = (yield (0, dbHandler_1.default)({ + query: finalAddFKSQL, + })); } }); } diff --git a/dist/package-shared/shell/utils/update-table-init.d.ts b/dist/package-shared/shell/utils/update-table-init.d.ts new file mode 100644 index 0000000..ad3f486 --- /dev/null +++ b/dist/package-shared/shell/utils/update-table-init.d.ts @@ -0,0 +1,17 @@ +import { DSQL_DatabaseSchemaType, DSQL_TableSchemaType } from "../../types"; +import { DSQL_DATASQUIREL_USER_DATABASES } from "../../types/dsql"; +type Params = { + dbFullName: string; + tableName: string; + tableSchema: DSQL_TableSchemaType; + dbSchema: DSQL_DatabaseSchemaType; + recordedDbEntry?: DSQL_DATASQUIREL_USER_DATABASES; + isMain?: boolean; +}; +/** + * # Update table function + */ +export default function updateTableInit({ dbFullName, tableName, tableSchema, recordedDbEntry, isMain, }: Params): Promise<{ + tableID: number | undefined; +}>; +export {}; diff --git a/dist/package-shared/shell/utils/update-table-init.js b/dist/package-shared/shell/utils/update-table-init.js new file mode 100644 index 0000000..848e27f --- /dev/null +++ b/dist/package-shared/shell/utils/update-table-init.js @@ -0,0 +1,136 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.default = updateTableInit; +const create_table_handle_table_record_1 = __importDefault(require("./create-table-handle-table-record")); +const dbHandler_1 = __importDefault(require("../../functions/backend/dbHandler")); +/** + * # Update table function + */ +function updateTableInit(_a) { + return __awaiter(this, arguments, void 0, function* ({ dbFullName, tableName, tableSchema, recordedDbEntry, isMain, }) { + /** + * @description Grab Table Record + */ + if (!recordedDbEntry && !isMain) { + throw new Error("Recorded Db entry not found!"); + } + let tableID = yield (0, create_table_handle_table_record_1.default)({ + recordedDbEntry, + tableSchema, + update: true, + isMain, + }); + if (!tableID && !isMain) { + throw new Error("Recorded Table entry not found!"); + } + /** + * Handle Table Default Collation + * + * @description Update Column Collation + */ + if (tableSchema.collation) { + try { + const existingCollation = (yield (0, dbHandler_1.default)({ + query: `SHOW TABLE STATUS LIKE '${tableName}'`, + config: { database: dbFullName }, + })); + const existingCollationStr = existingCollation === null || existingCollation === void 0 ? void 0 : existingCollation[0].Collation; + if (existingCollationStr !== tableSchema.collation) { + yield (0, dbHandler_1.default)({ + query: `ALTER TABLE \`${dbFullName}\`.\`${tableName}\` CONVERT TO CHARACTER SET utf8mb4 COLLATE ${tableSchema.collation}`, + }); + } + } + catch (error) { } + } + /** + * Drop All Foreign Keys + * =================================================== + * @description Find all existing foreign keys and drop + * them + */ + const allForeignKeys = (yield (0, dbHandler_1.default)({ + query: `SELECT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE CONSTRAINT_SCHEMA = '${dbFullName}' AND TABLE_NAME='${tableName}' AND CONSTRAINT_TYPE='FOREIGN KEY'`, + })); + if (allForeignKeys === null || allForeignKeys === void 0 ? void 0 : allForeignKeys[0]) { + let dropFkSQL = `ALTER TABLE \`${dbFullName}\`.\`${tableName}\``; + for (let c = 0; c < allForeignKeys.length; c++) { + const { CONSTRAINT_NAME } = allForeignKeys[c]; + if (CONSTRAINT_NAME.match(/PRIMARY/)) + continue; + dropFkSQL += ` DROP FOREIGN KEY \`${CONSTRAINT_NAME}\`,`; + } + const finalSQL = dropFkSQL.endsWith(",") + ? dropFkSQL.replace(/\,$/, "") + : undefined; + if (finalSQL) { + yield (0, dbHandler_1.default)({ + query: finalSQL, + }); + } + } + /** + * Drop All Unique Constraints + * =================================================== + * @description Find all existing unique field constraints + * and remove them + */ + const allUniqueConstraints = (yield (0, dbHandler_1.default)({ + query: `SELECT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE CONSTRAINT_SCHEMA = '${dbFullName}' AND TABLE_NAME='${tableName}' AND CONSTRAINT_TYPE='UNIQUE'`, + })); + if (allUniqueConstraints === null || allUniqueConstraints === void 0 ? void 0 : allUniqueConstraints[0]) { + let dropIndxSQL = `ALTER TABLE \`${dbFullName}\`.\`${tableName}\``; + for (let c = 0; c < allUniqueConstraints.length; c++) { + const { CONSTRAINT_NAME } = allUniqueConstraints[c]; + dropIndxSQL += ` DROP INDEX ${CONSTRAINT_NAME},`; + } + const finalDropIndxSQL = dropIndxSQL.endsWith(",") + ? dropIndxSQL.replace(/\,$/, "") + : undefined; + if (finalDropIndxSQL) { + yield (0, dbHandler_1.default)({ + query: finalDropIndxSQL, + }); + } + } + /** + * Drop All Indexes + * =================================================== + * @description Find all existing foreign keys and drop + * them + */ + const allMariadbIndexes = (yield (0, dbHandler_1.default)({ + query: `SHOW INDEXES FROM \`${dbFullName}\`.\`${tableName}\``, + })); + if (allMariadbIndexes === null || allMariadbIndexes === void 0 ? void 0 : allMariadbIndexes[0]) { + let dropIndxs = `ALTER TABLE \`${dbFullName}\`.\`${tableName}\``; + for (let c = 0; c < allMariadbIndexes.length; c++) { + const { Key_name } = allMariadbIndexes[c]; + if (Key_name.match(/PRIMARY/)) + continue; + dropIndxs += ` DROP INDEX \`${Key_name}\`,`; + } + const finalDropIndxs = dropIndxs.endsWith(",") + ? dropIndxs.replace(/\,$/, "") + : undefined; + if (finalDropIndxs) { + const dropFkRes = yield (0, dbHandler_1.default)({ + query: finalDropIndxs, + }); + } + } + return { tableID }; + }); +} diff --git a/dist/package-shared/shell/utils/updateTable.d.ts b/dist/package-shared/shell/utils/updateTable.d.ts index 80b513d..003d7f0 100644 --- a/dist/package-shared/shell/utils/updateTable.d.ts +++ b/dist/package-shared/shell/utils/updateTable.d.ts @@ -1,4 +1,4 @@ -import { DSQL_DatabaseSchemaType, DSQL_FieldSchemaType, DSQL_IndexSchemaType, DSQL_TableSchemaType } from "../../types"; +import { DSQL_DatabaseSchemaType, DSQL_FieldSchemaType, DSQL_IndexSchemaType, DSQL_TableSchemaType, DSQL_UniqueConstraintSchemaType } from "../../types"; import { DSQL_DATASQUIREL_USER_DATABASES } from "../../types/dsql"; type Param = { dbFullName: string; @@ -8,6 +8,7 @@ type Param = { userId?: number | string | null; dbSchema: DSQL_DatabaseSchemaType; tableIndexes?: DSQL_IndexSchemaType[]; + tableUniqueConstraints?: DSQL_UniqueConstraintSchemaType[]; clone?: boolean; recordedDbEntry?: DSQL_DATASQUIREL_USER_DATABASES; isMain?: boolean; @@ -15,5 +16,5 @@ type Param = { /** * # Update table function */ -export default function updateTable({ dbFullName, tableName, tableFields, userId, dbSchema, tableIndexes, tableSchema, clone, recordedDbEntry, isMain, }: Param): Promise; +export default function updateTable({ dbFullName, tableName, tableFields, userId, dbSchema, tableIndexes, tableSchema, clone, recordedDbEntry, isMain, tableUniqueConstraints, }: Param): Promise; export {}; diff --git a/dist/package-shared/shell/utils/updateTable.js b/dist/package-shared/shell/utils/updateTable.js index 2db0b7c..077f7f4 100644 --- a/dist/package-shared/shell/utils/updateTable.js +++ b/dist/package-shared/shell/utils/updateTable.js @@ -13,117 +13,72 @@ var __importDefault = (this && this.__importDefault) || function (mod) { }; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = updateTable; -const generateColumnDescription_1 = __importDefault(require("./generateColumnDescription")); -const handle_table_foreign_key_1 = __importDefault(require("./handle-table-foreign-key")); -const drop_all_foreign_keys_1 = __importDefault(require("./drop-all-foreign-keys")); -const create_table_handle_table_record_1 = __importDefault(require("./create-table-handle-table-record")); -const default_fields_regexp_1 = __importDefault(require("../../functions/dsql/default-fields-regexp")); const handle_indexes_1 = __importDefault(require("../createDbFromSchema/handle-indexes")); -const lodash_1 = __importDefault(require("lodash")); -const grab_required_database_schemas_1 = require("../createDbFromSchema/grab-required-database-schemas"); -const normalize_text_1 = __importDefault(require("../../utils/normalize-text")); -const dbHandler_1 = __importDefault(require("../../functions/backend/dbHandler")); +const handle_unique_constraints_1 = __importDefault(require("../createDbFromSchema/handle-unique-constraints")); +const handle_table_foreign_key_1 = __importDefault(require("./handle-table-foreign-key")); +const handle_dsql_schema_fields_1 = __importDefault(require("./handle-dsql-schema-fields")); +const handle_mariadb_existing_columns_1 = __importDefault(require("./handle-mariadb-existing-columns")); +const update_table_init_1 = __importDefault(require("./update-table-init")); /** * # Update table function */ function updateTable(_a) { - return __awaiter(this, arguments, void 0, function* ({ dbFullName, tableName, tableFields, userId, dbSchema, tableIndexes, tableSchema, clone, recordedDbEntry, isMain, }) { - /** - * Initialize - * ========================================== - * @description Initial setup - */ - let errorLogs = []; - /** - * @description Initialize table info array. This value will be - * changing depending on if a field is renamed or not. - */ - let upToDateTableFieldsArray = lodash_1.default.cloneDeep(tableFields); - /** - * @type {string[]} - * @description Table update query string array - */ - const updateTableQueryArray = []; - /** - * @description Push the query initial value - */ - updateTableQueryArray.push(`ALTER TABLE \`${dbFullName}\`.\`${tableName}\``); + return __awaiter(this, arguments, void 0, function* ({ dbFullName, tableName, tableFields, userId, dbSchema, tableIndexes, tableSchema, clone, recordedDbEntry, isMain, tableUniqueConstraints, }) { /** * @description Grab Table Record */ - if (!recordedDbEntry && !isMain) { - throw new Error("Recorded Db entry not found!"); - } - let tableID = yield (0, create_table_handle_table_record_1.default)({ - recordedDbEntry, + const { tableID } = yield (0, update_table_init_1.default)({ + dbFullName, + dbSchema, + tableName, tableSchema, - update: true, isMain, + recordedDbEntry, }); - if (!tableID && !isMain) { - throw new Error("Recorded Table entry not found!"); - } - /** - * Handle Table Default Collation - * - * @description Update Column Collation - */ - if (tableSchema.collation) { - try { - const existingCollation = (yield (0, dbHandler_1.default)({ - query: `SHOW TABLE STATUS LIKE '${tableName}'`, - config: { database: dbFullName }, - })); - const existingCollationStr = existingCollation === null || existingCollation === void 0 ? void 0 : existingCollation[0].Collation; - if (existingCollationStr !== tableSchema.collation) { - yield (0, dbHandler_1.default)({ - query: `ALTER TABLE \`${dbFullName}\`.\`${tableName}\` CONVERT TO CHARACTER SET utf8mb4 COLLATE ${tableSchema.collation}`, - }); - } - } - catch (error) { } - } /** * Handle Table updates * * @description Try to undate table, catch error if anything goes wrong */ try { + const { allExistingColumns, upToDateTableFieldsArray } = yield (0, handle_mariadb_existing_columns_1.default)({ + dbFullName, + dbSchema, + fields: tableFields, + tableName, + userId, + }); /** - * Handle MYSQL Table Indexes + * Handle DATASQUIREL schema fields for current table * =================================================== - * @description Iterate through each table index(if available) - * and perform operations + * @description Iterate through each field object and + * perform operations */ - const allExistingIndexes = (yield (0, dbHandler_1.default)({ - query: `SHOW INDEXES FROM \`${dbFullName}\`.\`${tableName}\` WHERE Index_comment LIKE '%schema_index%'`, - })); - if (allExistingIndexes) { - for (let f = 0; f < allExistingIndexes.length; f++) { - const { Key_name } = allExistingIndexes[f]; - try { - const existingKeyInSchema = tableIndexes === null || tableIndexes === void 0 ? void 0 : tableIndexes.find((indexObject) => indexObject.alias === Key_name); - if (!existingKeyInSchema) - throw new Error(`This Index(${Key_name}) Has been Deleted!`); - } - catch (error) { - /** - * @description Drop Index: This happens when the MYSQL index is not - * present in the datasquirel DB schema - */ - yield (0, dbHandler_1.default)({ - query: `ALTER TABLE \`${dbFullName}\`.\`${tableName}\` DROP INDEX \`${Key_name}\``, - }); - } - } - } + yield (0, handle_dsql_schema_fields_1.default)({ + dbFullName, + tableName, + fields: upToDateTableFieldsArray, + allExistingColumns, + }); + /** + * Handle MYSQL Foreign Keys + * =================================================== + * @description Iterate through each datasquirel schema + * table index(if available), and perform operations + */ + yield (0, handle_table_foreign_key_1.default)({ + dbFullName, + fields: upToDateTableFieldsArray, + tableName, + clone, + }); /** * Handle DATASQUIREL Table Indexes * =================================================== * @description Iterate through each datasquirel schema * table index(if available), and perform operations */ - if (tableIndexes && tableIndexes[0]) { + if (tableIndexes === null || tableIndexes === void 0 ? void 0 : tableIndexes[0]) { (0, handle_indexes_1.default)({ dbFullName, indexes: tableIndexes, @@ -131,226 +86,22 @@ function updateTable(_a) { }); } /** - * Handle MYSQL Foreign Keys + * Handle DATASQUIREL Table Unique Indexes * =================================================== * @description Iterate through each datasquirel schema - * table index(if available), and perform operations + * table unique constraint(if available), and perform operations */ - const allForeignKeys = (yield (0, dbHandler_1.default)({ - query: `SELECT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE CONSTRAINT_SCHEMA = '${dbFullName}' AND TABLE_NAME='${tableName}' AND CONSTRAINT_TYPE='FOREIGN KEY'`, - })); - if (allForeignKeys) { - for (let c = 0; c < allForeignKeys.length; c++) { - const { CONSTRAINT_NAME } = allForeignKeys[c]; - /** - * @description Skip if Key is the PRIMARY Key - */ - if (CONSTRAINT_NAME.match(/PRIMARY/)) - continue; - /** - * @description Drop all foreign Keys to avoid MYSQL errors when adding/updating - * Foreign keys - */ - yield (0, dbHandler_1.default)({ - query: `ALTER TABLE \`${dbFullName}\`.\`${tableName}\` DROP FOREIGN KEY \`${CONSTRAINT_NAME}\``, - }); - } - } - /** - * Handle MYSQL Unique Fields - * =================================================== - * @description Find all existing unique field constraints - * and remove them - */ - const allUniqueConstraints = (yield (0, dbHandler_1.default)({ - query: (0, normalize_text_1.default)(`SELECT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS \ - WHERE CONSTRAINT_SCHEMA = '${dbFullName}' AND TABLE_NAME='${tableName}' AND \ - CONSTRAINT_TYPE='UNIQUE'`), - })); - if (allUniqueConstraints) { - for (let c = 0; c < allUniqueConstraints.length; c++) { - const { CONSTRAINT_NAME } = allUniqueConstraints[c]; - yield (0, dbHandler_1.default)({ - query: `ALTER TABLE \`${dbFullName}\`.\`${tableName}\` DROP INDEX \`${CONSTRAINT_NAME}\``, - }); - } - } - /** - * Handle MYSQL Columns (Fields) - * =================================================== - * @description Now handle all fields/columns - */ - let allExistingColumns = (yield (0, dbHandler_1.default)({ - query: `SHOW COLUMNS FROM \`${dbFullName}\`.\`${tableName}\``, - })); - /** - * @type {string[]} - * @description Updated column names Array - */ - const updatedColumnsArray = []; - /** - * @description Iterate through every existing column - */ - for (let e = 0; e < allExistingColumns.length; e++) { - const { Field } = allExistingColumns[e]; - if (Field.match(default_fields_regexp_1.default)) - continue; - /** - * @description This finds out whether the fieldName corresponds with the MSQL Field name - * if the fildName doesn't match any MYSQL Field name, the field is deleted. - */ - let existingEntry = upToDateTableFieldsArray.find((column) => column.fieldName === Field || column.originName === Field); - if (existingEntry) { - /** - * @description Check if Field name has been updated - */ - if (existingEntry.updatedField && existingEntry.fieldName) { - updatedColumnsArray.push(existingEntry.fieldName); - yield (0, dbHandler_1.default)({ - query: `ALTER TABLE \`${dbFullName}\`.\`${tableName}\` RENAME COLUMN \`${existingEntry.originName}\` TO \`${existingEntry.fieldName}\``, - }); - console.log(`Column Renamed from "${existingEntry.originName}" to "${existingEntry.fieldName}"`); - /** - * Update Db Schema - * =================================================== - * @description Update Db Schema after renaming column - */ - try { - const updatedSchemaData = lodash_1.default.cloneDeep(dbSchema); - const targetTableIndex = updatedSchemaData.tables.findIndex((table) => table.tableName === tableName); - const targetFieldIndex = updatedSchemaData.tables[targetTableIndex].fields.findIndex((field) => field.fieldName === existingEntry.fieldName); - delete updatedSchemaData.tables[targetTableIndex] - .fields[targetFieldIndex]["originName"]; - delete updatedSchemaData.tables[targetTableIndex] - .fields[targetFieldIndex]["updatedField"]; - /** - * @description Set New Table Fields Array - */ - upToDateTableFieldsArray = - updatedSchemaData.tables[targetTableIndex].fields; - if (userId) { - (0, grab_required_database_schemas_1.writeUpdatedDbSchema)({ - dbSchema: updatedSchemaData, - userId, - }); - } - allExistingColumns = (yield (0, dbHandler_1.default)({ - query: `SHOW COLUMNS FROM \`${dbFullName}\`.\`${tableName}\``, - })); - } - catch (error) { - console.log("Update table error =>", error.message); - } - //////////////////////////////////////// - } - //////////////////////////////////////// - continue; - //////////////////////////////////////// - } - else { - yield (0, dbHandler_1.default)({ - query: `ALTER TABLE \`${dbFullName}\`.\`${tableName}\` DROP COLUMN \`${Field}\``, - }); - } - } - /** - * Handle DATASQUIREL schema fields for current table - * =================================================== - * @description Iterate through each field object and - * perform operations - */ - for (let i = 0; i < upToDateTableFieldsArray.length; i++) { - const column = upToDateTableFieldsArray[i]; - // const prevColumn = upToDateTableFieldsArray[i - 1]; - // const nextColumn = upToDateTableFieldsArray[i + 1]; - const { fieldName, dataType, foreignKey } = column; - if (!fieldName) - continue; - if (default_fields_regexp_1.default.test(fieldName)) - continue; - let updateText = ""; - const existingColumnIndex = allExistingColumns === null || allExistingColumns === void 0 ? void 0 : allExistingColumns.findIndex((_column, _index) => _column.Field === fieldName); - const existingColumn = existingColumnIndex >= 0 - ? allExistingColumns[existingColumnIndex] - : undefined; - let { fieldEntryText } = (0, generateColumnDescription_1.default)({ - columnData: column, + if (tableUniqueConstraints === null || tableUniqueConstraints === void 0 ? void 0 : tableUniqueConstraints[0]) { + (0, handle_unique_constraints_1.default)({ + dbFullName, + tableUniqueConstraints, + tableName, }); - /** - * @description Modify Column(Field) if it already exists - * in MYSQL database - */ - if (existingColumn === null || existingColumn === void 0 ? void 0 : existingColumn.Field) { - const { Field, Type } = existingColumn; - updateText += `MODIFY COLUMN ${fieldEntryText}`; - // if ( - // Field === fieldName && - // dataType?.toUpperCase() === Type.toUpperCase() - // ) { - // } else { - // updateText += `MODIFY COLUMN ${fieldEntryText}`; - // } - } - else { - /** - * @description Append new column to the end of existing columns - */ - updateText += `ADD COLUMN ${fieldEntryText}`; - } - /** - * @description Pust SQL code snippet to updateTableQueryArray Array - * Add a comma(,) to separate from the next snippet - */ - if (updateText.match(/./)) { - updateTableQueryArray.push(updateText + ","); - } } - /** - * @description Construct final SQL query by combning all SQL snippets in - * updateTableQueryArray Arry, and trimming the final comma(,) - */ - const updateTableQuery = updateTableQueryArray - .filter((q) => Boolean(q.match(/./))) - .join(" ") - .replace(/,$/, ""); - //////////////////////////////////////// - /** - * @description Check if SQL snippets array has more than 1 entries - * This is because 1 entry means "ALTER TABLE table_name" only, without any - * Alter directives like "ADD COLUMN" or "MODIFY COLUMN" - */ - if (updateTableQueryArray.length > 1) { - const updateTable = yield (0, dbHandler_1.default)({ - query: updateTableQuery, - }); - /** - * # Handle Foreign Keys - */ - yield (0, drop_all_foreign_keys_1.default)({ dbFullName, tableName }); - for (let i = 0; i < upToDateTableFieldsArray.length; i++) { - const { fieldName, foreignKey } = upToDateTableFieldsArray[i]; - if (!clone && foreignKey && fieldName) { - yield (0, handle_table_foreign_key_1.default)({ - dbFullName, - errorLogs, - foreignKey, - fieldName, - tableName, - }); - } - } - } - else { - /** - * @description If only 1 SQL snippet is left in updateTableQueryArray, this - * means that no updates have been made to the table - */ - } - return tableID; } catch (error) { console.log('Error in "updateTable" shell function =>', error.message); - return tableID; } + return tableID; }); } diff --git a/dist/package-shared/types/index.d.ts b/dist/package-shared/types/index.d.ts index 9ee1f9e..469497e 100644 --- a/dist/package-shared/types/index.d.ts +++ b/dist/package-shared/types/index.d.ts @@ -6,6 +6,7 @@ import type DataTypes from "../data/data-types"; import type { IncomingMessage, ServerResponse } from "http"; import type { CookieNames } from "../dict/cookie-names"; import { ConnectionConfig } from "mariadb"; +import { DbContextsArray } from "../functions/backend/db/runQuery"; export type DSQL_DatabaseFullName = string; export type DSQL_DATASQUIREL_USER_BACKUPS_JOIN = DSQL_DATASQUIREL_BACKUPS & { [k in (typeof UserSelectFields)[number]["alias"]]?: string; @@ -51,6 +52,7 @@ export interface DSQL_TableSchemaType { tableDescription?: string; fields: DSQL_FieldSchemaType[]; indexes?: DSQL_IndexSchemaType[]; + uniqueConstraints?: DSQL_UniqueConstraintSchemaType[]; childrenTables?: DSQL_ChildrenTablesType[]; childTable?: boolean; updateData?: boolean; @@ -144,6 +146,15 @@ export interface DSQL_IndexSchemaType { alias?: string; newTempIndex?: boolean; } +export interface DSQL_UniqueConstraintSchemaType { + id?: string | number; + constraintName?: string; + alias?: string; + constraintTableFields?: DSQL_UniqueConstraintFieldType[]; +} +export interface DSQL_UniqueConstraintFieldType { + value: string; +} export interface DSQL_IndexTableFieldType { value: string; dataType: string; @@ -166,6 +177,21 @@ export interface DSQL_MYSQL_SHOW_COLUMNS_Type { Default: string; Extra: string; } +export interface DSQL_MARIADB_SHOW_INDEXES_TYPE { + Table: string; + Non_unique: 0 | 1; + Key_name: string; + Seq_in_index: number; + Column_name: string; + Collation: string; + Cardinality: number; + Sub_part?: string; + Packed?: string; + Index_type?: "BTREE"; + Comment?: string; + Index_comment?: string; + Ignored?: "YES" | "NO"; +} export interface DSQL_MYSQL_FOREIGN_KEYS_Type { CONSTRAINT_NAME: string; CONSTRAINT_SCHEMA: string; @@ -1314,6 +1340,7 @@ export type DsqlCrudParam = { + dbContext?: (typeof DbContextsArray)[number]; + paradigm?: "Read Only" | "Full Access"; + dbFullName?: string; + tableName: K; + data?: T; + batchData?: T[]; + tableSchema?: DSQL_TableSchemaType; + duplicateColumnName?: keyof T; + duplicateColumnValue?: string | number; + /** + * Update Entry if a duplicate is found. + * Requires `duplicateColumnName` and `duplicateColumnValue` parameters + */ + update?: boolean; + encryptionKey?: string; + encryptionSalt?: string; + forceLocal?: boolean; + debug?: boolean; + dbConfig?: ConnectionConfig; + onDuplicate?: AddDbEntryParamOnDuplicate; +}; +export type AddDbEntryParamOnDuplicate = { + sql: string; + values?: string[]; +}; diff --git a/dist/package-shared/types/index.js b/dist/package-shared/types/index.js index 55fc129..b2d4372 100644 --- a/dist/package-shared/types/index.js +++ b/dist/package-shared/types/index.js @@ -177,10 +177,13 @@ exports.FileMimeTypes = [ exports.CurrentlyEditedFieldActions = [ "edit-field", "edit-index", + "edit-unique-constraint", "delete-field", "delete-index", + "delete-unique-constraint", "new-field", "new-index", + "new-unique-constraint", "move-up", "move-down", "complete", diff --git a/dist/package-shared/utils/data-fetching/crud.js b/dist/package-shared/utils/data-fetching/crud.js index 32394de..9b4795a 100644 --- a/dist/package-shared/utils/data-fetching/crud.js +++ b/dist/package-shared/utils/data-fetching/crud.js @@ -24,7 +24,7 @@ const deleteDbEntry_1 = __importDefault(require("../../functions/backend/db/dele function dsqlCrud(params) { return __awaiter(this, void 0, void 0, function* () { var _a; - const { action, data, table, targetValue, sanitize, targetField, targetId, dbFullName, deleteData, batchData, deleteKeyValues, debug, tableSchema, deleteKeyValuesOperator, dbConfig, query, } = params; + const { action, data, table, targetValue, sanitize, targetField, targetId, dbFullName, deleteData, batchData, deleteKeyValues, debug, tableSchema, deleteKeyValuesOperator, dbConfig, query, onDuplicate, } = params; const finalData = (sanitize ? sanitize({ data }) : data); const finalBatchData = (sanitize ? sanitize({ batchData }) : batchData); const queryObject = query @@ -46,8 +46,6 @@ function dsqlCrud(params) { switch (action) { case "get": return yield (0, crud_get_1.default)(params); - // case "batch-get": - // return await dsqlCrudBatchGet(params); case "insert": const INSERT_RESULT = yield (0, addDbEntry_1.default)({ data: finalData, @@ -57,6 +55,7 @@ function dsqlCrud(params) { debug, tableSchema, dbConfig, + onDuplicate, }); return INSERT_RESULT; case "update": diff --git a/dist/package-shared/utils/db/schema/resolve-schema-children-handle-children-tables.js b/dist/package-shared/utils/db/schema/resolve-schema-children-handle-children-tables.js index 566e1bd..ac49ac8 100644 --- a/dist/package-shared/utils/db/schema/resolve-schema-children-handle-children-tables.js +++ b/dist/package-shared/utils/db/schema/resolve-schema-children-handle-children-tables.js @@ -45,6 +45,9 @@ function default_1({ currentDbSchema, currentTableSchema, currentTableSchemaInde if (targetChildTableParentDatabaseTable === null || targetChildTableParentDatabaseTable === void 0 ? void 0 : targetChildTableParentDatabaseTable.childTable) { targetChildTableParentDatabase.tables[targetChildTableParentDatabaseTableIndex].fields = [...currentTableSchema.fields]; targetChildTableParentDatabase.tables[targetChildTableParentDatabaseTableIndex].indexes = [...(currentTableSchema.indexes || [])]; + targetChildTableParentDatabase.tables[targetChildTableParentDatabaseTableIndex].uniqueConstraints = [ + ...(currentTableSchema.uniqueConstraints || []), + ]; (0, grab_required_database_schemas_1.writeUpdatedDbSchema)({ dbSchema: targetChildTableParentDatabase, userId, @@ -133,6 +136,7 @@ function default_1({ currentDbSchema, currentTableSchema, currentTableSchemaInde targetParentDatabaseTable.fields; newCurrentDbSchema.tables[currentTableSchemaIndex].indexes = targetParentDatabaseTable.indexes; + newCurrentDbSchema.tables[currentTableSchemaIndex].uniqueConstraints = targetParentDatabaseTable.uniqueConstraints; (0, grab_required_database_schemas_1.writeUpdatedDbSchema)({ dbSchema: targetParentDatabase, userId }); } } diff --git a/dist/package-shared/utils/grab-sql-key-name.d.ts b/dist/package-shared/utils/grab-sql-key-name.d.ts index 8f793f2..cab09aa 100644 --- a/dist/package-shared/utils/grab-sql-key-name.d.ts +++ b/dist/package-shared/utils/grab-sql-key-name.d.ts @@ -1,5 +1,5 @@ type Param = { - type: "foreign_key" | "index" | "user"; + type: "foreign_key" | "index" | "user" | "unique_constraint"; userId?: string | number; addDate?: boolean; }; diff --git a/dist/package-shared/utils/grab-sql-key-name.js b/dist/package-shared/utils/grab-sql-key-name.js index bba7cd2..b8b8bc7 100644 --- a/dist/package-shared/utils/grab-sql-key-name.js +++ b/dist/package-shared/utils/grab-sql-key-name.js @@ -7,6 +7,8 @@ exports.default = grabSQLKeyName; */ function grabSQLKeyName({ type, userId, addDate }) { let prefixParadigm = (() => { + if (type == "unique_constraint") + return "unq"; if (type == "foreign_key") return "fk"; if (type == "index") @@ -15,12 +17,14 @@ function grabSQLKeyName({ type, userId, addDate }) { return "user"; return null; })(); + const uuid = crypto.randomUUID(); + const uidPrefx = uuid.split("-")[0]; let key = `dsql`; if (prefixParadigm) key += `_${prefixParadigm}`; if (userId) key += `_${userId}`; if (addDate) - key += `_${Date.now()}`; + key += `_${uidPrefx}`; return key; } diff --git a/dist/package-shared/utils/parse-env.d.ts b/dist/package-shared/utils/parse-env.d.ts index a2a3be5..03e8690 100644 --- a/dist/package-shared/utils/parse-env.d.ts +++ b/dist/package-shared/utils/parse-env.d.ts @@ -1,5 +1,15 @@ export default function parseEnv( /** The file path to the env. Eg. /app/.env */ envFile: string): { + DSQL_APP_DIR: string | undefined; + DSQL_DATA_DIR: string | undefined; + DSQL_USER_DB_PREFIX: string | undefined; + DSQL_USER_ID: string | undefined; + DSQL_DB_NAME: string | undefined; + DSQL_DB_HOST: string | undefined; + DSQL_DB_USERNAME: string | undefined; + DSQL_DB_PASSWORD: string | undefined; + DSQL_ENCRYPTION_PASSWORD: string | undefined; + DSQL_ENCRYPTION_SALT: string | undefined; DSQL_HOST: string | undefined; NEXT_PUBLIC_DSQL_HOST: string | undefined; DSQL_STATIC_HOST: string | undefined; @@ -13,19 +23,14 @@ export default function parseEnv( NEXT_PUBLIC_DSQL_REMOTE_SQL_HOST: string | undefined; DSQL_DB_TARGET_IP_ADDRESS: string | undefined; NEXT_PUBLIC_VERSION: string | undefined; - DSQL_USER_DB_PREFIX: string | undefined; DSQL_USER_DELEGATED_DB_COOKIE_PREFIX: string | undefined; DSQL_NETWORK_IP_PREFIX: string | undefined; DSQL_NETWORK_GATEWAY: string | undefined; DSQL_NETWORK_SUBNET: string | undefined; DSQL_MARIADB_MASTER_HOST: string | undefined; - DSQL_DB_HOST: string | undefined; DSQL_WEB_APP_HOST: string | undefined; - DSQL_DB_USERNAME: string | undefined; - DSQL_DB_PASSWORD: string | undefined; DSQL_MARIADB_ROOT_PASSWORD: string | undefined; DSQL_REPLICATION_USER_PASSWORD: string | undefined; - DSQL_DB_NAME: string | undefined; DSQL_MARIADB_REPLICATION_PASSWORD: string | undefined; DSQL_MAXSCALE_PASSWORD: string | undefined; DSQL_DB_READ_ONLY_USERNAME: string | undefined; @@ -33,8 +38,6 @@ export default function parseEnv( DSQL_DB_FULL_ACCESS_USERNAME: string | undefined; DSQL_DB_FULL_ACCESS_PASSWORD: string | undefined; DSQL_DB_EXPOSED_PORT: string | undefined; - DSQL_ENCRYPTION_PASSWORD: string | undefined; - DSQL_ENCRYPTION_SALT: string | undefined; DSQL_SU_USER_ID: string | undefined; DSQL_SU_USER_UUID: string | undefined; DSQL_SU_EMAIL: string | undefined; @@ -47,7 +50,6 @@ export default function parseEnv( DSQL_MAIL_PASSWORD: string | undefined; DSQL_TINY_MCE_API_KEY: string | undefined; DSQL_GITHUB_ID: string | undefined; - DSQL_USER_ID: string | undefined; DSQL_GITHUB_SECRET: string | undefined; DSQL_GITHUB_WEBHOOK_SECRET: string | undefined; DSQL_GITHUB_WEBHOOK_URL: string | undefined; @@ -62,8 +64,6 @@ export default function parseEnv( DSQL_VOLUME_DB_SSL: string | undefined; DSQL_USER_LOGIN_KEYS_PATH: string | undefined; DSQL_API_KEYS_PATH: string | undefined; - DSQL_APP_DIR: string | undefined; - DSQL_DATA_DIR: string | undefined; DSQL_CONTACT_EMAIL: string | undefined; DSQL_SSL_DIR: string | undefined; DSQL_DEPLOYMENT_NAME: string | undefined; diff --git a/package-shared/data/app-data.ts b/package-shared/data/app-data.ts new file mode 100644 index 0000000..f1ba1ae --- /dev/null +++ b/package-shared/data/app-data.ts @@ -0,0 +1,5 @@ +const AppData = { + UniqueConstraintComment: `dsql_schema_unique_constraint`, + IndexComment: `dsql_schema_index`, +} as const; +export default AppData; diff --git a/package-shared/functions/api/query/post.ts b/package-shared/functions/api/query/post.ts index 0a04f96..521ca30 100644 --- a/package-shared/functions/api/query/post.ts +++ b/package-shared/functions/api/query/post.ts @@ -89,6 +89,7 @@ export default async function apiPost({ delete clonedTargetTable.childrenTables; delete clonedTargetTable.updateData; delete clonedTargetTable.indexes; + delete clonedTargetTable.uniqueConstraints; tableSchema = clonedTargetTable; } diff --git a/package-shared/functions/backend/db/addDbEntry.ts b/package-shared/functions/backend/db/addDbEntry.ts index 5bdce18..0bb178e 100644 --- a/package-shared/functions/backend/db/addDbEntry.ts +++ b/package-shared/functions/backend/db/addDbEntry.ts @@ -3,41 +3,14 @@ import updateDbEntry from "./updateDbEntry"; import _ from "lodash"; import connDbHandler from "../../../utils/db/conn-db-handler"; import checkIfIsMaster from "../../../utils/check-if-is-master"; -import { DbContextsArray } from "./runQuery"; import debugLog from "../../../utils/logging/debug-log"; import { + AddDbEntryParam, APIResponseObject, - DSQL_TableSchemaType, PostInsertReturn, } from "../../../types"; import purgeDefaultFields from "../../../utils/purge-default-fields"; import grabParsedValue from "./grab-parsed-value"; -import { ConnectionConfig } from "mariadb"; - -export type AddDbEntryParam< - T extends { [k: string]: any } = any, - K extends string = string -> = { - dbContext?: (typeof DbContextsArray)[number]; - paradigm?: "Read Only" | "Full Access"; - dbFullName?: string; - tableName: K; - data?: T; - batchData?: T[]; - tableSchema?: DSQL_TableSchemaType; - duplicateColumnName?: keyof T; - duplicateColumnValue?: string | number; - /** - * Update Entry if a duplicate is found. - * Requires `duplicateColumnName` and `duplicateColumnValue` parameters - */ - update?: boolean; - encryptionKey?: string; - encryptionSalt?: string; - forceLocal?: boolean; - debug?: boolean; - dbConfig?: ConnectionConfig; -}; /** * Add a db Entry Function @@ -61,6 +34,7 @@ export default async function addDbEntry< forceLocal, debug, dbConfig, + onDuplicate, }: AddDbEntryParam): Promise> { const isMaster = forceLocal ? true @@ -205,7 +179,7 @@ export default async function addDbEntry< const { insertKeysArray, insertValuesArray, queryValuesArray } = generateQuery(newData); - const query = `INSERT INTO ${ + let query = `INSERT INTO ${ isMaster && !dbFullName ? "" : `\`${dbFullName}\`.` }\`${tableName}\` (${insertKeysArray.join( "," @@ -213,6 +187,13 @@ export default async function addDbEntry< const finalQueryValues = grabFinalQueryValuesArr(queryValuesArray); + if (onDuplicate) { + query += ` ON DUPLICATE KEY ${onDuplicate.sql}`; + if (onDuplicate.values) { + finalQueryValues.push(...onDuplicate.values); + } + } + const newInsert = await connDbHandler({ query, values: finalQueryValues, @@ -246,7 +227,7 @@ export default async function addDbEntry< batchQueryValuesArray.push(queryValuesArray); } - const query = `INSERT INTO ${ + let query = `INSERT INTO ${ isMaster && !dbFullName ? "" : `\`${dbFullName}\`.` }\`${tableName}\` (${batchInsertKeysArray?.join( "," @@ -258,6 +239,13 @@ export default async function addDbEntry< batchQueryValuesArray.flat() ); + if (onDuplicate) { + query += ` ON DUPLICATE KEY ${onDuplicate.sql}`; + if (onDuplicate.values) { + finalQueryValues.push(...onDuplicate.values); + } + } + const newInsert = await connDbHandler({ query, values: finalQueryValues, diff --git a/package-shared/shell/createDbFromSchema/handle-indexes.ts b/package-shared/shell/createDbFromSchema/handle-indexes.ts index 5029544..a73c531 100644 --- a/package-shared/shell/createDbFromSchema/handle-indexes.ts +++ b/package-shared/shell/createDbFromSchema/handle-indexes.ts @@ -4,6 +4,7 @@ import { } from "../../types"; import grabDSQLSchemaIndexComment from "../utils/grab-dsql-schema-index-comment"; import dbHandler from "../../functions/backend/dbHandler"; +import AppData from "../../data/app-data"; type Param = { tableName: string; @@ -22,48 +23,59 @@ export default async function handleIndexescreateDbFromSchema({ tableName, indexes, }: Param) { + /** + * Handle MYSQL Table Indexes + * =================================================== + * @description Iterate through each table index(if available) + * and perform operations + */ const allExistingIndexes = (await dbHandler({ - query: `SHOW INDEXES FROM \`${dbFullName}\`.\`${tableName}\``, - })) as DSQL_MYSQL_SHOW_INDEXES_Type[]; + query: `SHOW INDEXES FROM \`${dbFullName}\`.\`${tableName}\` WHERE Index_comment LIKE '%${AppData["IndexComment"]}%'`, + })) as DSQL_MYSQL_SHOW_INDEXES_Type[] | null; + if (allExistingIndexes) { + for (let f = 0; f < allExistingIndexes.length; f++) { + const { Key_name } = allExistingIndexes[f]; + + try { + const existingKeyInSchema = indexes?.find( + (indexObject) => indexObject.alias === Key_name + ); + if (!existingKeyInSchema) + throw new Error( + `This Index(${Key_name}) Has been Deleted!` + ); + } catch (error) { + /** + * @description Drop Index: This happens when the MYSQL index is not + * present in the datasquirel DB schema + */ + await dbHandler({ + query: `ALTER TABLE \`${dbFullName}\`.\`${tableName}\` DROP INDEX \`${Key_name}\``, + }); + } + } + } + + /** + * # Re-Add New Indexes + */ for (let g = 0; g < indexes.length; g++) { const { indexType, indexName, indexTableFields, alias } = indexes[g]; if (!alias?.match(/./)) continue; - /** - * @description Check for existing Index in MYSQL db - */ - try { - const existingKeyInDb = allExistingIndexes.filter( - (indexObject) => indexObject.Key_name === alias - ); + const queryString = `CREATE${ + indexType == "full_text" + ? " FULLTEXT" + : indexType == "vector" + ? " VECTOR" + : "" + } INDEX \`${alias}\` ON \`${dbFullName}\`.\`${tableName}\`(${indexTableFields + ?.map((nm) => nm.value) + .map((nm) => `\`${nm}\``) + .join(",")}) COMMENT '${AppData["IndexComment"]} ${indexName}'`; - if (!existingKeyInDb[0]) - throw new Error("This Index Does not Exist"); - } catch (error) { - /** - * @description Create new index if determined that it - * doesn't exist in MYSQL db - */ - const queryString = `CREATE${ - indexType == "full_text" - ? " FULLTEXT" - : indexType == "vector" - ? " VECTOR" - : "" - } INDEX \`${alias}\` ON \`${dbFullName}\`.\`${tableName}\`(${indexTableFields - ?.map((nm) => nm.value) - .map((nm) => `\`${nm}\``) - .join( - "," - )}) COMMENT '${grabDSQLSchemaIndexComment()} ${indexName}'`; - - const addIndex = await dbHandler({ query: queryString }); - } + const addIndex = await dbHandler({ query: queryString }); } - - const allExistingIndexesAfterUpdate = (await dbHandler({ - query: `SHOW INDEXES FROM \`${dbFullName}\`.\`${tableName}\``, - })) as DSQL_MYSQL_SHOW_INDEXES_Type[]; } diff --git a/package-shared/shell/createDbFromSchema/handle-unique-constraints.ts b/package-shared/shell/createDbFromSchema/handle-unique-constraints.ts new file mode 100644 index 0000000..3e811e0 --- /dev/null +++ b/package-shared/shell/createDbFromSchema/handle-unique-constraints.ts @@ -0,0 +1,80 @@ +import { + DSQL_MYSQL_FOREIGN_KEYS_Type, + DSQL_MYSQL_SHOW_INDEXES_Type, + DSQL_UniqueConstraintSchemaType, +} from "../../types"; +import dbHandler from "../../functions/backend/dbHandler"; +import AppData from "../../data/app-data"; +import normalizeText from "../../utils/normalize-text"; + +type Param = { + tableName: string; + dbFullName: string; + tableUniqueConstraints: DSQL_UniqueConstraintSchemaType[]; +}; + +/** + * Handle DATASQUIREL Table Unique Constraints + * =================================================== + * @description Iterate through each datasquirel schema + * table unique constraint(if available), and perform operations + */ +export default async function handleUniqueConstraintsCreateDbFromSchema({ + dbFullName, + tableName, + tableUniqueConstraints, +}: Param) { + /** + * # Delete All Existing Unique Constraints + */ + // const allExistingUniqueConstraints = (await dbHandler({ + // query: `SHOW INDEXES FROM \`${dbFullName}\`.\`${tableName}\` WHERE Index_comment LIKE '%${AppData["UniqueConstraintComment"]}%'`, + // })) as DSQL_MYSQL_SHOW_INDEXES_Type[] | null; + + // if (allExistingUniqueConstraints?.[0]) { + // for (let f = 0; f < allExistingUniqueConstraints.length; f++) { + // const { Key_name } = allExistingUniqueConstraints[f]; + + // try { + // const existingKeyInSchema = tableUniqueConstraints?.find( + // (indexObject) => indexObject.alias === Key_name + // ); + // if (!existingKeyInSchema) + // throw new Error( + // `This Index(${Key_name}) Has been Deleted!` + // ); + // } catch (error) { + // /** + // * @description Drop Index: This happens when the MYSQL index is not + // * present in the datasquirel DB schema + // */ + // await dbHandler({ + // query: `ALTER TABLE \`${dbFullName}\`.\`${tableName}\` DROP INDEX \`${Key_name}\``, + // }); + // } + // } + // } + + /** + * # Re-Add New Constraints + */ + for (let g = 0; g < tableUniqueConstraints.length; g++) { + const { constraintName, alias, constraintTableFields } = + tableUniqueConstraints[g]; + + if (!alias?.match(/./)) continue; + + /** + * @description Create new index if determined that it + * doesn't exist in MYSQL db + */ + const queryString = `CREATE UNIQUE INDEX \`${alias}\` ON \`${dbFullName}\`.\`${tableName}\`(${constraintTableFields + ?.map((nm) => nm.value) + .map((nm) => `\`${nm}\``) + .join(",")}) COMMENT '${ + AppData["UniqueConstraintComment"] + } ${constraintName}'`; + + const addIndex = await dbHandler({ query: queryString }); + } +} diff --git a/package-shared/shell/createDbFromSchema/index.ts b/package-shared/shell/createDbFromSchema/index.ts index ea9b01c..1a7f740 100644 --- a/package-shared/shell/createDbFromSchema/index.ts +++ b/package-shared/shell/createDbFromSchema/index.ts @@ -131,7 +131,7 @@ export default async function createDbFromSchema({ for (let t = 0; t < tables.length; t++) { const table = tables[t]; - const { tableName, fields, indexes } = table; + const { tableName, fields, indexes, uniqueConstraints } = table; if (targetTable && tableName !== targetTable) continue; @@ -169,6 +169,7 @@ export default async function createDbFromSchema({ recordedDbEntry, tableSchema: table, isMain, + tableUniqueConstraints: uniqueConstraints, }); if (table.childrenTables && table.childrenTables[0]) { @@ -201,6 +202,8 @@ export default async function createDbFromSchema({ userId, dbSchema: childTableParentDbSchema, tableIndexes: childTableSchema.indexes, + tableUniqueConstraints: + childTableSchema.uniqueConstraints, clone: true, recordedDbEntry, tableSchema: table, @@ -214,11 +217,13 @@ export default async function createDbFromSchema({ */ const createNewTable = await createTable({ tableName: tableName, - tableInfoArray: fields, + fields, dbFullName: dbFullName, tableSchema: table, recordedDbEntry, isMain, + indexes, + uniqueConstraints, }); /** diff --git a/package-shared/shell/utils/createTable.ts b/package-shared/shell/utils/createTable.ts index b1b2f29..560de9b 100644 --- a/package-shared/shell/utils/createTable.ts +++ b/package-shared/shell/utils/createTable.ts @@ -1,18 +1,27 @@ import generateColumnDescription from "./generateColumnDescription"; import supplementTable from "./supplementTable"; -import { DSQL_FieldSchemaType, DSQL_TableSchemaType } from "../../types"; +import { + DSQL_FieldSchemaType, + DSQL_IndexSchemaType, + DSQL_TableSchemaType, + DSQL_UniqueConstraintSchemaType, +} from "../../types"; import { DSQL_DATASQUIREL_USER_DATABASES } from "../../types/dsql"; import handleTableForeignKey from "./handle-table-foreign-key"; import createTableHandleTableRecord from "./create-table-handle-table-record"; import dbHandler from "../../functions/backend/dbHandler"; +import handleIndexescreateDbFromSchema from "../createDbFromSchema/handle-indexes"; +import handleUniqueConstraintsCreateDbFromSchema from "../createDbFromSchema/handle-unique-constraints"; type Param = { dbFullName: string; tableName: string; - tableInfoArray: DSQL_FieldSchemaType[]; + fields: DSQL_FieldSchemaType[]; tableSchema?: DSQL_TableSchemaType; recordedDbEntry?: DSQL_DATASQUIREL_USER_DATABASES; isMain?: boolean; + indexes?: DSQL_IndexSchemaType[]; + uniqueConstraints?: DSQL_UniqueConstraintSchemaType[]; }; /** @@ -21,12 +30,14 @@ type Param = { export default async function createTable({ dbFullName, tableName, - tableInfoArray, + fields: passedFields, tableSchema, recordedDbEntry, isMain, + indexes, + uniqueConstraints, }: Param) { - const finalTable = supplementTable({ tableInfoArray: tableInfoArray }); + const fields = supplementTable({ tableInfoArray: passedFields }); let tableId = await createTableHandleTableRecord({ recordedDbEntry, @@ -44,8 +55,8 @@ export default async function createTable({ let primaryKeySet = false; - for (let i = 0; i < finalTable.length; i++) { - const column = finalTable[i]; + for (let i = 0; i < fields.length; i++) { + const column = fields[i]; let { fieldEntryText, newPrimaryKeySet } = generateColumnDescription({ columnData: column, @@ -55,7 +66,7 @@ export default async function createTable({ primaryKeySet = newPrimaryKeySet; const comma = (() => { - if (i === finalTable.length - 1) return ""; + if (i === fields.length - 1) return ""; return ","; })(); @@ -74,20 +85,44 @@ export default async function createTable({ query: createTableQuery, }); - for (let i = 0; i < finalTable.length; i++) { - const column = finalTable[i]; - const { foreignKey, fieldName } = column; + /** + * Handle MYSQL Foreign Keys + * =================================================== + * @description Iterate through each datasquirel schema + * table index(if available), and perform operations + */ + await handleTableForeignKey({ + dbFullName, + fields, + tableName, + }); - if (!fieldName) continue; + /** + * Handle DATASQUIREL Table Indexes + * =================================================== + * @description Iterate through each datasquirel schema + * table index(if available), and perform operations + */ + if (indexes?.[0]) { + handleIndexescreateDbFromSchema({ + dbFullName, + indexes, + tableName, + }); + } - if (foreignKey) { - await handleTableForeignKey({ - dbFullName, - foreignKey, - tableName, - fieldName, - }); - } + /** + * Handle DATASQUIREL Table Unique Indexes + * =================================================== + * @description Iterate through each datasquirel schema + * table unique constraint(if available), and perform operations + */ + if (uniqueConstraints?.[0]) { + handleUniqueConstraintsCreateDbFromSchema({ + dbFullName, + tableUniqueConstraints: uniqueConstraints, + tableName, + }); } return tableId; diff --git a/package-shared/shell/utils/drop-all-foreign-keys.ts b/package-shared/shell/utils/drop-all-foreign-keys.ts deleted file mode 100644 index d8e6ca9..0000000 --- a/package-shared/shell/utils/drop-all-foreign-keys.ts +++ /dev/null @@ -1,53 +0,0 @@ -import grabSQLKeyName from "../../utils/grab-sql-key-name"; -import dbHandler from "../../functions/backend/dbHandler"; - -type Param = { - dbFullName: string; - tableName: string; -}; - -/** - * # Drop All Foreign Keys - */ -export default async function dropAllForeignKeys({ - dbFullName, - tableName, -}: Param) { - try { - // const rows = await varDatabaseDbHandler({ - // queryString: `SELECT CONSTRAINT_NAME FROM information_schema.REFERENTIAL_CONSTRAINTS WHERE TABLE_NAME = '${tableName}' AND CONSTRAINT_SCHEMA = '${dbFullName}'`, - // }); - - // console.log("rows", rows); - // console.log("dbFullName", dbFullName); - // console.log("tableName", tableName); - - // for (const row of rows) { - // await varDatabaseDbHandler({ - // queryString: `ALTER TABLE \`${dbFullName}\`.\`${tableName}\` DROP FOREIGN KEY \`${row.CONSTRAINT_NAME}\` - // `, - // }); - // } - - const foreignKeys = (await dbHandler({ - query: `SHOW INDEXES FROM \`${dbFullName}\`.\`${tableName}\` WHERE Key_name LIKE '${grabSQLKeyName( - { type: "foreign_key" } - )}%'`, - })) as any; - - for (const fk of foreignKeys) { - if ( - fk.Key_name.match( - new RegExp(grabSQLKeyName({ type: "foreign_key" })) - ) - ) { - await dbHandler({ - query: `ALTER TABLE \`${dbFullName}\`.\`${tableName}\` DROP INDEX \`${fk.Key_name}\` - `, - }); - } - } - } catch (error: any) { - console.log(`dropAllForeignKeys ERROR => ${error.message}`); - } -} diff --git a/package-shared/shell/utils/handle-dsql-schema-fields.ts b/package-shared/shell/utils/handle-dsql-schema-fields.ts new file mode 100644 index 0000000..c3e8c53 --- /dev/null +++ b/package-shared/shell/utils/handle-dsql-schema-fields.ts @@ -0,0 +1,85 @@ +import { + DSQL_FieldSchemaType, + DSQL_MYSQL_SHOW_COLUMNS_Type, +} from "../../types"; +import dbHandler from "../../functions/backend/dbHandler"; +import defaultFieldsRegexp from "../../functions/dsql/default-fields-regexp"; +import generateColumnDescription from "./generateColumnDescription"; + +type Param = { + dbFullName: string; + tableName: string; + fields: DSQL_FieldSchemaType[]; + clone?: boolean; + allExistingColumns: DSQL_MYSQL_SHOW_COLUMNS_Type[]; +}; + +/** + * Handle DATASQUIREL schema fields for current table + * =================================================== + * @description Iterate through each field object and + * perform operations + */ +export default async function handleDSQLSchemaFields({ + dbFullName, + tableName, + fields, + allExistingColumns, +}: Param) { + let sql = `ALTER TABLE \`${dbFullName}\`.\`${tableName}\``; + + for (let i = 0; i < fields.length; i++) { + const column = fields[i]; + // const prevColumn = fields[i - 1]; + // const nextColumn = fields[i + 1]; + + const { fieldName, dataType, foreignKey } = column; + + if (!fieldName) continue; + if (defaultFieldsRegexp.test(fieldName)) continue; + + let updateText = ""; + + const existingColumnIndex = allExistingColumns?.findIndex( + (_column, _index) => _column.Field === fieldName + ); + + const existingColumn = + existingColumnIndex >= 0 + ? allExistingColumns[existingColumnIndex] + : undefined; + + let { fieldEntryText } = generateColumnDescription({ + columnData: column, + }); + + /** + * @description Modify Column(Field) if it already exists + * in MYSQL database + */ + if (existingColumn?.Field) { + const { Field, Type } = existingColumn; + + updateText += ` MODIFY COLUMN ${fieldEntryText}`; + } else { + /** + * @description Append new column to the end of existing columns + */ + updateText += ` ADD COLUMN ${fieldEntryText}`; + } + + /** + * @description Pust SQL code snippet to updateTableQueryArray Array + * Add a comma(,) to separate from the next snippet + */ + if (updateText.match(/./)) { + sql += " " + updateText + ","; + } + } + + const finalSQL = sql.replace(/\,$/, ""); + + const updateTable = await dbHandler({ + query: finalSQL, + }); +} diff --git a/package-shared/shell/utils/handle-mariadb-existing-columns.ts b/package-shared/shell/utils/handle-mariadb-existing-columns.ts new file mode 100644 index 0000000..140804e --- /dev/null +++ b/package-shared/shell/utils/handle-mariadb-existing-columns.ts @@ -0,0 +1,126 @@ +import { + DSQL_DatabaseSchemaType, + DSQL_FieldSchemaType, + DSQL_MYSQL_SHOW_COLUMNS_Type, +} from "../../types"; +import dbHandler from "../../functions/backend/dbHandler"; +import defaultFieldsRegexp from "../../functions/dsql/default-fields-regexp"; +import { writeUpdatedDbSchema } from "../createDbFromSchema/grab-required-database-schemas"; +import _ from "lodash"; + +type Param = { + dbFullName: string; + tableName: string; + fields: DSQL_FieldSchemaType[]; + dbSchema: DSQL_DatabaseSchemaType; + userId?: number | string | null; +}; + +/** + * Handle MYSQL Columns (Fields) + * =================================================== + * @description Now handle all fields/columns + */ +export default async function handleMariaDBExistingColumns({ + dbFullName, + tableName, + fields, + dbSchema, + userId, +}: Param) { + let upToDateTableFieldsArray = _.cloneDeep(fields); + + let allExistingColumns: DSQL_MYSQL_SHOW_COLUMNS_Type[] = (await dbHandler({ + query: `SHOW COLUMNS FROM \`${dbFullName}\`.\`${tableName}\``, + })) as DSQL_MYSQL_SHOW_COLUMNS_Type[]; + + /** + * @description Iterate through every existing column + */ + for (let e = 0; e < allExistingColumns.length; e++) { + const { Field } = allExistingColumns[e]; + + if (Field.match(defaultFieldsRegexp)) continue; + + /** + * @description This finds out whether the fieldName corresponds with the MSQL Field name + * if the fildName doesn't match any MYSQL Field name, the field is deleted. + */ + let existingEntry = upToDateTableFieldsArray.find( + (column) => + column.fieldName === Field || column.originName === Field + ); + + if (!existingEntry) { + await dbHandler({ + query: `ALTER TABLE \`${dbFullName}\`.\`${tableName}\` DROP COLUMN \`${Field}\``, + }); + continue; + } + + if (existingEntry) { + /** + * @description Check if Field name has been updated + */ + if (existingEntry.updatedField && existingEntry.fieldName) { + await dbHandler({ + query: `ALTER TABLE \`${dbFullName}\`.\`${tableName}\` RENAME COLUMN \`${existingEntry.originName}\` TO \`${existingEntry.fieldName}\``, + }); + + console.log( + `Column Renamed from "${existingEntry.originName}" to "${existingEntry.fieldName}"` + ); + + /** + * Update Db Schema + * =================================================== + * @description Update Db Schema after renaming column + */ + try { + const updatedSchemaData = _.cloneDeep(dbSchema); + + const targetTableIndex = updatedSchemaData.tables.findIndex( + (table) => table.tableName === tableName + ); + const targetFieldIndex = updatedSchemaData.tables[ + targetTableIndex + ].fields.findIndex( + (field) => field.fieldName === existingEntry.fieldName + ); + + delete updatedSchemaData.tables[targetTableIndex].fields[ + targetFieldIndex + ]["originName"]; + delete updatedSchemaData.tables[targetTableIndex].fields[ + targetFieldIndex + ]["updatedField"]; + + /** + * @description Set New Table Fields Array + */ + upToDateTableFieldsArray = + updatedSchemaData.tables[targetTableIndex].fields; + + if (userId) { + writeUpdatedDbSchema({ + dbSchema: updatedSchemaData, + userId, + }); + } + + allExistingColumns = (await dbHandler({ + query: `SHOW COLUMNS FROM \`${dbFullName}\`.\`${tableName}\``, + })) as DSQL_MYSQL_SHOW_COLUMNS_Type[]; + } catch (error: any) { + console.log("Update table error =>", error.message); + } + + //////////////////////////////////////// + } + + continue; + } + } + + return { upToDateTableFieldsArray, allExistingColumns }; +} diff --git a/package-shared/shell/utils/handle-table-foreign-key.ts b/package-shared/shell/utils/handle-table-foreign-key.ts index 8349617..9df2542 100644 --- a/package-shared/shell/utils/handle-table-foreign-key.ts +++ b/package-shared/shell/utils/handle-table-foreign-key.ts @@ -1,51 +1,60 @@ -import { DSQL_ForeignKeyType } from "../../types"; +import { + DSQL_FieldSchemaType, + DSQL_ForeignKeyType, + DSQL_MYSQL_FOREIGN_KEYS_Type, +} from "../../types"; import dbHandler from "../../functions/backend/dbHandler"; type Param = { dbFullName: string; tableName: string; - foreignKey: DSQL_ForeignKeyType; - fieldName: string; - errorLogs?: any[]; + fields: DSQL_FieldSchemaType[]; + clone?: boolean; }; /** - * # Update table function + * Handle MYSQL Foreign Keys + * =================================================== + * @description Iterate through each datasquirel schema + * table index(if available), and perform operations */ export default async function handleTableForeignKey({ dbFullName, tableName, - foreignKey, - errorLogs, - fieldName, + fields, + clone, }: Param) { - const { - destinationTableName, - destinationTableColumnName, - cascadeDelete, - cascadeUpdate, - foreignKeyName, - } = foreignKey; + let addFkSQL = `ALTER TABLE \`${dbFullName}\`.\`${tableName}\``; - let finalQueryString = `ALTER TABLE \`${dbFullName}\`.\`${tableName}\``; + for (let i = 0; i < fields.length; i++) { + const { fieldName, foreignKey } = fields[i]; - finalQueryString += ` ADD CONSTRAINT \`${foreignKeyName}\` FOREIGN KEY (\`${fieldName}\`)`; - finalQueryString += ` REFERENCES \`${destinationTableName}\`(\`${destinationTableColumnName}\`)`; + if (!clone && foreignKey && fieldName) { + const { + destinationTableName, + destinationTableColumnName, + cascadeDelete, + cascadeUpdate, + foreignKeyName, + } = foreignKey; - if (cascadeDelete) finalQueryString += ` ON DELETE CASCADE`; - if (cascadeUpdate) finalQueryString += ` ON UPDATE CASCADE`; + addFkSQL += ` ADD CONSTRAINT \`${foreignKeyName}\` FOREIGN KEY (\`${fieldName}\`)`; + addFkSQL += ` REFERENCES \`${destinationTableName}\`(\`${destinationTableColumnName}\`)`; - // let foreinKeyText = `ADD CONSTRAINT \`${foreignKeyName}\` FOREIGN KEY (\`${destinationTableColumnType}\`) REFERENCES \`${destinationTableName}\`(\`${destinationTableColumnName}\`)${ - // cascadeDelete ? " ON DELETE CASCADE" : "" - // }${cascadeUpdate ? " ON UPDATE CASCADE" : ""}`; + if (cascadeDelete) addFkSQL += ` ON DELETE CASCADE`; + if (cascadeUpdate) addFkSQL += ` ON UPDATE CASCADE`; - // let finalQueryString = `ALTER TABLE \`${dbFullName}\`.\`${tableName}\` ${foreinKeyText}`; + addFkSQL += `,`; + } + } - const addForeignKey = (await dbHandler({ - query: finalQueryString, - })) as any; + const finalAddFKSQL = addFkSQL.endsWith(",") + ? addFkSQL.replace(/\,$/, "") + : undefined; - if (!addForeignKey?.serverStatus) { - errorLogs?.push(addForeignKey); + if (finalAddFKSQL) { + const addForeignKey = (await dbHandler({ + query: finalAddFKSQL, + })) as any; } } diff --git a/package-shared/shell/utils/update-table-init.ts b/package-shared/shell/utils/update-table-init.ts new file mode 100644 index 0000000..81266e5 --- /dev/null +++ b/package-shared/shell/utils/update-table-init.ts @@ -0,0 +1,166 @@ +import { + DSQL_DatabaseSchemaType, + DSQL_MARIADB_SHOW_INDEXES_TYPE, + DSQL_MYSQL_FOREIGN_KEYS_Type, + DSQL_TableSchemaType, +} from "../../types"; +import createTableHandleTableRecord from "./create-table-handle-table-record"; +import { DSQL_DATASQUIREL_USER_DATABASES } from "../../types/dsql"; +import _ from "lodash"; +import dbHandler from "../../functions/backend/dbHandler"; + +type Params = { + dbFullName: string; + tableName: string; + tableSchema: DSQL_TableSchemaType; + dbSchema: DSQL_DatabaseSchemaType; + recordedDbEntry?: DSQL_DATASQUIREL_USER_DATABASES; + isMain?: boolean; +}; + +/** + * # Update table function + */ +export default async function updateTableInit({ + dbFullName, + tableName, + tableSchema, + recordedDbEntry, + isMain, +}: Params) { + /** + * @description Grab Table Record + */ + if (!recordedDbEntry && !isMain) { + throw new Error("Recorded Db entry not found!"); + } + + let tableID = await createTableHandleTableRecord({ + recordedDbEntry, + tableSchema, + update: true, + isMain, + }); + + if (!tableID && !isMain) { + throw new Error("Recorded Table entry not found!"); + } + + /** + * Handle Table Default Collation + * + * @description Update Column Collation + */ + if (tableSchema.collation) { + try { + const existingCollation = (await dbHandler({ + query: `SHOW TABLE STATUS LIKE '${tableName}'`, + config: { database: dbFullName }, + })) as any[]; + + const existingCollationStr = existingCollation?.[0].Collation; + + if (existingCollationStr !== tableSchema.collation) { + await dbHandler({ + query: `ALTER TABLE \`${dbFullName}\`.\`${tableName}\` CONVERT TO CHARACTER SET utf8mb4 COLLATE ${tableSchema.collation}`, + }); + } + } catch (error) {} + } + + /** + * Drop All Foreign Keys + * =================================================== + * @description Find all existing foreign keys and drop + * them + */ + const allForeignKeys = (await dbHandler({ + query: `SELECT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE CONSTRAINT_SCHEMA = '${dbFullName}' AND TABLE_NAME='${tableName}' AND CONSTRAINT_TYPE='FOREIGN KEY'`, + })) as DSQL_MYSQL_FOREIGN_KEYS_Type[] | null; + + if (allForeignKeys?.[0]) { + let dropFkSQL = `ALTER TABLE \`${dbFullName}\`.\`${tableName}\``; + + for (let c = 0; c < allForeignKeys.length; c++) { + const { CONSTRAINT_NAME } = allForeignKeys[c]; + + if (CONSTRAINT_NAME.match(/PRIMARY/)) continue; + + dropFkSQL += ` DROP FOREIGN KEY \`${CONSTRAINT_NAME}\`,`; + } + + const finalSQL = dropFkSQL.endsWith(",") + ? dropFkSQL.replace(/\,$/, "") + : undefined; + + if (finalSQL) { + await dbHandler({ + query: finalSQL, + }); + } + } + + /** + * Drop All Unique Constraints + * =================================================== + * @description Find all existing unique field constraints + * and remove them + */ + const allUniqueConstraints = (await dbHandler({ + query: `SELECT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE CONSTRAINT_SCHEMA = '${dbFullName}' AND TABLE_NAME='${tableName}' AND CONSTRAINT_TYPE='UNIQUE'`, + })) as DSQL_MYSQL_FOREIGN_KEYS_Type[] | null; + + if (allUniqueConstraints?.[0]) { + let dropIndxSQL = `ALTER TABLE \`${dbFullName}\`.\`${tableName}\``; + + for (let c = 0; c < allUniqueConstraints.length; c++) { + const { CONSTRAINT_NAME } = allUniqueConstraints[c]; + + dropIndxSQL += ` DROP INDEX ${CONSTRAINT_NAME},`; + } + + const finalDropIndxSQL = dropIndxSQL.endsWith(",") + ? dropIndxSQL.replace(/\,$/, "") + : undefined; + + if (finalDropIndxSQL) { + await dbHandler({ + query: finalDropIndxSQL, + }); + } + } + + /** + * Drop All Indexes + * =================================================== + * @description Find all existing foreign keys and drop + * them + */ + const allMariadbIndexes = (await dbHandler({ + query: `SHOW INDEXES FROM \`${dbFullName}\`.\`${tableName}\``, + })) as DSQL_MARIADB_SHOW_INDEXES_TYPE[] | null; + + if (allMariadbIndexes?.[0]) { + let dropIndxs = `ALTER TABLE \`${dbFullName}\`.\`${tableName}\``; + + for (let c = 0; c < allMariadbIndexes.length; c++) { + const { Key_name } = allMariadbIndexes[c]; + + if (Key_name.match(/PRIMARY/)) continue; + + dropIndxs += ` DROP INDEX \`${Key_name}\`,`; + } + + const finalDropIndxs = dropIndxs.endsWith(",") + ? dropIndxs.replace(/\,$/, "") + : undefined; + + if (finalDropIndxs) { + const dropFkRes = await dbHandler({ + query: finalDropIndxs, + }); + } + } + + return { tableID }; +} diff --git a/package-shared/shell/utils/updateTable.ts b/package-shared/shell/utils/updateTable.ts index bc6a49b..e57bbc1 100644 --- a/package-shared/shell/utils/updateTable.ts +++ b/package-shared/shell/utils/updateTable.ts @@ -1,23 +1,18 @@ -import generateColumnDescription from "./generateColumnDescription"; import { DSQL_DatabaseSchemaType, DSQL_FieldSchemaType, DSQL_IndexSchemaType, - DSQL_MYSQL_FOREIGN_KEYS_Type, - DSQL_MYSQL_SHOW_COLUMNS_Type, - DSQL_MYSQL_SHOW_INDEXES_Type, DSQL_TableSchemaType, + DSQL_UniqueConstraintSchemaType, } from "../../types"; -import handleTableForeignKey from "./handle-table-foreign-key"; -import dropAllForeignKeys from "./drop-all-foreign-keys"; -import createTableHandleTableRecord from "./create-table-handle-table-record"; import { DSQL_DATASQUIREL_USER_DATABASES } from "../../types/dsql"; -import defaultFieldsRegexp from "../../functions/dsql/default-fields-regexp"; import handleIndexescreateDbFromSchema from "../createDbFromSchema/handle-indexes"; import _ from "lodash"; -import { writeUpdatedDbSchema } from "../createDbFromSchema/grab-required-database-schemas"; -import normalizeText from "../../utils/normalize-text"; -import dbHandler from "../../functions/backend/dbHandler"; +import handleUniqueConstraintsCreateDbFromSchema from "../createDbFromSchema/handle-unique-constraints"; +import handleTableForeignKey from "./handle-table-foreign-key"; +import handleDSQLSchemaFields from "./handle-dsql-schema-fields"; +import handleMariaDBExistingColumns from "./handle-mariadb-existing-columns"; +import updateTableInit from "./update-table-init"; type Param = { dbFullName: string; @@ -27,6 +22,7 @@ type Param = { userId?: number | string | null; dbSchema: DSQL_DatabaseSchemaType; tableIndexes?: DSQL_IndexSchemaType[]; + tableUniqueConstraints?: DSQL_UniqueConstraintSchemaType[]; clone?: boolean; recordedDbEntry?: DSQL_DATASQUIREL_USER_DATABASES; isMain?: boolean; @@ -46,113 +42,60 @@ export default async function updateTable({ clone, recordedDbEntry, isMain, + tableUniqueConstraints, }: Param): Promise { - /** - * Initialize - * ========================================== - * @description Initial setup - */ - - let errorLogs: any[] = []; - - /** - * @description Initialize table info array. This value will be - * changing depending on if a field is renamed or not. - */ - let upToDateTableFieldsArray = _.cloneDeep(tableFields); - - /** - * @type {string[]} - * @description Table update query string array - */ - const updateTableQueryArray: string[] = []; - - /** - * @description Push the query initial value - */ - updateTableQueryArray.push( - `ALTER TABLE \`${dbFullName}\`.\`${tableName}\`` - ); - /** * @description Grab Table Record */ - if (!recordedDbEntry && !isMain) { - throw new Error("Recorded Db entry not found!"); - } - - let tableID = await createTableHandleTableRecord({ - recordedDbEntry, + const { tableID } = await updateTableInit({ + dbFullName, + dbSchema, + tableName, tableSchema, - update: true, isMain, + recordedDbEntry, }); - if (!tableID && !isMain) { - throw new Error("Recorded Table entry not found!"); - } - - /** - * Handle Table Default Collation - * - * @description Update Column Collation - */ - if (tableSchema.collation) { - try { - const existingCollation = (await dbHandler({ - query: `SHOW TABLE STATUS LIKE '${tableName}'`, - config: { database: dbFullName }, - })) as any[]; - - const existingCollationStr = existingCollation?.[0].Collation; - - if (existingCollationStr !== tableSchema.collation) { - await dbHandler({ - query: `ALTER TABLE \`${dbFullName}\`.\`${tableName}\` CONVERT TO CHARACTER SET utf8mb4 COLLATE ${tableSchema.collation}`, - }); - } - } catch (error) {} - } - /** * Handle Table updates * * @description Try to undate table, catch error if anything goes wrong */ try { + const { allExistingColumns, upToDateTableFieldsArray } = + await handleMariaDBExistingColumns({ + dbFullName, + dbSchema, + fields: tableFields, + tableName, + userId, + }); + /** - * Handle MYSQL Table Indexes + * Handle DATASQUIREL schema fields for current table * =================================================== - * @description Iterate through each table index(if available) - * and perform operations + * @description Iterate through each field object and + * perform operations */ - const allExistingIndexes = (await dbHandler({ - query: `SHOW INDEXES FROM \`${dbFullName}\`.\`${tableName}\` WHERE Index_comment LIKE '%schema_index%'`, - })) as DSQL_MYSQL_SHOW_INDEXES_Type[] | null; + await handleDSQLSchemaFields({ + dbFullName, + tableName, + fields: upToDateTableFieldsArray, + allExistingColumns, + }); - if (allExistingIndexes) { - for (let f = 0; f < allExistingIndexes.length; f++) { - const { Key_name } = allExistingIndexes[f]; - - try { - const existingKeyInSchema = tableIndexes?.find( - (indexObject) => indexObject.alias === Key_name - ); - if (!existingKeyInSchema) - throw new Error( - `This Index(${Key_name}) Has been Deleted!` - ); - } catch (error) { - /** - * @description Drop Index: This happens when the MYSQL index is not - * present in the datasquirel DB schema - */ - await dbHandler({ - query: `ALTER TABLE \`${dbFullName}\`.\`${tableName}\` DROP INDEX \`${Key_name}\``, - }); - } - } - } + /** + * Handle MYSQL Foreign Keys + * =================================================== + * @description Iterate through each datasquirel schema + * table index(if available), and perform operations + */ + await handleTableForeignKey({ + dbFullName, + fields: upToDateTableFieldsArray, + tableName, + clone, + }); /** * Handle DATASQUIREL Table Indexes @@ -160,7 +103,7 @@ export default async function updateTable({ * @description Iterate through each datasquirel schema * table index(if available), and perform operations */ - if (tableIndexes && tableIndexes[0]) { + if (tableIndexes?.[0]) { handleIndexescreateDbFromSchema({ dbFullName, indexes: tableIndexes, @@ -169,274 +112,21 @@ export default async function updateTable({ } /** - * Handle MYSQL Foreign Keys + * Handle DATASQUIREL Table Unique Indexes * =================================================== * @description Iterate through each datasquirel schema - * table index(if available), and perform operations + * table unique constraint(if available), and perform operations */ - const allForeignKeys = (await dbHandler({ - query: `SELECT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE CONSTRAINT_SCHEMA = '${dbFullName}' AND TABLE_NAME='${tableName}' AND CONSTRAINT_TYPE='FOREIGN KEY'`, - })) as DSQL_MYSQL_FOREIGN_KEYS_Type[] | null; - - if (allForeignKeys) { - for (let c = 0; c < allForeignKeys.length; c++) { - const { CONSTRAINT_NAME } = allForeignKeys[c]; - - /** - * @description Skip if Key is the PRIMARY Key - */ - if (CONSTRAINT_NAME.match(/PRIMARY/)) continue; - - /** - * @description Drop all foreign Keys to avoid MYSQL errors when adding/updating - * Foreign keys - */ - await dbHandler({ - query: `ALTER TABLE \`${dbFullName}\`.\`${tableName}\` DROP FOREIGN KEY \`${CONSTRAINT_NAME}\``, - }); - } - } - - /** - * Handle MYSQL Unique Fields - * =================================================== - * @description Find all existing unique field constraints - * and remove them - */ - const allUniqueConstraints = (await dbHandler({ - query: normalizeText(`SELECT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS \ - WHERE CONSTRAINT_SCHEMA = '${dbFullName}' AND TABLE_NAME='${tableName}' AND \ - CONSTRAINT_TYPE='UNIQUE'`), - })) as DSQL_MYSQL_FOREIGN_KEYS_Type[] | null; - - if (allUniqueConstraints) { - for (let c = 0; c < allUniqueConstraints.length; c++) { - const { CONSTRAINT_NAME } = allUniqueConstraints[c]; - await dbHandler({ - query: `ALTER TABLE \`${dbFullName}\`.\`${tableName}\` DROP INDEX \`${CONSTRAINT_NAME}\``, - }); - } - } - - /** - * Handle MYSQL Columns (Fields) - * =================================================== - * @description Now handle all fields/columns - */ - let allExistingColumns: DSQL_MYSQL_SHOW_COLUMNS_Type[] = - (await dbHandler({ - query: `SHOW COLUMNS FROM \`${dbFullName}\`.\`${tableName}\``, - })) as DSQL_MYSQL_SHOW_COLUMNS_Type[]; - - /** - * @type {string[]} - * @description Updated column names Array - */ - const updatedColumnsArray: string[] = []; - - /** - * @description Iterate through every existing column - */ - for (let e = 0; e < allExistingColumns.length; e++) { - const { Field } = allExistingColumns[e]; - - if (Field.match(defaultFieldsRegexp)) continue; - - /** - * @description This finds out whether the fieldName corresponds with the MSQL Field name - * if the fildName doesn't match any MYSQL Field name, the field is deleted. - */ - let existingEntry = upToDateTableFieldsArray.find( - (column) => - column.fieldName === Field || column.originName === Field - ); - - if (existingEntry) { - /** - * @description Check if Field name has been updated - */ - if (existingEntry.updatedField && existingEntry.fieldName) { - updatedColumnsArray.push(existingEntry.fieldName); - - await dbHandler({ - query: `ALTER TABLE \`${dbFullName}\`.\`${tableName}\` RENAME COLUMN \`${existingEntry.originName}\` TO \`${existingEntry.fieldName}\``, - }); - - console.log( - `Column Renamed from "${existingEntry.originName}" to "${existingEntry.fieldName}"` - ); - - /** - * Update Db Schema - * =================================================== - * @description Update Db Schema after renaming column - */ - try { - const updatedSchemaData = _.cloneDeep(dbSchema); - - const targetTableIndex = - updatedSchemaData.tables.findIndex( - (table) => table.tableName === tableName - ); - const targetFieldIndex = updatedSchemaData.tables[ - targetTableIndex - ].fields.findIndex( - (field) => - field.fieldName === existingEntry.fieldName - ); - - delete updatedSchemaData.tables[targetTableIndex] - .fields[targetFieldIndex]["originName"]; - delete updatedSchemaData.tables[targetTableIndex] - .fields[targetFieldIndex]["updatedField"]; - - /** - * @description Set New Table Fields Array - */ - upToDateTableFieldsArray = - updatedSchemaData.tables[targetTableIndex].fields; - - if (userId) { - writeUpdatedDbSchema({ - dbSchema: updatedSchemaData, - userId, - }); - } - - allExistingColumns = (await dbHandler({ - query: `SHOW COLUMNS FROM \`${dbFullName}\`.\`${tableName}\``, - })) as DSQL_MYSQL_SHOW_COLUMNS_Type[]; - } catch (error: any) { - console.log("Update table error =>", error.message); - } - - //////////////////////////////////////// - } - - //////////////////////////////////////// - - continue; - - //////////////////////////////////////// - } else { - await dbHandler({ - query: `ALTER TABLE \`${dbFullName}\`.\`${tableName}\` DROP COLUMN \`${Field}\``, - }); - } - } - - /** - * Handle DATASQUIREL schema fields for current table - * =================================================== - * @description Iterate through each field object and - * perform operations - */ - for (let i = 0; i < upToDateTableFieldsArray.length; i++) { - const column = upToDateTableFieldsArray[i]; - // const prevColumn = upToDateTableFieldsArray[i - 1]; - // const nextColumn = upToDateTableFieldsArray[i + 1]; - - const { fieldName, dataType, foreignKey } = column; - - if (!fieldName) continue; - if (defaultFieldsRegexp.test(fieldName)) continue; - - let updateText = ""; - - const existingColumnIndex = allExistingColumns?.findIndex( - (_column, _index) => _column.Field === fieldName - ); - - const existingColumn = - existingColumnIndex >= 0 - ? allExistingColumns[existingColumnIndex] - : undefined; - - let { fieldEntryText } = generateColumnDescription({ - columnData: column, + if (tableUniqueConstraints?.[0]) { + handleUniqueConstraintsCreateDbFromSchema({ + dbFullName, + tableUniqueConstraints, + tableName, }); - - /** - * @description Modify Column(Field) if it already exists - * in MYSQL database - */ - if (existingColumn?.Field) { - const { Field, Type } = existingColumn; - - updateText += `MODIFY COLUMN ${fieldEntryText}`; - - // if ( - // Field === fieldName && - // dataType?.toUpperCase() === Type.toUpperCase() - // ) { - // } else { - // updateText += `MODIFY COLUMN ${fieldEntryText}`; - // } - } else { - /** - * @description Append new column to the end of existing columns - */ - updateText += `ADD COLUMN ${fieldEntryText}`; - } - - /** - * @description Pust SQL code snippet to updateTableQueryArray Array - * Add a comma(,) to separate from the next snippet - */ - if (updateText.match(/./)) { - updateTableQueryArray.push(updateText + ","); - } } - - /** - * @description Construct final SQL query by combning all SQL snippets in - * updateTableQueryArray Arry, and trimming the final comma(,) - */ - const updateTableQuery = updateTableQueryArray - .filter((q) => Boolean(q.match(/./))) - .join(" ") - .replace(/,$/, ""); - - //////////////////////////////////////// - - /** - * @description Check if SQL snippets array has more than 1 entries - * This is because 1 entry means "ALTER TABLE table_name" only, without any - * Alter directives like "ADD COLUMN" or "MODIFY COLUMN" - */ - if (updateTableQueryArray.length > 1) { - const updateTable = await dbHandler({ - query: updateTableQuery, - }); - - /** - * # Handle Foreign Keys - */ - await dropAllForeignKeys({ dbFullName, tableName }); - - for (let i = 0; i < upToDateTableFieldsArray.length; i++) { - const { fieldName, foreignKey } = upToDateTableFieldsArray[i]; - if (!clone && foreignKey && fieldName) { - await handleTableForeignKey({ - dbFullName, - errorLogs, - foreignKey, - fieldName, - tableName, - }); - } - } - } else { - /** - * @description If only 1 SQL snippet is left in updateTableQueryArray, this - * means that no updates have been made to the table - */ - } - - return tableID; } catch (error: any) { console.log('Error in "updateTable" shell function =>', error.message); - - return tableID; } + + return tableID; } diff --git a/package-shared/types/index.ts b/package-shared/types/index.ts index cf88350..a7e2492 100644 --- a/package-shared/types/index.ts +++ b/package-shared/types/index.ts @@ -23,6 +23,7 @@ import type DataTypes from "../data/data-types"; import type { IncomingMessage, ServerResponse } from "http"; import type { CookieNames } from "../dict/cookie-names"; import { ConnectionConfig } from "mariadb"; +import { DbContextsArray } from "../functions/backend/db/runQuery"; export type DSQL_DatabaseFullName = string; @@ -98,6 +99,7 @@ export interface DSQL_TableSchemaType { tableDescription?: string; fields: DSQL_FieldSchemaType[]; indexes?: DSQL_IndexSchemaType[]; + uniqueConstraints?: DSQL_UniqueConstraintSchemaType[]; childrenTables?: DSQL_ChildrenTablesType[]; childTable?: boolean; updateData?: boolean; @@ -178,6 +180,17 @@ export interface DSQL_IndexSchemaType { newTempIndex?: boolean; } +export interface DSQL_UniqueConstraintSchemaType { + id?: string | number; + constraintName?: string; + alias?: string; + constraintTableFields?: DSQL_UniqueConstraintFieldType[]; +} + +export interface DSQL_UniqueConstraintFieldType { + value: string; +} + export interface DSQL_IndexTableFieldType { value: string; dataType: string; @@ -203,6 +216,22 @@ export interface DSQL_MYSQL_SHOW_COLUMNS_Type { Extra: string; } +export interface DSQL_MARIADB_SHOW_INDEXES_TYPE { + Table: string; + Non_unique: 0 | 1; + Key_name: string; + Seq_in_index: number; + Column_name: string; + Collation: string; + Cardinality: number; + Sub_part?: string; + Packed?: string; + Index_type?: "BTREE"; + Comment?: string; + Index_comment?: string; + Ignored?: "YES" | "NO"; +} + export interface DSQL_MYSQL_FOREIGN_KEYS_Type { CONSTRAINT_NAME: string; CONSTRAINT_SCHEMA: string; @@ -1541,6 +1570,7 @@ export type DsqlCrudParam< dbName?: string; tableSchema?: DSQL_TableSchemaType; dbConfig?: ConnectionConfig; + onDuplicate?: AddDbEntryParamOnDuplicate; }; export type DsqlCrudParamWhereClause = { @@ -1939,10 +1969,13 @@ export const FileMimeTypes = [ export const CurrentlyEditedFieldActions = [ "edit-field", "edit-index", + "edit-unique-constraint", "delete-field", "delete-index", + "delete-unique-constraint", "new-field", "new-index", + "new-unique-constraint", "move-up", "move-down", "complete", @@ -1952,6 +1985,8 @@ export type CurrentlyEditedTableSchemaType = { action: (typeof CurrentlyEditedFieldActions)[number]; field?: DSQL_FieldSchemaType; fieldIndex?: number; + uniqueConstraint?: DSQL_UniqueConstraintSchemaType; + uniqueConstraintIndex?: number; index?: DSQL_IndexSchemaType; indexIndex?: number; spliceIndex?: number; @@ -3001,3 +3036,34 @@ export type DSQLFetchApiOptions< csrfKey?: string; fetchOptions?: RequestInit; }; + +export type AddDbEntryParam< + T extends { [k: string]: any } = any, + K extends string = string +> = { + dbContext?: (typeof DbContextsArray)[number]; + paradigm?: "Read Only" | "Full Access"; + dbFullName?: string; + tableName: K; + data?: T; + batchData?: T[]; + tableSchema?: DSQL_TableSchemaType; + duplicateColumnName?: keyof T; + duplicateColumnValue?: string | number; + /** + * Update Entry if a duplicate is found. + * Requires `duplicateColumnName` and `duplicateColumnValue` parameters + */ + update?: boolean; + encryptionKey?: string; + encryptionSalt?: string; + forceLocal?: boolean; + debug?: boolean; + dbConfig?: ConnectionConfig; + onDuplicate?: AddDbEntryParamOnDuplicate; +}; + +export type AddDbEntryParamOnDuplicate = { + sql: string; + values?: string[]; +}; diff --git a/package-shared/utils/data-fetching/crud.ts b/package-shared/utils/data-fetching/crud.ts index 5751fb1..d3ca239 100644 --- a/package-shared/utils/data-fetching/crud.ts +++ b/package-shared/utils/data-fetching/crud.ts @@ -33,6 +33,7 @@ export default async function dsqlCrud< deleteKeyValuesOperator, dbConfig, query, + onDuplicate, } = params; const finalData = (sanitize ? sanitize({ data }) : data) as T; @@ -65,9 +66,6 @@ export default async function dsqlCrud< case "get": return await dsqlCrudGet(params); - // case "batch-get": - // return await dsqlCrudBatchGet(params); - case "insert": const INSERT_RESULT = await addDbEntry({ data: finalData, @@ -77,7 +75,9 @@ export default async function dsqlCrud< debug, tableSchema, dbConfig, + onDuplicate, }); + return INSERT_RESULT; case "update": diff --git a/package-shared/utils/db/schema/resolve-schema-children-handle-children-tables.ts b/package-shared/utils/db/schema/resolve-schema-children-handle-children-tables.ts index 3425871..3caf25c 100644 --- a/package-shared/utils/db/schema/resolve-schema-children-handle-children-tables.ts +++ b/package-shared/utils/db/schema/resolve-schema-children-handle-children-tables.ts @@ -84,10 +84,17 @@ export default function ({ targetChildTableParentDatabase.tables[ targetChildTableParentDatabaseTableIndex ].fields = [...currentTableSchema.fields]; + targetChildTableParentDatabase.tables[ targetChildTableParentDatabaseTableIndex ].indexes = [...(currentTableSchema.indexes || [])]; + targetChildTableParentDatabase.tables[ + targetChildTableParentDatabaseTableIndex + ].uniqueConstraints = [ + ...(currentTableSchema.uniqueConstraints || []), + ]; + writeUpdatedDbSchema({ dbSchema: targetChildTableParentDatabase, userId, @@ -221,6 +228,9 @@ export default function ({ targetParentDatabaseTable.fields; newCurrentDbSchema.tables[currentTableSchemaIndex].indexes = targetParentDatabaseTable.indexes; + newCurrentDbSchema.tables[ + currentTableSchemaIndex + ].uniqueConstraints = targetParentDatabaseTable.uniqueConstraints; writeUpdatedDbSchema({ dbSchema: targetParentDatabase, userId }); } diff --git a/package-shared/utils/grab-sql-key-name.ts b/package-shared/utils/grab-sql-key-name.ts index e9cd6c4..32b9c29 100644 --- a/package-shared/utils/grab-sql-key-name.ts +++ b/package-shared/utils/grab-sql-key-name.ts @@ -1,5 +1,5 @@ type Param = { - type: "foreign_key" | "index" | "user"; + type: "foreign_key" | "index" | "user" | "unique_constraint"; userId?: string | number; addDate?: boolean; }; @@ -10,15 +10,21 @@ type Param = { */ export default function grabSQLKeyName({ type, userId, addDate }: Param) { let prefixParadigm = (() => { + if (type == "unique_constraint") return "unq"; if (type == "foreign_key") return "fk"; if (type == "index") return "indx"; if (type == "user") return "user"; return null; })(); + const uuid = crypto.randomUUID(); + const uidPrefx = uuid.split("-")[0]; + let key = `dsql`; + if (prefixParadigm) key += `_${prefixParadigm}`; if (userId) key += `_${userId}`; - if (addDate) key += `_${Date.now()}`; + if (addDate) key += `_${uidPrefx}`; + return key; } diff --git a/package.json b/package.json index a717b8f..de572ed 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@moduletrace/datasquirel", - "version": "5.7.15", + "version": "5.7.16", "description": "Cloud-based SQL data management tool", "main": "dist/index.js", "bin": {