"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 = updateTable; const fs_1 = __importDefault(require("fs")); const varDatabaseDbHandler_1 = __importDefault(require("./varDatabaseDbHandler")); const defaultFieldsRegexp = /^id$|^uuid$|^date_created$|^date_created_code$|^date_created_timestamp$|^date_updated$|^date_updated_code$|^date_updated_timestamp$/; const generateColumnDescription_1 = __importDefault(require("./generateColumnDescription")); const dbHandler_1 = __importDefault(require("./dbHandler")); /** * # Update table function */ function updateTable(_a) { return __awaiter(this, arguments, void 0, function* ({ dbFullName, tableName, tableInfoArray, userId, dbSchema, tableIndexes, tableSchema, clone, childDb, tableIndex, tableNameFull, recordedDbEntry, }) { /** * Initialize * ========================================== * @description Initial setup */ var _b; /** @type {any[]} */ let errorLogs = []; /** * @description Initialize table info array. This value will be * changing depending on if a field is renamed or not. */ let upToDateTableFieldsArray = tableInfoArray; /** * Handle Table updates * * @description Try to undate table, catch error if anything goes wrong */ try { /** * @type {string[]} * @description Table update query string array */ const updateTableQueryArray = []; /** * @type {string[]} * @description Constriants query string array */ const constraintsQueryArray = []; /** * @description Push the query initial value */ updateTableQueryArray.push(`ALTER TABLE \`${dbFullName}\`.\`${tableName}\``); if (childDb) { try { if (!recordedDbEntry) { throw new Error("Recorded Db entry not found!"); } const existingTable = yield (0, varDatabaseDbHandler_1.default)({ queryString: `SELECT * FROM datasquirel.user_database_tables WHERE db_id = ? AND table_slug = ?`, queryValuesArray: [recordedDbEntry.id, tableName], }); /** @type {import("../../types").MYSQL_user_database_tables_table_def} */ const table = existingTable === null || existingTable === void 0 ? void 0 : existingTable[0]; if (!(table === null || table === void 0 ? void 0 : table.id)) { const newTableEntry = yield (0, dbHandler_1.default)({ query: `INSERT INTO datasquirel.user_database_tables SET ?`, values: { user_id: recordedDbEntry.user_id, db_id: recordedDbEntry.id, db_slug: recordedDbEntry.db_slug, table_name: tableNameFull, table_slug: tableName, child_table: (tableSchema === null || tableSchema === void 0 ? void 0 : tableSchema.childTable) ? "1" : null, child_table_parent_database: (tableSchema === null || tableSchema === void 0 ? void 0 : tableSchema.childTableDbFullName) || null, child_table_parent_table: tableSchema.childTableName || null, date_created: Date(), date_created_code: Date.now(), date_updated: Date(), date_updated_code: Date.now(), }, }); } } catch (error) { } } /** * @type {import("../../types").DSQL_MYSQL_SHOW_INDEXES_Type[]} * @description All indexes from MYSQL db */ // @ts-ignore const allExistingIndexes = yield (0, varDatabaseDbHandler_1.default)({ queryString: `SHOW INDEXES FROM \`${dbFullName}\`.\`${tableName}\``, }); /** * @type {import("../../types").DSQL_MYSQL_SHOW_COLUMNS_Type[]} * @description All columns from MYSQL db */ // @ts-ignore const allExistingColumns = yield (0, varDatabaseDbHandler_1.default)({ queryString: `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(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.filter((column) => column.fieldName === Field || column.originName === Field); if (existingEntry && existingEntry[0]) { /** * @description Check if Field name has been updated */ if (existingEntry[0].updatedField && existingEntry[0].fieldName) { updatedColumnsArray.push(existingEntry[0].fieldName); const renameColumn = yield (0, varDatabaseDbHandler_1.default)({ queryString: `ALTER TABLE \`${dbFullName}\`.\`${tableName}\` RENAME COLUMN \`${existingEntry[0].originName}\` TO \`${existingEntry[0].fieldName}\``, }); console.log(`Column Renamed from "${existingEntry[0].originName}" to "${existingEntry[0].fieldName}"`); /** * Update Db Schema * =================================================== * @description Update Db Schema after renaming column */ try { const userSchemaData = dbSchema; const targetDbIndex = userSchemaData.findIndex((db) => db.dbFullName === dbFullName); const targetTableIndex = userSchemaData[targetDbIndex].tables.findIndex((table) => table.tableName === tableName); const targetFieldIndex = userSchemaData[targetDbIndex].tables[targetTableIndex].fields.findIndex((field) => field.fieldName === existingEntry[0].fieldName); delete userSchemaData[targetDbIndex].tables[targetTableIndex].fields[targetFieldIndex]["originName"]; delete userSchemaData[targetDbIndex].tables[targetTableIndex].fields[targetFieldIndex]["updatedField"]; /** * @description Set New Table Fields Array */ upToDateTableFieldsArray = userSchemaData[targetDbIndex].tables[targetTableIndex].fields; fs_1.default.writeFileSync(`${String(process.env.DSQL_USER_DB_SCHEMA_PATH)}/user-${userId}/main.json`, JSON.stringify(userSchemaData), "utf8"); } catch ( /** @type {any} */error) { console.log("Update table error =>", error.message); } //////////////////////////////////////// } //////////////////////////////////////// continue; //////////////////////////////////////// } else { yield (0, varDatabaseDbHandler_1.default)({ queryString: `ALTER TABLE \`${dbFullName}\`.\`${tableName}\` DROP COLUMN \`${Field}\``, }); } } /** * Handle MYSQL Table Indexes * =================================================== * @description Iterate through each table index(if available) * and perform operations */ for (let f = 0; f < allExistingIndexes.length; f++) { const { Key_name, Index_comment } = allExistingIndexes[f]; /** * @description Check if this index was specifically created * by datasquirel */ if (Index_comment === null || Index_comment === void 0 ? void 0 : Index_comment.match(/schema_index/)) { try { const existingKeyInSchema = tableIndexes === null || tableIndexes === void 0 ? void 0 : tableIndexes.filter((indexObject) => indexObject.alias === Key_name); if (!(existingKeyInSchema === null || existingKeyInSchema === void 0 ? void 0 : existingKeyInSchema[0])) 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, varDatabaseDbHandler_1.default)({ queryString: `ALTER TABLE \`${dbFullName}\`.\`${tableName}\` DROP INDEX \`${Key_name}\``, }); } } } /** * Handle DATASQUIREL Table Indexes * =================================================== * @description Iterate through each datasquirel schema * table index(if available), and perform operations */ if (tableIndexes && tableIndexes[0]) { for (let g = 0; g < tableIndexes.length; g++) { const { indexType, indexName, indexTableFields, alias } = tableIndexes[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 */ yield (0, varDatabaseDbHandler_1.default)({ queryString: `CREATE${(indexType === null || indexType === void 0 ? void 0 : indexType.match(/fullText/i)) ? " FULLTEXT" : ""} INDEX \`${alias}\` ON \`${dbFullName}\`.\`${tableName}\`(${indexTableFields === null || indexTableFields === void 0 ? void 0 : indexTableFields.map((nm) => nm.value).map((nm) => `\`${nm}\``).join(",")}) COMMENT 'schema_index'`, }); } } } /** * Handle MYSQL Foreign Keys * =================================================== * @description Iterate through each datasquirel schema * table index(if available), and perform operations */ /** * @description All MSQL Foreign Keys * @type {import("../../types").DSQL_MYSQL_FOREIGN_KEYS_Type[] | null} */ const allForeignKeys = yield (0, varDatabaseDbHandler_1.default)({ queryString: `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 */ const dropForeignKey = yield (0, varDatabaseDbHandler_1.default)({ queryString: `ALTER TABLE \`${dbFullName}\`.\`${tableName}\` DROP FOREIGN KEY \`${CONSTRAINT_NAME}\``, }); } } /** * 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, nullValue, primaryKey, autoIncrement, defaultValue, defaultValueLiteral, foreignKey, updatedField, } = column; //////////////////////////////////////// /** * @description Skip default fields */ if (fieldName === null || fieldName === void 0 ? void 0 : fieldName.match(/^id$|^date_/)) continue; /** * @description Skip columns that have been updated recently */ // if (updatedColumnsArray.includes(fieldName)) continue; //////////////////////////////////////// let updateText = ""; //////////////////////////////////////// /** @type {any} */ let existingColumnIndex; /** * @description Existing MYSQL field object */ let existingColumn = allExistingColumns && allExistingColumns[0] ? allExistingColumns.filter((_column, _index) => { if (_column.Field === fieldName) { existingColumnIndex = _index; return true; } }) : null; /** * @description Construct SQL text snippet for this field */ let { fieldEntryText } = (0, generateColumnDescription_1.default)({ columnData: column, }); /** * @description Modify Column(Field) if it already exists * in MYSQL database */ if (existingColumn && ((_b = existingColumn[0]) === null || _b === void 0 ? void 0 : _b.Field)) { const { Field, Type, Null, Key, Default, Extra } = existingColumn[0]; let isColumnReordered = i < existingColumnIndex; if (Field === fieldName && !isColumnReordered && (dataType === null || dataType === void 0 ? void 0 : dataType.toUpperCase()) === Type.toUpperCase()) { updateText += `MODIFY COLUMN ${fieldEntryText}`; // continue; } else { if (userId) { updateText += `MODIFY COLUMN ${fieldEntryText}${isColumnReordered ? (prevColumn === null || prevColumn === void 0 ? void 0 : prevColumn.fieldName) ? " AFTER `" + prevColumn.fieldName + "`" : (nextColumn === null || nextColumn === void 0 ? void 0 : nextColumn.fieldName) ? " BEFORE `" + nextColumn.fieldName + "`" : "" : ""}`; } else { updateText += `MODIFY COLUMN ${fieldEntryText}`; } } } else if (prevColumn && prevColumn.fieldName) { /** * @description Add new Column AFTER previous column, if * previous column exists */ updateText += `ADD COLUMN ${fieldEntryText} AFTER \`${prevColumn.fieldName}\``; } else if (nextColumn && nextColumn.fieldName) { /** * @description Add new Column BEFORE next column, if * next column exists */ updateText += `ADD COLUMN ${fieldEntryText} BEFORE \`${nextColumn.fieldName}\``; } 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 */ updateTableQueryArray.push(updateText + ","); /** * @description Handle foreing keys if available, and if there is no * "clone" boolean = true */ if (!clone && foreignKey) { const { destinationTableName, destinationTableColumnName, cascadeDelete, cascadeUpdate, foreignKeyName, } = foreignKey; const foreinKeyText = `ADD CONSTRAINT \`${foreignKeyName}\` FOREIGN KEY (\`${fieldName}\`) REFERENCES \`${destinationTableName}\`(\`${destinationTableColumnName}\`)${cascadeDelete ? " ON DELETE CASCADE" : ""}${cascadeUpdate ? " ON UPDATE CASCADE" : ""}`; // const foreinKeyText = `ADD CONSTRAINT \`${foreignKeyName}\` FOREIGN KEY (${fieldName}) REFERENCES ${destinationTableName}(${destinationTableColumnName})${cascadeDelete ? " ON DELETE CASCADE" : ""}${cascadeUpdate ? " ON UPDATE CASCADE" : ""}` + ","; const finalQueryString = `ALTER TABLE \`${dbFullName}\`.\`${tableName}\` ${foreinKeyText}`; const addForeignKey = yield (0, varDatabaseDbHandler_1.default)({ queryString: finalQueryString, }); if (!(addForeignKey === null || addForeignKey === void 0 ? void 0 : addForeignKey.serverStatus)) { errorLogs.push(addForeignKey); } } //////////////////////////////////////// } /** * @description Construct final SQL query by combning all SQL snippets in * updateTableQueryArray Arry, and trimming the final comma(,) */ const updateTableQuery = updateTableQueryArray .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, varDatabaseDbHandler_1.default)({ queryString: updateTableQuery, }); return updateTable; } else { /** * @description If only 1 SQL snippet is left in updateTableQueryArray, this * means that no updates have been made to the table */ return "No Changes Made to Table"; } } catch ( /** @type {any} */error) { console.log('Error in "updateTable" shell function =>', error.message); return "Error in Updating Table"; } }); }