2025-01-10 19:10:28 +00:00
import fs from "fs" ;
import varDatabaseDbHandler from "./varDatabaseDbHandler" ;
2023-09-21 14:00:04 +00:00
2024-10-14 06:49:01 +00:00
const defaultFieldsRegexp =
2024-12-06 10:31:24 +00:00
/^id$|^uuid$|^date_created$|^date_created_code$|^date_created_timestamp$|^date_updated$|^date_updated_code$|^date_updated_timestamp$/ ;
2023-09-21 14:00:04 +00:00
2025-01-10 19:10:28 +00:00
import generateColumnDescription from "./generateColumnDescription" ;
import dbHandler from "./dbHandler" ;
type Param = {
dbFullName : string ;
tableName : string ;
tableSchema : import ( "../../types" ) . DSQL_TableSchemaType ;
tableNameFull? : string ;
tableInfoArray : import ( "../../types" ) . DSQL_FieldSchemaType [ ] ;
userId? : number | string | null ;
dbSchema : import ( "../../types" ) . DSQL_DatabaseSchemaType [ ] ;
tableIndexes? : import ( "../../types" ) . DSQL_IndexSchemaType [ ] ;
clone? : boolean ;
tableIndex? : number ;
childDb? : boolean ;
recordedDbEntry? : any ;
} ;
2023-09-21 14:00:04 +00:00
/ * *
2025-01-10 19:10:28 +00:00
* # Update table function
2023-09-21 14:00:04 +00:00
* /
2025-01-10 19:10:28 +00:00
export default async function updateTable ( {
2024-10-14 06:49:01 +00:00
dbFullName ,
tableName ,
tableInfoArray ,
2024-12-06 10:31:24 +00:00
userId ,
2024-10-14 06:49:01 +00:00
dbSchema ,
tableIndexes ,
2024-12-06 10:31:24 +00:00
tableSchema ,
2024-10-14 06:49:01 +00:00
clone ,
2024-12-06 10:31:24 +00:00
childDb ,
2024-10-14 06:49:01 +00:00
tableIndex ,
2024-12-06 10:31:24 +00:00
tableNameFull ,
recordedDbEntry ,
2025-01-10 19:10:28 +00:00
} : Param ) {
2023-09-21 14:00:04 +00:00
/ * *
* Initialize
* === === === === === === === === === === === === === ===
* @description Initial setup
* /
2024-12-08 08:58:57 +00:00
/** @type {any[]} */
2025-01-10 19:10:28 +00:00
let errorLogs : any [ ] = [ ] ;
2024-12-08 08:58:57 +00:00
2023-09-21 14:00:04 +00:00
/ * *
* @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
* /
2025-01-10 19:10:28 +00:00
const updateTableQueryArray : string [ ] = [ ] ;
2023-09-21 14:00:04 +00:00
/ * *
* @type { string [ ] }
* @description Constriants query string array
* /
2025-01-10 19:10:28 +00:00
const constraintsQueryArray : string [ ] = [ ] ;
2023-09-21 14:00:04 +00:00
/ * *
* @description Push the query initial value
* /
updateTableQueryArray . push ( ` ALTER TABLE \` ${ tableName } \` ` ) ;
2024-12-06 10:31:24 +00:00
if ( childDb ) {
try {
if ( ! recordedDbEntry ) {
throw new Error ( "Recorded Db entry not found!" ) ;
}
const existingTable = await varDatabaseDbHandler ( {
database : "datasquirel" ,
queryString : ` SELECT * FROM user_database_tables WHERE db_id = ? AND table_slug = ? ` ,
queryValuesArray : [ recordedDbEntry . id , tableName ] ,
} ) ;
/** @type {import("../../types").MYSQL_user_database_tables_table_def} */
2025-01-10 19:10:28 +00:00
const table : import ( "../../types" ) . MYSQL_user_database_tables_table_def =
existingTable ? . [ 0 ] ;
2024-12-06 10:31:24 +00:00
if ( ! table ? . id ) {
const newTableEntry = await dbHandler ( {
query : ` INSERT INTO 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?.childTable ? "1" : null ,
child_table_parent_database :
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 ( ) ,
} ,
database : "datasquirel" ,
} ) ;
}
} catch ( error ) { }
}
2023-09-21 14:00:04 +00:00
/ * *
2024-12-06 10:31:24 +00:00
* @type { import ( "../../types" ) . DSQL_MYSQL_SHOW_INDEXES_Type [ ] }
2023-09-21 14:00:04 +00:00
* @description All indexes from MYSQL db
2024-12-06 10:31:24 +00:00
* / / / @ts - ignore
2025-01-10 19:10:28 +00:00
const allExistingIndexes : import ( "../../types" ) . DSQL_MYSQL_SHOW_INDEXES_Type [ ] =
await varDatabaseDbHandler ( {
queryString : ` SHOW INDEXES FROM \` ${ tableName } \` ` ,
database : dbFullName ,
} ) ;
2023-09-21 14:00:04 +00:00
/ * *
2024-12-06 10:31:24 +00:00
* @type { import ( "../../types" ) . DSQL_MYSQL_SHOW_COLUMNS_Type [ ] }
2023-09-21 14:00:04 +00:00
* @description All columns from MYSQL db
2024-12-06 10:31:24 +00:00
* / / / @ts - ignore
2025-01-10 19:10:28 +00:00
const allExistingColumns : import ( "../../types" ) . DSQL_MYSQL_SHOW_COLUMNS_Type [ ] =
await varDatabaseDbHandler ( {
queryString : ` SHOW COLUMNS FROM \` ${ tableName } \` ` ,
database : dbFullName ,
} ) ;
2023-09-21 14:00:04 +00:00
////////////////////////////////////////
/ * *
* @type { string [ ] }
* @description Updated column names Array
* /
2025-01-10 19:10:28 +00:00
const updatedColumnsArray : string [ ] = [ ] ;
2023-09-21 14:00:04 +00:00
/ * *
* @description Iterate through every existing column
* /
2024-12-06 10:31:24 +00:00
for ( let e = 0 ; e < allExistingColumns . length ; e ++ ) {
const { Field } = allExistingColumns [ e ] ;
2023-09-21 14:00:04 +00:00
2024-12-06 10:31:24 +00:00
if ( Field . match ( defaultFieldsRegexp ) ) continue ;
2023-09-21 14:00:04 +00:00
2024-12-06 10:31:24 +00:00
/ * *
* @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 ] ) {
2023-09-21 14:00:04 +00:00
/ * *
2024-12-06 10:31:24 +00:00
* @description Check if Field name has been updated
2023-09-21 14:00:04 +00:00
* /
2024-12-06 10:31:24 +00:00
if (
existingEntry [ 0 ] . updatedField &&
existingEntry [ 0 ] . fieldName
) {
updatedColumnsArray . push ( existingEntry [ 0 ] . fieldName ) ;
const renameColumn = await varDatabaseDbHandler ( {
queryString : ` ALTER TABLE ${ tableName } RENAME COLUMN \` ${ existingEntry [ 0 ] . originName } \` TO \` ${ existingEntry [ 0 ] . fieldName } \` ` ,
database : dbFullName ,
} ) ;
console . log (
` Column Renamed from " ${ existingEntry [ 0 ] . originName } " to " ${ existingEntry [ 0 ] . fieldName } " `
) ;
2023-09-21 14:00:04 +00:00
/ * *
2024-12-06 10:31:24 +00:00
* Update Db Schema
* === === === === === === === === === === === === === === === === ===
* @description Update Db Schema after renaming column
2023-09-21 14:00:04 +00:00
* /
2024-12-06 10:31:24 +00:00
try {
const userSchemaData = dbSchema ;
2023-09-21 14:00:04 +00:00
2024-12-06 10:31:24 +00:00
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
2024-10-14 06:49:01 +00:00
) ;
2023-09-21 14:00:04 +00:00
2024-12-06 10:31:24 +00:00
delete userSchemaData [ targetDbIndex ] . tables [
targetTableIndex
] . fields [ targetFieldIndex ] [ "originName" ] ;
delete userSchemaData [ targetDbIndex ] . tables [
targetTableIndex
] . fields [ targetFieldIndex ] [ "updatedField" ] ;
2023-09-21 14:00:04 +00:00
/ * *
2024-12-06 10:31:24 +00:00
* @description Set New Table Fields Array
2023-09-21 14:00:04 +00:00
* /
2024-12-06 10:31:24 +00:00
upToDateTableFieldsArray =
userSchemaData [ targetDbIndex ] . tables [
2024-10-14 06:49:01 +00:00
targetTableIndex
2024-12-06 10:31:24 +00:00
] . fields ;
fs . writeFileSync (
` ${ String (
process . env . DSQL_USER_DB_SCHEMA_PATH
) } / user - $ { userId } / main . json ` ,
JSON . stringify ( userSchemaData ) ,
"utf8"
) ;
2025-01-10 19:10:28 +00:00
} catch ( /** @type {any} */ error : any ) {
2024-12-06 10:31:24 +00:00
console . log ( "Update table error =>" , error . message ) ;
2023-09-21 14:00:04 +00:00
}
////////////////////////////////////////
2024-12-06 10:31:24 +00:00
}
2023-09-21 14:00:04 +00:00
2024-12-06 10:31:24 +00:00
////////////////////////////////////////
2023-09-21 14:00:04 +00:00
2024-12-06 10:31:24 +00:00
continue ;
////////////////////////////////////////
} else {
await varDatabaseDbHandler ( {
queryString : ` ALTER TABLE ${ tableName } DROP COLUMN \` ${ Field } \` ` ,
database : dbFullName ,
} ) ;
2023-09-21 14:00:04 +00:00
}
2024-12-06 10:31:24 +00:00
}
2023-09-21 14:00:04 +00:00
/ * *
* Handle MYSQL Table Indexes
* === === === === === === === === === === === === === === === === ===
* @description Iterate through each table index ( if available )
* and perform operations
* /
2024-12-06 10:31:24 +00:00
for ( let f = 0 ; f < allExistingIndexes . length ; f ++ ) {
const { Key_name , Index_comment } = allExistingIndexes [ f ] ;
2023-09-21 14:00:04 +00:00
2024-12-06 10:31:24 +00:00
/ * *
* @description Check if this index was specifically created
* by datasquirel
* /
if ( Index_comment ? . match ( /schema_index/ ) ) {
try {
const existingKeyInSchema = tableIndexes ? . filter (
( indexObject ) = > indexObject . alias === Key_name
) ;
if ( ! 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
* /
await varDatabaseDbHandler ( {
queryString : ` ALTER TABLE ${ tableName } DROP INDEX \` ${ Key_name } \` ` ,
database : dbFullName ,
} ) ;
2023-09-21 14:00:04 +00:00
}
}
2024-12-06 10:31:24 +00:00
}
2023-09-21 14:00:04 +00:00
/ * *
* 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 ++ ) {
2024-10-14 06:49:01 +00:00
const { indexType , indexName , indexTableFields , alias } =
tableIndexes [ g ] ;
2023-09-21 14:00:04 +00:00
if ( ! alias ? . match ( /./ ) ) continue ;
/ * *
* @description Check for existing Index in MYSQL db
* /
try {
2024-12-06 10:31:24 +00:00
const existingKeyInDb = allExistingIndexes . filter (
( indexObject ) = > indexObject . Key_name === alias
2024-10-14 06:49:01 +00:00
) ;
2024-12-06 10:31:24 +00:00
if ( ! existingKeyInDb [ 0 ] )
2024-10-14 06:49:01 +00:00
throw new Error ( "This Index Does not Exist" ) ;
2023-09-21 14:00:04 +00:00
} catch ( error ) {
/ * *
* @description Create new index if determined that it
* doesn ' t exist in MYSQL db
* /
await varDatabaseDbHandler ( {
2024-10-14 06:49:01 +00:00
queryString : ` CREATE ${
2024-10-18 04:15:04 +00:00
indexType ? . match ( /fullText/i ) ? " FULLTEXT" : ""
2024-10-14 06:49:01 +00:00
} INDEX \ ` ${ alias } \` ON ${ tableName } ( ${ indexTableFields
2024-10-18 04:15:04 +00:00
? . map ( ( nm ) = > nm . value )
2023-09-21 14:00:04 +00:00
. map ( ( nm ) = > ` \` ${ nm } \` ` )
. join ( "," ) } ) COMMENT 'schema_index' ` ,
database : dbFullName ,
} ) ;
}
}
}
/ * *
* Handle MYSQL Foreign Keys
* === === === === === === === === === === === === === === === === ===
* @description Iterate through each datasquirel schema
* table index ( if available ) , and perform operations
* /
/ * *
* @description All MSQL Foreign Keys
2024-12-06 10:31:24 +00:00
* @type { import ( "../../types" ) . DSQL_MYSQL_FOREIGN_KEYS_Type [ ] | null }
* / / / @ts - ignore
2025-01-10 19:10:28 +00:00
const allForeignKeys :
| import ( "../../types" ) . DSQL_MYSQL_FOREIGN_KEYS_Type [ ]
| null = await varDatabaseDbHandler ( {
2023-09-21 14:00:04 +00:00
queryString : ` SELECT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE CONSTRAINT_SCHEMA = ' ${ dbFullName } ' AND TABLE_NAME=' ${ tableName } ' AND CONSTRAINT_TYPE='FOREIGN KEY' ` ,
database : dbFullName ,
} ) ;
2024-12-06 10:31:24 +00:00
if ( allForeignKeys ) {
2023-09-21 14:00:04 +00:00
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 = await varDatabaseDbHandler ( {
queryString : ` ALTER TABLE ${ tableName } DROP FOREIGN KEY \` ${ CONSTRAINT_NAME } \` ` ,
database : dbFullName ,
} ) ;
}
2024-12-06 10:31:24 +00:00
}
2023-09-21 14:00:04 +00:00
/ * *
* 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 ] ;
2024-10-14 06:49:01 +00:00
const {
fieldName ,
dataType ,
nullValue ,
primaryKey ,
autoIncrement ,
defaultValue ,
defaultValueLiteral ,
foreignKey ,
updatedField ,
} = column ;
2023-09-21 14:00:04 +00:00
////////////////////////////////////////
/ * *
* @description Skip default fields
* /
2024-10-18 04:15:04 +00:00
if ( fieldName ? . match ( /^id$|^date_/ ) ) continue ;
2023-09-21 14:00:04 +00:00
/ * *
* @description Skip columns that have been updated recently
* /
// if (updatedColumnsArray.includes(fieldName)) continue;
////////////////////////////////////////
let updateText = "" ;
////////////////////////////////////////
2024-12-06 10:31:24 +00:00
/** @type {any} */
2025-01-10 19:10:28 +00:00
let existingColumnIndex : any ;
2023-09-21 14:00:04 +00:00
/ * *
* @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
* /
2024-10-14 06:49:01 +00:00
let { fieldEntryText } = generateColumnDescription ( {
columnData : column ,
} ) ;
2023-09-21 14:00:04 +00:00
/ * *
* @description Modify Column ( Field ) if it already exists
* in MYSQL database
* /
if ( existingColumn && existingColumn [ 0 ] ? . Field ) {
2024-10-14 06:49:01 +00:00
const { Field , Type , Null , Key , Default , Extra } =
existingColumn [ 0 ] ;
2024-12-06 10:31:24 +00:00
let isColumnReordered = i < existingColumnIndex ;
2024-10-14 06:49:01 +00:00
if (
Field === fieldName &&
! isColumnReordered &&
2024-10-18 04:15:04 +00:00
dataType ? . toUpperCase ( ) === Type . toUpperCase ( )
2024-10-14 06:49:01 +00:00
) {
2023-09-21 14:00:04 +00:00
updateText += ` MODIFY COLUMN ${ fieldEntryText } ` ;
// continue;
} else {
2024-12-06 10:31:24 +00:00
if ( userId ) {
updateText += ` MODIFY COLUMN ${ fieldEntryText } ${
isColumnReordered
? prevColumn ? . fieldName
? " AFTER `" + prevColumn . fieldName + "`"
: nextColumn ? . fieldName
? " BEFORE `" + nextColumn . fieldName + "`"
: ""
: ""
} ` ;
} else {
updateText += ` MODIFY COLUMN ${ fieldEntryText } ` ;
}
2023-09-21 14:00:04 +00:00
}
} else if ( prevColumn && prevColumn . fieldName ) {
/ * *
* @description Add new Column AFTER previous column , if
* previous column exists
* /
updateText += ` ADD COLUMN ${ fieldEntryText } AFTER \` ${ prevColumn . fieldName } \` ` ;
2024-12-06 10:31:24 +00:00
} else if ( nextColumn && nextColumn . fieldName ) {
2023-09-21 14:00:04 +00:00
/ * *
2024-12-06 10:31:24 +00:00
* @description Add new Column BEFORE next column , if
2023-09-21 14:00:04 +00:00
* next column exists
* /
2024-12-06 10:31:24 +00:00
updateText += ` ADD COLUMN ${ fieldEntryText } BEFORE \` ${ nextColumn . fieldName } \` ` ;
2023-09-21 14:00:04 +00:00
} 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 ) {
2024-10-14 06:49:01 +00:00
const {
destinationTableName ,
destinationTableColumnName ,
cascadeDelete ,
cascadeUpdate ,
foreignKeyName ,
} = foreignKey ;
2024-12-08 08:58:57 +00:00
const foreinKeyText = ` ADD CONSTRAINT \` ${ foreignKeyName } \` FOREIGN KEY ( \` ${ fieldName } \` ) REFERENCES \` ${ destinationTableName } \` ( \` ${ destinationTableColumnName } \` ) ${
2024-10-14 06:49:01 +00:00
cascadeDelete ? " ON DELETE CASCADE" : ""
} $ { cascadeUpdate ? " ON UPDATE CASCADE" : "" } ` ;
2023-09-21 14:00:04 +00:00
// const foreinKeyText = `ADD CONSTRAINT \`${foreignKeyName}\` FOREIGN KEY (${fieldName}) REFERENCES ${destinationTableName}(${destinationTableColumnName})${cascadeDelete ? " ON DELETE CASCADE" : ""}${cascadeUpdate ? " ON UPDATE CASCADE" : ""}` + ",";
const finalQueryString = ` ALTER TABLE \` ${ tableName } \` ${ foreinKeyText } ` ;
const addForeignKey = await varDatabaseDbHandler ( {
database : dbFullName ,
queryString : finalQueryString ,
} ) ;
2024-12-08 08:58:57 +00:00
if ( ! addForeignKey ? . serverStatus ) {
errorLogs . push ( addForeignKey ) ;
}
2023-09-21 14:00:04 +00:00
}
////////////////////////////////////////
}
/ * *
* @description Construct final SQL query by combning all SQL snippets in
* updateTableQueryArray Arry , and trimming the final comma ( , )
* /
2024-10-14 06:49:01 +00:00
const updateTableQuery = updateTableQueryArray
. join ( " " )
. replace ( /,$/ , "" ) ;
2023-09-21 14:00:04 +00:00
////////////////////////////////////////
/ * *
* @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 varDatabaseDbHandler ( {
queryString : updateTableQuery ,
database : dbFullName ,
} ) ;
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" ;
}
2025-01-10 19:10:28 +00:00
} catch ( /** @type {any} */ error : any ) {
2024-12-06 10:31:24 +00:00
console . log ( 'Error in "updateTable" shell function =>' , error . message ) ;
2023-09-21 14:00:04 +00:00
return "Error in Updating Table" ;
}
2025-01-10 19:10:28 +00:00
}